Skip to content

feat(guardrails): fail-loud DB mode + seed/validate helpers + exclusivity warning (closes #166)#196

Merged
initializ-mk merged 1 commit into
mainfrom
feat/issue-166-guardrails-db-hardening
Jun 25, 2026
Merged

feat(guardrails): fail-loud DB mode + seed/validate helpers + exclusivity warning (closes #166)#196
initializ-mk merged 1 commit into
mainfrom
feat/issue-166-guardrails-db-hardening

Conversation

@initializ-mk

Copy link
Copy Markdown
Contributor

Summary

Closes the three quiet behaviors in BuildGuardrailChecker's DB-mode resolution ladder that made the feature unusable for production-grade guardrails deploys:

  • Fail-loud knobFORGE_GUARDRAILS_DB_REQUIRED=true makes an unreachable Mongo at startup a hard error instead of a silent downgrade. BuildGuardrailChecker now returns (GuardrailChecker, error); the runner propagates the failure as a non-zero exit.
  • Seed + validate subcommandsforge guardrails seed-defaults prints DefaultStructuredGuardrails as JSON; forge guardrails validate-db inspects the agent's AgentConfig and warns when below baseline. Closes the "DB mode bypasses built-in defaults" footgun without library-side coordination.
  • Exclusivity warning — one-shot startup warning when both FORGE_GUARDRAILS_DB is set AND a guardrails.json is present, pointing at the specific path being ignored.

Why

DefaultStructuredGuardrails ships 11 vendor-secret patterns, four PII categories, and three threat-detection thresholds — none of which are applied in DB mode (the operator's MongoDB AgentConfig is the ONLY source). An empty or partial seed leaves the agent strictly less protected than a clean default deploy, and the silent fall-back path masked the failure. This PR makes both surfaces actionable.

Operator-facing changes

Env vars

Var Default Behavior
FORGE_GUARDRAILS_DB_REQUIRED false New. When true and FORGE_GUARDRAILS_DB is set, unreachable DB at startup → runner returns non-nil error, agent refuses to serve.

CLI

forge guardrails seed-defaults > defaults.json
# pipe into MongoDB as the agent's AgentConfig seed

forge guardrails validate-db
# connects to FORGE_GUARDRAILS_DB, fetches the agent's AgentConfig,
# reports coverage; warns on missing PII / security / fewer than 5
# secret-pattern rules; exits non-zero on missing document.

Connect timeout

Trimmed NewDBGuardrailEngine Mongo connect timeout from 10s to 3s. Production fails fast on misconfigured URIs; tests measurably faster too.

Schema-shape compatibility

scoreAgentConfig tolerates both camelCase (the library struct tags — what production MongoDB carries) and snake_case (legacy seeds / hand-written docs from older guidance) without operator intervention. Pinned by TestScoreAgentConfig_SnakeCaseCompat.

Out of scope (deferred)

  • Option A: library-side config merge — requires coordinating a stable merge-semantics contract with the guardrails library. Issue notes this; the seed-defaults subcommand is the issue's recommended "Option B" for first cut.
  • Hot-reload of DB config — separate concern.
  • Per-agent DB connection multiplexing — multi-agent single-Mongo deploy still holds one client per agent.

Test plan

  • golangci-lint run ./forge-core/... ./forge-cli/... ./forge-plugins/... — 0 issues
  • gofmt -w across all modules
  • go test ./... in forge-core/ and forge-cli/ — all green
  • New unit tests pin: REQUIRED-mode fail-loud, default warn-and-fallback, forgiving-parse on REQUIRED, one-shot exclusivity warning (fires once across multiple builds, doesn't fire when only one source is present, honors cfg.GuardrailsPath), seed-defaults round-trips through models.StructuredGuardrails, scoreAgentConfig flags empty docs / fewer-than-5 secret rules / snake_case compat / camelCase-default-no-warnings, extractCustomRules defensive across BSON shape edge cases.
  • Manual smoke: FORGE_GUARDRAILS_DB=mongodb://127.0.0.1:1 FORGE_GUARDRAILS_DB_REQUIRED=true forge run — assert non-zero exit + Error log line referencing the env var.

…vity warning (closes #166)

Three quiet behaviors in the BuildGuardrailChecker resolution ladder
mismatched what operators expect from a production-grade guardrails
deploy: a silent fall-back on Mongo unreachable, no application of
DefaultStructuredGuardrails in DB mode (so an empty AgentConfig was
strictly less protective than the no-config file path), and silent
suppression of a checked-in guardrails.json when DB mode was active.
This change closes all three from the operator surface.

- FORGE_GUARDRAILS_DB_REQUIRED=true flips the silent fall-back to a
  startup-time error. BuildGuardrailChecker now returns
  (GuardrailChecker, error) so the runner can propagate the failure;
  the agent process refuses to serve. Default off for back-compat.
- forge guardrails seed-defaults prints DefaultStructuredGuardrails
  as JSON suitable for piping into MongoDB. Round-trips through
  models.StructuredGuardrails so the output is library-consumable
  verbatim — operators have a one-liner baseline seed.
- forge guardrails validate-db connects, fetches the agent's
  AgentConfig, and reports coverage. Warns when below baseline (fewer
  than 5 secret-pattern rules / missing PII / missing security
  thresholds / disabled core gates). Exits non-zero on missing
  document so CI hooks can fail rollout.
- One-shot startup warning when both FORGE_GUARDRAILS_DB and a
  guardrails.json are present, pointing at the specific path being
  ignored. Through the ops logger only (not the audit stream).
- NewDBGuardrailEngine connect timeout trimmed 10s → 3s. Production
  fails fast on misconfigured URIs; tests run in seconds, not tens
  of seconds.

Pinned by TestBuildGuardrailChecker_{DBRequired_FailsLoudOnUnreachable,
DBUnreachable_FallsBackByDefault,DBRequiredAcceptsForgivingParse,
DBAndFile_WarnsOnce,DBOnly_NoFile_NoExclusivityWarn,FileOnly_NoWarn,
HonorsCustomGuardrailsPath}, TestGuardrailsSeedDefaults_RoundTripsThroughLibraryModel,
TestScoreAgentConfig_{FullDefaultsHaveNoWarnings,SnakeCaseCompat,
EmptyDocFlagsEverything,FewerThan5SecretRulesWarns}, and
TestExtractCustomRules_DefensiveOnShape.

Library-side merge of defaults+DB config remains out of scope per the
issue — Option B (operator seeds via the new subcommand) ships now;
Option A waits on a stable library merge-semantics contract.
@initializ-mk initializ-mk merged commit f49dd0c into main Jun 25, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant