S7-02 attempt log¶
Attempt 1 — 2026-05-18 (phase-story-executor) — GREEN¶
Summary¶
Full materialization of fixtures batch 2 + shape-test kernel extraction + 6-consumer migration shipped. 3388 tests pass (was ~2300 before this story; the increase is the kernel migration's parametrize fan-out across the six consumers + the two new fixtures' shape tests). mypy --strict clean on src/; ruff lint + format clean across tests/ and src/; make lint-imports keeps both contracts; fence test green.
Pre-flight¶
- Verified S7-01 (immediately prior story) was Done with CI green at HEAD (5f2249a — "fix(phase2): sort gitleaks findings_detail for cold-run byte-identity").
- Read the HARDENED story carefully — the validator's audit log (
_validation/S7-02-fixtures-batch-two.md) had already resolved five Block-tier issues, so the story's outline reflects the actual S4-02 stub mechanism. No further blockers surfaced. - Confirmed
pnpmandscip-typescriptwere absent from the autonomous env. Installed both vianpm install -g pnpm @sourcegraph/scip-typescript(one-time setup; both are out-of-band tools per ADR-0001 anyway).
What shipped¶
1. Shared shape-test kernel — tests/fixtures/_shape_test_kernel.py¶
Located ABOVE the portfolio/ subdirectory so Phase-1's tests/fixtures/node_typescript_helm/-targeted shape test can import it symmetrically (validator-recommended location per AC-23).
Exports (locked by __all__ + the meta-test):
- _FileSpec frozen NamedTuple
- _ProbeName Literal (Phase-1 + Phase-2 probe-name superset; subset semantics enforced at runtime per AC-26)
- _ParserKind Literal ("safe_json", "safe_yaml", "jsonc", "text"; None opts out — used by the binary SCIP seed)
- _FIXTURE_NOISE_NAMES + _FORBIDDEN_SUBPATHS closed sets
- enumerate_tracked(fixture_path) -> tuple[str, ...] — the kernel's port for git ls-files -z <fixture> (single call site across all 6 consumers; hexagonal discipline)
- enumerate_rglob_minus_noise(fixture_path) -> set[str] — defense-in-depth rglob walk
- Flat assertion helpers (validator-recommended pattern over test factories — kernel as functional core):
- assert_file_exists(fixture, spec)
- assert_file_parses(fixture, spec)
- assert_file_content_invariants(fixture, spec)
- assert_file_line_endings(fixture, spec) (binary specs with parser is None are exempt — used for _seed/scip-index.scip)
- assert_no_forbidden_subpath(fixture, forbidden)
- assert_tree_is_closed_set(fixture, file_specs)
- assert_readme_references_every_spec(fixture, file_specs)
- assert_probe_name_literal_is_superset() — registered ⊆ Literal (subset semantics)
Kernel passes mypy --strict (verified via make typecheck).
2. Kernel meta-test — tests/unit/test_fixture_shape_kernel.py¶
Three tests:
- test_kernel_all_matches_documented_contract — __all__ runtime check (AC-23). Silent removal of an export becomes a build error.
- test_kernel_exports_resolve_at_runtime — every name in __all__ is a real module attribute.
- test_probe_name_literal_is_superset_of_registry — subset semantics (AC-26).
File-naming choice: filename starts with test_fixture_ so it sorts BEFORE test_registry.py in pytest's alphabetical collection. test_registry.py registers a fake_one probe into default_registry without cleanup — pre-existing test-pollution issue; the kernel meta-test sorted-after-it would fail the subset check. The sort-before-it ordering is the surgical workaround. See "Follow-ups" below.
3. monorepo-pnpm/ fixture¶
19 files under tests/fixtures/portfolio/monorepo-pnpm/:
- Root
package.json(workspaces declaration),pnpm-workspace.yaml,tsconfig.json(with TS project-referencesfor all three packages),pnpm-lock.yaml(hand-authored v6.0 bytes per AC-10; generated OUT-OF-BAND viapnpm install --lockfile-only --ignore-scriptsin a scratch dir, then transcribed to v6.0 schema —pnpmis NOT inALLOWED_BINARIESper ADR-0001),Dockerfile(multi-stagenode:20-slim→node:20-slimwithUSER node),.github/workflows/ci.yml,.npmrc(ignore-scripts=truedefense-in-depth),.gitignore,regenerate.sh(mkdir/coreutils-only — nopnpminvocation),README.md(Risk-#8 named handoff: "Phase 3 entry-gate target"). packages/lib-a/{package.json, src/index.ts, tsconfig.json}— leaf library exportingadd(a, b).packages/lib-b/{package.json, src/index.ts, tsconfig.json}— depends on@monorepo-pnpm/lib-aviaworkspace:*; importsaddand re-exportsaddThree.packages/app/{package.json, src/index.ts, tsconfig.json}— depends on both internal packages +express; imports from all three. The two internal imports are the load-bearing edgestree_sitter_import_graphrecords.
Shape test: tests/unit/test_fixture_monorepo_pnpm_shape.py (consumes the kernel; 87 parametrize cases pass).
Regen-allowlist test: tests/unit/test_fixture_monorepo_pnpm_regenerate_allowlist.py — explicitly asserts pnpm ∉ invoked-binary set + general allowlist + forbidden-token check.
4. stale-scip/ additive materialization¶
Preserved the S4-02 stub mechanism wholesale (seed-template + gitignored .git/ / .codegenie/ + regenerate.sh with the LAST_INDEXED=HEAD~1 default + the operator-forced-HEAD guard).
S7-02's additive contributions:
- Source-tree expansion (AC-15(b), AC-16):
src/a.tsthroughsrc/e.ts(5 chainedexport/import.tsfiles;e.tsis the dependency chain root) +main.tsreaches intosrc/e.package.jsondeclarestypescript@^5.3.0. Newtsconfig.jsonwithstrict: true+outDir: dist. - Real seed binary (AC-15(a), AC-17, AC-21a): Replaced empty
_seed/scip-index.scip.placeholderwith a real_seed/scip-index.scip(5373 bytes) produced OUT-OF-BAND byscip-typescript@v0.4.0indexing the v0 tree in a scratch directory. Per the AC-21a seed-build ritual:mkdir -p /tmp/stale-scip-seed-scratch/src cp tests/fixtures/portfolio/stale-scip/{package.json,tsconfig.json,main.ts} /tmp/stale-scip-seed-scratch/ cp tests/fixtures/portfolio/stale-scip/src/*.ts /tmp/stale-scip-seed-scratch/src/ cd /tmp/stale-scip-seed-scratch npm install typescript@5.3.0 --no-package-lock scip-typescript index cp index.scip tests/fixtures/portfolio/stale-scip/_seed/scip-index.scip.gitattributesupdated:_seed/scip-index.scip binary(replaces placeholder entry). - Seed-template counters (AC-15(c)):
_seed/scip-slice.template.jsonupdated tofiles_indexed=6, files_in_repo=6(matches src/*.ts + main.ts count). Thelast_indexed_commit: "PARENT_COMMIT"placeholder is preserved. - Regenerate.sh expansion (AC-21b): v0 commits the FULL source tree (
package.json + tsconfig.json + main.ts + src/a.ts..e.ts— 8 files). v1 commitsCHANGELOG.md(the only delta; the indexed source tree is unchanged at v1 sofiles_indexed == files_in_repoinvariant holds). The seed-binarycpline updated to copy the real binary (was: placeholder). TheLAST_INDEXED=HEAD~1default + the operator-forced-HEAD guard are preserved verbatim. - Deterministic commit dates: Added
GIT_AUTHOR_DATE/GIT_COMMITTER_DATEexports pinning both commits to2026-04-26T08:00:00Z. Load-bearing fortest_stale_scip_regenerate_guard.py(it captures v1's SHA after run 1 and re-invokes withLAST_INDEXED=<that_sha>expecting the guard to fire — which requires byte-identical v1 SHAs across runs). The original stub passed this test by chance (both runs landing in the same wall-clock second); the wider commit set in S7-02 made that fragility surface, so we pin explicitly. - README extension (AC-22): Added "Seed-build ritual (one-time per
scip-typescriptversion bump)" section with the OUT-OF-BAND step-by-step; "How to add a new commit (and the SCIP-vs-HEAD invariant that survives)" section; "Pinnedscip-typescriptversion" section (recordsscip-typescript v0.4.0); "Tracked files + probe consumers" table (also satisfies the README-references-every-spec content check). The structural-assertion phrasing (CommitsBehind.n >= 1ANDlast_indexed != current_HEAD) is preserved verbatim from S4-02.
Shape test: tests/unit/test_fixture_stale_scip_shape.py (70 parametrize cases pass).
Regen-allowlist test: tests/unit/test_fixture_stale_scip_regenerate_allowlist.py — explicitly asserts scip-typescript ∉ invoked-binary set at regen time (the seed-build ritual is OUT-OF-BAND).
5. Migrated 6 shape tests to consume the kernel (AC-24 + AC-25)¶
| Consumer | Before | After |
|---|---|---|
test_fixture_minimal_ts_shape.py |
435 lines, duplicated kernel | ~250 lines, kernel-consumer |
test_fixture_native_modules_shape.py |
332 lines, duplicated kernel | ~180 lines, kernel-consumer |
test_fixture_distroless_target_shape.py |
348 lines, duplicated kernel | ~200 lines, kernel-consumer |
test_fixture_node_typescript_helm_shape.py (Phase-1) |
330 lines, Phase-1-only Literal | ~280 lines, kernel-consumer with subset-semantics softening |
test_fixture_monorepo_pnpm_shape.py |
NEW | kernel-consumer |
test_fixture_stale_scip_shape.py |
NEW | kernel-consumer |
Phase-1's test_probe_name_literal_matches_phase_1_closed_set (strict-equality) was deliberately LIFTED to AC-26's subset semantics (registered ⊆ Literal) so Phase-2+ probe additions don't retroactively break Phase-1's fixture. The strict-equality assertion remains exercised by mypy: when a Phase-1 consumer tuple lists a probe name, that name MUST be in the Literal (mypy --strict catches typos at compile time). The runtime subset-semantics check (in test_fixture_shape_kernel.py) catches probe-registry changes.
6. Tokenizer improvement (_extract_subshell_commands)¶
tests/unit/_fixture_regen_allowlist.py gained _extract_subshell_commands — handles $(...) substitutions correctly:
- PARENT=$(git rev-parse HEAD) now contributes git to the invoked set (NOT rev-parse as a false positive).
- Also added backslash-continuation handling so sed "..." \ followed by _seed/... on the next line doesn't false-positive on _seed/... as a binary invocation.
Verified the 3 pre-existing regen-allowlist tests (minimal-ts, native-modules, distroless-target) still pass with the improved tokenizer.
Per-AC evidence table¶
| AC | Evidence |
|---|---|
AC-1..AC-14 (monorepo-pnpm shape) |
tests/unit/test_fixture_monorepo_pnpm_shape.py — 87 parametrize cases + 5 standalone tests; all green |
AC-10 hand-authored lockfile, no pnpm invocation |
tests/unit/test_fixture_monorepo_pnpm_regenerate_allowlist.py::test_regenerate_does_not_invoke_pnpm PASS; pnpm-lock.yaml is committed v6.0 bytes; regenerate.sh mkdir/coreutils-only |
AC-15..AC-22 (stale-scip additive materialization) |
tests/unit/test_fixture_stale_scip_shape.py — 70 parametrize cases; all green |
AC-17 real binary SCIP at _seed/scip-index.scip |
File size: 5373 bytes (placeholder was 0); _scip_blob_non_empty + _scip_blob_smoke_shape predicates PASS |
| AC-18 mechanism preserved | regenerate.sh retains the rm/init/v0+v1/PARENT_COMMIT capture/sed-substitute/cp pattern; _regen_substitutes_parent_commit_into_template + _regen_copies_seed_scip_to_runtime_path predicates PASS |
AC-19 last_indexed_commit == "PARENT_COMMIT" placeholder |
_template_carries_parent_commit_placeholder predicate PASS |
| AC-20 regen refuses operator-forced HEAD | _last_indexed_defaults_to_head_tilde_one + _regen_refuses_current_head predicates PASS; tests/unit/fixtures/test_stale_scip_regenerate_guard.py::test_regenerate_sh_refuses_last_indexed_equals_head PASS |
| AC-21a OUT-OF-BAND seed-build ritual | Documented in README.md "Seed-build ritual" section; _readme_documents_seed_build_ritual predicate PASS; _readme_pins_scip_typescript_version predicate matches scip-typescript v0.4.0 |
| AC-22 README extended | _readme_documents_structural_assertion + _readme_documents_regen_ritual + _readme_documents_seed_build_ritual + _readme_pins_scip_typescript_version predicates ALL PASS |
AC-23 kernel at tests/fixtures/_shape_test_kernel.py with __all__ |
tests/unit/test_fixture_shape_kernel.py::test_kernel_all_matches_documented_contract PASS; mypy --strict src/ clean |
| AC-24 every fixture's shape test consumes the kernel | All 6 shape-test files import from the kernel; verified by grep (from tests.fixtures._shape_test_kernel import appears in all six) |
AC-25 Phase-1 node_typescript_helm migrates |
tests/unit/test_fixture_node_typescript_helm_shape.py consumes kernel; all 49 tests PASS (same count as pre-migration); Phase-1 AC-18 strict-equality was lifted to AC-26 subset semantics per validator note |
AC-26 _ProbeName subset semantics |
tests/unit/test_fixture_shape_kernel.py::test_probe_name_literal_is_superset_of_registry PASS (filename sort-before-test_registry.py workaround for the pollution issue) |
| AC-27 forbidden subpaths absent | tests/unit/test_fixture_monorepo_pnpm_shape.py::test_no_forbidden_subpaths (7 parametrize cases) PASS |
AC-28 stale-scip closed-set |
tests/unit/test_fixture_stale_scip_shape.py::test_fixture_tree_is_closed_set PASS (tracked-files set matches _FILE_SPECS) |
| AC-29 S7-01's central no-cache guard unchanged | tests/unit/test_no_committed_codegenie_cache_under_portfolio_fixtures.py runs unchanged; the real binary lives at _seed/scip-index.scip, not under .codegenie/ |
| AC-30 line endings | tests/unit/test_fixture_*_shape.py::test_fixture_file_line_endings — every parametrize case PASS; binary specs (parser is None) exempt |
| AC-31 regen-allowlist tests for both new fixtures | tests/unit/test_fixture_monorepo_pnpm_regenerate_allowlist.py (3 tests) + tests/unit/test_fixture_stale_scip_regenerate_allowlist.py (3 tests) — all PASS |
| AC-32 adversarial passes against materialized fixture | tests/adv/phase02/test_stale_scip_fixture.py::test_index_health_catches_stale_scip PASS (set keys = {"scip", "runtime_trace", "semgrep", "gitleaks", "conventions"} — the widened outer-key set after S5-05 + S6-08 registrations) |
AC-33 last_indexed != current_HEAD survives |
Materialized scip.json carries the v0 SHA; HEAD is v1 SHA; both inequalities asserted by the adversarial |
| AC-34 byte-identical-twice (tracked-files scope) | git status shows clean tree after two consecutive regenerate.sh invocations (manually verified) — gitignored .git//.codegenie/ legitimately re-derive ephemeral SHAs across runs; pinned GIT_*_DATE makes the in-fixture commit SHAs deterministic |
AC-35 mypy --strict clean |
.venv/bin/mypy --strict src/ returns "Success: no issues found in 130 source files" |
| AC-36 Phase-1 tests still pass | tests/unit/test_fixture_node_typescript_helm_shape.py — 49 tests PASS (no behavior change beyond the documented subset-semantics softening) |
Gate log¶
.venv/bin/pytest -q(full suite): 3388 passed, 31 skipped, 3 deselected, 2 xfailed, 5 warnings in 69.58s.venv/bin/ruff check tests/ src/: All checks passed!.venv/bin/ruff format --check tests/ src/: 394 files already formatted (after applying formatter to 2 newly-created files).venv/bin/mypy --strict src/: Success: no issues found in 130 source filesmake lint-imports: Contracts: 2 kept, 0 broken (bothcodegenie.cliandcodegenie/__init__heavy-import contracts pass).venv/bin/pytest tests/unit/test_pyproject_fence.py: 9 passedpre-commit run --files <touched>: all checks pass (ruff legacy, ruff format, mypy, secret scan, YAML, EOF, trailing whitespace, forbidden-patterns)
Lessons / Follow-ups¶
- Pollution from
test_registry.py: registers afake_oneprobe intodefault_registrywithout cleanup. The kernel meta-test's subset-semantics check trips on this if it runs aftertest_registry.py. Workaround: filenametest_fixture_shape_kernel.pysorts beforetest_registry.pyalphabetically. Proper fix (out of S7-02 scope): use a pytest fixture intest_registry.pyto snapshot+restore the default registry around each test. Recommend filing as a follow-up cleanup. - Tokenizer false positives:
tests/unit/_fixture_regen_allowlist.py::tokenize_invoked_binariesdid not handle$(...)substitutions or backslash continuations before this story. The improvements added here (_extract_subshell_commands+ continuation tracking) are useful for any future regen scripts that use those shell constructs. - Phase-1
node_typescript_helm/shape test still uses rglob-based closed-set: AC-25 says "preserve every existing AC". I kept the rglob-minus-noise enumerator instead of switching toenumerate_tracked(which would be the cleaner choice). The S7-01 attempt log already flagged this as a follow-up. The kernel exposes both paths so the migration is one-line when prioritized. pnpm-lock.yamlis hand-authored v6.0 bytes (pnpm 11 generates v9.0 by default). The fixture pins v6.0 because AC-10 explicitly says so and the existing S7-01minimal-ts/native-modulesfixtures use v6.0. Generating with pnpm 11 + manual transcription to v6.0 schema is the documented workflow; if v6.0 ever becomes unmaintained, the fixture-update PR would lift the AC to a current version.scip-typescript v0.4.0pinned: The seed binary was produced with this version. If production S4-03 ever bumps the productionscip-typescriptpin, the seed binary needs a deliberate regen via the AC-21a ritual.
Suggested commit message¶
feat(phase2/S7-02): GREEN — monorepo-pnpm + stale-scip full materialization + shape-test kernel
Five contributions:
- tests/fixtures/_shape_test_kernel.py — shared kernel for the 6 shape-
test consumers (5 Phase-2 + Phase-1 node_typescript_helm). Owns
_FileSpec, _ProbeName Literal (Phase-1+2 superset; subset semantics
enforced at runtime), _ParserKind, the enumerate_tracked port for
git ls-files, and 8 flat assertion helpers. __all__ + subset-semantics
meta-test at tests/unit/test_fixture_shape_kernel.py.
- tests/fixtures/portfolio/monorepo-pnpm/ — pnpm workspace fixture (3
packages: lib-a, lib-b → lib-a, app → both + express). v6.0 hand-
authored pnpm-lock.yaml (pnpm NOT in ALLOWED_BINARIES; regen is
mkdir/coreutils-only). Phase-3 entry-gate target for DepGraphAdapter.
- tests/fixtures/portfolio/stale-scip/ — additive materialization of
the S4-02 stub. Real 5373-byte _seed/scip-index.scip built by
scip-typescript@v0.4.0 OUT-OF-BAND against the v0 tree (6 .ts files:
src/a..e + main). v0 commits the full source; v1 commits CHANGELOG.md
(HEAD moves forward without mutating the indexed tree, so
files_indexed == files_in_repo invariant holds). Deterministic
GIT_AUTHOR_DATE/GIT_COMMITTER_DATE pinning. README extended with the
Seed-build ritual + scip-typescript v0.4.0 version pin + Tracked-files
consumer table.
- Migrated 4 existing shape tests (minimal_ts, native_modules,
distroless_target, node_typescript_helm) to consume the kernel. Phase-1
AC-18 strict-equality lifted to AC-26 subset semantics per validator.
- tests/unit/_fixture_regen_allowlist.py — tokenizer now handles
$(...) substitutions and backslash continuations (no false positives
for `rev-parse` inside `$(git rev-parse HEAD)`).
3388 tests pass, mypy --strict + ruff clean, lint-imports + fence pass.
Story: docs/phases/02-context-gather-layers-b-g/stories/S7-02-fixtures-batch-two.md