GitNexus/gitnexus/scripts
Gergő Magyar ceaff27c1e
fix(parse-cache): retire a chunk whose durable generation could not be reset (#3271)
* fix(parse-cache): retire a chunk whose durable generation could not be reset

#3200 skipped the parse-cache write when `prepareDurableParsedFileChunk`
failed, but the chunk hash was already in `usedKeys` from the lookup. When a
previous generation existed on disk — reachable because the coherence gate
re-dispatches a chunk whose `.v8` shard is live but whose durable shards are
unreadable — `saveParseCache` copied that old shard forward, and the durable
prune, which keeps exactly the saved keys, retained the mixed directory. The
next run then served a warm hit out of a directory the previous run had
already decided it could not account for.

Retire the hash instead of only skipping the write:

- `ParseCache.staleKeys` is a transient set that `saveParseCache` filters out
  of its key list. Filtering at save is what makes it survive the post-parse
  key merges in run-analyze (#2106 sibling fold, unreadable-meta retention),
  and it reaches both stores at once because the durable prune keeps exactly
  the keys `saveParseCache` returns.
- The hash is retired at the reset-failure site, which runs unconditionally.
  The parse-cache write branch sits behind `rawResults.length > 0`, so a chunk
  whose worker round returns nothing would never have been retired there.
- Worker-quarantined chunks get the same treatment for the same reason: they
  also reach the save with no in-memory entry, which is what triggers the
  copy-forward. That branch was previously unreachable when the worker died on
  the chunk and returned no results.
- Guard the durable prune's non-survivor `fs.rm`. The causes that break the
  reset break that delete too, and it sat outside the validation try — one
  undeletable directory aborted the loop and cost every remaining chunk its
  index entry.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(review): apply review findings

Retire a chunk only when a generation nobody cleared is still on disk.
`prepareDurableParsedFileChunk` is rm-then-mkdir, and the catch could not tell
the two apart: an rm that succeeded before a failing mkdir leaves NO directory,
so the workers recreate it and write a clean generation. Retiring there
discarded a good `.v8` for no safety gain — and under a correlated failure
(an empty durable index turns every chunk into a re-dispatched miss, then a
descriptor burst rejects the resets en masse) it would have wiped both shared
stores for every branch, where the pre-#3204 posture cost only the writes.
`durableChunkHasStaleShards` is the discriminator.

Finish the delete guard on the path that runs before it. The staged→live
overlay in `mergeStagedDurableParsedFileStore` awaited `replaceDurableChunkDir`
unguarded, so on the cold-rebuild path one undeletable directory threw out of
the merge before the prune ever ran — the durable index was never rewritten and
a retired chunk kept its directory. Same log-and-continue treatment, plus
best-effort handling of the two `.replacing` backup removals.

Aggregate the prune's delete-failure warning: a store-wide cause hits every
non-survivor, and one line per directory buries the message that matters.

Tests:
- Guard the chmod-based prune test with the repo's `skipIf` for root/Windows
  and assert the directory survived, so it cannot pass vacuously where the
  delete succeeds.
- Add a two-chunk control: one chunk's reset fails, and the sibling must stay
  warm through the next run. One chunk plus a global spawn marker could not
  tell "retires the failing chunk" from "retires everything".
- Add the rm-succeeded/mkdir-failed case, which must NOT retire.
- Model both post-parse merges in the R4 test (the sibling fold re-adds the
  key, the unreadable-meta fallback unions `entries`), and move the in-memory
  `entries` assertion to a direct helper test — the sharded path never
  populates `entries`, so the old assertion proved nothing.
- Type the cache factory as `ParseCache`; the `staleKeys` assertions were
  TS2339 and `?? false` read as a pass regardless.
- Register the store test in the cross-platform filesystem list.

Correct two comments that still described a quarantined chunk by the premise
this fix disproves.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(review): stop swallowing the backup removal in replaceDurableChunkDir

Swallowing that `fs.rm` manufactured the very hazard this PR removes. When a
non-empty `${to}.replacing` survives, the following `fs.rename(to, backup)`
cannot overwrite it and is suppressed as "dest was missing", so `backedUp`
stays false and the `fs.cp(from, to)` fallback merges the staged generation
INTO the live directory — old shards alongside new, which the prune then
indexes as one valid survivor. Let it throw; the per-entry guard added to
`mergeStagedDurableParsedFileStore` already stops one such chunk from costing
the others their prune. The post-publish backup cleanup stays best-effort,
where an undeletable leftover really is litter.

Also:
- Make the sibling-isolation test perform the run its title claims. It asserted
  index membership and stopped; an index entry does not exercise the warm-hit
  path, so it would have passed even if the sibling re-dispatched. Each chunk
  now runs alone so the single spawn marker names which one re-parsed.
- Correct two comments that outran the implementation: retirement is gated on
  shards actually surviving, and an undeletable directory is dropped from the
  index rather than removed from disk.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-12 12:16:45 +01:00
..
bench fix: batch query enrichment, bake FTS extension into CLI image, add FTS memory repro (#2108) 2026-06-09 08:46:46 +01:00
spikes feat(ingestion): M0 — taint/PDG substrate (schema + seams + spikes) (#2080) (#2092) 2026-06-08 18:56:10 +01:00
assert-publish-grammar-coverage.cjs fix(grammars): load vendored tree-sitter grammars from vendor/ by absolute path (#2111) (#2144) 2026-06-10 14:20:42 +01:00
assert-web-assets.mjs fix(build): build the web UI from prepack, not from every npm ci (#3166) 2026-09-04 13:28:11 +01:00
bench-scope-resolution.ts refactor(ingestion): delete legacy call-resolution DAG + heritage processor (RING4-1, #942) (#2023) 2026-06-04 11:07:37 +01:00
build-tree-sitter-grammars.cjs feat(indexing): add Objective-C semantic indexing support (#3179) 2026-09-09 09:40:21 +00:00
build-web.js fix(build): build the web UI from prepack, not from every npm ci (#3166) 2026-09-04 13:28:11 +01:00
build.js fix(build): build the web UI from prepack, not from every npm ci (#3166) 2026-09-04 13:28:11 +01:00
cross-platform-shard.ts fix(resolution): label fallback guesses and preserve export visibility (#3190) 2026-09-10 11:38:19 +01:00
cross-platform-tests.ts fix(parse-cache): retire a chunk whose durable generation could not be reset (#3271) 2026-09-12 12:16:45 +01:00
ensure-fts.ts fix(analyze): load VECTOR before the incremental writeback touches embedding rows (#2623) (#2624) 2026-07-22 12:27:00 +01:00
install-duckdb-extension.mjs fix(fts): diagnose Windows FTS missing-dependency load failures (#2374, Phase 1) (#2383) 2026-07-06 21:27:37 +01:00
run-cross-platform.ts perf(mcp): cut the analyze-only language-provider closure out of MCP server startup (#2802) (#2806) 2026-08-03 21:26:13 +01:00
shard-arg.ts fix(ci): shard platform-sensitive matrix + spawn built CLI to fix Windows cross-platform timeout (#2394) 2026-07-08 09:09:11 +01:00
sync-plugin-manifests.mjs feat(skills): GitNexus Engineering Tool Kits (#2566) 2026-07-19 15:07:24 +01:00