Claim storage cleanup files privately before verification and removal #742
Closed
kayg
wants to merge 4 commits from
fix/storage-exact-file-quarantine-642-3b into dev
pull from: fix/storage-exact-file-quarantine-642-3b
merge into: kayg:dev
kayg:main
kayg:feat/style-sheet-fixes
kayg:storage/wal-checkpoint-off-save-1051
kayg:feat/contrast
kayg:t3/paged-search-reconcile-final
kayg:feat/pass8
kayg:review/calendar-2
kayg:fix/shared-row-swipes
kayg:dev
kayg:feat/collections-db
kayg:fix/instant-keys-1056
kayg:feat/money-round
kayg:perf/stalls-1054
kayg:wip/tiering-v3-cold4
kayg:wip/tiering-v3-cold5
kayg:port/rows-actions
kayg:port/rows-drop
kayg:port/hidden-blocks
kayg:port/rows
kayg:port/port-tables
kayg:port/port-tables-2
kayg:t3/mail-1011-liveness
kayg:port/devices
kayg:port/port-attachments
kayg:port/port-attachments-2
kayg:port/port-attachments-3
kayg:docs/design-consistency
kayg:docs/engineering-standards
kayg:fix/rusqlite-0.41-rustsec-2026-0459
kayg:feat/iphone-fixes
kayg:shared/restore-and-stale-918-919
kayg:fix/phone-top-band-contrast
kayg:feat/notes-approve
kayg:perf/soak-max-gate
kayg:feat/files-s1-identity
kayg:fix/lifecycle-event-id
kayg:feat/quickfix
kayg:wip/tiering-v3-cold
kayg:perf/web-mockup-frame-fixes
kayg:port/contacts-merge
kayg:port/contacts-edit
kayg:test/shared-room-no-deleted-text
kayg:perf/first-paint-credits
kayg:fix/shared-history-after-grant
kayg:fix/phone-swipe-regressions
kayg:feat/contrast-r1
kayg:review/sharing-pr3-review
kayg:feat/tiering-v3-s2-tags
kayg:feat/folder-drop
kayg:fix/deskpills-lazy-share
kayg:feat/sharing-s1-pr3
kayg:tmp/money-round-before
kayg:fix/phone-pane-backdrop-roots
kayg:fix/palette-preload-chips
kayg:fix/invite-defaults
kayg:port/tasks-matrix
kayg:fix/restore-green-gates
kayg:fix/sharing-fast-path-hardening
kayg:t3/photos-s1
kayg:feat/tiering-v3-s1-namespace
kayg:notes-archive-hide
kayg:feat/tasks-actions
kayg:t3/photos-plan-slice-one
kayg:feat/whiteboard-s1
kayg:t3/paged-search-reconcile
kayg:t3/smooth-pane-transitions
kayg:t3/index-position-startup-scan
kayg:fix/private-staging-759b-r2
kayg:feat/dav-transport
kayg:feat/imap-notes-fetch
kayg:feat/imap-notes-projection
kayg:feat/imap-notes-readonly
kayg:feat/imap-notes-auth
kayg:feat/imap-notes-server-foundation
kayg:docs/undo-block-ids-plan
kayg:fix/undo-768
kayg:proto/block-identity-v7d
kayg:issue/carddav-read-perf-fix-r2
kayg:issue/carddav-read-r2
kayg:docs/imap-766-plan-v2
kayg:docs/imap-notes-plan
kayg:docs/quota-plan-2
kayg:fix/parallel-test-flakes
kayg:feat/contacts-companion-pure
kayg:docs/calendar-backend-v2-plan
kayg:feat/calendar-ical-converter
kayg:docs/tasks-writes-v2-plan
kayg:docs/carddav-writes-v2-plan
kayg:docs/contacts-import-v2-plan
kayg:issue/610-pm-midnight
kayg:feat/calendar-v2-s1-containment
kayg:docs/dav-recorder-v2-plan
kayg:plan/previews-865
kayg:plan/save-log-durable-entry
kayg:fix/e2e-data-on-ram
kayg:feat/appwindow3
kayg:feat/appwindow2
kayg:feat/appwindow
kayg:feat/money-p1
kayg:feat/keys-sheet
kayg:review/calendar-2-themes
kayg:feat/smooth-cursor
kayg:fix/float-style
kayg:review/calendar-2-float
kayg:release/2026-11-06-hotfix
kayg:archive/port/rows-drop-code
kayg:archive/port/rows-actions
kayg:archive/port/rows
kayg:scratch/tasks-894-ui-gates
kayg:review/calendar-2-night-wip
kayg:release/2026-11-05
kayg:archive/feat/tiering-v3-s1-namespace
kayg:archive/flaky/rows-drop-fixed
kayg:archive/flaky/rows-actions
kayg:archive/feat/tiering-v3-reader
kayg:archive/feat/tiering-v3-roots
kayg:archive/flaky/rows
kayg:sim/bm15-final
kayg:sim/bm15-fix
kayg:sim/bm15-bis-932
kayg:sim/bm15-bis-909
kayg:sim/bm15
kayg:sim/bm15-bis-887
kayg:perf/tiering-option-b
kayg:port/style-3
kayg:port/style-2
kayg:port/style
kayg:fix/sw-lazy-precache
kayg:fix/vim-w-waits-for-save
kayg:fix/signout-test-races
kayg:fp-scratch/all
kayg:sim/bm15-first-paint
kayg:archive/bm15/rows-drop
kayg:archive/bm15/rows-actions
kayg:fix/palette-snippet-contrast
kayg:archive/bm15/rows
kayg:archive/bm15/hidden
kayg:archive/plan/save-log-durable-entry
kayg:mock/money-fix2
kayg:archive/fix/e2e-data-on-ram
kayg:mock/appearance-all
kayg:mock/pill-press
kayg:archive/docs/design-consistency
kayg:mock/auto-swipes
kayg:mock/mail-actions
kayg:archive/feat/mail-p1
kayg:mock/money-static
kayg:archive/review/standards-917
kayg:archive/docs/engineering-standards
kayg:perf/tiering-761-fix
kayg:mock/type-audit
kayg:mock/third-pane
kayg:feat/contacts-import-v2-s1
kayg:perf/executor-hot-path
kayg:archive/port/hidden-blocks
kayg:archive/port/devices
kayg:scratch/executor-on-894
kayg:scratch/executor-spans2
kayg:scratch/executor-spans
kayg:train/2026-11-03-bm14c
kayg:archive/port/rows-drop
kayg:feat/tiering-761-d-scale
kayg:feat/tiering-761-c-index
kayg:feat/tiering-761-b-reader
kayg:archive/review/sharing-pr3-review-9fec3d1f
kayg:train/2026-11-03-bm14b
kayg:perf/ports-lazy-budgets
kayg:feat/tiering-761-a-migration
kayg:train/2026-11-03-bm14
kayg:train/2026-11-03-bm12
kayg:archive/review/sharing-pr3-review
kayg:archive/feat/sharing-s1-pr3
kayg:train/2026-11-03-bm11
kayg:archive/feat/imap-notes-fetch
kayg:train/2026-11-03-bm10
kayg:archive/review/sharing-pr3-review-code
kayg:archive/plan/previews-865
kayg:train/2026-11-02-bm9
kayg:archive/port/port-attachments-3
kayg:archive/port/contacts-merge
kayg:fix/motion-audit-859
kayg:train/2026-11-02-bm8
kayg:archive/scratch/bm8-prep
kayg:archive/port/port-pickers
kayg:archive/port/port-tables-2
kayg:archive/port/port-tables
kayg:archive/feat/sharing-s1-pr3-code
kayg:archive/scratch/bm7-fixed
kayg:train/2026-11-02-bm7
kayg:archive/fix/parallel-test-flakes-code-minipc01
kayg:train/2026-11-02-bm6
kayg:docs/imap-766-readonly-slice
kayg:feat/dav-recorder-v3
kayg:research/788-diagnosis
kayg:archive/fix/parallel-test-flakes
kayg:archive/perf/pr792-cold-index-edit
kayg:docs/sharing-slice-1
kayg:archive/docs/imap-766-plan-v2
kayg:feat/dav-recorder-v2
kayg:archive/docs/dav-recorder-v2-plan
kayg:spike/imap-766-s0-cases
kayg:archive/fix/undo-768
kayg:feat/dav-recorder
kayg:archive/docs/contacts-import-v2-plan
kayg:archive/feat/calendar-ical-converter
kayg:feat/contacts-import
kayg:perf/cold-growth-on-764
kayg:perf/cold-growth-on-tiering
kayg:archive/docs/tasks-writes-v2-plan
kayg:archive/docs/calendar-backend-v2-plan
kayg:archive/feat/contacts-companion-pure
kayg:feat/tasks-set-status
kayg:feat/tasks-move-reorder
kayg:archive/docs/carddav-writes-v2-plan
kayg:archive/feat/calendar-v2-s1-containment
kayg:feat/tasks-delete-promote-duplicate
kayg:archive/feat/dav-transport
kayg:archive/fix/carddav-rebuild-r1
kayg:archive/fix/carddav-retention-r1
kayg:feat/carddav-writes
kayg:perf/quota-on-tiering
kayg:perf/quota-simple
kayg:perf/cold-growth-on-quota-stack
kayg:feat/calendar-identity
kayg:feat/calendar-event-parser
kayg:feat/calendar-props-maps
kayg:port/glass-when-ring
kayg:feat/tiering-761-s1
kayg:wip/10by32-01-impl-759b-scratch-20261030
kayg:wip/6by32-01-bm3-webkit-pause-20261030
kayg:wip/10by32-01-bm3-chromium-pause-20261030
kayg:wip/6by32-01-tier-761-s1-pause-20261030
kayg:spike/imap-766-s0
kayg:wip/6by32-02-calternal-pause-20261030
kayg:wip/8by64-01-review-688-pause-20261030
kayg:wip/6by32-01-verify-744-pause-20261030
kayg:wip/6by32-01-verify-742-pause-20261030
kayg:fix/private-staging-759b
kayg:wip/6by32-01-review-748-pause-20261030
kayg:wip/6by32-01-review-733c-pause-20261030
kayg:wip/6by32-01-review-733b-pause-20261030
kayg:wip/6by32-01-perf-idle-8-pause-20261030
kayg:wip/6by32-01-perf767-base-pause-20261030
kayg:wip/6by32-01-perf767b-767-pause-20261030
kayg:wip/6by32-01-perf767b-764-pause-20261030
kayg:wip/6by32-01-perf767-767-pause-20261030
kayg:wip/6by32-01-perf767-764-pause-20261030
kayg:wip/6by32-01-lists-b-pause-20261030
kayg:wip/6by32-01-ids-verify5-pause-20261030
kayg:wip/6by32-01-ids-verify4-pause-20261030
kayg:review/calendar-month
kayg:wip/10by32-02-undo-768-20261030
kayg:test/ids-verify3
kayg:wip/10by32-02-bm3-chromium-20261030
kayg:wip/10by32-01-review-753-20261030
kayg:wip/10by32-01-perf-idle-2-20261030
kayg:wip/10by32-01-bm2-webkit2-20261030
kayg:plan/759b-staging
kayg:train/2026-10-30-bm2
kayg:review/758b-tests
kayg:review/758-tests
kayg:review/764d-tests
kayg:review/767b-tests
kayg:review/preview-2026-10-29
kayg:review/767-tests
kayg:docs/tiering-731-plan
kayg:wip/c6b02-when2
kayg:wip/c6b02-tasks-create-fix5
kayg:wip/c6b02-settings-frost-mock
kayg:wip/c6b02-settings-frost-base
kayg:wip/c6b02-settings-frost
kayg:wip/c6b02-ring
kayg:wip/c6b02-review-733d
kayg:wip/c6b02-bm1-full-a
kayg:wip/compute02-when-field
kayg:wip/compute02-webkit-lists-repro
kayg:wip/compute02-train-bl2-dev
kayg:wip/compute02-dev-green-pw164
kayg:docs/deploy-plan
kayg:review/764c-tests
kayg:review/764b-tests
kayg:review/748g-tests
kayg:review/764-tests
kayg:review/759b-tests
kayg:fix/storage-owner-liveness-642
kayg:fix/storage-fix-7
kayg:wip/compute02-storage-fix-7
kayg:review/759-tests
kayg:feat/tiering-731
kayg:wip/tiering-731-first-attempt
kayg:wip/compute02-storage-fix-1
kayg:review/754g-tests
kayg:perf/idle-honest-2
kayg:review/754f-tests
kayg:review/754e-tests
kayg:review/755-tests
kayg:review/754d-tests
kayg:review/754c-tests
kayg:review/754b-tests
kayg:review/754-tests
kayg:fix/storage-private-tmp-642-dev
kayg:wip/compute02-verify-750
kayg:test/ids-verify5
kayg:test/ids-verify4
kayg:fix/storage-exact-file-quarantine-642
kayg:wip/compute02-verify-740
kayg:perf/one-flush-save
kayg:review/calendar-static
kayg:wip/compute02-verify-737
kayg:review/indexing-ring
kayg:review/when-field-2
kayg:wip/compute02-review-733
kayg:proto/block-identity-2-review
kayg:proto/block-identity-review
kayg:wip/c6b02-tc-design
kayg:review/when-field
kayg:issue/tasks-create-ids
kayg:issue/tasks-create
kayg:review/combined
kayg:wip/compute02-review-combined
kayg:review/task-polish
kayg:review/top-buttons
kayg:review/q1-q5
kayg:train/2026-10-26-bl2
kayg:archive/wt-398h
kayg:archive/train-bm-proptest-seed
kayg:archive/integ325-293
kayg:archive/integ325-ci
kayg:archive/client-auth-review-baseline
kayg:archive/wt-sim-4853
kayg:archive/wt-sim-4811
kayg:archive/wt-448m
kayg:archive/wt-398a-allocator
kayg:issue/calendar-adr
kayg:archive/old/train/2026-10-23bk2
kayg:archive/old/train/2026-10-23bk
kayg:issue/469-mockup-9a
kayg:issue/smart-tasks-mockup
kayg:issue/469-menus
kayg:issue/652-signout-flake
kayg:issue/652-history-fix7
kayg:issue/652-history-fix6
kayg:archive/old/issue/spring-check-perf
kayg:issue/spring-check-perf
kayg:issue/659-rename-fast
kayg:archive/old/train/2026-10-21ar
kayg:issue/search-hardening-3
kayg:issue/652-history-fix5
kayg:issue/660-port-sheets-glass
kayg:issue/652-history-fix4
kayg:issue/652-history-fix3
kayg:issue/652-history-fix2
kayg:issue/652-history-flake
kayg:train/2026-10-21an
kayg:issue/settings-admin-port
kayg:issue/search-port
kayg:train/2026-10-20am
kayg:issue/storage-split-fix2
kayg:issue/storage-split
kayg:archive/old/issue/folder-ids-fix
kayg:train/2026-10-20al
kayg:archive/old/issue/folder-ids
kayg:train/2026-10-20ak
kayg:issue/tables-mockup
kayg:train/2026-10-19aj
kayg:train/2026-10-19ai
kayg:issue/469-merge-fixes
kayg:issue/sidebar-band
kayg:archive/old/issue/608-settings-spec
kayg:train/2026-10-19ah
kayg:issue/swipe-offscreen
kayg:train/2026-10-19ag
kayg:archive/old/issue/590-nlp-adr
kayg:archive/old/issue/587-event-after-write-2
kayg:train/2026-10-19af
kayg:archive/old/issue/587-event-after-write
kayg:issue/469-pill-room-c
kayg:issue/469-pill-room
kayg:train/2026-10-19ae
kayg:issue/469-narrow-handle
kayg:issue/397-ios-swipe-2
kayg:archive/old/issue/adrs-photos-files-tasks
kayg:issue/469-desk-fixes
kayg:archive/old/issue/auto-update-adr
kayg:issue/469-filter-gap
kayg:issue/469-port-1b
kayg:issue/442-opacity-flake
kayg:archive/397-ios-swipe-2
kayg:issue/327-web-fast-3
kayg:archive/old/issue/469-port-1
kayg:issue/469-port-1
kayg:issue/327-web-fast-2
kayg:archive/old/issue/327-web-fast
kayg:archive/old/issue/425-stt-bench-vq1
kayg:archive/old/issue/445-no-bleed
kayg:archive/old/issue/400-swipe-mockup-2
kayg:issue/426-client-review
kayg:issue/426-client-2
kayg:issue/426-client
kayg:archive/old/issue/476-safe-area-lab
kayg:archive/445-top-light-2
kayg:archive/old/issue/445-top-light
kayg:issue/471-blocks-mockup
kayg:archive/old/issue/448-links-app-2
kayg:archive/old/issue/448-links-app
kayg:archive/old/issue/398-cold-links
kayg:issue/434-442-small-2
kayg:issue/434-442-small
kayg:archive/old/issue/398-unload
kayg:archive/old/issue/445-bottom-band
kayg:issue/445-bottom-band
kayg:issue/351-rem-3
kayg:archive/312-views-mockup
kayg:issue/351-rem-2
kayg:issue/351-rem
kayg:archive/old/issue/327-first-note
kayg:issue/395-width-phone
kayg:archive/old/issue/327-typing
kayg:issue/396-ios-edges
kayg:issue/397-ios-swipe
kayg:archive/398-allocator
kayg:archive/old/issue/327-notes-index
kayg:issue/345-347-contrast-empty-2
kayg:archive/old/issue/44-arenas
kayg:issue/44-arenas
kayg:archive/integ325
kayg:archive/old/issue/327-perf-harness
kayg:archive/old/issue/44-thp
kayg:issue/44-thp
kayg:archive/old/issue/285-sticky-pills
kayg:archive/old/issue/44-trim-3
kayg:issue/44-trim-3
kayg:archive/old/issue/165-cards
kayg:archive/old/issue/165-picker-2
kayg:archive/old/issue/212-multi-select
No reviewers
Labels
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".
No due date set.
Dependencies
No dependencies set
Reference
kayg/calternal!742
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/storage-exact-file-quarantine-642-3b"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Replaces #740 for #642. This new branch continues the saved quarantine work at
90d8834635, including the interrupted rename and independent journal-proof fixes carried by that PR. The forge ignores pushes to an open PR branch, so the saved branch is preserved and this PR uses fix/storage-exact-file-quarantine-642-3b. Target: dev, recorded based83e54dbe5.Recovery previously checked an inode and then removed a public pathname. Another program could replace that pathname after the check, causing cleanup to remove unjournaled bytes. Cleanup now moves the entry into a fresh private directory on the source filesystem before verifying and removing it. Device/inode, length, read lease and byte/journal proof guard disposal. Unknown versions restore exclusively or remain as reported conflict copies. Upload, guarded-save, temporary-write and abandoned-directory cleanup use the same fsio quarantine interface.
This finishes the saved tests with cfg(test) channel pauses in the real cleanup paths. A worker blocks at the selected boundary while another thread atomically replaces the public name, then signals the worker to continue. Missing pauses fail the tests; there is no completion fallback. Both C fixtures and all preload use are removed. Hooks compile out of production builds. Ordinary-binary canonical before/after tests remain.
All requested gates passed at
ccc7a2b790. Gate output, verbatim:Settled loop output, verbatim:
Every local heavy command ran through build-slot.sh, one job at a time, with cargo -j3. Existing ignored tests remain ignored. The loop tests process death on local storage, not block-device power loss. The recovery pause was tightened to follow the final identity and byte checks; final-fmt, final-fsio, final-clippy and final-workspace revalidate that exact final test placement. No new latency claim is made.
Only product code and permanent tests are in the branch. Evidence and run scripts remain untracked in the job folder and in issue comments. No merge, deployment or issue closure.
Verdict: fix first. Independent review of PR #742 at
ccc7a2b790, into dev. Two reproducible ways to lose completed external writes remain, plus one unmet restoration requirement. No product edits, commits, pushes, merges or deployments remain from this review.P1: a crash during attachment cleanup loses the original identity proof; the next startup deletes an external replacement. In fsio/src/beneath.rs:145-149, cleanup records the original inode, then claims the public staging name. An independent Python process atomically replaced that name with fsynced external bytes at BeforeMove. I SIGKILLed cleanup at AfterMove, before it could reject the changed inode. On restart, remove_staging recursively visits the crash-left quarantine (:160-165), records the replacement as a fresh baseline and discards it using the always-true byte predicate. The pending marker contains only a pathname, so the original authorization is not recoverable. This was reproduced through the ordinary server binary's real startup sweeper as well as the unit helper. No writer touched private metadata.
Reproduction: create a pending upload marker naming Documents/2026/2026-10-28/.calternal-tmp-742-Note.md; replace that public file with an external inode at BeforeMove; kill cleanup at AfterMove; start the ordinary binary on the same home/state. Before restart the sole retained file contains the external bytes. After restart, verbatim:
Real startup sweeper: marker remains= False ; external-byte survivors= []. The preservation assertion exits 1. The unit reproduction reportsrestart removed=true, surviving images=0.P1: equal-length in-place external rewrites are deleted even without a crash. The attachment reaper, TempFile::Drop and failed TempFile::persist pass
|_| trueas the content proof (beneath.rs:149, :272 and :252). Pause after capturing metadata but before the move. Another process opens the original public inode with r+b, writesoutside-neweroverjournal-known(both 13 bytes), fsyncs and closes. Resume. Device, inode and length still match, and the newly acquired read lease only freezes the already changed bytes. All three upload cleanup paths delete those bytes. The guarded note cancellation and journal-recovery controls correctly retain them.Verbatim observations:
equal-length rewrite attachment: surviving images=0;equal-length rewrite upload-drop: surviving images=0;equal-length rewrite upload-reject: surviving images=0. The independent test fails withcleanup deleted completed, fsynced outside rewrites in ["attachment", "upload-drop", "upload-reject"]. Recheck exact original byte authorization, rather than accepting all bytes on the same inode; conservatively retain files when that proof is unavailable.P2: unknown crash-left note images never return from the hidden private folder. Kill recover_staging at AfterMove with unknown bytes. On next start, enumerate the moved image and reject its journal proof. staging.rs:123 restores it to its input path, which is already inside the crash-left quarantine. The bytes survive, but remain at
.calternal-tmp-<random>-quarantine/.calternal-tmp-742-Note.mdrather than returning to an adjacent recovery/conflict path. Collisions likewise retain versions in random private directories. This fails the job's explicit next-start restoration and predictable conflict-copy requirements; it is not a byte-loss claim. Twelve colliding versions survived three subsequent recovery passes at stable retained paths and did not overwrite each other.The independent expanded unit suite finished 10 passed, 3 failed. All failures are preservation assertions for the three ranked findings, not fixture startup failures. The tests use cfg(test) pauses and separate-process writers, with a required handshake before each SIGKILL. No preloaded libraries, syscall interposition or fixed sleeps were used.
Checks that passed: 18 public atomic-replacement cases across recovery, guarded cancellation, attachment reaping, upload drop, failed upload publication and accepted guarded replacement; nine known-image deaths at BeforeMove/AfterMove/AfterCheck; both upload-publication move boundaries; first and second rollback exchange boundaries; import-directory replacement boundaries; exclusive rename, named bulk, anonymous bulk, guarded-create and directory publication; private cancellation; repeated conflict preservation. The original fsio suite passed all 49 tests, note staging passed 3, rename crash boundaries passed 3, split-save acknowledged-byte recovery passed both prepare/place phases, and anchors passed 6.
The ordinary binary excluded quarantine content from note lists, settled search, journal catch-up, notes.get and live WebSocket note-room joins. Both ordinary markdown basenames and hidden staging basenames were checked.
A fresh 1,000/1,000 stop loop also passed: 250 saves, 250 renames, 250 Trash operations and 250 restores. All 1,000 acknowledged Unicode markers survived exactly once after every restart; same-key retry jobs completed; auxiliary path/title/body checks passed; final canonical bytes equaled notes.get; no staging files or directories remained after restart and explicit save barriers in any round. This loop kills at the after-send boundary; it does not prove which kernel syscall was interrupted. The independent pause tests provide that separate coverage. The passing loop does not discharge the external-writer failures.
Every local heavy command ran sequentially under build-slot.sh, with cargo -j3. The audit inventory distinguishes guarded note writes from explicitly unconditional application-metadata replacement and caller-authorized deletion. No claim is made about hostile writes into private metadata, non-Linux cleanup or block-device power failure. Access to the earlier reviewer host failed (DNS/refused connection/timeout); earlier reviewer findings were obtained from Forgejo comments instead of those remote files.
Evidence and exact reproduction instructions: /home/agent/wt/verify-742/findings.md and .verification/REPRODUCE.md. The temporary test patch is .verification/review-tests.patch; it applies cleanly to the reviewed head. Tracked files have been restored byte for byte; only uncommitted review artifacts remain.
Head:
ccc7a2b790. Local review branch: verify/742-independent. Gate output, verbatim:independent-initial exit=101is the first pair of preservation failures.independent-expanded exit=101is the final three preservation failures.upload-startup-preservation exit=1is the actual external-byte loss through the ordinary binary. None of these are safety passes. No issue was closed.Starting storage-fix-4 on branch fix/storage-exact-file-quarantine-642-4, base
ccc7a2b790. Fetched origin first. The assigned unsuffixed branch already has a published PR; using a new branch as required because this forge ignores pushes to open PR branches. Scope: one durable ownership and move-then-verify cleanup primitive, all cleanup callers, permanent regressions and required gates. Evidence stays in issue comments.Finding: cleanup had no durable creation record. Folder::remove_staging re-baselined current inode metadata on each restart, and both TempFile cancellation paths accepted every byte on that inode. Existing fsio::remove_file, Folder::remove, metadata replacement, and recursive import-cache disposal bypassed even those checks. Evidence: beneath.rs passed |_| true at all three upload cleanup sites; lib.rs directly called std::fs::remove_file and std::fs::rename; directory.rs used remove_dir_all. The new shared check requires a creator-written inode receipt (device, inode, size, blake3) as well as a read lease. Streaming writes maintain their own incremental hash rather than reading current public bytes to authorize deletion. Unknown crash-left private entries are returned to exclusive, predictable conflict names. Reviewer host SSH timed out; exact reviewer evidence was read from comment 68130.
Additional finding: creation proof must stay tied to its public name. An inode receipt alone can follow a different app-created file moved in by another program. Receipts now include that binding, and only fsio exclusive moves/exchanges can update it; public cleanup still verifies device, inode, size and content hash under a lease. The new shared concurrency test and all three #742 regressions passed before this strengthening; they are being rerun with it. Performance measurement is blocked: ssh to the designated 10by32-01-std returns "Could not resolve hostname 10by32-01-std: No address associated with hostname". No performance command ran on any other VM or production. Required local gates continue through build-slot.sh.
Gate finding: the initial centralized verifier loaded every moved file into a Vec. The existing bounded-memory upload regression correctly rejected that implementation. Verbatim: "peak above idle: 32 MiB upload 34920 KiB, 128 MiB upload 132212 KiB"; workspace exit=101 at attachments_crash::a_big_upload_streams_in_bounded_memory. Receipt-only cleanup and upload publication now use a streaming hash with a fixed 64 KiB buffer; note journal predicates retain their bounded note image check. Missing or identity/name-mismatched receipts are rejected before allocating content. The preservation, abrupt-stop, three reviewer repro and ordinary-binary startup gates all passed before this change; affected checks will be repeated. This failure is not reported as a safety pass.
Settled-loop finding: restart 790 failed with a remaining staging file at Notes/quarantine.conflict-27/.calternal-tmp-24d9af18e2c9d87f4aa7d6-Durable text.md. All acknowledged markers had survived to that point; the failure was the no-hidden-staging assertion. The shared startup drain correctly skipped a locked nested transaction during recursion but then treated that same directory as an unknown entry in its second pass and moved it. That invalidated the active recovery worker's path. A deterministic two-worker regression is being added before the central fix; the full 1,000-stop loop will restart from a fresh Home. The 790-stop attempt is a failed gate, not a pass.
Central fix verified: the drain now leaves managed nested transaction directories to their lock-holding recovery worker instead of moving them as unknown objects. The new startup_drain_does_not_reparent_an_active_nested_recovery test failed on the prior code with NotFound and passes with the fix. All 59 fsio tests and both shared 14-path properties pass. The streaming verifier also passes the existing ordinary-binary upload-memory test. Verbatim: "peak above idle: 32 MiB upload 2668 KiB, 128 MiB upload 432 KiB". The prior failing implementation measured 34920 KiB and 132212 KiB. Final workspace tests and a fresh 1,000-stop loop are running sequentially in one build slot. Current gate output, verbatim:
Workspace fixture finding: kill_nine_mid_upload_leaves_nothing_half_written rejected a visible 4,321,381-byte conflict prefix from an earlier interrupted round: Photos/2026/2026-10-17/calternal-tmp-740c954db6214182bad2b0307a7e0a64.conflict-1. A process can die between a streaming write and its receipt update; the exact prefix then has no valid durable hash and must be preserved under the requested invariant. The fixture is being corrected to exclude explicit cleanup conflict copies from its completed-attachment assertion, while continuing to forbid publication under an attachment name and requiring the hidden staging/marker cleanup to settle. No product check is being weakened to delete those unproven bytes. Gate output for that run: workspace exit=101. The streaming-memory check and 59 fsio tests passed, including the new nested recovery regression.
Execution note: during early investigation I started a targeted fsio/clippy job before the first workspace run had returned. Both commands were wrapped in build-slot.sh, but this briefly held two slots, contrary to the one-slot limit. I corrected the orchestration; the final gate scripts hold one slot and run their commands sequentially. No unrelated process was stopped, and no production or staging VM was touched.
Fresh settled loop passed 1,000/1,000 stops after the nested-recovery fix: 250 saves, 250 renames, 250 Trash operations and 250 restores. All 1,000 acknowledged Unicode markers survived exactly once after every restart; same-key retries finished, auxiliary title/path/body checks passed, final canonical bytes equal notes.get, and no staging files or directories remained after explicit save barriers in any round. This is after-send randomized SIGKILL coverage; the product-pause property provides the exact cleanup-boundary coverage. A final run now checks the added after-check alias breakers, the receipt flush, lint and workspace. Verbatim loop gate output:
Expanded-property finding: all foreign bytes survived, but AtomicReplace / AfterMove / in-place rewrite returned the conflict as .calternal-tmp-742-Note.md.conflict-1, which is still hidden. The conflict formatter stripped dots in its fallback branch but did not strip a dot inherited from staging_destination. The deterministic expanded property failed with that exact path. The shared formatter now strips leading dots in both cases, and a permanent hidden-metadata replacement test covers .prefs.json as well. Final gates and a new fresh-Home 1,000-stop loop are being repeated. No unproven bytes are deleted to satisfy the visibility assertion.
The expanded 14-path property now passes with after-check alias breakers and guaranteed equal-length payloads. Workspace found one more old interrupted-upload assertion that I missed: its final files(data) equality still expected only whole.jpg and no retained conflicts. The preserved 6,017,125- and 6,439,141-byte prefixes are required when no exact durable receipt survived. The test now separately checks that whole.jpg is the only published attachment, every visible conflict is an exact prefix of the submitted 8 MiB input, no hidden staging remains, and the completed attachment is byte-for-byte correct. This is a fixture correction, not permission to delete unproven prefixes. The previous workspace gate exited 101 at that assertion; the final run is restarting.
Finished storage-fix-4. Head
c19aef3072on fix/storage-exact-file-quarantine-642-4. New PR: #744. The new PR is required because pushes to the existing #742 branch are ignored by this forge.Call sites now using the shared removal and replacement boundary:
Creation proofs are written by RecordedFile, including the upload Receiver's bounded chunks, and by the anonymous bulk and lock-file creation paths. Device, inode, size, BLAKE3 and the public-name binding travel with the inode in a durable xattr. Exclusive app moves can change the binding; an external move cannot transfer deletion authority. A held read lease covers the final verification and unlink. The verifier flushes an existing creator receipt before using it, including cancellation before the writer reached its final flush. Unknown bytes return to exclusively numbered visible conflict names. Locked live private directories are skipped by startup recovery.
The generic preservation property covers 14 entry points at BeforeCheck, BeforeMove, AfterMove and AfterCheck, with independent Python writers, generated binary payloads, inode replacement and completed in-place rewrites. In-place aliases are tested both before acquisition and as lease breakers at the final check; the alias is removed after the writer finishes so it cannot conceal a missing survivor. Every path also uses an explicit equal-length payload. The same 14-entry table also runs abrupt child-process kills at all four pauses. Tests separately cover an app-created replacement carrying another valid receipt, unknown import-cache bytes, live prepared-image exclusion, all three #742 repros and the ordinary startup binary with and without markers.
The retained private namespaces stay on the source filesystem inside each Home. Unsupported receipt or lease operations fail conservatively. These Linux tests cover process death and ordinary outside writers, not hostile changes to private application metadata or block-device power cuts. The designated performance VM cannot be resolved through either its direct or NetBird hostname, so there is no before/after latency claim.
Gate output, verbatim:
Stop-loop result, verbatim:
The final pass includes the memory correction, nested active-recovery regression, visible conflict naming and the interrupted-upload prefix checks. Earlier failed gates are recorded in the preceding comments. All final gates passed. No evidence or artifacts were committed; no issue was closed; no merge or deployment was performed.
Closing: superseded by #744, then #749 and now #750 (named in the PR body or comments). Branch kept.
Pull request closed