Skip to content

Desktop fs-data key-rotation migration is not crash-resumable (mixed-key state possible) #359

Open

Description

@qnbs

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

#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 a recovery-required terminal state surfaced by EncryptionRecoveryModal.

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

  1. qnbs commented on Aug 21, 2026

    @qnbs
    OwnerAuthor

    Live status audit against main 34b83a22: the historical services/fs/fsEncryptionMigration.ts / protected-FS migration path is not present on current main; project persistence currently writes plaintext through projectFsStore.ts and writeTextFileAtomic, while encryptText/decryptText in fsCore.ts are 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.

  2. qnbs commented on Aug 24, 2026

    @qnbs
    OwnerAuthor

    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:

    • operation/version;
    • deterministic record inventory or resumable cursor;
    • source/target key epochs;
    • pending/in-progress/done or equivalent state;
    • durable commit/finalization phase;
    • interruption/restart semantics;
    • recovery-required and rollback policy.

    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.

  3. qnbs commented on Sep 5, 2026

    @qnbs
    OwnerAuthor

    Queue / execution status — 2026-09-05

    Priority: P0 as 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.

  4. qnbs commented on Sep 24, 2026

    @qnbs
    OwnerAuthor

    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.

  5. qnbs commented on Sep 30, 2026

    @qnbs
    OwnerAuthor

    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.

  6. qnbs commented on Oct 4, 2026

    @qnbs
    OwnerAuthor

    Gate 4D sequence checkpoint — 2026-10-04

    #957 is merged and resulting-main remains exact at 8f1400c4bb6a84be8da940584b86b00233df1d4b. Vercel Production remains READY on 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:

    POST_#957_RETENTION = NOT COMPLETE
    PR_958 = FROZEN
    MERGE_#958 = NO
    FURTHER_PUSH_#958 = NO
    SLICE_B_IMPLEMENTATION = NO
    NEXT = authenticated local Vercel cleanup via Claude Code CLI Sonnet / existing operator session
    PRODUCTION_AUTHORITY_SWITCH_ALLOWED = NO
    

    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.

  7. 144 remaining items

  8. qnbs commented on Oct 15, 2026

    @qnbs
    OwnerAuthor

    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-adoption fsync regression was fixed in commit 15, but fresh Codex final-head P2 x2 arrived: CHANGELOG overstates remove_if_named identity guarantee and Inside::Foreign can 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/rmdir is not inode-atomic; Disturbed is 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.

  9. qnbs commented on Oct 15, 2026

    @qnbs
    OwnerAuthor

    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 head 1602d79e. Protected squash with --match-head-commit, no admin. test-support only, no caller, no new unsafe, 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/Stranded and never Confirmed; a removal becomes Removed(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 is Disturbed, not Stranded/Swapped. Each is pinned by a test, mutation-checked, and the failing-sync cases use a cfg(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. On ae96312f: CHANGELOG overstated the name-based cleanup and Inside::Foreign carried 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 warnings host + compile-only macOS, pinned:: unit tests.

    Resulting main. CI / CD run 38821876379 and CodeQL SAST run 38821876470 success; Pages record 7106972339 success (HTTP 200); Production dpl_7WmkbcuhchdeR8EFs9igbZBCr4L2 READY 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:prepush failed on pnpm's own dependency pre-check (stale node_modules); after the frozen-lockfile deps: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) The Foreign P2 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 main as 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.

    H1A_2_HARDENING = MERGED_1f37cd83 (test-support only) | H1_PROVED = NO | SAME_USER_RESIDUAL = OWNER_GATED_FOR_PRODUCTION
    R1_PRODUCTION_RECLAMATION = NOT_ADMITTED | I1C_PRODUCTION_GC = NOT_ADMITTED | GATE4_TERMINAL = NO | GATE5_ADMITTED = NO | RELEASE_AUTHORIZED = NO
    
  10. qnbs commented on Oct 16, 2026

    @qnbs
    OwnerAuthor

    2026-10-16 ~07:10 CEST — H1a-2 complete on main (test-support only): #1073, #1074, #1075 merged; #1072, #1070, #1071 superseded, not merged

    State. main = 0ba4b94a4c70b36a74b2db25dd271626cafaf84d. Three replacement PRs were cut fresh from main, 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.

    Slice PR → main Supersedes Resulting-main CI/CD, CodeQL Retention (#627)
    Unix hardening (quarantine sync, identity, fail-closed look) #1073 → 1f37cd83 #1072 (15/15, saturated) 38821876379, 38821876470 success 7 deleted, 51/51 protected: receipt
    Unix directory removal (Linux) and leftover recovery #1074 → 52eb9f6f #1070 38838765824, 38838765751 success 9 deleted, 52/52: receipt
    Windows removal through the authenticated handle #1075 → 0ba4b94a #1071 38852100613, 38852100651 success 5 deleted, 53/53: receipt

    Production 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 becomes NotConfirmed); a retained entry is reported only if it was looked at and is the one seen, otherwise Disturbed; 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 is Unsupported, a directory converted in place is looked at again before the disposition. All of it is test-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_GETPATH failed 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_GETPATH keeps the old path for the removed one, openat("..") and statat(".") 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. So remove_empty_dir answers Unsupported on 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 the unlinkat(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 reported Disturbed.

    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 main were 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:prepush failed on pnpm's dependency pre-check (stale node_modules); after the frozen-lockfile deps:reconcile the 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 on main; the measured answer is in the PR body and the evidence document). (4) The Foreign-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.

    H1A_2_REMOVAL = MERGED_1f37cd83_52eb9f6f_0ba4b94a (test-support only, Linux dirs, Windows files+dirs) | H1_PROVED = NO
    SAME_USER_RESIDUAL = OWNER_GATED_FOR_PRODUCTION | MACOS_DIRECTORY_REMOVAL = UNSUPPORTED_OPEN_QUESTION
    R1_PRODUCTION_RECLAMATION = NOT_ADMITTED | I1C_PRODUCTION_GC = NOT_ADMITTED | GATE4_TERMINAL = NO | GATE5_ADMITTED = NO | RELEASE_AUTHORIZED = NO
    
  11. qnbs commented on Oct 16, 2026

    @qnbs
    OwnerAuthor

    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:

    Frozen outstanding decisions: H1A_2_TEST_SUPPORT=MERGED, but H1_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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions