fix(storage): stop a credential deletion from bumping a retired connection - #3415
Conversation
A retained retired connection is a tombstone: readable, queryable, deletable, and byte-stable until it is deleted. apache#3183 established that against direct writes and against the global verification sweep, and left one path open. Deleting the credential is the write a tombstone must still accept — it is how a user removes the token retirement deliberately kept. The deletion itself was never the problem. On its way out it invalidated the connection's verification through `clearConnectionLastTest`, which rewrote the row and advanced its revision. Reproduced: revision 1 becomes 2 for removing a credential. That bump is not cosmetic. A revision is what a concurrent deletion of the row itself compares against, so removing the credential could make deleting the connection fail as stale — and the two are the same user action, one after the other. `clearAllConnectionLastTests` already skips retired rows. This is the same invariant reached one connection at a time rather than all of them at once, so it gets the same predicate rather than a second rule. The test asserts the tombstone is untouched — revision and `lastTest` both — and carries a live connection as a control: the same deletion against it must still clear the verification and bump the revision. Without that half the test would pass just as well if the guard stopped every invalidation instead of only the retired one. Verified in both directions: with the guard reverted the test fails on the revision it was written for, and passes with it in place. No wire contract changes, so no compatibility epoch bump. Claude-Session: https://claude.ai/code/session_014ajaRxC4jydavY9nYUFj5J
Astro-Han
left a comment
There was a problem hiding this comment.
Thanks for fixing this at the catalog-document boundary. I reviewed exact head f9f0702328a7f34f6d7f075a7c1045be2e0d1605. Treating a retained retired connection as a byte-stable tombstone is consistent with the existing bulk invalidation path, and the one-line guard avoids an unnecessary revision change without weakening credential CAS. The live-connection control in the regression also confirms that ordinary verification invalidation still works. I found no actionable issues.
Hosted CI has not run successfully for this head yet, so merge should still wait for the required checks.
AI-assisted review disclosure: Codex performed the exact-head analysis, and an independent reviewer agent conducted an adversarial storage, revision, and concurrency pass. I verified the source paths, live GitHub state, and final review decision.
Summary
A retained retired connection is a tombstone: readable, queryable, deletable, and byte-stable until it is deleted. #3183 established that against direct writes and against the global verification sweep, and left one path open.
Deleting the credential is the write a tombstone must still accept — it is how a user removes the token that retirement deliberately kept. The deletion itself was never the problem. On its way out it invalidated the connection's verification through
clearConnectionLastTest, which rewrote the row and advanced its revision.Reproduced: revision
1becomes2for removing a credential.That bump is not cosmetic. A revision is what a concurrent deletion of the row itself compares against, so removing the credential could make deleting the connection fail as stale — and those two are the same user action, one after the other.
The fix
clearAllConnectionLastTestsalready skips retired rows. This is the same invariant reached one connection at a time rather than all of them at once, so it gets the same predicate rather than a second rule:One line of behaviour. The asymmetry it removes is the whole bug: the global sweep was taught the rule and the single-connection path was not.
Verification
The test asserts the tombstone is untouched — revision and
lastTestboth — and carries a live connection as a control: the same deletion against it must still clear the verification and bump the revision. Without that half, the test would pass just as well if the guard stopped every invalidation instead of only the retired one.Verified in both directions rather than only the passing one:
actual: 2, expected: 1)Gates:
build,lint,format:check,typecheck,check:stale,check:release(32),check:asf-source(9),windows:inventory, the CI planner tests (53, including the pre-npm cicase), knip on both workspaces, and the Astryx inventory and theme checks. Suites: storage 849 (848 + this one), core 576, runtime-host 1045, desktop 1023 — all green.No wire contract changes, so no
RUNTIME_HOST_COMPATIBILITY_EPOCHbump.Review focus
Whether the tombstone invariant is the right frame for this path at all. The alternative reading is that deleting a credential should invalidate verification unconditionally, and that a retired row's revision moving is acceptable because nothing will test it again. I do not think so — deletability is part of what the invariant promises, and a revision bump is exactly what breaks it — but that is the judgement worth a second opinion.
AI use
Select exactly one:
Tool(s) and scope: Claude Code — reproduced the defect, wrote the test and its control, made the one-line fix, and ran the verification above. Reviewed and submitted by the contributor of record.
Checklist
Does this PR entail a change in behavior?