Repository navigation
Desktop fs-data key-rotation migration is not crash-resumable (mixed-key state possible) #359
Description
LIVE C2c-2 CRASH/RESUME GOVERNANCE — 2026-10-09
MAIN = 4428fdd81c9e656be37861895a6cb001ef129dbc
LAST_TERMINAL = #1016 / C2c-1 / resulting-main CI+CodeQL + Vercel/retention proved
ACTIVE_PR = #1017 / C2c-2 / HEAD ca26d16a1f17a613baac2adad6654a739938052e / OPEN READY
CODEX_OLD_HEAD_44ba1be = 2 P1 + 1 P2 / 3 original threads RESOLVED
CODEX_NEW_HEAD_ca26d16 = COMPLETED / NEW P1 OPEN
CODEX_NEW_FINDING = manual cursor invalidation is volatile across begin_conversion restart
GATE_4D = IN_PROGRESS / NOT TERMINAL
GATE_4E_5_6_7 = GATED; PRODUCTION_AUTHORITY_SWITCH_ALLOWED = NO
Latest Codex P1 durable cursor invalidation across restart demonstrates that after advance_cursor skips directly to the final entry, begin_conversion clears the session-only cursor_moved invalidation, allowing the newly resumed session to enter VERIFY with preceding inventory entries unconverted. This is a live exact-head security/correctness blocker, not review-noise or a reason for another unrelated refactor. Fix/accept only with regression that persists the malicious manual jump through process/session restart, proves restart cannot grant completion, and ensures adversarial takeover/missing admission never invokes EntryStep::convert prematurely. A design could remove completion-capable raw cursor advancement or durably encode/prove contiguous progress; Claude/maintainer must decide without weakening preserve-first or extending the Core clock boundary. Also prove checkpoint error replay, last-entry idempotent replay, page boundary, authenticated final inventory, CONVERT→VERIFY guarded transition and Root/Journal fence after every committed step. Unit-only loop tests do not close the crash/resume acceptance. Existing tracked Gate-4E handle-relative platform I/O remains separate.
Retain one coherent correction epoch; require exact-final-head Codex disposition and deterministic CI/CodeQL, then normal protected merge and resulting-main + Production/retention proof before calling C2c-2 terminal. No simultaneous new source writer, no Gate5 record conversion in this PR, no premature Gate7 switch. Earlier issue-body snapshots below are historical; authoritative current slice links: PR #1017, C2c-2 admission, QNB-11.
CURRENT CONTROL-PLANE CHECKPOINT — 2026-10-06 — RETENTION TERMINAL / R4 READ-ONLY ADMISSION NEXT
CURRENT_MAIN = bf745090f7e980e167b1bbf01c7f6dea51fb451f
RESULTING_MAIN_CI_CD = 37434052556 / SUCCESS
RESULTING_MAIN_CODEQL = 37434052501 / SUCCESS
VERCEL_PRODUCTION = dpl_75HQcQhuYP6CxX43EjMov8Sjosdb / READY / exact CURRENT_MAIN / canonical HTTP 200
ROLLBACK = dpl_7XTy9jGDikUauvgT8oPQUtq7YAmq / READY
OPEN_PULL_REQUESTS = 0
DEPENDENCY_TRAIN = TERMINAL
FINAL_RETENTION = TERMINAL
RETENTION_RESULT = 11 stale deployments deleted; 79 orphaned aliases removed; 4 deployments remain; 3 protected Production/Main aliases remain; UNKNOWN = 0
B2A = TERMINAL
B2 = NOT TERMINAL
B2_IMPLEMENTATION_ALLOWED = NO until fresh read-only R4 admission returns READY
PRODUCTION_AUTHORITY_SWITCH_ALLOWED = NO
ACTIVE_OWNER = Gate 4D / #359 / QNB-11
NEXT = fresh read-only R4 admission on exact current main
R4 is the next planned bounded question: a stale caller manifest paired with its matching stale fence must not be allowed to publish when committed LiveMigration authority names another durable generation. Before source mutation, re-audit the live contracts and prove whether the smallest safe slice is a pre-I/O authority refusal in promote_manifest_fenced / promote_page_fenced.
Explicitly out of scope for this R4 slice: root LiveMigration advance, deletion of r+1, R2B semantic-open recovery, key-epoch changes/alignment, mixed-key conversion, Gate 4E/5/6/7, and production authority switch.
HISTORICAL / SUPERSEDED CONTROL-PLANE CHECKPOINT — 2026-10-04 — SEQUENCE CORRECTION
CURRENT_MAIN = 8f1400c4bb6a84be8da940584b86b00233df1d4b
PR_957 = MERGED / Slice A
POST_#957_RETENTION = NOT COMPLETE
PR_958 = OPEN / docs-only Slice-B gap matrix
PR_958_HEAD = 0eac3d4c10ddd854eee9027db038f55899b66caf
SEQUENCE_DRIFT = YES — #958 opened before #957 retention terminal
PR_958_PROGRAM_STATE = FROZEN
MERGE_#958 = NO
FURTHER_SLICE_B_IMPLEMENTATION = NO
NEXT = finish #957 retention first → then resume #958
PRODUCTION_AUTHORITY_SWITCH_ALLOWED = NO
HISTORICAL / SUPERSEDED — previous control-plane checkpoint
CURRENT_MAIN = 2001e63f3b7b1fbf2faa0270238f0de43ba84db1
PR_953 = MERGED / Successor A read-snapshot slice landed
PR_954 = OPEN / DRAFT / MERGEABLE
PR_954_HEAD = 93384b5a71f8bed4f2cde1428b762d2db0b13790
PR_954_BASE = 2001e63f3b7b1fbf2faa0270238f0de43ba84db1
PR_954_SCOPE = Successor B — Mutation / Root-Recovery / Lifecycle Closure
PR_954_SIZE = 16 files / 2 commits / +2121 -100
CODEANT = Quality / Coverage / SCR / SAST / SCA SUCCESS exact head
CODERABBIT = status SUCCESS, but Draft PR auto-review is disabled; do not call review clean
DEEPSOURCE = Python / Shell / Docker SUCCESS; review grade A; Rust status not inferred
VERCEL_PREVIEW = dpl_2JuoEypL2STM2hXvq9ESVa1Gmz9N BUILDING exact head
CI_CD = IN PROGRESS / not yet terminally proven here
CODEQL = not yet terminally proven here
REVIEWS = no submitted reviewer epoch yet
UNRESOLVED_REVIEW_THREADS = 0 at this checkpoint
PR_952 = OPEN / DRAFT / FROZEN PROVENANCE — DO NOT MERGE
GATE_4B = ACTIVE
4C / 4D / 4E = PENDING
PRODUCTION_AUTHORITY_SWITCH_ALLOWED = NO
Successor B is correctly based on the #953 resulting main and carries the semantic A+B union: #953 SessionBinding/rebind semantics plus the mutation/root-recovery/lifecycle mechanisms extracted from frozen #952. The prepared-root Step-F same-instance recovery R1–R5 proof remains this PR's material closure scope.
Do not close Gate 4B, #922, #445, or advance #359/4D until #954 converges on its exact final head, merges normally, and the exact resulting-main CI/CD + CodeQL + Vercel Production gate is terminal.
HISTORICAL / SUPERSEDED — predecessor update — 2026-10-03
PR #953 successor A is now on final-correction head 12cf7833822c453aeba1fb2156ba06301c6172c6. This still does not admit Gate 4D. The prepared-root coordinator-recovery Critical remains successor-B scope; #359 remains gated behind complete 4B and 4C.
LIVE PREDECESSOR NOTE — 2026-10-03
PR #953 is the live Gate 4B successor A (Read/Snapshot Admission Closure), head 590bdb868054648a82edad3a61680add5a876e07. This does not admit 4D. The prepared-root coordinator-recovery Critical remains successor-B scope; #359 remains gated behind complete 4B and 4C.
CURRENT EXECUTION HOLD — 2026-10-03 — 4D NOT PULLED FORWARD
OWNER = Gate 4D / WorldScript-Studio#359 / QNB-11
CURRENT_GATE_4_WORK = 4B superseding split
PR_952 = frozen at e34aae28bba31269a814a9a2778346e568c3577e
FIRST_SUCCESSOR = Read/Snapshot Admission Closure
SECOND_SUCCESSOR = Mutation/Root-Recovery/Lifecycle Closure
4C = after complete 4B
4D = after 4C
PRODUCTION_AUTHORITY_SWITCH_ALLOWED = NO
The newly validated prepared-root Step-F recovery gap does not pull this issue forward. That gap is ordinary-operation/root-publication coordinator recovery and belongs to Gate 4B successor B, using the existing authenticated recover_root primitive. This issue remains the later crash-resumable rotation/rekey owner and must not be used to import journal/rekey state-machine scope into the current 4B split.
The current resource-bounded coding-agent run stops after the first successor PR reaches terminal resulting-main state; it must not begin this issue.
CURRENT R-15 RECONCILIATION — 2026-10-02
- OWNER = Gate 4 / security(core/R-15): Gate 4 — journal, admission, rekey/recovery and cross-process serialization #922
- Gate 3 predecessor = TERMINAL via feat(core): close R-15 Gate 3 with its evidence matrix and residual owners (#445) #949
- Gate 4 active = slice 4A / PR feat(core): serialize every root commit through a cross-process root_commit_mutex (#445) #950
- This issue's closure slice = 4D crash-resumable rotation/rekey
- Predecessors inside Gate 4 = 4A root mutex → 4B unified admission → 4C journal/paged manifest
- Required closure = authenticated journal/checkpoints + exact resume/recovery + replay-safe rekey
- PRODUCTION_AUTHORITY_SWITCH_ALLOWED = NO
#359 is no longer blocked on Gate 3. It remains open and sequenced to Gate 4 slice 4D, where it also closes the Gate-3-carried key-epoch crash window.
HISTORICAL / SUPERSEDED — CURRENT R-15 RECONCILIATION — 2026-10-01 — superseded 2026-10-02
OWNER = Gate 4 / #922
PREDECESSOR = Gate 3 terminal
REQUIRED_FIX = authenticated journal/checkpoints + crash resume/recovery + exact replay semantics
PRODUCTION_AUTHORITY_SWITCH_ALLOWED = NO
The historical Tauri migration defect below remains valid provenance, but the implementation destination is now renderer-neutral Gate 4. Do not add a second Tauri-only migration authority; Gate 4 owns the shared journal/state machine and cross-process transition semantics.
Summary
services/fs/fsEncryptionMigration.ts#migrateAllProtectedFsData (added to fix the disable/rotate/set data-stranding bugs — see the CHANGELOG entries for those fixes) re-keys every fs-backed protected file directly, one file at a time, with no persistent journal, checkpoint, or resumable cursor. If the desktop process is killed (crash, forced quit, power loss) partway through a passphrase rotation, the result can be a mixed-key filesystem state: some files already re-encrypted under the new key, others still under the old one — while the durable IDB sentinel (which determines which key gets derived on next unlock) has not yet been updated, since rotateIdbPassphrase() runs after the fs bridge completes.
Why this is safer than it sounds, but still a real gap
- The app's protected-read paths (
unprotectTextValue,getApiKey, etc.) already fail closed and preserve (never discard) a file that fails to decrypt under the current key — a mixed-key state does not crash the app or silently destroy data outright. A file re-keyed by the interrupted migration will simply read as "temporarily unreadable," the same as a locked session. - There is currently no way for the user or the app to know which specific files were re-keyed before the crash, and no automated path back to a fully-consistent state — recovering requires manual intervention (or, in the worst case, re-entering the old passphrase and treating some files as permanently stuck).
- This is architecturally weaker than the IDB migration path, which already has a full journal/phase state machine (
services/storage/encryptionMigrationJournal.ts,encryptionMigrationOrchestrator.ts) with per-store checkpoints, a resumable cursor, and arecovery-requiredterminal state surfaced byEncryptionRecoveryModal.
Interim mitigation shipped alongside this issue
migrateAllProtectedFsData now writes a durable marker file (config/fs-migration-marker.json) before starting and deletes it only on successful completion. On next startup, FsCore.initialize() detects a stale marker and surfaces a status notification warning the user that a previous encryption migration did not finish and some files may be in a mixed key state — an honest "stuck state detected" signal rather than silence, but not automated recovery.
Real fix
Give the fs bridge the same journal/checkpoint architecture the IDB path already has: a durable per-file inventory + status (pending/done), a source/target key epoch, a resumable cursor, and a commit marker — so an interrupted rotation can resume exactly where it left off (or be cleanly rolled back) instead of requiring manual recovery. Ideally this should be a shared state machine coordinating both IDB and fs stores under one journal, since a rotation logically spans both.
Found via
Surfaced during the PR #356 (fix/desktop-project-data-encryption) review-correction loop, external assessment of the migration bridge's crash-safety properties (2026-08-13).
Activity
fix(desktop): migrate existing plaintext on first-time setup, detect …
docs: adopt Qt+GPUI native desktop strategy, retire CEF (Wave 0, PR A) (
chore(cef): retire obsolete desktop CEF target (Wave 0, PR B) (#407)
docs(native): correct false desktop fs-encryption claim in Qt/GPUI ro…
qnbs commentedon Aug 21, 2026
Live status audit against main
34b83a22: the historicalservices/fs/fsEncryptionMigration.ts/ protected-FS migration path is not present on current main; project persistence currently writes plaintext throughprojectFsStore.tsandwriteTextFileAtomic, whileencryptText/decryptTextinfsCore.tsare not production callers. The issue wording therefore describes a former/proposed path, not a currently shipped encrypted migration. The underlying requirement remains OPEN and is a prerequisite of the renderer-neutral R-15 secure-storage implementation: if encrypted FS storage is admitted, rotation must have a durable inventory/journal/cursor, key epochs, commit semantics, and crash-resumable recovery. Do not close this issue based on the existing IDB journal.qnbs commentedon Aug 24, 2026
R-15 / Rev-3 architecture reconciliation — 2026-08-24
Keep the original failure analysis as provenance, but implement the real fix under #445/R-15 renderer-neutral migration authority, not by growing a second long-lived fs/Tauri migration subsystem.
Reuse-first direction
The existing IndexedDB migration journal/orchestrator is an important semantic seed: resumable cursor, phases, recovery-required state and deterministic admission behavior already exist. Reuse the proven state-machine ideas and failure invariants where appropriate, but do not force browser-specific IDB implementation details into native Core.
Native R-15 should own one versioned migration/rekey journal model for native protected records, including:
Avoid duplicate migration authority
Do not leave Tauri fs migration and future Qt/Core migration as separate permanent authorities. Tauri may adapt to the Core implementation during transition.
Closure rule
Close only when #445 explicitly maps this issue's mixed-key interruption threat to implementation plus interruption/resume tests. The existing marker-file warning is an interim diagnostic, not closure.
qnbs commentedon Sep 5, 2026
Queue / execution status — 2026-09-05
Priority:
P0as an R-15 acceptance requirement. Canonical implementation owner: #445.This issue preserves the crash-resumable rekey/migration invariant. Do not revive the historical filesystem bridge as a separate authority merely to close this ticket.
The renderer-neutral #445 path must provide a versioned, idempotent, resumable migration/journal contract with source/target key epochs, deterministic protected-record inventory, progress/checkpoint/finalization semantics and recovery-required behavior. Interruption must not silently strand a mixed-key dataset.
Close #359 only when the canonical #445 implementation has explicit interruption/fault evidence satisfying this requirement.
qnbs commentedon Sep 24, 2026
2026-09-24 curation checkpoint
Still OPEN as an R-15/#445 acceptance/provenance child. The temporary fs migration marker remains mitigation/evidence, not crash-resumable migration closure.
Do not start a competing Tauri-only journal. #553 has been reopened after an incorrect merge-time closure; #445 must recompute live readiness before implementation. Once admitted, reconcile this issue through the canonical Core migration/rekey journal and interruption evidence, then close it explicitly from #445 evidence.
qnbs commentedon Sep 30, 2026
R-15 / v1.30.0 sequencing update — 2026-10-01
This P0 is now a direct closure input to Gate 4 owner #922 under #445 / release #926.
Gate 4 owns the durable authenticated journal/checkpoint, source/target epochs, resumable cursor/state machine, recovery-required semantics and exact resume/abort/finalize behavior needed to eliminate the mixed-key crash state described here.
Do not implement a second independent migration state machine beside R-15. Close #359 from Gate 4 evidence when the original crash-resumability acceptance is actually proven.
fix(r15): Gate 4D migration state review-wave correction
fix(r15): Gate 4D final review convergence for PR #957
feat(r15): Gate 4D migration journal execution state (slice A) (#359) (…
qnbs commentedon Oct 4, 2026
Gate 4D sequence checkpoint — 2026-10-04
#957 is merged and resulting-main remains exact at
8f1400c4bb6a84be8da940584b86b00233df1d4b. Vercel Production remainsREADYon that SHA. The five #957 preview candidates remain present and individually classify as non-production / non-rollback.Cursor could not mutate Vercel: MCP has read capability but no deployment/alias delete wrapper, its CLI session is logged out, and no token is present. No deletion was attempted. Therefore:
After retention becomes terminal, re-fetch the full #958 three-channel review census and address its two root-cause families in one docs-only correction wave.
docs(r15): tighten Gate 4D slice B gap matrix invariants
144 remaining items
fix(core): sync the quarantine directory after a put-back and report …
fix(core): sync the quarantine directory for an entry found gone, che…
qnbs commentedon Oct 15, 2026
Gate 4 H1a-2 live hard-stop — 2026-10-15 ~20:33 CEST
Operational report, NOT an approval of destructive production use. #1072 remains OPEN/Ready at exact
ae96312f192a4c2cdc54fd465027660462e8c0a7; cumulative 15/15 absolute commits. The previous vanish-after-adoptionfsyncregression was fixed in commit 15, but fresh Codex final-head P2 x2 arrived: CHANGELOG overstatesremove_if_namedidentity guarantee andInside::Foreigncan misidentify the retained object after a second swap. 11 review threads / 2 OPEN. CodeQL success, CI/CD not terminal at readback; MERGE_READY=NO.No 16th commit, force-push, budget exception or unsafe merge. Existing SATURATED recovery requires a clean signed replacement from protected main carrying the #1072 net diff plus both P2 fixes and adversarial tests, independently reviewed with complete exact-head CI. Only after that replacement is published and verified may #1072 be closed as superseded. #1070 and #1071 remain stacked on stale feature ancestry; after safe replacement merge, reconstruct their unique work without blind rebase/retarget/CI inheritance. Postmerge #627 retention/Production/CI receipts are per-new-main, not transferred.
Threat-model boundary unchanged: POSIX same-user name-based
unlinkat/rmdiris not inode-atomic;Disturbedis a heuristic detection, and deleting an actor's empty replacement remains possible during the last race. Test-support-only code does not admit productive H1/R1 reclamation. Explicit production residual decision must precede H1b/R1 enablement; Gate4 incomplete; Gate5/6/7 and v1.30 release gated.Clarification of an older control item: Q1-D1 Core-sealed lease-clock refinement was already affirmed in this owner's earlier decision. Its unattested OS-time residual is still real, but Q1-D1 acceptance should not be relisted as awaiting an owner decision.
The curated execution/contingency record and cross-issue updates live in Linear Gate4–7 control ledger, QNB-168, QNB-11, QNB-145. PR-level freeze receipt.
fix(core): treat a failed look at a retained entry as no proof, state…
fix(core): complete Unix quarantine hardening and foreign-entry ident…
qnbs commentedon Oct 15, 2026
2026-10-16 ~01:42 CEST — H1a-2 part 2b integrated: #1073 merged as
1f37cd83(supersedes #1072)Result.
main=1f37cd8368e6f647a21cf9d77fbdf20f7748076f, one parent (45ce3d0c), signature verified, squash tree identical to the validated head1602d79e. Protected squash with--match-head-commit, no admin.test-supportonly, no caller, no newunsafe, no new package. #1072 (15/15 commits,SATURATED, two valid P2 open) is closed as superseded, not merged; its branch is kept, no history was rewritten.What the Unix removal now does that the merged #1069 did not. The quarantine directory is synced after every move through it (put-back, kept aside, rollback, entry found gone), and a sync that fails is an error after the parent sync, never a false
Disturbed/Strandedand neverConfirmed; a removal becomesRemoved(NotConfirmed). The cleanup of a failed setup is identity-checked. A retained entry is reported only if it was looked at and is the object seen (a foreign entry by a no-follow stat identity); a failed look isDisturbed, notStranded/Swapped. Each is pinned by a test, mutation-checked, and the failing-sync cases use acfg(test)seam that models the error (no crash is simulated).Findings on the way (all on the record in #1072/#1073). CodeAnt Medium on put-back durability: valid, fixed. Wave 2 on
aff16785: entry found gone not synced (Codex P2 + CodeRabbit independently) and failed-setup cleanup without identity check (CodeAnt): valid, fixed, and my earlier "every path" statement corrected. Onae96312f: CHANGELOG overstated the name-based cleanup andInside::Foreigncarried no identity: valid, fixed in #1073. On #1073 first head: a failed look counted as "present" (valid, fixed fail-closed) and "foreign identity without a held handle" (valid; stated as a limit, not closed by a mechanism).Limit added to the H1 residual (owner-gated, unchanged status). A foreign entry (link, FIFO, device node, directory) is recorded by a no-follow stat identity and no handle is held, because opening such entries has effects and no handle exists for every kind on every Unix. A file system may give the identity of a removed object to its replacement; the check does not notice. This joins the
unlinkat(name)window and the link-count heuristic. Nothing here is claimed as race-free;H1_PROVED = NO.Evidence (exact head
1602d79e, 44 runs, 0 failures). Hosted Windows / macOS / Ubuntu Secure Store evidence, Core Rust Gate, Quality Gate Node 22/24, E2E, CodeQL,✅ CI Success. Review: 0 unresolved threads; Codex completed with no unresolved finding; CodeRabbit "no actionable comments" on the full net diff and on the final delta (it was rate-limited in between; one request after the window); CodeAnt Quality Gate passed (risk Low); CodeScene approved.pr:budget: 3 commits, 8 files, 1,014 meaningful lines,CAUTION/OK. Local only:fmt,clippy -D warningshost + compile-only macOS,pinned::unit tests.Resulting main. CI / CD run 38821876379 and CodeQL SAST run 38821876470
success; Pages record 7106972339success(HTTP 200); Productiondpl_7WmkbcuhchdeR8EFs9igbZBCr4L2READY on the exact SHA, canonical alias HTTP 200. SAFE-only retention (#627): 7 deleted, post-proof 51/51 protected present — receipt #627 (comment).Deviations disclosed. (1) The first
pnpm run ci:prepushfailed on pnpm's own dependency pre-check (stalenode_modules); after the frozen-lockfiledeps:reconcile(no lockfile change) the canonical wrapper passed on the final heads. The direct script call used before is not counted as the wrapper result. (2) The CodeAnt risk note on #1073 still names the first head; its gate passed on the final head. (3) TheForeignP2 is closed by a stated limit, not a mechanism.Next (no production caller). #1070 (Unix directories/leftovers: 2 commits, +342/−22, 5 files) and #1071 (Windows authenticated-handle removal: 2 commits, +343/−49, 6 files) are re-cut fresh from this
mainas signed branches with replacement PRs; both touch files the hardening changed (unix/removal.rs,remove_unix.rs, evidence document, CHANGELOG), so the unique changes are re-applied on the hardened code, not retargeted.fix(core): report a swapped-in non-empty directory as disturbed, stat…
fix(core): tell a removed directory from a moved one on macOS by its …
fix(core): remove a directory on Linux only, answer Unsupported where…
fix(core): sync the parent when a leftover's quarantine directory goe…
feat(core): remove a directory on Unix and finish the leftovers of a …
fix(core): look at a directory again right before its disposition on …
feat(core): remove on Windows through the authenticated handle (#445) (…
qnbs commentedon Oct 16, 2026
2026-10-16 ~07:10 CEST — H1a-2 complete on
main(test-support only): #1073, #1074, #1075 merged; #1072, #1070, #1071 superseded, not mergedState.
main=0ba4b94a4c70b36a74b2db25dd271626cafaf84d. Three replacement PRs were cut fresh frommain, one signed commit each plus corrections, and integrated in order by protected squash with--match-head-commit(no admin, trees identical to the validated heads). The three old PRs are closed as superseded and their branches kept; no history was rewritten.1f37cd83success52eb9f6fsuccess0ba4b94asuccessProduction READY on each exact SHA, canonical alias HTTP 200, Pages success.
What the primitives now guarantee, and what they do not. Unix: the quarantine directory is synced after every move through it, a sync that fails is an error and never a false
Disturbed/Stranded/Confirmed(a removal becomesNotConfirmed); a retained entry is reported only if it was looked at and is the one seen, otherwiseDisturbed; the cleanup by name is identity-checked; the parent is synced whenever a leftover removal removed the emptied quarantine directory. Linux: directories, with the recovery of an empty directory a crash left. Windows: files and directories through the authenticated handle, every "no POSIX delete" code isUnsupported, a directory converted in place is looked at again before the disposition. All of it istest-support, with no caller.Measured, not guessed: directory removal is Linux only. The first hosted macOS run failed it, and a second attempt via
F_GETPATHfailed the same way. A temporary probe on the hosted runner (since removed) showed for an open directory that was removed / moved / untouched: link count 2/2/2,F_GETPATHkeeps the old path for the removed one,openat("..")andstatat(".")succeed, and only creating an entry in the removed one fails (ENOENT) — a write into a directory that, in the attack case, an actor moved away, and it fails on a read-only one. Soremove_empty_diranswersUnsupportedon any Unix system but Linux, and macOS directory removal is an open question for the production decision (H1b).Same-user residual — owner-gated, not accepted for production,
H1_PROVED = NO. Besides theunlinkat(name)window and the link-count heuristic: (a) a foreign entry (link, FIFO, device node, directory) is recorded by a no-follow stat identity without a held handle, so a reused identity is not noticed; (b) the removal binds the object, not its bytes, on every platform — on Unix any same-user writer can change them between authentication and unlink (this was not stated before), on Windows a writable memory-mapped view that existed before is not stopped by the sharing rule; (c) a foreign entry that is neither a file nor an empty directory stays in its.h1q-*directory; (d) an empty foreign directory that wins the last window is removed instead of the authenticated one (it holds no data) and reportedDisturbed.Reviewer record, without rounding up. Every finding was validated against the code; each fix has a test, and the Unix/Linux ones are mutation-checked (12+ mutations, one of which survived the first tests and drove extra cases). Findings that were already closed by the hardening on
mainwere answered as such; the 7 open on #1070 and the 2 on #1071 are accounted for in the bodies of #1074 and #1075. Codex: findings on every wave, all dispositioned; on #1075 its review failed technically on the last two heads, including one manual request — no signal, not counted as a pass. CodeRabbit was rate-limited between waves and reviewed the code ranges that matter (no code finding on the final heads). CodeAnt gates passed, risk Low on the final heads; its Medium note on #1075 is answered in writing. CodeScene approved every head.Disclosed deviations. (1) The first
pnpm run ci:prepushfailed on pnpm's dependency pre-check (stalenode_modules); after the frozen-lockfiledeps:reconcilethe canonical wrapper passed on every pushed head. (2) My own wrong statements were corrected on the record: "every path that moved an entry" (#1072), the thread count in the #1074 receipt, the PR-body line count of #1075. (3) #1074 carries two temporary probe commits in its PR history (squashed away onmain; the measured answer is in the PR body and the evidence document). (4) TheForeign-identity finding is closed by a stated limit, not a mechanism.Not done / next. Gate 4 is not terminal: R1 producer-equivalence (three P2s: global cross-page order, canonical/staged exact bytes, aggregate ≤ 1,000,000), I1c generation-reachability (65,536) and the owner decisions on the residual above and on macOS directory removal are open; H1b (production use) is not admitted. Q1 clock provenance is not treated as normative. Gate 5 is not admitted, 6/7 stay locked,
v1.30.0 RELEASE = NO_GO. The routine Dependabot train stays behind a verified v1.30.qnbs commentedon Oct 16, 2026
OPERATOR PAUSE — 2026-10-16 (owner instruction; Claude weekly budget ~97% consumed, user-reported)
Operational override, effective now: PAUSED_BY_OWNER. The owner has explicitly requested a stop/pause to conserve the remaining Claude Code CLI weekly usage. The former 'continue autonomously without stopping' directions in Linear / GitHub do not authorize new source work, commits, pushes, PRs, reviews requested by agents, auto-merge, retention writes, tags, production switches or release activity during this pause. Do not queue automatic follow-up tasks or infer a restart from a passing CI job or a quota reset. Resume only after an explicit new owner instruction and a fresh live state/review/CI census. External already-triggered passive GitHub runs may complete; record their results only when work is explicitly resumed. This is a process-level hold, not a declaration that repository branch protection was modified or local Claude process was remotely stopped.
Verified safe checkpoint: protected
main=0ba4b94a4c70b36a74b2db25dd271626cafaf84d; three protected-squash merges completed in series, all test-support-only and no production caller:1f37cd8368e6f647a21cf9d77fbdf20f7748076f(Unix hardening), supersedes unmerged feat(core): harden the Unix file removal against a same-user actor in the quarantine directory (#445) #1072.52eb9f6f75f2390699772796b9ed4685967f0981(Linux directory removal and Unix leftover recovery), supersedes unmerged feat(core): remove a directory on Unix and finish the leftovers of a crash (#445) #1070.0ba4b94a4c70b36a74b2db25dd271626cafaf84d(Windows authenticated-handle file/dir removal), supersedes unmerged feat(core): remove on Windows through the authenticated handle (#445) #1071.NO_SIGNAL, not clean PASS, while the Codex P2 was corrected and tested on hosted Windows. Preserve this explicit review deviation for later security gate/admission analysis.Frozen outstanding decisions:
H1A_2_TEST_SUPPORT=MERGED, butH1_PROVED=NO,H1B_PRODUCTION_DELETE=NOT_ADMITTED,R1_PRODUCTION_RECLAMATION=NOT_ADMITTED,I1C_PRODUCTION_GC=NOT_ADMITTED,GATE4_TERMINAL=NO,GATE5_ADMITTED=NO,GATE6/GATE7=GATED,v1.30 RELEASE=NO_GO. Unix name-bound deletion remains race-exposed; same-user object/byte mutation residual and Windows pre-existing writable mmap limit remain; non-Linux Unix directory removal (notably macOS) remains Unsupported. R1 producer equivalence still requires three proofs (cross-page total order, canonical/staged exact bytes, aggregate ≤1,000,000); I1c stored-set liveness bound 65,536 remains. Explicit owner threat-model decisions are still required before production H1b/reclamation. Dependabot train stays post-verified-release unless separately authorized.Resume protocol, only on request: read current AGENTS.md/security gates, re-fetch protected main/PRs/reviews/Linear/CI and operator quota situation; independently examine the #1075 no-signal review deviation and H1/macos decision, then select the smallest admitted next critical-path slice and sign/fully review it. No retroactive 'release approved' inference. This pause creates no new development or policy exception.