Skip to content

fix(585): validate context_scope directives against the closed registry#800

Merged
Muizzkolapo merged 2 commits into
mainfrom
fix/585-context-scope-directive-validation
Jul 17, 2026
Merged

fix(585): validate context_scope directives against the closed registry#800
Muizzkolapo merged 2 commits into
mainfrom
fix/585-context-scope-directive-validation

Conversation

@Muizzkolapo

Copy link
Copy Markdown
Owner

Summary

normalize_context_scope silently accepted any unrecognized context_scope directive: it copied the value into the normalized scope, where no downstream consumer ever reads an unknown key (consumers only read observe/passthrough/drop/drops/seed). So a typo like observ: scoped nothing and raised no error — a silent-config-typo class. A hardcoded REMOVED_DIRECTIVES = {"seed_path": "seed", "static_data": "seed"} map loudly caught two specific removed names with a rename-migration message, while every other typo stayed silent.

Both are replaced with one closed-set guard: a directive name not in DIRECTIVE_REGISTRY raises ConfigurationError naming the key and listing the valid set (derived from the registry). Typos, removed names, and unknown keys now fail the same loud, uniform way — no per-name rename narrative, nothing project-specific in the framework.

Single authority for the valid set: ContextScopeDict carried a dead keep (no runtime consumer, no YAML uses it as a directive — only as a field ref like aggregate_votes.keep) and lacked drops. Aligned it to the registry and removed the dead keep read in the docs parser.

Design decisions

  • Message tells you what to use, not what changed. The old rename map only ever helped the one project with unmigrated seed_path: configs — a backward-compat shim. Every wrong name now gets the same generic "not a valid directive. Valid directives: …" line.
  • Single-authority reconciliation (DIRECTIVE_REGISTRY is the truth), not registry-only, so the type stops lying.

Verification

  • RED: 4 tests fail on main — 2 inverted (test_handles_unknown_directives asserted the silent pass-through; TestRemovedDirectives asserted the rename hint) + 2 new (observ typo, unknown key). Genuine assertion failures.
  • GREEN: full suite 8113 passed; ruff + mypy clean.
  • Blind review (fresh context, diff + bug only): YES — root cause fixed, all five valid directives still accept, keep field-refs untouched, valid-set can't drift (it is sorted(DIRECTIVE_REGISTRY)), and workflow_static_analyzer.py already catches the ConfigurationError and maps it to StaticTypeError.
  • Real project (agac inspect -a ql_domc through the actual loader, not a unit dict):
    Error: Configuration Error: Invalid configuration in agent 'ql_domc'
      Problem: context_scope.seed_path is not a valid directive. Valid directives: drop, drops, observe, passthrough, seed.
    
    Loud, lists the valid set, no rename story, exit 1.
  • Smoke: skipped — scan-project canary divergence is pre-existing (baseline.sh confirms it fails on main too; stale project action registry, unrelated to directive validation). Filed as a separate follow-up.

Notes

  • Test inversion (not test-weakening): the 7 removed test-assert lines are the two tests that encoded the old behavior (silent pass-through + rename hint), flipped to assert the loud generic error.
  • Consequence, not a hint: the qanalabs workflows using seed_path: (8+) now get the generic error — they already failed on main via the old map. Migrating them seed_path:seed: is a separate change in the qanalabs repo; the framework carries no per-user migration text.

… registry

Unknown/typo'd directives must raise (were silently copied through, inert);
removed names (seed_path/static_data) raise the SAME generic error naming the
valid set, with no rename narrative.
normalize_context_scope silently copied any unrecognized directive into the
scope, where no consumer ever reads it — a typo like 'observ:' scoped nothing
and raised no error. A hardcoded old->new rename map (REMOVED_DIRECTIVES) loudly
caught two specific removed names while every other typo stayed silent.

Replace both with one closed-set guard: a directive name not in DIRECTIVE_REGISTRY
raises ConfigurationError naming the key and listing the valid set (derived from
the registry). Typos, removed names, and unknown keys now fail the same loud,
uniform way — no per-name rename narrative.

Make DIRECTIVE_REGISTRY the single authority for the valid set: ContextScopeDict
carried a dead 'keep' (no runtime consumer, no YAML uses it) and lacked 'drops';
align it to the registry and drop the dead 'keep' read in the docs parser.

Test inversion: test_handles_unknown_directives asserted the silent pass-through
(bug-as-feature) and TestRemovedDirectives asserted the rename hint; both now
assert the loud generic error.
@Muizzkolapo
Muizzkolapo merged commit 65c4cc2 into main Jul 17, 2026
5 checks passed
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 17, 2026
@Muizzkolapo
Muizzkolapo deleted the fix/585-context-scope-directive-validation branch July 17, 2026 19:33
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant