Give the migration's staging orphan a home in the runbook - #217
Merged
Conversation
Closes #198. Clean-room reconstruction of PR #216 by @7487 — both the Macro and Micro filters cleared it, so per CONTRIBUTING.md the idea lands here and the bytes are ours. Their diagnosis is the whole basis of this entry. `migrate_state_root` stages each entry as `<name>.<hex>.tmp` beside its destination and `os.replace`s it into position, so a copy killed halfway never leaves a truncated file at the real path — which matters because the re-run guard is "does the destination exist". Cleanup runs in a `finally`, so an error or Ctrl-C takes the staging file with it; SIGKILL and power loss do not. Nothing reclaims the leftover afterwards, deliberately: a name no other process can touch is also a name no later run can find, and sweeping for the suffix would restore the shared-name collision the unique name exists to prevent. It stays visible because an orphan is the operator's to delete. None of that was written anywhere an operator would look. The only mention was the 0.21.0/0.22.0 repair paragraph in the changelog — version-scoped history, useless to someone arriving on 0.23+ who finds the file and never ran either release. The reconstruction adds one thing the contribution did not have. "Deleting it is safe" is true of an orphan and false of a staging file a migration is using right now, and the two are indistinguishable by filename while every console script migrates on startup. Deleting a live one makes `os.replace` raise and the run print `could not move state from …`; nothing is lost and the next startup finishes the move, but an operator acting on an unqualified "safe" would have caused an alarm the runbook told them could not happen. Verified against the failure arm in router.py rather than inferred: the re-raise reaches the outer handler because `destination.exists()` is false. `docs/design/operations.md` is under docs/design/, so the Design Overview Artifact is republished in the same turn per CLAUDE.md. Its #139 entry described the unique-name fix without ever naming the orphan that fix deliberately creates — an accepted cost that goes unstated reads as an oversight. Docs only, no behaviour change. Suite 805 green, lint clean.
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.
Closes #198. Clean-room reconstruction of #216 by @7487 — both the Macro and Micro filters cleared it, so per
CONTRIBUTING.mdthe idea lands here and the bytes are ours. Their diagnosis is the whole basis of this entry, and the credit is for the contribution, not a claim about who typed these characters.What
docs/design/operations.md§ Runbook — a symptom-shaped entry forusage.jsonl.<hex>.tmp.CHANGELOG.mdunder### Internal— docs only, patch tier.Why
migrate_state_rootstages each entry as<name>.<hex>.tmpbeside its destination andos.replaces it into position, so a copy killed halfway never leaves a truncated file at the real path — which matters because the re-run guard is "does the destination exist". Cleanup runs in afinally, so an error orCtrl-Ctakes the staging file with it;SIGKILLand power loss do not. Nothing reclaims the leftover afterwards, deliberately: a name no other process can touch is also a name no later run can find, and sweeping the directory for the suffix would restore the shared-name collision the unique name exists to prevent. It stays visible because an orphan is the operator's to delete.None of that was written anywhere an operator would look. The only mention was the 0.21.0/0.22.0 repair paragraph in the changelog — version-scoped history, no use to someone arriving on 0.23+ who finds the file and never ran either release.
What the reconstruction adds
"Deleting it is safe" is true of an orphan and false of a staging file a migration is using right now — and the two are indistinguishable by filename, while every console script migrates on startup. Deleting a live one makes
os.replaceraise;destination.exists()is false so it re-raises to the outer handler and the run printscould not move state from …. Nothing is lost and the next startup finishes the move, but an operator acting on an unqualified "safe" would have caused exactly the alarm the runbook told them could not happen.That was the one finding of the Micro Filter audit, and it is verified against the failure arm in
router.pyrather than inferred.Test plan
make test— 805 passed, 17 skipped; ruff and mypy clean.tanglebrain/atomic.pyandtanglebrain/router.pydirectly and checking every claim the prose makes: thef"{path.name}.{uuid.uuid4().hex}.tmp"format (no leading dot, hence visible), staging as a sibling of the destination,os.replace,_discard(staged)inside afinally, thedestination.exists()re-run guard, and that the migration usescopytree/copy2and never unlinks the source.router.py:225), not a paraphrase.docs/design/mentioned.tmpnowhere before this.Artifact parity
docs/design/operations.mdis underdocs/design/, so the Design Overview Artifact is republished to its existing URL in the same turn perCLAUDE.md. Its#139entry described the unique-name fix without ever naming the orphan that fix deliberately creates — an accepted cost that goes unstated reads as an oversight. @7487 correctly identified this step as ours: a fork cannot reach that URL.Note for the reviewer
prawduct-hook check-change-log-entryreportsno-entrystructurally here —.prawduct/is gitignored by owner ruling, so git cannot see.prawduct/change-log.md. The trackedCHANGELOG.mdentry is under[Unreleased]/### Internal, verified by hand. Tracked as #180.PR #216 is still open and unanswered. The contributor-facing reply is drafted and queued for the operator at
.prawduct/artifacts/queued-pr-216-reconstruction.md; per ADR-009 nobody but the operator posts to an external PR.