feat(ci): vendored-config schema guard (A3) - #117
Merged
Conversation
Add a validate-vendored-config CI job that validates every config key AAF ships to a vendored app against the schema the PINNED vendored source actually reads, so silently-dropped keys (the Honcho 3.0.7 flat-key removal class, the Hermes config-schema-shift class) fail the build instead of silently degrading a deployment. Per-app strategy (docs/design/vendored-config-schema-guard.md): - Honcho: dynamically load the vendored pydantic-settings model and derive the full accepted env-key universe (nested MODEL_CONFIG paths, closed Literal dialectic-level set, aliases) — exact, zero curation, auto-tracks vendor bumps. Curated removed-keys map upgrades diagnostics to migration hints. - Hermes config.yaml: AST-parse the vendored parser's DEFAULT_CONFIG tree (no imports); polymorphic sections (dict-form `model:`) come from a manifest pinned to the submodule gitlink — a vendor bump fails loudly until re-validated. - Hermes env: static consumption scan of the vendored tree + bundled AAF override/agent-runtime scripts. - PaperClip (source not in-tree): curated manifest pinned to PAPERCLIP_VERSION, cross-checked against the Dockerfile ARG and compose default. Hard-fail policy; suppression only via allowlist entries with mandatory reasons. --self-test seeds known-bad keys and proves detection before any pass is trusted; 19 pytest unit tests in tests/vendored-config. REAL DRIFT FOUND AND FIXED by the guard's first run (the vendor bump that migrated docker-compose.yml/.env.example in #111 missed these): - infrastructure/modules/container-apps/honcho.tf: all three resources shipped the pre-3.0.7 flat keys (SUMMARY_/DERIVER_ PROVIDER+MODEL, DIALECTIC_LEVELS__<lvl>__PROVIDER/MODEL/THINKING_BUDGET_TOKENS) — 57 removed env keys silently ignored; a Terraform deploy of Honcho 3.0.11 would run every specialist on direct-OpenAI gpt-5.4-mini. Migrated to the nested MODEL_CONFIG shape. - deploy/mac-site/docker-compose.yml: same flat keys in the honcho service (19 keys). Migrated. - infrastructure/modules/container-apps/hermes.tf: HERMES_DB_PATH is not read by the pinned Hermes (no state-db path override exists at v2026.5.16) — the documented SQLite-on-SMB mitigation for state.db was silently inert. Removed with an honest note; also removed the unread LOG_LEVEL from the hermes container (the router sidecar keeps its own). - .env.example: dropped the dead HERMES_DB_PATH knob. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
mrobinson2
force-pushed
the
feat/a3-vendored-config-schema-guard
branch
from
July 12, 2026 08:30
e4d07dc to
03790f2
Compare
Owner
Author
|
Rebased onto main after #115 (A1) and #116 (A6) landed. The new job's first CI run actually caught the A1 collision: A1 moved the Hermes config.yaml generation out of |
3 tasks
mrobinson2
added a commit
that referenced
this pull request
Jul 12, 2026
…) (#118) Six PRs merged to main since v1.7 (A1-A6: vendored incident-fix defaults, agent-loop canary smoke, vendored-config schema guard, provider-flexible embeddings, canonical user-peer identity, hard cost-envelope enforcement) turn silent failures into loud ones across the vendored-app config surface, the router, and the agent-loop itself. Document them honestly as merged-to- main-but-unreleased in README's What's new/Roadmap and ROADMAP.md's new v1.8 section, and clean up a stale Future-releases item the v1.5/A6 cost governance work already supersedes. Co-authored-by: Michael Robinson <michaelrobinson@Michaels-PC.local> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
mrobinson2
pushed a commit
that referenced
this pull request
Jul 21, 2026
v1.7 hardened the security posture; applying that hardening to a real subscription then broke the paths that install and update the platform. v1.8 collects the repairs and the guards that make the same class of breakage fail loudly next time. Adds docs/releases/v1.8.0.md (fresh-deploy firewall/PG15 fix, the paperclip build-pin drift, the DSN username guard and its six-day outage, the gitleaks allowlists, dependency bumps, upgrade notes), a README section framing it as a repair release rather than a feature one, a ROADMAP "v1.8: shipped" section, and the release badge. The v1.7 tag was cut before the reliability-hardening merges (#112-#117) reached main, so those are documented under v1.7 and contained in this tag; v1.8.0.md covers only what is new since that documentation. Renames the horizon section to post-1.8 and adds the still-open services/paperclip Python 3.14 bump (#124) to it — its smoke job fails, so it is explicitly not in this release. The notes describe the demo feature-flag false positive rather than quoting the string: the allowlist that exempts it is scoped to docs/notes/, so reproducing it here would fail the scan, and widening a security allowlist to accommodate prose is the wrong trade. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
mrobinson2
added a commit
that referenced
this pull request
Jul 21, 2026
…131) v1.7 hardened the security posture; applying that hardening to a real subscription then broke the paths that install and update the platform. v1.8 collects the repairs and the guards that make the same class of breakage fail loudly next time. Adds docs/releases/v1.8.0.md (fresh-deploy firewall/PG15 fix, the paperclip build-pin drift, the DSN username guard and its six-day outage, the gitleaks allowlists, dependency bumps, upgrade notes), a README section framing it as a repair release rather than a feature one, a ROADMAP "v1.8: shipped" section, and the release badge. The v1.7 tag was cut before the reliability-hardening merges (#112-#117) reached main, so those are documented under v1.7 and contained in this tag; v1.8.0.md covers only what is new since that documentation. Renames the horizon section to post-1.8 and adds the still-open services/paperclip Python 3.14 bump (#124) to it — its smoke job fails, so it is explicitly not in this release. The notes describe the demo feature-flag false positive rather than quoting the string: the allowlist that exempts it is scoped to docs/notes/, so reproducing it here would fail the scan, and widening a security allowlist to accommodate prose is the wrong trade. Co-authored-by: Michael Robinson <michaelrobinson@Michaels-PC.local> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
A new
validate-vendored-configCI job that validates every config key AAF ships to a vendored app (compose env blocks, Terraform env blocks, the generated Hermesconfig.yaml) against the schema the pinned vendored source actually reads. A key the app silently drops — the exact class behind the Honcho 3.0.7 flat-key incident ("boots clean, runs every specialist on direct-OpenAI gpt-5.4-mini") and the Hermes config-schema-shift incident ("provider=openrouter, model=empty, 401") — now fails the build instead of silently degrading a deployment.Design doc first, as specified:
docs/design/vendored-config-schema-guard.md.REAL DRIFT FOUND (the headline)
The guard's first honest run found the Honcho 3.0.7 incident alive in this repo — the vendor bump that migrated
docker-compose.ymland.env.example(#111) missed two artifacts:infrastructure/modules/container-apps/honcho.tf— all three resources (API app, always-on deriver, scheduled deriver job) still shipped the pre-3.0.7 flat keys (SUMMARY_PROVIDER,SUMMARY_MODEL,DERIVER_PROVIDER,DERIVER_MODEL,DIALECTIC_LEVELS__<lvl>__PROVIDER/__MODEL/__THINKING_BUDGET_TOKENS× 5 levels) — 57 removed env keys, all silently ignored (extra="ignore"). A Terraform deploy of the pinned Honcho 3.0.11 image would have run every specialist and all five dialectic levels on direct-OpenAIgpt-5.4-mini. The file's own comment even warns about "the old DIALECTIC_MINIMAL_PROVIDER style is silently ignored" — while using the other silently-ignored style. Fixed: migrated to the nestedMODEL_CONFIGshape (values preserved;MAX_TOOL_ITERATIONSsurvived 3.0.7 and stays a per-level field).deploy/mac-site/docker-compose.yml— same flat keys in the honcho service (19 keys), three lines below a comment correctly stating "the flat form is ignored". Fixed the same way.infrastructure/modules/container-apps/hermes.tf—HERMES_DB_PATHappears nowhere in the pinned Hermes (v2026.5.16):hermes_state.pyhas no path override, so the documented SQLite-on-SMB mitigation forstate.dbwas silently inert (state.db actually lives on the Azure File Share). Removed with an honest note; the twinHERMES_KANBAN_DBfix IS read (hermes_cli/kanban_db.py) and stays. Also removed the unreadLOG_LEVELfrom the hermes container (the model-router sidecar reads its own and keeps it)..env.example's deadHERMES_DB_PATHknob dropped too.Observation, not changed here:
apps/paperclip/patch-adapter.mjssets a per-sessionHERMES_DB_PATHfor the same reason — equally a no-op on this pin; worth revisiting at the next Hermes bump.Per-app validation strategy
apps/honcho/src/src/config.py, standalone import) and derive the full accepted env-key universe: per-sectionenv_prefix,__nested paths through sub-models (incl.…MODEL_CONFIG__OVERRIDES__BASE_URL),dict[Literal[…]]closes the dialectic level-name set (a typo'd level is flagged), aliases honored, case-insensitive. Zero curation — a vendor bump recomputes the universe and fails iff shipped keys became invalid. A curated removed-keys map upgrades diagnostics to migration hints (SUMMARY_PROVIDER→ "REMOVED in 3.0.7 — replace withSUMMARY_MODEL_CONFIG__TRANSPORT").config.yamlhermes_cli/config.pyDEFAULT_CONFIGtree ∪_KNOWN_ROOT_KEYS; no imports, no heavy deps) and validate the entrypoint-heredoc-generated config. Polymorphic sections (dict-formmodel:whose vendored default is a scalar) come fromscripts/vendored-config/manifest-hermes.yaml, pinned to the submodule gitlink — a Hermes bump fails the job until the manifest is re-validated. That loud failure is the point.$VARreads in bundled AAF override/agent-runtime scripts). Over-accepts by design — its job is catching keys the app never reads. Terraform env is scoped per-container so the model-router sidecar isn't validated against Hermes.manifest-paperclip.yaml) carries only upstream-consumed keys, pinned toPAPERCLIP_VERSIONand cross-checked against both the DockerfileARGand the compose default — a version bump fails until re-validated. Everything in-tree (entrypoint, auth-proxy.mjs, patches, bundled Hermes CLI) is derived by scanning, not curated.Failure policy: every finding is a hard fail — no warn tier (warnings in green builds are how the incidents shipped). Suppression only via
scripts/vendored-config/allowlist.yaml, where an entry without a non-emptyreasonis itself a failure. Current allowlist: platform env consumed by the runtime, not the app (AZURE_CLIENT_ID→ azure-identity,APPLICATIONINSIGHTS_CONNECTION_STRING→ App Insights,GOOGLE_WORKSPACE_CLI_CREDENTIALS_FILE→ the gws CLI binary).Test evidence
Local run (Python 3.13, pydantic 2.13.4 / pydantic-settings 2.14.2):
Unit tests cover: removed-flat-key detected with migration hint; unknown key detected per app; typo'd dialectic level name detected (closed Literal set); valid nested/OVERRIDES/case shapes pass; allowlisted key passes; reason-less allowlist entry rejected; both manifest version-pin tripwires; heredoc-generated configs validate; per-container tf scoping excludes the sidecar; whole-repo clean run; self-test passes.
terraform fmt/validateanddocker compose config -qstill green.Vendor-bump behavior (by design)
pinned_commit≠ new gitlink → fails always until the polymorphic sections are re-validated.pinned_version≠ Dockerfile/compose pin → fails always until the env list is re-validated.🤖 Generated with Claude Code