Skip to content

v0.2.0: one index run at a time, fail-closed vault switch, durable cleanup debt - #10

Merged
puretensor merged 2 commits into
mainfrom
fix/codex-audit-2026-10-25-b
Oct 26, 2026
Merged

puretensor merged 2 commits into
mainfrom
fix/codex-audit-2026-10-25-b

Conversation

@puretensor

@puretensor puretensor commented Oct 25, 2026 •

Copy link
Copy Markdown
Owner

Originally stacked on #9, which is now merged; this branch is rebased onto main and holds only its own two commits.

  • index_vault holds an fcntl lock on <db>.lock for the whole run. A puremind index that overlaps another run is refused with StoreError (a RuntimeError, which the CLI reports cleanly). A puremind watch run waits for the other run to finish and then checks the model again, so a watch on an old model cannot write its vectors into an index rebuilt for another model.
  • A changed vault root (another vault, or the same one moved) needs --rebuild. The rebuild first has the model embed a test passage, then clears the old rows and records the new root in one transaction before embedding. A model that cannot load leaves the index as it was.
  • Purge debt is recorded in the same transaction as each replace or delete. It is cleared only after compaction and a successful checkpoint, and only if the generation has not changed.
  • The lock reports a read-only index as read-only, opens its sidecar with O_NOFOLLOW, and turns other open or lock errors into a StoreError that names the lock file.

Tests: full suite: 729 passed, 6 skipped (Linux, Python 3.12). ruff 0.17.1 is clean.

🤖 Generated with Claude Code

puretensor and others added 2 commits October 26, 2026 13:54
…bt recorded with each write

- index_vault holds an fcntl lock on <db>.lock for the whole run; an overlapping
  run is refused with StoreError instead of writing one model's vectors into an
  index labelled with another.
- A changed vault root clears the old rows and records the new root in one
  transaction before embedding, so a failed switch cannot leave the old vault's
  notes labelled as the new one.
- purge debt is recorded in the same transaction as each replace/delete and is
  cleared only after compaction and a successful checkpoint, conditional on the
  generation, so a failed run or a busy reader no longer loses it.

Tests: full suite 710 passed, 18 skipped (Linux).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ex, let watch wait for a running index

- A rebuild for a vault at another path (moved, or another vault) now embeds the
  test passage before clearing the old rows, as a model change already did, so a
  model that cannot load leaves the index and its recorded root as they were.
- `puremind watch` runs wait for a `puremind index` that holds the lock, then check
  the model again, instead of ending the watch on its first run or dropping the
  change until the next rescan. `puremind index` is still refused.
- The index lock reports a read-only index as read-only before touching the lock
  file, opens the lock file with O_NOFOLLOW, and turns other open or lock errors
  into StoreError naming the lock file.
- README and CHANGELOG describe both behaviours.

Tests: full suite 729 passed, 6 skipped (Linux, Python 3.12); ruff clean.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@puretensor
puretensor changed the base branch from fix/codex-audit-2026-10-25 to main October 26, 2026 14:00
@puretensor
puretensor force-pushed the fix/codex-audit-2026-10-25-b branch from 040e759 to a8e1f35 Compare October 26, 2026 14:00
@puretensor puretensor changed the title 0.2.0: one index run at a time, fail-closed vault switch, durable cleanup debt (stacked on #9) v0.2.0: one index run at a time, fail-closed vault switch, durable cleanup debt Oct 26, 2026
@puretensor

Copy link
Copy Markdown
Owner Author

Verdict: fix, then merge.

What I examined. I read the lock, vault-switch and purge-debt changes against the code. Three independent reviews (correctness, safety, integration) examined the PR, including mutation probes. All three core claims hold:

  • index runs are serialised by flock;
  • the vault switch clears the rows and records the new root in one transaction;
  • purge debt is written with each replace or delete, and cleared only after a successful checkpoint while the generation is unchanged.

Stack. #9 merged with its lint fixes. I retargeted this PR to main and rebased only its own commit onto main (clean), so the inherited ruff failure is gone. The commit subject now uses the repo's v0.2.0: form.

Fixed in a8e1f35:

  • A --rebuild for a vault at another path cleared the index before checking that the model could run. A model that could not load left the index empty, where v0.2.0: bound search queries, record the embedding window, refresh unchanged stats, lock the inbox #9 had kept it. The rebuild now embeds the test passage first, as a model change already does, so the old rows and the recorded root survive. New test: test_a_model_that_cannot_run_does_not_cost_a_moved_vault_its_index.
  • puremind watch ended at once if its first run met the lock. A later refused run dropped its change until the 10-minute rescan. Watch runs now wait for the other run, then check the model again, so the stale-model protection still applies. puremind index is still refused. New tests cover both the waiting run and the watch command.
  • A read-only index failed with a raw PermissionError on <db>.lock. The lock now reports the read-only store first, opens its sidecar with O_NOFOLLOW (matching the inbox lock), and turns other open or lock errors into a StoreError that names the lock file. Tests added for both.
  • The README and CHANGELOG now describe the watch-waits behaviour, and say that a rebuild for a moved vault empties the index after checking the model. The PR body is corrected.

Dismissed:

  • When the indexer calls clear() without vault_root, a mutation of that survived the tests. It has no safety impact, because the root is set right after, under the same lock.
  • After a purging run the WAL keeps a few KB of metadata while a connection is open. This is a documented trade-off, and the canary tests confirm no note text remains.

Tests:

  • Full suite: 729 passed, 6 skipped (Linux, Python 3.12).
  • ruff 0.17.1: clean.
  • gitleaks over the branch: no leaks.
  • Five mutation probes, one undoing each fix: all caught.
  • The lock and vault tests passed 10 times in a row.
  • CI on a8e1f35 is green on every job (macOS and Ubuntu, Python 3.11 to 3.14, mcp 1.x, lowest dependencies, installed wheel). "real models" is skipped, as it runs only on schedule or dispatch.

@puretensor
puretensor merged commit f8e4e14 into main Oct 26, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant