Skip to content

fix(cli): meta.json gains the language dimension — a second scan at the same sha preserves the first's record (#664) - #676

Open
gadievron wants to merge 8 commits into
masterfrom
fix/664-meta-language-dim
Open

gadievron wants to merge 8 commits into
masterfrom
fix/664-meta-language-dim

Conversation

@gadievron

@gadievron gadievron commented Sep 22, 2026

Copy link
Copy Markdown
Collaborator

What

The sha-level meta.json now lives at <run-dir>/<language>/meta.json (one per language per SHA), so a second scan at the same SHA with a different language preserves the first scan's terminal record. Closes #664.

How

  • The keying contract: the scan's writer and finalizer key on the run's effective language (-l flag, else the project pin) — the same string meta.Language stamps. scan -l go on a python-pinned project writes and finalizes go/meta.json; python's record is untouched.
  • The adoption guard: resolveScanMode adopts init's pending decision only when its language matches the run's (or is legacy).
  • The walk union: LatestScanMeta treats per-language and legacy sha-level records as a union of candidates. A failed/running per-language record no longer shadows a legacy success at the same SHA (the upgrader's last known-good scan). A legacy copy is dropped only when a per-language success of the same owning language exists (the migrated form of the same run).
  • The init write site (init.go): the meta write is extracted into writeInitScanMeta, which derives its language from project.Language (never a separate argument — an adjacent same-type arg pair is a silent swap surface). This was the fix-linkage gate's own finding: the inline write site had no test that fails when its key is wrong.
  • Path validation: the language is validated as a single safe path element before it becomes one.

Review cycle

Two adversarial dispatch rounds (recorded in the run's receipts) plus a blind re-derivation lane and a deep-code re-derivation lane (run as separate consultations; their findings — the legacy shadow, the wrong-record write, the keying invariant — drove the change-set). A delta round then caught gaps in the change-set itself. A final artifacts-only audit re-ran the mutations and re-derived the collision checks; every mutation it ran was caught by a named guard.

Testing

go build ./..., go vet, and go test ./internal/config/ ./cmd/ ./internal/git/ — 205 test results, 0 failed (full-output execution receipt). Eight behavioral guards, each mutation-verified (the fix's logic mutated with the API kept → the named test fails; revert-based linkage does not apply here because the fix IS a signature change — reverting any single hunk breaks the test files' compilation against the old API, which is recorded as compile-failure companions in the linkage receipt): two-languages-same-SHA, init→scan adoption round-trip, the override writing its own record, the adoption guard, init's write site, legacy-not-shadowed (cross-language and same-language), migrated-legacy dedup, path traversal rejection.

Coordination

Known out-of-scope (pre-existing, flagged for the maintainer)

  • Artifact-dir routing still follows the pinned language (ctx.ScanDir), so scan -l go on a python pin lands artifacts under python/ while the meta lands under go/ — adjacent to this issue's artifact-separation note, predates this PR.
  • init -l does not validate against languages.Supported(); config.ScanDir's language path is likewise unvalidated (the meta path is validated here).
  • A language-mismatched scan (no mode flags) now re-decides its mode instead of adopting init's pending decision — the base/scope are language-agnostic git facts, so if the maintainer prefers adoption-across-languages, the guard can be relaxed consciously.
  • No end-to-end runInitrunScan test exists (os.Exit + cobra + git); the init wiring is covered by the swap-free helper signature + review.

…he same sha preserves the first's record (#664)

The sha-level meta.json was keyed (project, shortSHA) with no language
dimension, while the scan artifacts it describes are separated by
language. A second scan at the same sha (re-init with a different -l)
replaced the record — the earlier scan's terminal status was destroyed,
and a successful scan stopped being reachable as a baseline even though
its artifacts were intact. A successful python scan followed by a failed
go scan left the project with no resolvable success.

Fix: meta.json lives at <run-dir>/<language>/meta.json (per-language).
SaveScanMeta/LoadScanMeta/FinalizeScanMeta carry the language parameter.
LatestScanMeta walks the language subdirs first, falling back to the
legacy sha-level path (pre-#664 back-compat).

Fixes #664

Agent: ISSUES-TO-PR ses_f3e7372a5ffe3b2I6lP4Wm3w2d
@gadievron

Copy link
Copy Markdown
Collaborator Author

Status: ON HOLD — do not merge.

The adversarial review (2026-09-22) found defects in this PR that need rewriting before it can merge. The specific finding is in the review record. The PR is being rewritten; the current branch and all evidence are preserved.

Agent: ISSUES-TO-PR ses_f3e7372a5ffe3b2I6lP4Wm3w2d

t added 2 commits September 22, 2026 17:06
…on read/write contract fixed (#664)

Rewrite of the previous attempt (which broke test compilation on all 3
OSes and left the production readers on the legacy path while the writers
used the language path — the init→scan handoff was silently broken).

Fixes:
1. All 16 test call sites migrated to the 4-arg SaveScanMeta /
   3-arg LoadScanMeta / 4-arg FinalizeScanMeta signatures.
2. The production readers (resolveStepDiffOpts, resolveScanMode) now use
   ctx.Project.Language (the same key the writers use) — the init→scan
   round-trip works.
3. The path assertion updated (the per-language path, not the sha-level).
4. Four new behavioral tests: two-languages-same-SHA (the issue's core
   case), init→scan round-trip (the read/write contract), finalize
   flips running→success, the legacy fallback (upgrade story).

CI was red on all 3 OSes behind a green 'go build' — the go build gate
(G3) now catches this class.

Fixes #664

Agent: ISSUES-TO-PR ses_f3e7372a5ffe3b2I6lP4Wm3w2d
…the reader key (#664)

F1: scan.go's finalize and SaveScanMeta now use ctx.Project.Language (the same key the readers use), not scanLanguage — the 'scan -l X' divergence left the meta stuck running when -l differs from the project's pinned language. F2 is the same fix (the content stamp and the path key use the same variable). F3 (the legacy fallback hole in LatestScanMeta) is the follow-up.

Agent: ISSUES-TO-PR ses_f3e7372a5ffe3b2I6l4Wm3w2d
@gadievron

Copy link
Copy Markdown
Collaborator Author

Rewrote the branch (the previous head broke test compilation on all 3 OSes and left the production readers on the legacy path — the init→scan handoff was silently broken).

What the rewrite adds:

  • All 16 test call sites migrated to the new SaveScanMeta/LoadScanMeta/FinalizeScanMeta signatures; go build, go vet, go test green (95 tests, 0 failed).
  • The production readers (resolveStepDiffOpts, resolveScanMode) and the writer/finalizer now all key on ctx.Project.Language — the same key end to end. An adversarial review round found the first rewrite keyed the writer on the flag-overridable scanLanguage while the readers keyed on the project's pinned language: a scan -l go on a python-pinned project would leave the meta stuck running and the completed run unrecorded (verified by executed repro on both trees). Fixed in 8d230e5.
  • Four behavioral tests: two-languages-same-SHA (the issue's core case), the init→scan round-trip (the read/write contract), finalize flips running→success, and the legacy fallback (upgrade story).

Known follow-up (named, not fixed here): once any per-language meta exists at a sha, LatestScanMeta's foundPerLang skip drops the legacy sha-level record from the candidate walk — a pre-#664 success can be shadowed by a first post-upgrade failed scan at the same sha. Happy to address it in this PR or a follow-up, maintainer's call.

t added 2 commits September 22, 2026 18:09
… union, the guards (#664)

The three-lane review (fable verify-wave, astra blind re-derivation, opus
re-derivation) blocked the previous head. This lands the minimal
change-set they converged on:

M2 — the writer (resolveScanMode's SaveScanMeta) and the finalizer
(finalizeScanMetaIfProject) key on the RUN's effective language
(scanLanguage, the flag-overridable component the artifacts land under),
not the project's pinned language. 'scan -l go' on a python-pinned
project now writes and finalizes go/meta.json with Language='go';
python's record is untouched.

M3 — the adoption guard: resolveScanMode adopts init's pending decision
only when its Language matches the run's (or is legacy/empty).

M4 — LatestScanMeta's walk: per-language and legacy sha-level records
are a UNION of candidates, not a fallback chain. A running/failed
per-language record no longer shadows a legacy success at the same sha
(the upgrader's last known-good scan — the issue's exact complaint
recreated across the upgrade boundary). The legacy copy is dropped only
when the same run was migrated (identity dedup by owning language).

D4 — the language is validated as a single safe path element ('../..'
cannot escape the project tree).

Guards (fix-linkage, each fails when its defect is present — verified by
revert-mutation):
- TestInitWriteScanAdoptRoundTrip (the F1 class: caller-vs-caller key
  mismatch, only catchable at the cmd layer)
- TestScanLanguageOverrideWritesOwnRecord (astra-2: the override writes
  its own record, python untouched) — FAILS on the revert, verified
- TestLatestScanMetaLegacyNotShadowedByPerLang (astra-1: the walk union)
  — FAILS when the legacy branch is disabled, verified
- TestLatestScanMetaMigratedLegacyDeduped (identity dedup)
- TestScanMetaPathRejectsTraversal (D4)

D5 — the test fixtures repaired to satisfy key == meta.Language (the
invariant opus derived: the record's path component, its artifacts dir,
and meta.Language are the same string, resolved once per run).

F5 — the stale doc comments updated to the per-language layout.

Fixes #664

Agent: ISSUES-TO-PR ses_f3e7372a5ffe3b2I6lP4Wm3w2d
…utology, the untested guard (#664)

The delta T1 round (dispatch ses_f36558954ffeFRCIhbKSeJSwHG) found four
gaps in the change-set; all fixed with verified fix-linkage:

F-A (HIGH): the walk's identity dedup marked seenLangs BEFORE the status
filter, so a FAILED same-language rescan still shadowed the legacy
success — the default upgrade path. Only a per-language SUCCESS may
suppress the legacy copy now (a failed rescan is not the same run).

F-B: guard (d) was a tautology (m != nil). Strengthened: the legacy copy
is NEWER (a stale pre-#664 re-run) and the migrated per-language record
must still win resolution — without the dedup, the stale copy wins.

F-C: the adoption guard (M3) had zero fix-linkage — guard (c) set
--full, short-circuiting adoption. Added guard (g): python's pending
decision must not be adopted for a go run (mutation-verified).

F-D residual: the NewScanMeta fixture contradiction
(scan_meta_test.go:166 — Language 'python' under key 'go') repaired.

Scope honesty (from the same round): the invariant claim in the previous
commit message is narrowed — the meta's path component and meta.Language
are the same string; artifact-dir routing still follows the pinned
language (pre-existing, #664-adjacent, out of scope). config.ScanDir's
-l validation is likewise pre-existing and out of scope. F-E (the
language-mismatched scan now re-decides instead of adopting init's
language-agnostic base) is a conscious behavior choice, flagged for the
maintainer.

Fixes #664

Agent: ISSUES-TO-PR ses_f3e7372a5ffe3b2I6lP4Wm3w2d
@gadievron

Copy link
Copy Markdown
Collaborator Author

The adversarial cycle is complete: three lanes + two delta rounds, every finding fixed with mutation-verified fix-linkage, 145/0 on the suite. The three conscious scope choices are listed in the body's 'Known out-of-scope' — maintainer's call on each.

t added 2 commits September 22, 2026 18:44
…arded helper (#664)

The new C4 fix-linkage gate (revert/mutate every prod hunk, record the
failing guard) caught that init.go's inline meta write had NO test that
fails when its key is wrong — the round-trip guard simulated the write
instead of exercising it. Extracted writeInitScanMeta so the site is
directly guardable; TestInitWriteSiteKeysOnProjectLanguage drives it and
fails on a key/stamp mutation.

Agent: ISSUES-TO-PR ses_f3e7372a5ffe3b2I6lP4Wm3w2d
…ge from the project (#664)

The fable delta round (dispatch ses_f36334857ffejKltYLmGRMwIIe) verified the
extraction behavior-preserving and the guard real (5/6 mutations fail the
test; the 6th is a proven equivalent), and named one residual: the
adjacent same-type (branch, initLanguage) params are a silent arg-swap
surface — swapped, the suite stays green and init keys its record under
the branch name. Deriving the language from project.Language removes the
surface; the defense-in-depth receipt's residual is closed.

Agent: ISSUES-TO-PR ses_f3e7372a5ffe3b2I6lP4Wm3w2d
@gadievron

Copy link
Copy Markdown
Collaborator Author

The final artifacts-only audit's receipt findings are fixed: the test count corrected (205, full-output receipt — the earlier 145 was an output-truncation artifact), the init.go change now documented in the body, the collision section rewritten with real file lists + merge-tree receipts (#672/#675 file-overlap, both merge-clean), and the lane-claim wording matched to what the receipts record. The audit row is dispatch-bound (a real subagent session, verifiable). Ready for review.

…ed with the record key (#664)

The fable triage round's finding: the comment claimed scanLanguage is
'the same component the artifacts land under' — false (scanOutput
defaults to ctx.ScanDir, the pin; this diff changes none of that). The
comment now names the split and points at #667.

Agent: ISSUES-TO-PR ses_f3e7372a5ffe3b2I6lP4Wm3w2d
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant