Skip to content

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

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. 74 remaining items

  8. qnbs commented on Oct 9, 2026

    @qnbs
    OwnerAuthor

    Gate 4D C2c-1 (authenticated page access, the page-local cursor) TERMINAL — PR #1016

    CURRENT_MAIN = 4428fdd81c9e656be37861895a6cb001ef129dbc      PR_1016 = MERGED (final head ab016ac9; 3 signed commits; 6 files; 0 inline review threads, 1 reviewer note fixed)
    RESULTING_MAIN_CI_CD = 37901688894 / SUCCESS      CODEQL = 37901688906 / SUCCESS
    VERCEL_PRODUCTION = dpl_DBXbZkbSJDN2ii4yqSxfsDuC9XMp / READY / exact main / canonical HTTP 200      ROLLBACK = dpl_BKPsiSMc6X6PipP9Nt9S3oJuUYaL
    RETENTION = TERMINAL (2 previews deleted by exact ID, the one branch alias removed first, absence proven; 33 production/PROMOTED deployments + 3 canonical aliases retained; 0 previews; UNKNOWN = 0)
    B2 = TERMINAL      C1 = TERMINAL      D1 = TERMINAL      D2a = TERMINAL      D2b = TERMINAL      EPOCH_RELATION = TERMINAL      D3 = TERMINAL
    STREAMING_S1 = TERMINAL      STREAMING_S2A = TERMINAL      STREAMING_S2B = TERMINAL      C2A = TERMINAL      C2B1 = TERMINAL      C2B2 = TERMINAL      C2C1 = TERMINAL
    GATE_4D = IN PROGRESS      OPEN_PRS = 0      PRODUCTION_AUTHORITY_SWITCH_ALLOWED = NO
    

    Landed. ConversionSession::verify_inventory authenticates the whole page set the committed root names, once, keylessly (the key is routed per call through two new keyless readers in authority.rs, verify_committed_inventory and load_committed_page), one page in memory at a time with only the references kept; the set must be the inventory the session's snapshot names (otherwise Superseded, spent); a read failure writes nothing and does not spend the session. page reads one authenticated page by the exact path of its reference (the envelope must hash to the reference before it is opened). advance_cursor now needs the verified set (InventoryNotVerified) and refuses an entry index outside the authenticated page_entry_count of the selected page, after the existing extent check and before any write. The contract fixes what the cursor means: cursor_entry_index is the index, from zero, within page cursor_page_index of the next entry to process; finishing a page moves it to (page + 1, 0); the end of the last page has no cursor value, so completion is the transition to VERIFY. This closes the criterion recorded from the review of #1015 (issuecomment-6074817348).

    Proof. 41 cases in gate4d_conversion_entry_test, 6 new and 2 rebuilt, over a stored inventory of ten entries in pages of six and four: the references say six and four and both pages equal what was captured; (1, 9) and (1, 4) are refused (inside the total, outside page 1), forward and equal cursors accepted, a regressive cursor and an out-of-extent one refused; the cursor without the verified set refused with nothing written; a tampered, a missing and a swapped page refused by the verification with nothing written and the session not spent; a lost admission reported before a read, after a read and at the moment a read fails; a session another owner took over from cannot move the cursor. Mutation-checked; two checks survive by design (the unreachable None arm for a page index outside the references, and the comparison of the verified set with the snapshot's inventory, which cannot differ without a defect because the inventory fields are frozen once captured). Windows evidence green.

    Review. One correction wave (the QNB-11 target). No inline thread. CodeAnt's PR Risk "Medium" noted that a failed inventory read returned before the post-read admission check, so a loss during a failed read was reported as the read error: VALID_AND_FIXED in ab016ac9 (tested, mutation-checked, recorded on the PR); Low Risk on the final head, quality gate passed. CodeRabbit: no actionable comment. CodeScene approved. Codex was rate-limited for this PR and never reviewed it (NO_SIGNAL, recorded on the PR); the substantive review of this security-adjacent slice is CodeAnt, CodeRabbit and CodeScene plus the mutation-checked tests, and the Codex review of the first slice that follows is the check on this contract change.

    Disclosed. Size: 6 files, 595 meaningful lines, 3 commits (target 400 lines / 6 commits): over the line target, inside the hard tier; about 330 lines are tests (a stored-inventory fixture and the rebuilt cursor tests). The page-local meaning of the entry index is proposed and fixed in the contract by this slice; the maintainer may correct it before C2c-2 builds on it.

    Open before Gate 4 terminal:

    • C2c-2: the cursor-driven iteration with a caller-supplied per-entry step (idempotent by contract), checkpoint and lease renewal inside the loop, crash/resume evidence, CONVERT -> VERIFY.
    • Handle-relative journal and root I/O in the platform adapter (issuecomment-6073349264; Gate 4E/5 adapter work).
    • Inheriting unchanged pages; bounded read_at_most / list_dir_at_most defaults before a second adapter; pending-directory reclamation; Gate 4E write barrier. Gate 5 must not start early.
    • For the maintainer (unchanged): may exclusive admission replace lease expiry as the proof a former owner is gone (takeover question).

    NEXT. Read-only admission of C2c-2 on exact main; no source mutation before it returns READY. PRODUCTION_AUTHORITY_SWITCH_ALLOWED = NO.

  9. qnbs commented on Oct 9, 2026

    @qnbs
    OwnerAuthor

    Gate 4D C2c-2 (the cursor-driven iteration, CONVERT -> VERIFY) — read-only admission: READY as one bounded slice

    Base main 4428fdd8, 0 open PRs. Authority: the QNB-11 "Next — C2c-2" section, maintainer decision D, §10.3 (CONVERT: each record is converted, verified, then checkpointed with the current fencing generation; already checkpointed records are replayed idempotently, pending records keep source authority; VERIFY follows when every record of the final inventory is done), and the page-local cursor fixed by C2c-1.

    Findings on exact main.

    • The session has every part of the loop but not the loop: verify_inventory, page, enter_convert, advance_cursor (page-local, forward-only), renew_lease. Nothing calls a per-entry step, nothing decides when the inventory is done, and nothing leaves CONVERT.
    • The end of the last page has no cursor value (C2c-1), so "done" cannot be read from the cursor: the iteration has to know it saw the end, and a crash after the last entry must resume by processing that last entry again.
    • Core reads no clock, so lease renewal inside a loop needs the caller's time; a loop that owns the clock would put a policy in Core that the maintainer has not decided.

    Scope (<= 8 files / <= 400 meaningful lines, one correction wave).

    • conversion.rs: a trait EntryStep { type Error; fn convert(&mut self, entry: &JournalInventoryEntry) -> Result<(), Self::Error> }, idempotent by contract (it may run again for an entry after a crash or a failed checkpoint; real record conversion is Gate 5), and one bounded unit per call: ConversionSession::convert_next(fs, provider, ConvertBatch{step, entries}) processes up to entries entries of the page the cursor points into (a batch never crosses a page boundary), then records the new cursor through advance_cursor and returns Progress::More or Progress::Done. Cursor after a batch: (page, next), or (page + 1, 0) at a page end, or the last entry itself at the end of the last page. A session that has seen the end answers Done without processing; an empty inventory is Done at once. The caller loops, renewing the lease with its own clock between calls (renew_lease); the module documentation shows the loop.
    • finish_convert: CONVERT -> VERIFY through the shared step (transition_phase, cursor back to (0, 0)), only after this session saw the end (NotConverted otherwise), so a premature call cannot skip entries.
    • Failure semantics: a step error returns Step(error) with nothing written and the session intact (the already converted prefix of the batch is replayed after the next call or a resume); a failed checkpoint after the steps returns the session error, the batch is replayed on retry; a spent session, a lost admission or an unverified inventory are refused as everywhere else. Memory: one page at a time.
    • Tests (in the conversion test file, over the stored inventory of pages of six and four): a whole run in batches, every entry converted once in order and VERIFY reached (a fresh gate refuses VERIFY, the root names it); batches do not cross a page boundary; a step failing in the middle: nothing written, then a restart (drop session and guard, begin again, verify) resumes at the persisted cursor and converts only the entries from there on, all entries converted at least once, none before the cursor again; the anchor refusing a checkpoint: the batch is replayed on retry; finish_convert before the end is NotConverted, in ADMIT WrongPhase; the empty inventory; the last entry processed again after a crash that followed it. Mutation-checked; contract, evidence, CHANGELOG. The feat(core): renew the lease through the conversion session (#445) #1014 checklist is applied before the first push.

    Decisions flagged for correction (mine).

    1. A stepper (convert_next), not a monolithic loop: each call is a bounded, testable unit and the lease/clock policy stays with the caller.
    2. A batch never crosses a page boundary, so one call loads one page and writes one checkpoint.
    3. The last entry of the last page is processed again after a crash that followed it, because the cursor cannot point past the end; harmless under the idempotent contract.
    4. No checkpoint on a step failure: the prefix is replayed. The contract's per-record checkpoint is satisfied with entries = 1; a larger batch is a caller's performance choice that relies on the same idempotence.
    5. VERIFY only after this session saw the end of the last page.

    Not in C2c-2: real record conversion and source adapters (Gate 5), the VERIFY work itself and later phases, takeover through the session, handle-relative adapter I/O, inheriting unchanged pages, adapter defaults, pending-directory reclamation, the write barrier. Open for the maintainer (unchanged): may exclusive admission replace lease expiry as the proof a former owner is gone. PRODUCTION_AUTHORITY_SWITCH_ALLOWED = NO. Implementation of C2c-2 proceeds on a branch from exact main.

  10. qnbs commented on Oct 9, 2026

    @qnbs
    OwnerAuthor

    C2c-2 (PR #1017): answer to the exact-head blocker of 2026-10-09 09:38 — NOT terminal, merge pending final-head review

    The P1 on ca26d16 (the invalidation of a hand-moved cursor is lost through begin_conversion) is closed at the root in 371de5dd: advance_cursor exists only with the test-only test-support feature, the cursor step is private and only the iteration moves the cursor, so a persisted cursor is the record of entries a session converted (residual: the authority's lower level checkpoint can still write a cursor inside the extent; for the Gate 4E/5 orchestrator not to use). Codex completed its review of 371de5dd with Didn't find any major issues; CodeAnt's note on the same head (the wording "a failed step writes nothing" could be read as a rollback) was valid and is fixed in 20afde67, comments and documents only. The point-by-point proof against the QNB-11 list (durable restart repros, stored cursor continuity, idempotent last-entry replay, takeover/fence re-check across the callback, page bounds, phase transition, keyless/no-clock) is on the PR: #1017. The merge waits for the Codex review of 20afde67 and for CI on that head; C2c-2 stays non-terminal until the post-merge sequence is complete. PRODUCTION_AUTHORITY_SWITCH_ALLOWED = NO.

  11. qnbs commented on Oct 9, 2026

    @qnbs
    OwnerAuthor

    Gate 4D C2c-2 — TERMINAL (PR #1017, merged as 92bf64ac)

    C2c-2 is terminal. The conversion session now iterates the inventory and leaves CONVERT for VERIFY. Real record conversion (Gate 5), handle-relative adapter I/O, the write barrier and the rest of Gate 4E are not part of it. PRODUCTION_AUTHORITY_SWITCH_ALLOWED = NO.

    Merge squash 92bf64ac185fde9a399ed476f701d424778441ca, exact reviewed head 20afde67, merged with --match-head-commit
    Size 6 files, +782 / −21 (source, tests, contract, evidence, CHANGELOG), 5 signed commits, inside the hard tier
    Resulting main CI / CD 37923143912 success, CodeQL 37923143923 success
    Production dpl_B31kADJeBvN6uXPhg4dAFusYkEw3, READY, PROMOTED, exact 92bf64ac; the three canonical aliases are on it; https://worldscript-studio.vercel.app/ answers 200 (the two team-scoped hosts redirect to Vercel SSO, deployment protection)
    Rollback dpl_DBXbZkbSJDN2ii4yqSxfsDuC9XMp (previous Production, exact 4428fdd8)
    Retention the four #1017 previews (44ba1be0, ca26d16a, 371de5dd, 20afde67) and one branch alias removed by exact ID after a dry classification with every gate passed; final: 34 Production deployments READY / PROMOTED, 3 aliases, 0 previews, 0 unclassified
    Git branch tree identical to merged main, remote and local branch deleted, worktree detached at 92bf64ac

    Capability. convert_next(fs, provider, ConvertBatch { step, entries }) converts up to entries entries of the page the cursor points into, never across a page boundary, through the caller's idempotent EntryStep (which is handed the owner's fence), and records the next cursor (page, next) / (page + 1, 0) / the last entry itself at the end of the last page. Progress::Done is a property of the session; finish_convert (CONVERT -> VERIFY, cursor back to (0, 0)) needs a session that saw the end. A step failure records no checkpoint and rolls nothing back, so the step must be idempotent; the lease policy and the clock stay with the caller (renew_lease between calls). The cursor is moved only by the iteration: advance_cursor exists only with the test-only test-support feature, so a persisted cursor is the record of entries a session converted.

    Review of the exact heads. Codex on 44ba1be0: three findings, fixed in ca26d16a; on ca26d16a: one P1 (the invalidation of a hand-moved cursor lost through begin_conversion), closed at the root in 371de5dd; on 371de5dd and on the final head 20afde67: "Didn't find any major issues". CodeAnt's wording note (a failed step "writes nothing" read as a rollback) fixed in 20afde67. CodeScene approved every head. CodeRabbit raised one cosmetic point on the final head (see below). 5 threads, 0 unresolved. The point-by-point proof against the maintainer's QNB-11 list of 2026-10-09 09:38 (durable restart repros, stored cursor continuity, idempotent last-entry replay, takeover and fence re-check across the callback, page bounds, phase transition, keyless / no-clock) is on the PR.

    CI. 38 checks green on 20afde67. E2E Deep Coverage was cancelled once at the 30-minute job timeout while still in "Install Playwright browsers" (a runner hang, before any test ran) and passed on a re-run of that job on the same head; no code or commit was changed for it.

    Deferred, with a criterion (VALID_AND_DEFERRED_WITH_ACCEPTANCE_CRITERION): CodeRabbit on the final head, conversion.rs:519 reads "The cursor The cursor is page-local" in the rustdoc of the test-only advance_cursor. Cosmetic, no behaviour or contract text, and the head was frozen after the consolidated wave. Criterion: the next Gate 4D slice that touches conversion.rs corrects the sentence in its first commit.

    Still residual (unchanged list, none closed by this slice): the authority's lower-level commit_journal_checkpoint can still write a cursor inside the manifest's extent (for the Gate 4E/5 orchestrator not to use to claim progress); handle-relative journal/root I/O (criterion above); inheriting unchanged inventory pages across retries; bounded read_at_most / list_dir_at_most defaults before a second DurableFs adapter; pending-directory reclamation; Gate 4E write barrier, phase-aware refusal and restart evidence. Open for the maintainer: may exclusive admission replace lease expiry as the proof a former owner is gone. Gate 5 does not start early.

  12. qnbs commented on Oct 9, 2026

    @qnbs
    OwnerAuthor

    Gate 4D F1 (bounded reads are required of every DurableFs adapter) — read-only admission: READY as one bounded slice

    Base main 92bf64ac, 0 open PRs, no new maintainer text on QNB-11 or here since the C2c-2 terminal proof (issuecomment-6080320510). Authority: the QNB-11 list "Remaining before Gate 4 terminal" (bounded read_at_most / list_dir_at_most defaults before additional DurableFs adapters), the Gate 4D evidence (the manifest read "is bounded"; residual list: both defaults must be overridden by an adapter over real files).

    Findings on exact main.

    • DurableFs::read_at_most and list_dir_at_most have default bodies that call read / list_dir and compare the length afterwards, i.e. they load the whole file or directory before the limit is applied. They exist so that the test doubles that intercept read / list_dir stay faithful. The only protection against an adapter that forgets to override them is a sentence in the doc comment. Every Gate 4D journal read (manifest envelope, page envelope, the page-directory listing of at most 64 names, the staged-capture reads) goes through these two methods precisely so that a corrupted or hostile size cannot exhaust memory; an adapter that keeps the default loses that bound silently and nothing fails.
    • StdFs overrides both correctly (take(limit + 1)); there are 15 other implementors, all test doubles under tests/. Only 5 test files share tests/support, so a helper kept there would not reach the rest.
    • Outside this slice, and recorded rather than fixed here: the Gate 3 record, root and catalog paths (commit.rs, root_store.rs, authority.rs) still call the unbounded read / list_dir on authority files and directories, and journal/durable.rs reads back its own just-promoted file with read. They predate the Gate 4D bounded-read rule and have no limit constants yet; a per-file limit needs the codec's maximum envelope size and is its own admission.

    Scope (<= ~13 files, small line count, one correction wave). The trait is fixed at the type level, not by a comment:

    • durable.rs: read_at_most and list_dir_at_most lose their default bodies and become required methods, so an adapter cannot compile without stating how it bounds the allocation. The docs say what the contract is (never more than limit + 1 bytes / names in memory; None above the limit; the error of the underlying call otherwise). Two helpers, read_at_most_via_read and list_dir_at_most_via_list_dir, compiled only with the existing test-support feature (the same pattern as generate_with_random behind test-randomness), give a test double its old faithful behaviour in one line, documented as loading everything first and never for an adapter over real files.
    • The 15 test doubles implement the two methods in one line each (the helper, or a delegation to the inner StdFs where the double already wraps it). No behaviour of any test changes.
    • A compile-fail doctest pair on the trait pins the rule mechanically: one doctest implements every method and compiles; the other differs only by omitting the two methods and must not compile. Existing StdFs tests (std_read_at_most_never_loads_more_than_the_limit, the listing test) stay and keep covering the bound itself.
    • Contract / evidence / CHANGELOG: the adapter obligation moves from "should override" to "must implement"; the unbounded Gate 3 paths are listed as an explicit residual with this finding as the reason.

    Decisions flagged for correction (mine).

    1. Required methods rather than a bounded default: a bounded default is not expressible without an additional required primitive (an open/handle or an iterator), which would change every double's fault interception and belongs to the handle-relative adapter work (Gate 4E/5).
    2. The faithful helpers are public items behind test-support rather than a tests/support module, because only 5 of the test files share that module and the feature is already the crate's boundary for test-only API.
    3. The Gate 3 unbounded reads are recorded, not changed: they need a limit constant per file kind, which is a design decision about the record envelope.

    Not in F1: the Gate 3 bounded reads, inheriting unchanged inventory pages, pending-directory reclamation, handle-relative adapter I/O, Gate 4E, any behaviour change in conversion.rs (the deferred rustdoc typo from C2c-2 is therefore not triggered here and stays on its criterion). Open for the maintainer (unchanged): may exclusive admission replace lease expiry as the proof a former owner is gone. PRODUCTION_AUTHORITY_SWITCH_ALLOWED = NO. Implementation proceeds on a branch from exact main; the #1014 checklist (outcome matrix, Windows portability of every helper, residual lists) is applied before the first push.

  13. qnbs commented on Oct 9, 2026

    @qnbs
    OwnerAuthor

    Gate 4D F1 — TERMINAL (PR #1018, merged as d1710f0a)

    F1 is terminal. DurableFs::read_at_most and list_dir_at_most are required trait methods: an adapter cannot compile without stating how it bounds the allocation. This closes the QNB-11 item bounded read_at_most / list_dir_at_most defaults before additional DurableFs adapters. No runtime behaviour of StdFs or of any journal read changed. PRODUCTION_AUTHORITY_SWITCH_ALLOWED = NO.

    Merge squash d1710f0a0f14ef3e3a8ce70f0c1e59582c3209f9, exact reviewed head 65de48617e91078d5d2d941f45c29d0e6f7a3d62, merged with --match-head-commit after the exact-head CI and review closeout the maintainer's 14:20 CEST checkpoint asked for
    Size 13 files, +340 / −15 (about 90 lines of it rustdoc; 9 of the files are one-purpose test doubles), 2 signed commits, inside the hard tier
    Resulting main CI / CD 37930964197 success, CodeQL 37930964418 success
    Production dpl_9T9o3RvF7LkaFmToPnaUX5VqMMgb, READY, PROMOTED, exact d1710f0a; the three canonical aliases are on it; https://worldscript-studio.vercel.app/ answers 200 (the two team-scoped hosts redirect to Vercel SSO, deployment protection)
    Rollback dpl_B31kADJeBvN6uXPhg4dAFusYkEw3 (previous Production, exact 92bf64ac)
    Retention the one #1018 preview (65de4861) and its branch alias removed by exact ID after a dry classification with every gate passed; final: 35 Production deployments READY / PROMOTED, 3 aliases, 0 previews, 0 unclassified
    Git branch tree identical to merged main, remote and local branch deleted, worktree detached at d1710f0a

    What landed. The two methods lose their default bodies (which loaded the whole file or directory before applying the limit, protected only by a doc sentence). The contract is stated on the trait: at most limit + 1 bytes or names held, None above the limit, the underlying error otherwise; StdFs is unchanged (take(limit + 1)). The 15 in-tree test doubles implement them through two public helpers compiled only with test-support (read_at_most_via_read, list_dir_at_most_via_list_dir: what the defaults did, documented as never for an adapter over real files). A doctest triple on the trait pins the rule: an adapter implementing every method compiles; the same adapter without read_at_most, and without list_dir_at_most, each fails with E0046.

    Proof. The rule is compile-time, so the mutation is the rule itself: re-adding the default body of read_at_most, or of list_dir_at_most, makes exactly its own compile-fail doctest fail ("compiled successfully, but it's marked compile_fail"); the file was restored byte-identical each time. An earlier version of the doctest (one compile-fail case omitting both methods) was found by the mutation check to leave each method unpinned on its own, and was split before the first push. Every suite that uses a double passes unchanged (13 binaries, listed on the PR). Clippy over all targets, cargo fmt --check, docs:check; the library builds without test-support.

    Review of the exact head. Codex on 65de48617e: "Didn't find any major issues". CodeAnt: gate passed, PR Risk Low. CodeScene approved. CodeRabbit: no actionable comment. Sourcery: rate-limited (NO_SIGNAL). 0 review threads. 38 checks green on the exact head (codecov/patch, the Windows secure-store evidence, E2E and E2E Deep Coverage included), no re-run needed.

    Still residual (none closed by this slice): the Gate 3 post-promotion verify and the page, marker, root, record and catalog reads (commit.rs, root_store.rs, authority.rs) still use the whole-file read / list_dir (limits per file kind, the separate acceptance criterion already recorded on #359); inheriting unchanged inventory pages across retries; pending-directory reclamation; handle-relative journal/root I/O (Gate 4E/5 adapter, criterion above); Gate 4E write barrier, phase-aware refusal and restart evidence; the lower-level commit_journal_checkpoint cursor residual. Open criterion still pending: the next Gate 4D slice that touches conversion.rs corrects the repeated "The cursor The cursor" in the rustdoc of advance_cursor in its first commit (F1 did not touch that file). Open for the maintainer: may exclusive admission replace lease expiry as the proof a former owner is gone. Gate 5 does not start early.

  14. qnbs commented on Oct 9, 2026

    @qnbs
    OwnerAuthor

    Gate 4E E1 (the ordinary-operation barrier of a bound root, pinned with evidence) — read-only admission: READY as one bounded, tests-and-docs slice

    Base main d1710f0a, 0 open PRs, no new maintainer text on QNB-11 since the 14:20 CEST checkpoint, which lists Gate 4E ("complete phase-aware writer barrier, final capture/admission refusal, restart/crash evidence") as pending. This admission takes the write barrier because it is the part of Gate 4 that protects data. Authority: contract §10.3 (phase table: DISCOVER admits no ordinary writes; PREPARE admits ordinary reads and writes, the root binding copied forward unchanged; ADMIT onward and RECOVERY_REQUIRED block ordinary writes; DONE permits normal policy), §5.4 / §10.1.1, the open criterion "write barrier of the final capture" on #359 (issuecomment-6038697749), the predicate ordinary_mutating_writes_admitted (true exactly for PREPARE and DONE).

    Findings on exact main (a first reading of this item, that the writer never looks at the migration, was wrong and is corrected here).

    • Every ordinary operation of ProtectedStorage (try_write_record, try_reconcile_record, list_records and read_record, the last two also through an AuthoritySnapshotGuard) loads the catalog through catalog(), which returns MigrationRequired as soon as the committed root carries a live-migration binding (operations.rs, catalog). try_lock (from an unlocked key), try_unlock and try_shutdown (when not already locked) refuse with RecoveryPending for the same reason (verify_transition). The input is the authenticated committed root alone, so the barrier is durable across a crash and needs no journal read and no journal location.
    • The barrier is therefore phase-independent and stricter than §10.3: while a binding exists it also refuses the reads and the PREPARE-window writes that the contract admits. That is fail-closed and safe; it is not yet "phase-aware".
    • No test combines ProtectedStorage with a bound root (the test files that use ProtectedStorage and those that use a live binding are disjoint). The barrier that stops an ordinary writer from mixing keys during a migration has no direct evidence, and no cold-start evidence.
    • ordinary_mutating_writes_admitted has no caller outside its own test; it is the specification for the relaxation, not a gate in use.
    • No production caller binds a root today (bind is a Gate 4E/5 enable step), so the barrier is currently unobservable in production; the evidence is what keeps it from regressing before the first binder exists.
    • A phase transition out of PREPARE is only safe against a running ordinary writer if it happens under exclusive admission (a shared writer holds shared admission for its whole write). The conversion gate holds it by type (C2a); the lower-level checkpoint API does not require it. That is criterion (5) and belongs to E3.

    Split by proof boundary (Gate 4E).

    • E1 (this slice): evidence and documentation of the existing barrier. No behaviour change.
    • E2: the phase-aware relaxation, only when the first binder needs it: PREPARE and DONE admit ordinary reads and writes through the root-named manifest (read_committed_journal), which needs a journal location as writer configuration (it is caller-supplied everywhere else and is not in the root), a fail-closed rule for a binding without a location, and a policy for reconciliation under the barrier.
    • E3: the ADMIT transition and the final capture's entry require exclusive admission at the type level; the final-capture admission refusal.

    E1 scope (<= 8 files, tests and documents). In tests/support/operations.rs a constructor that commits a root binding a live migration before the storage is built (the same body-commit as the conversion fixture's bind), so the storage starts cold over an already bound tree. A new test file over the real tree: try_write_record, try_reconcile_record, list_records, read_record (through an AuthoritySnapshotGuard; whether the guard itself is obtained over a bound root is pinned by the test, not assumed) refuse with MigrationRequired; try_lock / try_unlock / try_shutdown refuse with RecoveryPending; the tree is byte-identical afterwards (nothing written, no marker, no staging, no root); the refusal does not depend on what the binding names (an operation id, a fence and a manifest digest that name no journal at all are refused identically, which pins "the root alone decides"); the same fixture without the binding admits (control, so the refusal is the binding's); a second, cold storage over the same tree refuses the same way. Mutation-checked: the live-binding refusal in catalog() and in verify_transition removed one at a time must fail the tests that own them. Contract §10.3 and the evidence document state the current behaviour as an intentional, stricter interim, with the relaxation as E2.

    Decisions flagged for correction (mine).

    1. Keep the stricter behaviour for now and prove it, rather than relax to the contract's PREPARE window: relaxing needs a journal location and a reconciliation policy, and no binder exists to need it. Say so if you want the PREPARE-window availability earlier.
    2. try_authority_snapshot is not changed in E1: whatever it does over a bound root is recorded by the test; the guard's reads refuse either way.
    3. Reconciliation of a pending ordinary write under the barrier stays refused in E1 (a pending write from before the barrier must be reconciled by the orchestrator under its exclusive admission); the policy question is recorded for E2.

    Not in E1: the relaxation, the journal location, the transition rule (E3), binding (bootstrap) and clearing, the Gate 3 read limits, inheriting unchanged pages, pending reclamation, handle-relative I/O, any conversion.rs change (so the rustdoc typo criterion is not triggered). Open for the maintainer (unchanged): may exclusive admission replace lease expiry as the proof a former owner is gone. PRODUCTION_AUTHORITY_SWITCH_ALLOWED = NO. Implementation proceeds on a branch from exact main; the #1014 checklist is applied before the first push.

  15. qnbs commented on Oct 9, 2026

    @qnbs
    OwnerAuthor

    Gate 4E E1 — TERMINAL (PR #1019, merged as 8424a7c6)

    E1 is terminal. The ordinary-operation barrier of a bound root is pinned with evidence. No behaviour changed in src/. This does not close Gate 4E: E2 (phase-aware relaxation) and E3 (exclusive ADMIT / final capture) are not admitted and not started; per the maintainer's instruction the source lane is paused after this merge. PRODUCTION_AUTHORITY_SWITCH_ALLOWED = NO.

    Merge squash 8424a7c640074dedac8de24a98fd6932c80c8230, exact reviewed head 10cfd539ad7af3b44819071fe957adf752c19b5e, merged with --match-head-commit
    Size 6 files, +863 / −55 (test, test support in two modules, contract, evidence, CHANGELOG), 8 signed commits (over the target of 6, inside the hard tier of 10)
    Resulting main CI / CD 37953963432 success, CodeQL 37953963093 success, both on exactly 8424a7c6
    Production dpl_BXtcjwQKG8XC8uS8kZziTyNb2SZz, READY, PROMOTED, exact 8424a7c6; the three canonical aliases are on it; https://worldscript-studio.vercel.app/ answers 200 (the two team-scoped hosts redirect to Vercel SSO, deployment protection)
    Rollback dpl_9T9o3RvF7LkaFmToPnaUX5VqMMgb (previous Production, exact d1710f0a)
    Retention the seven #1019 previews (feb62f91, d41d55ce, 65307534, 616ec8a7, 324d22f8, 992c2440, 10cfd539) removed by exact ID after a dry classification with every gate passed (PR merged, branch gone, no Production alias, not rollback, not an open-PR head); final: 7 × ABSENT, 36 Production deployments READY / PROMOTED, 3 aliases, 0 previews, 0 unclassified
    Git branch tree identical to merged main, remote and local branch deleted, worktree detached at 8424a7c6

    What is pinned (gate4b_operations_test, 38 cases: 20 existing, 18 new). With an unlocked provider, a loadable catalog and no interrupted root commit pending, a committed root that carries a live-migration binding makes ProtectedStorage refuse every ordinary operation, in every phase. The data operations (write, reconcile, list, read) decide in catalog() from the committed root before any catalog page is read (MigrationRequired); the lifecycle transitions (lock from an unlocked key, unlock, shutdown) load the catalog first and then refuse with RecoveryPending. Also pinned: the installation tree unchanged apart from the two top-level coordination resources (which must hold no data), with a negative proof that the snapshot sees every other change including symlink replacements; a binding that names no journal; a cold start; a provider that starts locked (try_lock idempotent, try_unlock refused, a later write fails with Locked) against an unbound control; a lost catalog page (transitions RecoveryRequired(CatalogSetMismatch), data operations including reconcile still MigrationRequired); an interrupted root commit with a completed and with a discarded recovery, write and reconcile each on a fresh fixture. Mutation-checked at both refusal sites and at the two snapshot rules (top-level-only exclusion, links recorded as links); every mutation restored byte-identical.

    Review convergence on the exact head. Codex on 10cfd539ad: "Didn't find any major issues". CodeScene approved. CodeAnt: quality gate passed. 12 review threads, 0 unresolved, all replied to with the resolving commit. 38 checks green (codecov/patch, Windows / macOS / Linux secure-store evidence, E2E, E2E Deep Coverage, CodeQL, the CHANGELOG-number guard and the governance check included). Five correction waves, all bounded: 12 Codex P2 findings (every one valid, every one about a claim in the contract, evidence or tests that was broader than the code or the tests established, or about the depth of the evidence itself), and CodeScene failed the gate twice on my own fixture changes (critical Low Cohesion on tests/support/operations.rs, then Large Method in the extracted module): fixed structurally by moving the setup into tests/support/preconfigured.rs and decomposing it, not suppressed. Dispositions of the two late CodeAnt notes are on the PR: the Medium-Risk note is the documented stricter-than-§10.3 interim (INVALID_AS_DEFECT), the symlink nitpick on the coordination data check is deferred with a criterion.

    One CI event, recorded as an event. On head 65307534 the Secure Store Platform Evidence (ubuntu-latest) job failed once: platform_keystore_test::full_lifecycle_against_the_real_os_secure_store, cleanup r15-anchor-v1: SecureAnchorUnavailable; left behind: []. Diagnosis (inference, not proof): the test body never deletes the anchor and asserts it present immediately before cleanup, so it existed when the delete call returned the platform error; the following get found it absent with the daemon reachable, so the delete took effect while the call reported failure (keyring opens a new D-Bus connection with a DH key exchange for every call and maps PlatformFailure / NoStorageAccess to SecureAnchorUnavailable); the failed attempt ran 7.15 s against 3.4–5.9 s on passing runs. The 29 earlier runs of the job passed; a same-SHA re-run of only the failed jobs passed (3.5 s); the job passed on the final head. Not reproduced and not fixed; no assertion or cleanup verification was changed. Criterion (separate, not admitted): harden the evidence cleanup so a reported delete error is retried a bounded number of times and the final state is still verified absent, without weakening either check.

    Criteria recorded by this slice. (1) The next slice that touches the operations test support records the coordination resources with symlink_metadata and treats a link at those names as not data-free (CodeAnt nitpick). (2) The Linux evidence-cleanup hardening above. Unchanged and still open: the rustdoc typo in advance_cursor (first commit of the next slice that touches conversion.rs); the phase-aware relaxation (E2), including the decision to check the binding before the catalog is loaded in verify_transition; exclusive ADMIT / final capture (E3); Gate 3 whole-file read limits; unchanged-page inheritance; pending-directory reclamation; handle-relative I/O; the refusal codes this slice states as not pinned (a tampered page, an unresolvable key, an I/O error, shutdown over a locked provider). Open for the maintainer: may exclusive admission replace lease expiry as the proof a former owner is gone. Gate 5 / 6 / 7 and v1.30.0 remain gated.

  16. qnbs commented on Oct 11, 2026

    @qnbs
    OwnerAuthor

    WorldScript control-plane reconciliation — 2026-10-11

    Protected main is c87d1cda171ac511ec65350ec7feed1780e829af. Push-triggered CI/CD run 38071809656 and CodeQL run 38071809617 are SUCCESS on that exact SHA. Latest published release remains v1.29.1.

    PR #1017 is merged, not an active unmerged lane. Crash/restart resume, replay safety and explicit recovery from mixed-key state remain required. Neither #1017's merge nor the later tag-boundary documentation fix proves the historical Codex P1 resolved. No closure or new migration implementation is claimed.

    Gate 4 remains open. Gate 4E E1 is terminal; E2/E3 are not admitted. Gates 5/6/7 and v1.30 are not admitted. RELEASE_ALLOWED=NO; PRODUCTION_AUTHORITY_SWITCH_ALLOWED=NO.

    Canonical mirror: https://linear.app/qnbs/issue/QNB-11. This publishes the revalidated QNB-194 handover packet through the existing maintainer gh principal. Historical specifications and issue state remain preserved; no source or workflow change is implied.

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