Validation report — S6-06 (Phase 5 contract snapshot test)¶
Date: 2026-05-19
Validator: phase-story-validator (inline four-lens analysis — Coverage, Test-Quality, Consistency, Design-Patterns — applied directly after Stage 1's Context Brief surfaced multiple block-tier Consistency conflicts the four critic lenses would all converge on).
Verdict: HARDENED
Story path: docs/phases/03-vuln-deterministic-recipe/stories/S6-06-phase5-contract-snapshot.md
Why HARDENED (not STRONG, not RESCUE)¶
The story's architectural intent is correct and well-anchored:
- It is the CI-gating handshake test ADR-0001 §Consequences row 2 mandates.
- It correctly identifies the six ADR-0001 named symbols and the additive-vs-breaking distinction High-level-impl §Risks #4 calls out.
- It correctly proposes the
inspect.signature+ Pydanticmodel_json_schema+ golden-file pattern (precedent:tests/unit/test_probe_contract.pyfrom Phase 0,tests/unit/probes/test_repo_context_envelope_extra.pyfrom Phase 1). - It correctly anchors the directive-message UX pattern.
But the story was written from the ADR-0001 contract surface alone, before its six dependency stories went through validation. It drifted from shipped reality and from those validated stories in multiple block-tier ways an executor without the validator's eye would have followed straight into an ImportError:
-
Wrong import paths —
from codegenie.transforms.trust_scorer import TrustSignal, TrustOutcomeandfrom codegenie.transforms.report import RemediationReportandfrom codegenie.transforms.apply_context import AttemptSummary. S5-05 validation reached Option A:TrustSignal/TrustOutcomelive incodegenie/transforms/outcomes.py, nottrust_scorer.py. S5-05 also re-exportsRemediationReportfromcodegenie.transforms.__init__.AttemptSummaryis already re-exported. Worst, the story's own AC pins "imports via the public re-export path" — but the example code violates it. -
StageOutcomemissing from the snapshot list. S6-04 validation report C-F2 resolved an ambiguity by introducingStageOutcome: TypeAlias = TrustOutcomedeclared at S6-02's canonical site and re-exported. Phase 5 reads the alias name. The contract snapshot must include it; the story did not. -
Depends on:too narrow. Only listed S6-04. Real chain: S5-01 (RecipeEngine Protocol home), S5-05 (RemediationReport + co-located TrustSignal/TrustOutcome), S6-01 (EventLog — constructor-injected into orchestrator), S6-02 (TrustScorer + StageOutcome), S6-03 (SubgraphNode), S6-04 (orchestrator). The test imports symbols owned by all six. -
Meta-test surface incomplete vs. Notes. Notes mentioned
extra="forbid"→extra="allow", runtime_checkable removal, discriminator changes — but no meta-test case existed for any of them. False-positive additive is the scariest failure mode; a meta-test gap is the exact bug that lets that ship silently. -
No determinism, no-silent-rewrite, or CI-strict tests. AC text mentioned "deterministic" and "intentionally explicit"; no AC pinned a regression test. A future refactor could invert the env-var check and silently regenerate the golden in CI — no test catches it.
-
Design-Patterns rule-of-three crossed without surfacing. Today's snapshotter must handle 5 kinds (class, ABC, Protocol, Pydantic, TypeAlias); the classifier must handle 6 rule families (class-method, Pydantic-field, Protocol-method, model_config, decorator, discriminator). The story's sketch is "dispatches on
objtype" → an if/elif chain. Above the rule-of-three threshold, this should be@register_snapshot_kind+@register_delta_ruleregistries (Strategy + Open/Closed) — mirroring the codebase's@register_probe,@register_index_freshness_check,@register_dep_graph_strategyprecedents. -
No purity fence on the helpers. AC sketched "pure helpers" but no fence test would catch a future refactor that sneaks
os.environ.get(...)intosnapshot_symboland silently shadows the golden compare. -
No Pydantic-version pin awareness. Pydantic minor-version bumps perturb
model_json_schemaoutput. Without a recorded version in the snapshot + an exact-minor pin inpyproject.toml, the snapshot will drift for the wrong reason and look like a Phase 3 contract regression.
All in-place fixable. The story's structure (TDD plan, file layout, additive-vs-breaking framing, golden under tests/golden/phase5-contract/) survives — only the specific shapes that conflict with shipped reality + validated-dep stories + design-pattern best practice need correction. Verdict: HARDENED.
Context Brief (Stage 1)¶
Story snapshot¶
- Goal (post-edit): ship
tests/integration/test_phase5_contract_snapshot.py+..._meta.py+tests/integration/_phase5_contract_helpers.py+tests/golden/phase5-contract/snapshot.json+tests/fence/test_phase5_contract_helpers_purity.pythat together freeze the public surface of the 7 ADR-0001-derived named symbols (6 +StageOutcome); the test allows additive deltas, rejects breaking deltas, prints a load-bearing directive on failure, and is CI-gating undermake check. - Status pre-validation:
Ready— never executed; never validated. - Status post-validation:
HARDENED.
Shipped reality (HEAD ecfff74)¶
src/codegenie/transforms/__init__.pyre-exports:Transform,TransformProvenance,ApplyContext,AttemptSummary,CapabilityBundle,SandboxedPath, everyoutcomes.pysymbol. NOT yet re-exportingRemediationOrchestrator,TrustScorer,TrustSignal,TrustOutcome,RecipeEngine,RemediationReport,StageOutcome— those are added by S6-04/S6-02/S5-05/S5-01.src/codegenie/transforms/apply_context.py:138-141—ApplyContextrequiresworkflow_id: WorkflowId+capabilities: CapabilityBundle;prior_attempts: tuple[AttemptSummary, ...] = ()(immutable tuple, not list — S6-04 validation C-F1).src/codegenie/plugins/protocols.py:80-90—@runtime_checkable\nclass RecipeEngine(Protocol)— the canonical home; ADR-0001 mandates re-export fromcodegenie.transforms.src/codegenie/transforms/transform.py:64-115—Transform(ABC)+ abstract methods; concrete subclasses to grow over time (Phase 4 addsLLMProducedTransform, Phase 7 addsDockerfileBaseImageTransform).
Dependency status (all HARDENED, none GREEN at validation time)¶
| Story | Status | What S6-06 needs from it |
|---|---|---|
| S5-01 | HARDENED | RecipeEngine Protocol home (plugins/protocols.py); re-export wiring |
| S5-05 | HARDENED | RemediationReport + co-located TrustSignal/TrustOutcome (Option A); re-exports |
| S6-01 | HARDENED | EventLog (constructor-injected into RemediationOrchestrator) |
| S6-02 | HARDENED | TrustScorer class + StageOutcome: TypeAlias = TrustOutcome declaration + re-exports |
| S6-03 | HARDENED | SubgraphNode Protocol (used by orchestrator's 5-node loop) |
| S6-04 | HARDENED | RemediationOrchestrator + _validate_stage6 signature + context: ApplyContext \| None = None signature |
S6-06 cannot run end-to-end until all six are GREEN. The meta-test, however, has zero dep on shipped code (synthetic local fixtures) and can be written + landed first.
Cross-phase contract (immutable inputs)¶
- ADR-0001 §Decision — six named symbols. §Consequences row 1: re-export from
codegenie.transforms. Row 2: this CI-gating test. §Tradeoffs row 5: schema rigidity — every additive Pydantic field is a contract amendment. - ADR-0007 §Decision —
_validate_stage6body shape; signature pinned for Phase 5 wrap. - ADR-0010 §Decision (3) — discriminated unions via
Annotated[..., Field(discriminator="kind")]; every dispatch usesmatch+assert_never. - ADR-0010 Amendment 2026-05-18 — single canonical declaration site (S5-05's
outcomes.pyforTrustSignal/TrustOutcome). - ADR-0011 — honest framing: the snapshot is a structural pin, not a behavioural contract. Don't oversell.
- High-level-impl §Risks #4 — explicit additive-vs-breaking distinction; encoded in the test, not in reviewer judgment.
- Phase 5 ADR-0001 — two-chokepoint sandbox seam (names
_validate_stage6as the swap point). - Phase 5 ADR-0002 — additive
prior_attemptskwarg. - Phase 5 ADR-0003 —
TrustScorerextension viaSignalKindregistry (variant additions are additive).
Open ambiguities resolved before critics¶
- Q1 — Where do
TrustSignal/TrustOutcomelive? S5-05 Option A:codegenie/transforms/outcomes.py. Re-exported fromcodegenie.transforms.__init__. The story'scodegenie.transforms.trust_scorerimport was wrong. Resolution: use the re-export path everywhere. - Q2 — Is
StageOutcomepart of the snapshot? Yes — per S6-04 validation C-F2, Phase 5 reads the alias name. Snapshot records both the alias name and the qualified name of its target. - Q3 — Should the snapshotter / classifier be registry-dispatched? Rule-of-three test: 5 snapshot kinds + 6 delta-rule families today, with more coming in Phase 4 (Enum snapshots for SignalKind), Phase 6.5 (eval-rubric snapshots may copy the helpers). Yes — both are above the threshold. Codebase precedents:
@register_probe,@register_index_freshness_check,@register_dep_graph_strategy.
All three resolved from precedent + shipped code + validated-dep stories; no user clarification needed.
Findings¶
Severity legend: block (story unshippable without fix) · harden (in-place fix applied) · nit (small clarification).
Consistency lens (highest priority — source-of-truth wins)¶
C-F1 (block → fixed) — wrong import paths violate the story's own re-export AC¶
- What was wrong: TDD plan imported
from codegenie.transforms.trust_scorer import TrustSignal, TrustOutcome,from codegenie.transforms.apply_context import AttemptSummary,from codegenie.transforms.report import RemediationReport— three deep-import paths. Two of them are wrong (S5-05 Option A putsTrustSignal/TrustOutcomeinoutcomes.py, nottrust_scorer.py;AttemptSummaryis already re-exported), and all three contradict the story's own AC that imports must be viacodegenie.transforms. - Source of truth: ADR-0001 §Consequences row 1 + S5-05 AC-Surface-2 (Option A decision) + S5-05 AC-Surface-1 (RemediationReport re-export).
- Fix applied: All imports rewritten to
from codegenie.transforms import …. The deep-module map for the re-export-identity check (AC-6) lives only in_phase5_contract_helpers.py, where one centralized declaration is acceptable.
C-F2 (block → fixed) — StageOutcome missing from the snapshot¶
- What was wrong: S6-04 validation C-F2 added
StageOutcome: TypeAlias = TrustOutcomeand recorded that "Phase 5 reads the alias name". The S6-06 story did not include it inSYMBOLS. The snapshot would freeze the 6 symbols ADR-0001 names directly, but_validate_stage6 -> StageOutcomewould not be caught if someone later swapped the alias toAliased = SomeOther. - Source of truth: S6-04 validation report C-F2 + ADR-0010 Amendment 2026-05-18 (single-canonical-declaration-site).
- Fix applied:
StageOutcomeadded as the 7th named symbol. AC-5 explicitly snapshots both the alias name AND the qualified name of its target. NewSnapshotKind.TYPE_ALIASenum member + corresponding snapshotter registration (AC-21).
C-F3 (block → fixed) — Depends on: only listed S6-04¶
- What was wrong: The test imports symbols owned by S5-01 (RecipeEngine), S5-05 (RemediationReport + TrustSignal/TrustOutcome), S6-01 (EventLog wired via orchestrator constructor), S6-02 (TrustScorer + StageOutcome), S6-03 (SubgraphNode used by orchestrator), S6-04 (orchestrator). An executor following only
Depends on: S6-04could open the story prematurely. - Fix applied:
Depends on:expanded to the full chain with a one-line annotation per dep explaining what it owns.
C-F4 (harden → fixed) — re-export check uses hasattr instead of is¶
- What was wrong: Original
test_phase5_named_symbols_re_exported_from_transforms_packageassertedhasattr(pkg, name). A duplicate Protocol declaration (class RecipeEngine(Protocol): ...re-defined insidetransforms/__init__.py) would passhasattrwhile silently breaking Phase 5'sisinstance(obj, RecipeEngine)against the deep-imported class — Phase 5 consumers using either import path would see a different class object. - Source of truth: Python's
Protocolisinstancesemantics + S6-04 validation D-P2 (dependency-inversion + identity-by-is). - Fix applied: AC-6 +
re_export_identity_violations(SYMBOLS)helper assertgetattr(codegenie.transforms, name) is <deep-module>.<name>. Identity, not attribute presence.
C-F5 (harden → fixed) — ADR-0011 (honest framing) not honored¶
- What was wrong: Story did not mention ADR-0011. Its language ("the most load-bearing test in Phase 3") risks overselling — the snapshot is structural pinning only, not a behavioural contract.
- Fix applied: ADRs honored line extended with ADR-0011. Notes "What the snapshot does NOT catch (and why that's OK)" section added — explicitly disowns behavioural-bug detection, docstring drift, type-alias collapse.
Coverage lens¶
C-Cv1 (harden → fixed) — no AC for model_config deltas¶
- What was wrong: Notes mentioned
extra="forbid"→extra="allow"is breaking. No AC. The implementer could ship a classifier that ignoresmodel_configand the test would still pass; the meta-test would not catch it because there was no meta-test case either. - Fix applied: AC-13 lists
model_config["extra"]change andmodel_config["frozen"]change as explicit breaking deltas. Meta-test gains_model_extra_flipped_to_allow+_model_frozen_flipped_offparametric cases.
C-Cv2 (harden → fixed) — no AC for @runtime_checkable removal¶
- Fix applied: AC-13 lists explicitly. Meta-test gains
_proto_runtime_checkable_removed+_proto_method_removedcases. (@runtime_checkableis what makes Phase 5'sisinstance(obj, RecipeEngine)work at gate-runner time; removal silently turns it intoTypeError.)
C-Cv3 (harden → fixed) — no AC for discriminator removal¶
- What was wrong: Replacing
Annotated[A | B | C, Field(discriminator="kind")]with a plainUnionloses staticmatchnarrowing — Phase 5'sassert_neverexhaustiveness arms regress to runtime errors. No AC. No meta-test. - Source of truth: ADR-0010 §Decision (3) + Phase 5 ADR-0003.
- Fix applied: AC-13 lists explicitly. Meta-test gains
_union_lost_discriminatorcase +_union_minus_variant(variant removal is also breaking).
C-Cv4 (harden → fixed) — no AC for required-field type narrowing¶
- What was wrong: Notes obliquely mentioned "extra="forbid"-violating field-type narrowing" with no meta-test case. Narrowing
int | str→intremoves a shape consumers were allowed to send — strict-AND breaking. - Fix applied: AC-13 explicit. Meta-test gains
_model_field_type_narrowed+_model_field_type_narrowed_aftercases.
C-Cv5 (harden → fixed) — no AC for symbol removal from __all__¶
- Fix applied: AC-13 lists
__all__entry removal as breaking; folded into the same rule-family as renames.
C-Cv6 (harden → fixed) — no AC for snapshot determinism across invocations¶
- What was wrong: AC text mentioned "deterministic" but no regression test. A future refactor that switches
sort_keys=Truetosort_keys=False(or uses a Python-dict-default-order assumption) would silently flake. - Fix applied: AC-8 +
test_phase5_contract_snapshot_is_deterministicruns the snapshot 10 times in the same process and compares.
C-Cv7 (harden → fixed) — no AC for "no silent rewrite when env var unset"¶
- Fix applied: AC-16 + the test's monkeypatch of
Path.write_textonGOLDEN_PATH. A refactor that inverts the env-var check fails immediately.
C-Cv8 (harden → fixed) — no AC for CI-strict mode¶
- What was wrong: Notes mentioned "intentionally explicit — a developer must opt in" but did not pin "in CI, the opt-in is ignored". A developer who accidentally sets the env var in a CI workflow could silently regenerate the golden mid-run.
- Fix applied: AC-17 +
test_ci_ignores_update_golden_env_var.
C-Cv9 (harden → fixed) — no AC for directive-message format¶
- Fix applied: AC-19/20 + meta-test
test_directive_message_contains_required_sections. Asserts the seven required substrings.
C-Cv10 (harden → fixed) — no AC for Pydantic-version pin awareness¶
- What was wrong: A Pydantic minor-version bump that perturbs
model_json_schemaoutput silently invalidates the snapshot for the wrong reason. The diff would look like a Phase 3 contract regression; the cause is a transparent dep bump. - Fix applied: AC-11 + snapshot records
_pydantic_version.pyproject.tomlcarries exact-minor pin.
Test-quality lens¶
T-Q1 (harden → fixed) — meta-test surface incomplete vs. Notes¶
Notes mentioned extra="forbid" flip, runtime_checkable removal, discriminator changes — none had meta-test cases. This is the worst class of bug: false-positive additive ships silently. The classifier could be wrong about any of these and the original meta-test would not catch it.
- Fix applied: Meta-test gains 13 new parametric cases — every AC-12 and AC-13 case has at least one fixture (the meta-test is the safety net per Notes). AC-14 adds a meta-meta count check:
_BREAKING_KINDS_COVERED_BY_THIS_FILEis compared toDELTA_RULE_REGISTRY.all_breaking_kinds(). A future PR that adds a classifier rule without a meta-test case is a red test.
T-Q2 (harden → fixed) — original meta-test asserts shape, not delta-kind¶
- What was wrong: Original test
assert all(isinstance(d, Additive) for d in deltas). A buggy classifier that returnsAdditivefor everything passes the additive cases. A more brittle bug: classifier returnsBreaking(kind="random")for a breaking case — the originalisinstance(d, Breaking)check still passes, even though the recordedkindis meaningless. - Fix applied: Each parametric case now asserts
d.kind == delta_kind— the exact recorded kind must match. TheBreakingdataclass'skind: Literal[...]discriminator carries the contract; the meta-test pins each.
T-Q3 (harden → fixed) — directive-message has no test¶
- Fix applied:
test_directive_message_contains_required_sectionsparametric assertion on the seven required substrings.
T-Q4 (harden → fixed) — --update-golden decision is not unit-testable¶
- What was wrong: Original code read
os.environ.get(...)inline. Testing AC-17 requires monkeypatching the global env — slow, racy, and leaks state across tests. - Fix applied: Extracted
should_update_golden(env: Mapping[str, str]) -> bool(AC-15). The CI-strict test (AC-17) calls the pure function directly.
T-Q5 (harden → fixed) — registry shape never tested¶
- Fix applied:
test_snapshotter_registry_covers_every_snapshot_kind+test_classifier_registry_covers_every_snapshot_kind(AC-21/22 meta-test).
T-Q6 (nit → fixed) — fixture isolation discipline not pinned¶
- Fix applied: Notes explicitly: meta-test fixtures use locally-defined classes, NOT modules under
src/codegenie/— same isolation as Phase 0 ADR-0002's per-test registry.
Design-patterns lens¶
D-P1 (harden → fixed) — snapshotter dispatch crosses rule-of-three¶
- Observation: Today's snapshotter must handle 5 kinds:
class,abc,protocol,pydantic_model,type_alias. Phase 4 likely addsenumforSignalKindregistry. Phase 6.5's eval-rubric snapshots may copy the helpers. The original story's sketch is "dispatches onobjtype" — an if/elif chain that someone has to edit each time. Above rule-of-three threshold. - Codebase precedents:
@register_probe(Phase 0),@register_index_freshness_check(Phase 2),@register_dep_graph_strategy(Phase 3),@register_signal_kind(Phase 5 ADR-0003). CLAUDE.md §"Open/Closed seams". - Fix applied: AC-21 — snapshotter is registry-dispatched via
@register_snapshot_kind(SnapshotKind). Adding a 6th kind is one new module-local function + one decorator call, no edits tosnapshot_symbol. AC-23 —SnapshotKindis aStrEnum(single canonical home); typos areKeyErrorat registration.
D-P2 (harden → fixed) — classifier dispatch crosses rule-of-three¶
- Observation: 7 rule families today (class-method, Pydantic-field, Protocol-method, model_config, decorator-presence, discriminator, type-narrowing). Each is naturally its own module. Above threshold.
- Fix applied: AC-22 — classifier is registry-dispatched via
@register_delta_rule(SnapshotKind). AC-14's meta-meta count check enforces that adding a rule without a meta-test case is a red test.
D-P3 (harden → fixed) — Delta shape only loosely typed¶
- What was wrong: Story sketched
Delta = Additive(...) | Breaking(...). The implementer could shipBreaking(message="...")as a single shape — nokinddiscriminator, nomatchexhaustiveness, no way for AC-14 to enforce coverage. - Fix applied: AC-25 —
Deltaalgebra is a frozen tagged union (@dataclass(frozen=True)AdditiveandBreaking, both carryingkind: Literal[...]discriminator). Every dispatch overDeltausesmatch+assert_never(ADR-0010 §Decision (3)).format_breaking_delta_messageis amatchoverdelta.kind— single source of truth for the operator UX.
D-P4 (harden → fixed) — functional core / imperative shell not enforced¶
- What was wrong: Story sketched "pure helpers" in passing. A future refactor that sneaks
os.environ.get(...)intosnapshot_symbol(to read a debug flag) silently shadows the golden compare. - Codebase precedent:
tests/fence/test_transforms_module_purity.pyAST-walks the production module. - Fix applied: AC-24 + new fence file
tests/fence/test_phase5_contract_helpers_purity.py. AST-walks_phase5_contract_helpers.pyand rejects any ofos.environ,Path.read_text,Path.write_text,logging.getLogger,subprocess.*.
D-P5 (harden → fixed) — smart constructor for env-var decision¶
- What was wrong:
os.environ.get(...)inline read makes AC-17 require monkeypatching. - Fix applied: AC-15 —
should_update_golden(env: Mapping[str, str]) -> boolis testable without monkeypatching. (Capability pattern variant — passing the capability instead of reading it ambiently.)
D-P6 (note → surfaced) — tests/helpers/ would be premature¶
- Observation: A reviewer may suggest extracting
_phase5_contract_helpers.pytotests/helpers/contract_snapshot/for reuse by Phase 5's own contract snapshot or Phase 6.5's rubric snapshot. - Decision: Rule 2 — premature abstraction. The helpers are scoped to this one test pair today. When Phase 5 / Phase 6.5 lands its second consumer, extract then. AC-28 pins the location at
tests/integration/_phase5_contract_helpers.py.
D-P7 (note → surfaced) — Phase 4 / Phase 6.5 will copy this pattern¶
- Observation: Phase 4 will add a snapshot for the
LLMProducedTransformsubstrate (Phase 4 ADR-amendments to the kernel). Phase 6.5 will likely want a rubric-snapshot test for its eval harness. Both can copy the registry + Delta + purity-fence pattern. - Decision: Surface as Note; do not pre-emptively extract. The first cross-phase consumer triggers extraction.
D-P8 (harden → fixed) — _pydantic_version metadata in snapshot¶
- What was wrong: A Pydantic minor-version bump perturbs
model_json_schemaoutput. Without a recorded version, the resulting diff looks like a Phase 3 contract drift. Confusing operator UX. - Fix applied: AC-11 — snapshot records
_pydantic_versionat top level. A diff that only changes the_pydantic_versionfield is a transparent dep bump; a diff that changes the_pydantic_versionfield AND schema content is a contract event.
Edits applied¶
All edits in-place. The story file's Validation notes block records the changes. Summary:
- Status flipped to
HARDENEDwith_validation/S6-06-phase5-contract-snapshot.mdreference. - Depends on expanded from
S6-04only to the full six-dep chain. - ADRs honored extended with ADR-0010 Amendment 2026-05-18 (single-canonical-declaration-site) + ADR-0011 (honest framing).
- Validation notes block (14 numbered points) inserted after the header.
- Acceptance criteria rewritten and expanded from 14 unnumbered bullets to 31 numbered ACs grouped under: Files + collection / Canonical symbol set / Determinism + canonical encoding / Additive vs breaking classifier / Update-golden mode (safety + opt-in) / Directive message UX / Classifier + snapshotter extension points (rule-of-three) / Functional-core + purity fences / Documentation + CI wiring / Bar.
- Implementation outline rewritten in 11 ordered steps. Meta-test now precedes the main test (the classifier can be proven against synthetic fixtures with zero dep on S6-01..S6-05 actually existing).
- TDD plan red tests rewritten:
- Meta-test gains 13 new parametric cases covering every AC-12/13 delta kind, the directive-message format (AC-19/20), and the meta-meta registry count check (AC-14).
- Main test rewritten with corrected imports (re-export only),
should_update_goldenfactor, no-silent-rewrite monkeypatch (AC-16), determinism property (AC-8), CI-strict mode test (AC-17), identity-not-hasattr re-export check (AC-6). - Files to touch rewritten with AC-coverage column; new files added (
tests/fence/test_phase5_contract_helpers_purity.py,Makefile,pyproject.toml). - Notes for the implementer restructured into Operational / Imports + re-export contract (corrected) / Classifier semantics / Design patterns / Snapshot reproducibility / What the snapshot does NOT catch / Out-of-scope reminders — about 35 new bullets that pin every block + harden + note above. The original Notes section's 12 bullets all survive, corrected and re-grouped.
Verdict¶
HARDENED. Ready for phase-story-executor once S5-01, S5-05, S6-01, S6-02, S6-03, S6-04 are all GREEN on the executor's branch. The Consistency closures (C-F1..C-F5) reconcile the story with the validated-dep stories' shipped reality; the Coverage closures (C-Cv1..C-Cv10) pin every AC-12/13 delta kind, the no-silent-rewrite invariant, the CI-strict mode, and the Pydantic-version pin; the Test-Quality closures (T-Q1..T-Q6) ensure a wrong implementation fails an assertion verbatim rather than slipping through a thin test (the meta-test is now a real safety net); the Design-Patterns closures (D-P1..D-P5) elevate the snapshotter + classifier to registry dispatch (Strategy + Open/Closed), pin Delta as a tagged union, and fence the helpers as a functional core — making this test extensible by addition for Phase 4 (Enum snapshots, LLMProducedTransform subclass) and Phase 6.5 (eval-rubric snapshot) without editing the kernel.