fix(workspace-registry): serialize mutations and stop emptying a corrupt registry - #1
Draft
andreiverdes wants to merge 7 commits into
Draft
andreiverdes wants to merge 7 commits into
andreiverdes wants to merge 7 commits into
Conversation
…upt registry
Two overlapping rpc calls that each did readRegistry() + writeRegistry()
interleaved and corrupted a user's ~/.beadbox/registry.json: the loser's
whole-file writeFile truncated in place, leaving unparseable bytes, and
readRegistry's catch-all then turned that into an empty workspace list
which the next write made permanent.
- writeRegistry writes a randomUUID-suffixed tmp file with
{ flag: "wx", mode: 0o600 } and renames it into place, unlinking the
tmp on failure, so a reader never observes a partial registry.
- New mutateRegistry() serializes read-modify-write through one
process-wide queue and skips the write when the mutation changed
nothing; every mutation now goes through it.
- readRegistry only treats ENOENT as "missing"; any other read failure
propagates instead of degrading to an empty registry.
- A registry that exists but does not parse is quarantined to
registry.json.corrupt-<ts> and logged. The quarantine gate covers
JSON.parse only: a structurally odd entry inside otherwise valid JSON
is skipped by deduplicateEntries rather than costing the user the
whole list.
AI-assisted PRs are welcome. Our default is to fix, split, or partially land a valuable PR ourselves rather than request changes, with Co-authored-by attribution and release-notes credit. States the hygiene that makes that possible and that external PRs land as a single squashed commit.
Beadbox is built and maintained with initech, a runtime for agents that collaborate from one terminal. A two-line note under the title so readers find it.
…t (beadbox-0qx) The eight `dtolnay/rust-toolchain@4360b52 # stable` pins (quality-gates, release, build-arm-dmg) went red on zizmor's impostor-commit audit (exit 14). Not an impostor: 4360b52's parent is on upstream master. The upstream `stable` branch is regenerated on every Rust release and its history rewritten, so the pinned commit is no longer reachable from any ref — which is exactly the property the audit checks. Re-pinning to the current `stable` head would go red again at the next release. Pin d1031067 instead: on master (never rewritten) and the exact base the current `stable` branch was generated from, with `toolchain: stable` passed as an input — the action's README recommends this form for full-length SHA pins. zizmor 1.29.0 with the gate's exact invocation: 8 impostor-commit findings before, 0 after. actionlint clean.
…nge (beadbox-0qx) The dependency-audit gate (`bun audit --audit-level=high`) started failing on advisories published after the last push: browserslist <=4.28.6 GHSA-c83g-rgw3-j3cx, GHSA-73wf-gq98-2v4g fast-uri <3.1.6 GHSA-jqff-g426-hqxp, GHSA-f65p-4m7j-42xc nanoid <3.3.12 GHSA-xwg4-73v4-xw9w All three are dev-scope transitives (babel via the router plugin, ajv via the mutation tester, postcss via vite); none ships in the app. Lockfile-only bump inside the dependents' declared ranges: browserslist 4.28.9 (plus its five data packages), fast-uri 3.1.7, nanoid 3.3.19. package.json untouched. `bun install --frozen-lockfile` clean from an empty node_modules; the audit gate goes from 5 high to 0.
…adbox-0qx) `mutation-rust` runs 96 cargo-mutants mutants at ~70 s each against a 30-minute job budget, and each mutant executes 0 s of tests — so the job burned ~30 minutes of runner time per push for no information. Disabled by an always-false `if:` on the job (actionlint rejects a literal `if: false`), with the job and its configuration left intact so the restore is a one-line change once the mutant runs actually execute tests (tracked as beadbox-l5i.8). Header comment updated to match.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Layer 1 of 3. Base:
main.Two failure modes in
~/.beadbox/registry.json:writeFilein place, an ill-timed pair could leave a half-written file that no longer parses.Mutations now serialize through a process-wide queue and land via tmp-file + atomic rename. Unparseable content is quarantined to
registry.json.corrupt-<ts>exactly once; read errors propagate instead of presenting as an empty registry.Known limit: the queue is per-process, so two Beadbox instances can still lose an update. Measured, not assumed — two concurrent processes produced 2 lost updates across 36 sampled reads with 0 parse failures. The file always stays valid.
Recovery drills (6/6): ENOENT yields an empty registry; unreadable propagates EACCES; truncated JSON quarantines exactly once; valid-JSON-with-malformed-entries survives; a symlink at the tmp path is refused with EEXIST and never followed; no
.tmpsurvives a failure.