Skip to content

Give the migration's staging orphan a home in the runbook - #217

Merged
Jason-Vaughan merged 1 commit into
mainfrom
docs/tmp-orphan-runbook-198
Sep 8, 2026
Merged

Give the migration's staging orphan a home in the runbook#217
Jason-Vaughan merged 1 commit into
mainfrom
docs/tmp-orphan-runbook-198

Conversation

@Jason-Vaughan

Copy link
Copy Markdown
Owner

Closes #198. Clean-room reconstruction of #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, 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 for usage.jsonl.<hex>.tmp.
  • CHANGELOG.md under ### Internal — docs only, patch tier.

Why

migrate_state_root stages each entry as <name>.<hex>.tmp beside its destination and os.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 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 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.replace raise; destination.exists() is false so it re-raises to the outer handler and the run prints 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 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.py rather than inferred.

Test plan

  • make test805 passed, 17 skipped; ruff and mypy clean.
  • Nothing here is executable, so that is a no-regression check rather than coverage. The substantive verification was reading tanglebrain/atomic.py and tanglebrain/router.py directly and checking every claim the prose makes: the f"{path.name}.{uuid.uuid4().hex}.tmp" format (no leading dot, hence visible), staging as a sibling of the destination, os.replace, _discard(staged) inside a finally, the destination.exists() re-run guard, and that the migration uses copytree/copy2 and never unlinks the source.
  • The quoted failure string is the real one (router.py:225), not a paraphrase.
  • Confirmed the entry matches its neighbours' symptom-shaped convention and that docs/design/ mentioned .tmp nowhere before this.

Artifact parity

docs/design/operations.md is under docs/design/, so the Design Overview Artifact is republished to its existing URL 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. @7487 correctly identified this step as ours: a fork cannot reach that URL.

Note for the reviewer

prawduct-hook check-change-log-entry reports no-entry structurally here — .prawduct/ is gitignored by owner ruling, so git cannot see .prawduct/change-log.md. The tracked CHANGELOG.md entry 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.

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.
@Jason-Vaughan
Jason-Vaughan merged commit de30c4b into main Sep 8, 2026
5 checks passed
@Jason-Vaughan
Jason-Vaughan deleted the docs/tmp-orphan-runbook-198 branch September 8, 2026 21:08
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.

docs: give the migration .tmp orphan a home in the operations runbook

1 participant