Skip to content

Validation report: S7-02 — Fixtures batch 2 (monorepo-pnpm + stale-scip full materialization)

Validated: 2026-05-18 Verdict: HARDENED Validator version: phase-story-validator v1 (consolidated single-pass critics + S7-01 precedent template)

Summary

S7-02 plants the remaining two of the five Phase-2 portfolio fixtures and extracts the shared _shape_test_kernel.py deferred from S7-01 (6 consumers total → conclusively past Rule of Three). The story's intent and scope are sound and trace cleanly to phase-arch-design.md §"Fixture portfolio engineering" + High-level-impl.md §Step 7 + S7-01's Notes-for-implementer "Patterns DELIBERATELY deferred".

Five block-tier and ten harden-tier issues found. The block-tier set has the same root pattern as S7-01: the story prescribes mechanisms that contradict either ADR-0001 (pnpm not allowlisted) or the actual S4-02 stub mechanism (the story names last-indexed-commit.txt, but the existing stub uses a seed-template + gitignored-.git/ + gitignored-.codegenie/ pattern). All five are mechanically fixable in place; none is a goal/scope rewrite.

Story edited in place. Verdict: HARDENED.

Stage 1 — Context Brief

What the story promises. Two fixture trees: 1. tests/fixtures/portfolio/monorepo-pnpm/ — pnpm workspace with three packages (lib-a, lib-b depending on lib-a, app depending on both); root + per-package manifests; root pnpm-lock.yaml; Dockerfile; .github/workflows/ci.yml; per-package tsconfig.json with root references. 2. tests/fixtures/portfolio/stale-scip/ — "full materialization" of the S4-02 stub: real TypeScript source tree, real (binary) SCIP blob built from a prior commit, committed history with HEAD ahead by ≥ 1.

Plus the deferred shared tests/fixtures/portfolio/_shape_test_kernel.py consumed by all five S7-01/S7-02 fixtures + Phase 1's node_typescript_helm/ shape test (sixth consumer; kernel earns its keep).

Arch + ADR constraints (load-bearing).

  • phase-arch-design.md §"Fixture portfolio engineering" — fixtures ≤ 200 files (stale-scip story permits ≤ 50 .ts); regenerate.sh reviewed-as-code; .codegenie/cache/ NOT committed.
  • phase-arch-design.md §"Edge cases" row 11 — stale-scip is the load-bearing CI-gating fixture; build FAILS if IndexHealthProbe regresses.
  • ADR-0001 closed binary set (verified at src/codegenie/exec/__init__.py:96-111): {git, node, semgrep, syft, grype, gitleaks, scip-typescript, ast-grep, ripgrep, tree-sitter, docker, strace}. pnpm is NOT in the set; npm is NOT; node-gyp is NOT. Same Phase-2 constraint S7-01 was hardened against.
  • ADR-0006 — IndexFreshness = Fresh | Stale(reason: StaleReason); CommitsBehind is the structural assertion the adversarial reads.
  • ADR-0007 — no plugin loader; no fixture seeds plugins/.
  • ADR-0009 — pytest-xdist veto; portfolio CI lane runs serial.

Existing S4-02 stub — source-of-truth (read at validation time). Critical: the story's prescription for stale-scip mechanism diverges from what S4-02 actually shipped. The actual stub at tests/fixtures/portfolio/stale-scip/:

  • Has a gitignored .git/ directory (its own micro-repo, regenerated by regenerate.sh).
  • Has a gitignored .codegenie/ directory (regenerated by regenerate.sh).
  • Committed seed bytes: _seed/scip-slice.template.json (JSON template with PARENT_COMMIT placeholder substituted at regen time) + _seed/scip-index.scip.placeholder (empty placeholder — S7-02's job is to replace it with a real binary) + package.json + main.ts + regenerate.sh + README.md + .gitignore + .gitattributes.
  • No last-indexed-commit.txt. The fixture's last_indexed_commit lives only inside the materialized scip.json (substituted by regenerate.sh from _seed/scip-slice.template.json).
  • The current regenerate.sh already implements the "refuse-against-current-HEAD" guard: LAST_INDEXED="${LAST_INDEXED:-$(git rev-parse HEAD~1)}" defaults to HEAD~1; the if [[ "$LAST_INDEXED" == "$(git rev-parse HEAD)" ]]; then exit 1; fi block exits non-zero if forced.
  • The adversarial test at tests/adv/phase02/test_stale_scip_fixture.py reads .codegenie/context/raw/scip.json (the materialized template), NOT the binary scip-index.scip. The binary blob is for S4-03's future ScipIndexProbe, not S4-01's IndexHealthProbe.
  • The adversarial's outer-key invariant has widened since S4-02: it now asserts set(index_health.keys()) == {"scip", "runtime_trace"} (S5-05 widened it). Future S6-08 registrations may widen it further.

S7-01 precedent (HARDENED 2026-05-17). Two block-tier and seven harden-tier issues hardened. Most relevant patterns for this story: - pnpm/npm/node-gyp not allowlisted → hand-author lockfile bytes, regenerate.sh does NOT run any install command. - Shell can't call run_allowlisted (it's a Python function) → bash invokes allowlisted binaries directly; the AC-31 static check is the structural guarantee. - Closed-set enumeration via git ls-files <fixture-path> (not rglob) so gitignored artifacts don't dirty the test. - Shared _fixture_regen_allowlist.py lives at tests/unit/_fixture_regen_allowlist.py (Rule-of-Three carve-out for load-bearing-policy ownership; reusable unchanged by S7-02). - _ProbeName Literal uses subset semantics in AC-37 (not equality) so Phase-3+ probes added later don't retroactively break Phase-2 fixtures.

Ambiguities surfaced (resolved by edits).

  • AC-18 says "S4-02's stub already chose one path, this story honors it" — but AC-19/AC-20 then concretely prescribe last-indexed-commit.txt, which S4-02 did NOT choose. The actual stub uses _seed/scip-slice.template.json with PARENT_COMMIT substitution. Resolved: rewrite to honor the actual mechanism.
  • AC-15 says "the S4-02 stub directory + minimal SCIP blob is replaced wholesale by the full fixture" — but wholesale replacement destroys the working seed-template mechanism the adversarial relies on. Resolved: replacement is restricted to the _seed/scip-index.scip.placeholder binary (substituted with a real scip-typescript-built blob) plus an expanded src/ tree.
  • AC-17 says .codegenie/context/raw/scip-index.scip is committed with a .gitignore carve-out — conflicts with the stub's gitignored-.codegenie/ invariant and the central no-committed-.codegenie/cache/ guard from S7-01. Resolved: the real binary lives in _seed/scip-index.scip (replacing _seed/scip-index.scip.placeholder); regenerate.sh copies it into .codegenie/context/raw/scip-index.scip at runtime; .codegenie/ stays gitignored.
  • AC-10 says regenerate.sh is pnpm install --frozen-lockfilepnpm not allowlisted (ADR-0001). Resolved by mirroring S7-01's native-modules precedent — hand-author the pnpm-lock.yaml; no install at regen.
  • AC-21 step (b) says regen runs scip-typescript "(via run_allowlisted)" — bash can't call Python. Resolved by mirroring S7-01 AC-22 — bash calls scip-typescript directly; AC-31's static check is the structural guarantee.

Stage 2 — Critic reports (consolidated)

Coverage critic — Verdict: HARDEN

  • [block] C1 — AC-10 + Implementation Outline §1 prescribe pnpm install --frozen-lockfile in regenerate.sh; pnpmALLOWED_BINARIES (verified at src/codegenie/exec/__init__.py:96-111). Story's AC-31 ("regenerate.sh invokes only allowlisted binaries per fixture") would fail. Fix applied — AC-10 rewritten to require hand-authored bytes; Implementation Outline §1 rewritten; regen script is now mkdir/touch + invariant-assertions only (no install).
  • [block] C2 — Story prescribes last-indexed-commit.txt (AC-19, AC-20, content predicates _last_indexed_not_equal_to_current_head, _regen_refuses_current_head); actual S4-02 stub uses _seed/scip-slice.template.json with PARENT_COMMIT placeholder substitution. AC-18's "honor what S4-02 chose" makes the contradiction internal to the story. Fix applied — AC-18 rewritten to name the actual mechanism; AC-19 rewritten to describe the seed-template; AC-20 rewritten to point at the existing regenerate.sh guard (LAST_INDEXED="${LAST_INDEXED:-$(git rev-parse HEAD~1)}" + if [[ "$LAST_INDEXED" == "$(git rev-parse HEAD)" ]]; then exit 1; fi).
  • [block] C3 — AC-17 commits .codegenie/context/raw/scip-index.scip with a .gitignore carve-out; conflicts with the stub's gitignored-.codegenie/ pattern AND with S7-01's central no-committed-cache guard. Fix applied — AC-17 rewritten: the real binary lives in _seed/scip-index.scip (committed; replaces _seed/scip-index.scip.placeholder); .codegenie/ stays gitignored; regenerate.sh copies _seed/scip-index.scip to .codegenie/context/raw/scip-index.scip at runtime.
  • [block] C4 — AC-15 says "the S4-02 stub directory + minimal SCIP blob is replaced wholesale by the full fixture" — wholesale replacement destroys the seed-template mechanism the adversarial relies on. Fix applied — AC-15 rewritten as "the placeholder .scip blob in _seed/ is replaced with a real scip-typescript-built blob; the seed-template + regenerate-script mechanism is preserved; the source tree expands to ≤ 50 .ts files."
  • [block] C5 — AC-21 step (b) prescribes "runs scip-typescript (via run_allowlisted)" inside regenerate.sh. run_allowlisted is a Python function (src/codegenie/exec/__init__.py); bash cannot call it. Same architectural mismatch S7-01 AC-22 had. Fix applied — AC-21 split into "seed-build ritual" (one-time, contributor's local box, invokes scip-typescript directly; produces _seed/scip-index.scip) and "regenerate.sh runtime" (deterministic; no scip-typescript invocation at regen time; just copies the committed seed blob).
  • [harden] C6 — AC-32/AC-33 framing implies "full materialization makes the adversarial assertion non-trivially true" — but the adversarial reads .codegenie/context/raw/scip.json (the materialized template), not the binary .scip blob. The assertion is ALREADY non-trivially true against the stub (the templated scip.json carries PARENT_COMMIT, not HEAD). S7-02's actual contribution is producing a real binary SCIP for S4-03's future ScipIndexProbe, not changing what S4-02's adversarial currently passes/fails. Fix applied — AC-32/AC-33 reworded; the adversarial-still-passes claim is preserved; the binary-SCIP-for-S4-03 framing is made explicit.
  • [harden] C7 — Adversarial outer-key set has widened to {"scip", "runtime_trace"} (S5-05) since S4-02 landed; story still says the adversarial asserts only isinstance(slice.freshness, Stale) etc. — accurate but incomplete. Fix applied — AC-32 footnote acknowledges the wider outer-key set as a tripwire so a future contributor doesn't think their materialization PR broke an unrelated invariant.
  • [harden] C8 — No AC for the kernel module's own structural-export test. AC-23 requires mypy --strict but a silent removal of enumerate_tracked or one of the make_* factories would still pass mypy if the consumers are concurrently updated. Fix applied — AC-23 amended: kernel exposes a documented __all__ set; a test at tests/unit/test_shape_test_kernel.py asserts the export set matches the documented contract.

Test-Quality critic — Verdict: HARDEN

  • [harden] T1 — TDD-plan stale-scip _FILE_SPECS lists last-indexed-commit.txt and .codegenie/context/raw/scip-index.scip. Neither matches the actual stub mechanism. Fix applied_FILE_SPECS rewritten to list _seed/scip-slice.template.json, _seed/scip-index.scip (committed real binary), regenerate.sh, README.md, the source tree (package.json, tsconfig.json, src/*.ts).
  • [harden] T2 — Content predicates need rewrite to match the corrected mechanism:
  • _last_indexed_not_equal_to_current_head originally read last-indexed-commit.txt + runs git rev-parse HEAD. Rewrite: predicate reads regenerate.sh and asserts the line LAST_INDEXED="${LAST_INDEXED:-$(git rev-parse HEAD~1)}" is present (the structural guarantee that last_indexed defaults to HEAD~1, never HEAD).
  • _regen_refuses_current_head originally grepped for an if block. Rewrite: predicate greps regenerate.sh for [[ "$LAST_INDEXED" == "$(git rev-parse HEAD)" ]] (or equivalent) + the exit 1 branch.
  • _scip_blob_metadata_records_prior_commit originally read .codegenie/context/raw/scip-index.scip and parsed SCIP wire-format. Rewrite: predicate reads _seed/scip-index.scip (committed seed); asserts non-empty AND the file size is ≥ a documented minimum (e.g., ≥ 200 bytes, since a real scip-typescript output for ≥ 5 .ts files exceeds this; the placeholder is 0 bytes — so any non-empty seed blob passes the placeholder-replaced check).
  • _readme_documents_structural_assertion — preserves; checks for CommitsBehind.n >= 1 AND last_indexed != current_HEAD phrases.
  • [harden] T3 — AC-26 says the kernel exposes _ProbeName as the closed Phase-1 + Phase-2 set with a test that asserts runtime-equality; S7-01's hardened AC-37 uses subset semantics so Phase-3+ additions don't retroactively break Phase-2 fixtures. Inconsistency between the story and its sibling. Fix applied — AC-26 rewritten to use subset semantics (set(registered_phase_2_names) ⊆ set(get_args(_ProbeName))), matching S7-01.
  • [harden] T4 — Story has no AC for the _fixture_regen_allowlist.py reuse pattern. The shared module lives at tests/unit/_fixture_regen_allowlist.py per S7-01; the two new fixtures need corresponding allowlist tests (tests/unit/test_fixture_monorepo_pnpm_regenerate_allowlist.py + tests/unit/test_fixture_stale_scip_regenerate_allowlist.py). Fix applied — AC-31 amended to reference the shared module + add two test files to "Files to touch".
  • [harden] T5 — TDD-plan stale-scip _FILE_SPECS says the source tree has src/{a,b,c,d,e}.ts (5 trivial chain-import files), but the _seed/scip-slice.template.json content currently records "files_indexed": 1, "files_in_repo": 1. If the source tree grows to 5+ files, B2 will surface CoverageGap, not CommitsBehind. Fix applied — TDD plan amended: the seed template's files_indexed / files_in_repo counts must match the seeded source tree footprint (or the seeded SCIP must actually index all files). Added a content predicate _seed_template_counters_match_source_tree that asserts scip-slice.template.json's counts equal the count of *.ts files under src/.
  • [harden] T6 — Mutation table predicates _regen_refuses_current_head and _last_indexed_not_equal_to_current_head predicate intent statements need updating to match the corrected mechanism. Fix applied — mutation-resistance witness table rewritten to point at the corrected predicates and the corrected files they read.
  • [harden] T7 — AC-21 step list contains "seed-build ritual" steps and "regen-runtime" steps blended together. A reader would conclude regenerate.sh runs scip-typescript (block C5 above). Fix applied — AC-21 split into AC-21a (seed-build ritual; contributor-owned; one-time per tool-version bump) and AC-21b (regen-runtime; deterministic; no scip-typescript). Documented in README.md per AC-22.

Consistency critic — Verdict: HARDEN

  • [block] Cn1 — Same as C1 (pnpmALLOWED_BINARIES, ADR-0001). Source-of-truth (ADR-0001) wins; AC-10 + Implementation Outline §1 rewritten.
  • [block] Cn2 — Same as C2/C4/C5 — story's prescribed stale-scip mechanism contradicts the existing stub (which IS the source of truth per AC-18). All four contradictions resolved.
  • [harden] Cn3 — AC-26's runtime-equality test contradicts S7-01's HARDENED AC-37 (subset semantics). Fix applied — subset semantics (T3 above).
  • [harden] Cn4 — Kernel location. AC-23 says tests/fixtures/portfolio/_shape_test_kernel.py. AC-25 says Phase 1's test_fixture_node_typescript_helm_shape.py migrates to consume the kernel; Phase 1's fixture lives at tests/fixtures/node_typescript_helm/ (NOT under portfolio/). The Phase 1 test would import across the portfolio/-namespace boundary, which is awkward. Fix applied — kernel relocated to tests/fixtures/_shape_test_kernel.py (above the portfolio/ subdirectory) so all six consumers can import without crossing fixture-namespace boundaries.
  • [harden] Cn5 — Implementation Outline §4 says the central no-cache-committed test (tests/unit/test_no_committed_codegenie_cache_under_portfolio_fixtures.py) needs to be updated to allowlist stale-scip/.codegenie/context/raw/scip-index.scip. With the corrected design (C3 above — .codegenie/ stays gitignored; the real binary lives in _seed/), no carve-out is needed; the existing S7-01 guard passes unchanged. Fix applied — AC-29 amended; Implementation Outline §4 rewritten.

Design-Patterns critic — Verdict: HARDEN

  • [harden] DP1 — Kernel timing is correct (6 consumers; conclusively past Rule of Three). Kernel structure is sound. Endorsed.
  • [harden] DP2make_*_test factory pattern returns pytest test functions for module-level assignment. This is awkward for pytest's natural module-level @pytest.mark.parametrize discovery; a flatter pattern is to export pure helper functions (assert_file_exists(fixture, spec), assert_file_parses(fixture, spec), etc.) and let each consumer module write minimal @pytest.mark.parametrize test bodies. Recommended as Notes-for-implementer (not promoted to AC — pattern advice is contextual per the validator skill prescription). Fix applied — added to Notes-for-implementer.
  • [harden] DP3 — Kernel cohesion question: should _fixture_regen_allowlist.py (S7-01's shared module at tests/unit/) be subsumed into the kernel? Different responsibilities (closed-set discovery vs. allowlist policy) argue for keeping them separate; module sizes are small; consumers import both today. Endorse keeping them separate; documented in Notes-for-implementer.
  • [harden] DP4_FileSpec immutability. S2-03's precedent is NamedTuple (immutable by construction). The kernel should preserve this. Endorse; added to Notes-for-implementer.
  • [harden] DP5 — Kernel's enumerate_tracked should be the only call site for git ls-files <fixture-path> (port-and-adapter discipline — the subprocess port is encapsulated; consumers receive a tuple[str, ...] of relpaths). Already implicit in AC-23; made explicit in Notes-for-implementer.
  • [nit] DP6_SHELL_COREUTILS_ALLOWLIST lives in _fixture_regen_allowlist.py (S7-01). The kernel does NOT take ownership; consumers of the regen-allowlist test import from _fixture_regen_allowlist, consumers of the shape test import from _shape_test_kernel. Two flat modules; no cross-coupling. Endorsed.
  • [harden] DP7_ProbeName Literal location. Currently each shape test declares its own _ProbeName; with the kernel, _ProbeName lives there (single source of truth). Story's AC-23 already prescribes this — good. Note for implementer: _ProbeName is exported from the kernel's __all__ so all six consumers reference it via from tests.fixtures._shape_test_kernel import _ProbeName. AC-26 amended to make this explicit.

Stage 3 — Research

Skipped — no critic finding tagged NEEDS RESEARCH. All fixes have direct precedents: - pnpm/install-discipline pattern: S7-01 native-modules HARDENED precedent (hand-authored pnpm-lock.yaml). - scip-typescript bash-direct-invocation: S7-01 AC-22 HARDENED precedent (bash invokes docker directly). - Closed-set via git ls-files: S7-01 AC-26 HARDENED precedent. - Subset semantics for _ProbeName: S7-01 AC-37 HARDENED precedent. - Seed-template + _seed/ mechanism: existing S4-02 stub at tests/fixtures/portfolio/stale-scip/_seed/. - Shared regen-allowlist module: S7-01 _fixture_regen_allowlist.py HARDENED precedent.

Stage 4 — Synthesizer + conflict resolution

No critic conflicts. All four lenses agree on the directional fixes. The block-tier set is mechanically identical to S7-01's pattern; the harden-tier set tightens cross-story consistency, kernel cohesion, and the mechanism-vs-stub alignment.

Edits applied to the story (in order):

  1. Status: Ready → HARDENED (validated 2026-05-18).
  2. Inserted ## Validation notes (2026-05-18) block under header documenting every change.
  3. Depends on: annotated with the S7-01 HARDENED precedents (regen-allowlist module + lockfile-hand-author pattern).
  4. monorepo-pnpm block: AC-10 rewritten — hand-authored pnpm-lock.yaml bytes; regenerate.sh does NOT invoke pnpm install (consistent with S7-01 native-modules).
  5. stale-scip block: AC-15 rewritten — placeholder-replacement, not wholesale stub replacement; the seed-template + regenerate-script mechanism is preserved.
  6. AC-17 rewritten — real binary SCIP lives in _seed/scip-index.scip (replaces _seed/scip-index.scip.placeholder); .codegenie/ stays gitignored; no .gitignore carve-out.
  7. AC-18 rewritten — explicitly names the actual stub mechanism (seed-template + gitignored .git/ + gitignored .codegenie/); drops the "implementer picks" hedge.
  8. AC-19 rewritten — last_indexed mechanism is _seed/scip-slice.template.json's last_indexed_commit field, substituted at regen time; no last-indexed-commit.txt file.
  9. AC-20 rewritten — the existing regenerate.sh guard already implements refusal; story preserves + documents the guard rather than prescribing a new mechanism.
  10. AC-21 split into AC-21a (seed-build ritual; contributor-owned; one-time per tool-version bump) and AC-21b (regen-runtime; deterministic; no scip-typescript invocation at regen time).
  11. AC-22 amended — README sections preserve the existing stub's prose; story is additive (Phase-3 entry-gate handoff note, structural-assertion section, seed-build ritual section).
  12. AC-23 amended — kernel relocated to tests/fixtures/_shape_test_kernel.py (above portfolio/); kernel exposes documented __all__ set; test at tests/unit/test_shape_test_kernel.py asserts export set matches contract.
  13. AC-26 rewritten — subset semantics (matching S7-01 AC-37); explicit phrasing set(registered_phase_2_names) ⊆ set(get_args(_ProbeName)).
  14. AC-28 rewritten — closed-set complement test for stale-scip enumerates via git ls-files from the parent repo (gitignored .git/ and .codegenie/ don't appear); no include_paths carve-out.
  15. AC-29 amended — the existing S7-01 central no-cache-committed guard passes unchanged; no allowlist edit needed.
  16. AC-31 amended — references tests/unit/_fixture_regen_allowlist.py (S7-01's shared module); adds two new regen-allowlist test files (test_fixture_monorepo_pnpm_regenerate_allowlist.py, test_fixture_stale_scip_regenerate_allowlist.py).
  17. AC-32 reworded — adversarial assertion has been passing since S4-02 against the seed-templated scip.json; S7-02's contribution is the real binary SCIP for S4-03's future consumer, not changing what S4-02's adversarial asserts. Footnote acknowledges the widened outer-key set ({"scip", "runtime_trace"} since S5-05).
  18. AC-33 reworded — preserves the structural assertion framing; ties to S4-02's existing test code rather than implying this story introduces the inequality.
  19. TDD plan — _FILE_SPECS for stale-scip rewritten to list the actual files (_seed/scip-slice.template.json, _seed/scip-index.scip, etc.).
  20. TDD plan — content predicates rewritten for the corrected mechanism (_last_indexed_not_equal_to_current_head reads regenerate.sh; _regen_refuses_current_head greps the guard; _scip_blob_metadata_records_prior_commit reads _seed/scip-index.scip non-emptiness; new predicate _seed_template_counters_match_source_tree).
  21. Mutation-resistance witness table updated — predicates and files corrected.
  22. Implementation Outline §1 rewritten — no pnpm install invocation; hand-authored lockfile.
  23. Implementation Outline §2 rewritten — explicitly preserves the existing stub mechanism; the seed-build ritual is OUT-OF-BAND (contributor's local box; not in regenerate.sh).
  24. Implementation Outline §3 amended — kernel location at tests/fixtures/_shape_test_kernel.py; consumers' import paths spelled out.
  25. Implementation Outline §4 rewritten — no central no-cache-committed test edit needed (the existing guard passes unchanged).
  26. Files-to-touch table — added tests/unit/_fixture_regen_allowlist.py consumers (the two new allowlist tests); added tests/unit/test_shape_test_kernel.py; kernel path updated; last-indexed-commit.txt removed; .codegenie/context/raw/scip-index.scip .gitignore` carve-out removed.
  27. Notes for the implementer — added paragraphs on:
    • Kernel factory pattern alternatives (flatter helper-function style vs. test-factory style — recommended flatter style for pytest discovery naturalness, but not mandated).
    • Why _fixture_regen_allowlist.py and _shape_test_kernel.py are separate modules.
    • enumerate_tracked as the kernel's port for git ls-files.
    • _FileSpec is a frozen NamedTuple.
    • Why the seed-template mechanism is preserved (S4-02's adversarial reads scip.json, not the binary blob; the binary lives in _seed/ for S4-03's future consumer).

ACs touched: 12 modified (AC-10, AC-15, AC-17, AC-18, AC-19, AC-20, AC-22, AC-23, AC-26, AC-28, AC-29, AC-31, AC-32, AC-33). AC-21 split into AC-21a + AC-21b. Total ACs net: 36 → 37.

Final verdict

HARDENED. The story now:

  • Conforms structurally to ADR-0001 (no silent pnpm/npm/node-gyp expansion); the monorepo-pnpm regen script is mkdir/coreutils-only.
  • Removes the bash-calls-Python architectural mismatch (the scip-typescript invocation moves to the contributor-owned seed-build ritual, OUT-OF-BAND).
  • Aligns with the actual S4-02 stub mechanism (seed-template + gitignored .git/ + gitignored .codegenie/); no contradictory last-indexed-commit.txt prescription.
  • Preserves the seed-template mechanism the adversarial test relies on; clarifies the binary-SCIP contribution as S4-03-forward-looking, not S4-02-assertion-changing.
  • Adopts subset semantics for the kernel's _ProbeName Literal, matching S7-01 (Phase-3+ probes won't retroactively break Phase-2 fixtures).
  • Relocates the kernel to tests/fixtures/_shape_test_kernel.py so all six consumers (incl. Phase 1's node_typescript_helm/) import cleanly across the fixture-namespace boundary.
  • Specifies the kernel's __all__ contract as a runtime check (silent export removal becomes a build error).
  • Defers the factory-vs-helper kernel pattern choice to the implementer with a Notes-for-implementer recommendation (flatter helper-function style is more pytest-natural).
  • Preserves S7-01's shared _fixture_regen_allowlist.py policy ownership unchanged; adds two new consumer tests for monorepo-pnpm + stale-scip.

Ready for phase-story-executor.