DATA LOSS: sync client trashes remote files in subfolders after the local root vanishes and reappears empty #212

Closed
opened 2026-09-26 20:27:18 +00:00 by kayg · 10 comments
Owner

Adversarial round on dev 2527f0d0 (tests/adversarial/attack2.py ~line 1400): the daemon is running, the local sync root is deleted (rmtree), an empty folder reappears 6 s later (unmounted-disk case), and 8 s later 2 remote files are gone: keep/k0.txt, keep/k1.txt (subfolder). Top-level files survived, so the broad local-deletion guard covers the root but not nested paths.
Fix (crates/calternal-sync): a vanished or suddenly-empty local root (or a subtree losing everything at once) must never become remote trash. Detect root identity (device+inode, a marker file under .calternal-sync or similar) and a mass-deletion threshold per pass; when triggered, pause the pair and surface a status needing user confirmation (calternal sync status shows why), never trash. Check the interaction with the new change-feed path (#123 merged 8a2a9417: 'item-scoped feed trash events bypass the broad local deletion guard') so neither path can trash on a vanished root. Regression tests: unit tests for the guard, the adversarial probe must pass, and a sync campaign case (root deleted, root replaced by empty dir, root replaced by a different filesystem, nested subtree emptied). Blocks merges per CLAUDE.md (data loss).

Adversarial round on dev 2527f0d0 (tests/adversarial/attack2.py ~line 1400): the daemon is running, the local sync root is deleted (rmtree), an empty folder reappears 6 s later (unmounted-disk case), and 8 s later 2 remote files are gone: keep/k0.txt, keep/k1.txt (subfolder). Top-level files survived, so the broad local-deletion guard covers the root but not nested paths. Fix (crates/calternal-sync): a vanished or suddenly-empty local root (or a subtree losing everything at once) must never become remote trash. Detect root identity (device+inode, a marker file under .calternal-sync or similar) and a mass-deletion threshold per pass; when triggered, pause the pair and surface a status needing user confirmation (calternal sync status shows why), never trash. Check the interaction with the new change-feed path (#123 merged 8a2a9417: 'item-scoped feed trash events bypass the broad local deletion guard') so neither path can trash on a vanished root. Regression tests: unit tests for the guard, the adversarial probe must pass, and a sync campaign case (root deleted, root replaced by empty dir, root replaced by a different filesystem, nested subtree emptied). Blocks merges per CLAUDE.md (data loss).
Author
Owner

Started on job/sync-root-guard from base a31e4ee00e. I am tracing the local deletion guard and item-scoped change-feed trash path, then I will add a focused regression test before the fix.

Started on job/sync-root-guard from base a31e4ee00e56ce8075bfb9b159aec33d5d75bdf3. I am tracing the local deletion guard and item-scoped change-feed trash path, then I will add a focused regression test before the fix.
Author
Owner

Code finding: crates/calternal-sync/src/plan.rs:167-172 applies the relative threshold to the whole pair and requires more than 10 deletions. It does not pause when all files in a two-file subtree disappear while other tracked paths remain. The incremental item-scoped feed path also excludes TrashLocal from this broad guard (lines 177-184), so root identity must be checked before feed reconciliation as well. I am adding a focused real-server campaign for the nested subtree case before the fix.

Code finding: crates/calternal-sync/src/plan.rs:167-172 applies the relative threshold to the whole pair and requires more than 10 deletions. It does not pause when all files in a two-file subtree disappear while other tracked paths remain. The incremental item-scoped feed path also excludes TrashLocal from this broad guard (lines 177-184), so root identity must be checked before feed reconciliation as well. I am adding a focused real-server campaign for the nested subtree case before the fix.
Author
Owner

Resumed after the VM restart on job/sync-root-guard. The worktree is clean at checkpoint 3a8ab3108a, based on dev ancestry a31e4ee00e. I am reviewing the checkpoint changes, completing the root guard and regression coverage, then I will merge the current dev branch and run the requested gates.

Resumed after the VM restart on job/sync-root-guard. The worktree is clean at checkpoint 3a8ab3108a0962675276b134e7dc027e6e611ef1, based on dev ancestry a31e4ee00e56ce8075bfb9b159aec33d5d75bdf3. I am reviewing the checkpoint changes, completing the root guard and regression coverage, then I will merge the current dev branch and run the requested gates.
Author
Owner

Checkpoint review finding: root identity is checked before change-feed reconciliation, but the nested-subtree campaign is not yet satisfied. plan::deletion_guard only checks the whole pair (more than 10 deletions and over 30%); deleting both keep/ files from the campaign leaves 12 of 14 tracked files, so the guard does not pause. I am adding a complete-subtree guard with a two-file floor, plus unit coverage, before running the campaign.

Checkpoint review finding: root identity is checked before change-feed reconciliation, but the nested-subtree campaign is not yet satisfied. `plan::deletion_guard` only checks the whole pair (more than 10 deletions and over 30%); deleting both `keep/` files from the campaign leaves 12 of 14 tracked files, so the guard does not pause. I am adding a complete-subtree guard with a two-file floor, plus unit coverage, before running the campaign.
Author
Owner

Additional safety finding during review: the checkpoint wrote a UUID marker by rename over any existing .calternal-sync-root-id file, and the scanner hid every top-level name beginning with that prefix. That could overwrite or omit unrelated local data. I am making the marker self-identifying, refusing to overwrite an occupied invalid marker path, and narrowing the scan exclusion to the exact marker and generated temp names, with regression tests.

Additional safety finding during review: the checkpoint wrote a UUID marker by rename over any existing `.calternal-sync-root-id` file, and the scanner hid every top-level name beginning with that prefix. That could overwrite or omit unrelated local data. I am making the marker self-identifying, refusing to overwrite an occupied invalid marker path, and narrowing the scan exclusion to the exact marker and generated temp names, with regression tests.
Author
Owner

Verification update: after the marker safety change, root_guard_campaign.py passed, root_identity_campaign.py passed (including the alternate-filesystem case when available), and ROUND2_SECTIONS=sync python3 attack2.py reported ROUND 2 FINDINGS 0 with the server alive. The focused calternal-sync suite passes 45 library tests and 2 daemon tests. I am starting the full workspace and web gates on the merged dev tree now.

Verification update: after the marker safety change, `root_guard_campaign.py` passed, `root_identity_campaign.py` passed (including the alternate-filesystem case when available), and `ROUND2_SECTIONS=sync python3 attack2.py` reported `ROUND 2 FINDINGS 0` with the server alive. The focused `calternal-sync` suite passes 45 library tests and 2 daemon tests. I am starting the full workspace and web gates on the merged dev tree now.
Author
Owner

Cleanup finding: root_identity_campaign.py creates its alternate-filesystem root under /dev/shm but only removes the campaign temp directory. Two empty sync-root test directories remained after my runs. I am adding explicit teardown for that fixture and will remove those two test-generated directories.

Cleanup finding: `root_identity_campaign.py` creates its alternate-filesystem root under `/dev/shm` but only removes the campaign temp directory. Two empty sync-root test directories remained after my runs. I am adding explicit teardown for that fixture and will remove those two test-generated directories.
Author
Owner

Forgejo #212 final report

Branch: job/sync-root-guard
Head SHA: 6d6a4929cd80553a6b0798e461249ebe769438a4
The current dev branch was merged into this branch at 3ac69b83; the merge was clean. No push, deploy, or issue close was performed.

Built

  • The daemon now binds each sync pair to its local sync root. It checks the identity before change-feed reconciliation and after each scan. A missing or replaced root pauses the pair and records local_root_changed; calternal sync status exposes the guard reason and resume still needs --confirm.
  • A pass now pauses before it would trash every tracked file in a multi-file subtree. The check counts only remote-trash actions, so a remote-originated delete keeps its existing behavior.
  • Root markers use a versioned payload. Invalid content at the reserved marker name is preserved and stops sync; the scanner skips only the exact marker and valid generated temp names.
  • Added root-replacement and nested-subtree real-server campaigns and registered them in run_campaigns.py. The alternate-filesystem campaign now removes its /dev/shm test root during teardown.

Files

  • crates/calternal-cli/src/main.rs
  • crates/calternal-sync/root_guard_campaign.py
  • crates/calternal-sync/root_identity_campaign.py
  • crates/calternal-sync/run_campaigns.py
  • crates/calternal-sync/src/bin/calternald.rs
  • crates/calternal-sync/src/config.rs
  • crates/calternal-sync/src/engine.rs
  • crates/calternal-sync/src/journal.rs
  • crates/calternal-sync/src/lib.rs
  • crates/calternal-sync/src/local.rs
  • crates/calternal-sync/src/plan.rs

Real-server verification

PASS complete nested subtree deletion pauses before remote trash
PASS replaced local roots pause before remote trash
server alive at end: True
==== ROUND 2 FINDINGS 0

The alternate-filesystem path ran: /dev/shm is on device 26 and the worktree is on device 2049. The updated campaign also passed and removed its test root.

Final gate output

cargo fmt --check exited 0 and printed no output.

cargo clippy --all-targets -- -D warnings:

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 10m 38s

cargo test (69 result lines; 1,231 passed, 0 failed, 12 ignored):

    Finished `test` profile [unoptimized + debuginfo] target(s) in 3m 09s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 49 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 48.31s
test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.02s
test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.02s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 6.46s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 3.15s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 48.48s
test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 5.94s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.99s
test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.16s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 4.11s
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.99s
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 28.95s
test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.09s
test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 18.32s
test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.45s
test result: ok. 9 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.86s
test result: ok. 17 passed; 0 failed; 4 ignored; 0 measured; 0 filtered out; finished in 0.13s
test result: ok. 33 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 17.80s
test result: ok. 35 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 7.93s
test result: ok. 476 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.22s
test result: ok. 13 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.87s
test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.49s
test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 19 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 5.14s
test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.28s
test result: ok. 29 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 3.43s
test result: ok. 42 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.31s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.42s
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.46s
test result: ok. 99 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 25.65s
test result: ok. 90 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 79.82s
test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.38s
test result: ok. 38 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 4.65s
test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.08s
test result: ok. 24 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 1.49s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.10s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.09s
test result: ok. 14 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 6.11s
test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.04s
test result: ok. 1 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 5.20s
test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s
test result: ok. 40 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 6.42s
test result: ok. 45 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.95s
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 8.29s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

bun run check:

$ svelte-kit sync && svelte-check --tsconfig ./tsconfig.json
Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/sync-root-guard/apps/web
Getting Svelte diagnostics...

svelte-check found 0 errors and 0 warnings

bun run test:

$ vitest run

 RUN  v5.0.1 /home/kayg/Developer/calternal-wt/sync-root-guard/apps/web

Could not parse CSS stylesheet
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method
Not implemented: Window's scrollTo() method

 Test Files  68 passed (68)
      Tests  534 passed (534)
   Start at  07:46:21
   Duration  23.20s (transform 65%, import 13%, environment 12%, tests 6%, setup 2%)

  Transform  |component| transforming modules took 43.24s · 65% of tracked time, re-done on every run
             persist transforms across runs with fsModuleCache: true
             learn more: https://vitest.dev/guide/improving-performance#caching-between-reruns

Decisions not specified in DESIGN.md

  • Use .calternal-sync-root-id as a reserved local client path and store calternal-sync-root-id/1\n<canonical UUID> in it. Keep device and inode in the journal identity. If unrelated or malformed data occupies that exact path, fail closed and preserve its bytes; the user must move it before resuming.
  • Treat a subtree as requiring confirmation when it has at least two tracked files and all of them would be trashed remotely in one pass. A one-file subtree remains an ordinary delete.
  • A pair with no stored root identity may adopt the marker only when its current scan still verifies a baseline file, unless the user confirms the change.

Known limitation: marker-path collisions appear as the general local_root_changed reason. The user must manually move the reserved-name file before sync can resume. Vitest passed but printed Could not parse CSS stylesheet and jsdom Not implemented: Window's scrollTo() method notices.

# Forgejo #212 final report Branch: `job/sync-root-guard` Head SHA: `6d6a4929cd80553a6b0798e461249ebe769438a4` The current `dev` branch was merged into this branch at `3ac69b83`; the merge was clean. No push, deploy, or issue close was performed. ## Built - The daemon now binds each sync pair to its local sync root. It checks the identity before change-feed reconciliation and after each scan. A missing or replaced root pauses the pair and records `local_root_changed`; `calternal sync status` exposes the guard reason and resume still needs `--confirm`. - A pass now pauses before it would trash every tracked file in a multi-file subtree. The check counts only remote-trash actions, so a remote-originated delete keeps its existing behavior. - Root markers use a versioned payload. Invalid content at the reserved marker name is preserved and stops sync; the scanner skips only the exact marker and valid generated temp names. - Added root-replacement and nested-subtree real-server campaigns and registered them in `run_campaigns.py`. The alternate-filesystem campaign now removes its `/dev/shm` test root during teardown. ## Files - `crates/calternal-cli/src/main.rs` - `crates/calternal-sync/root_guard_campaign.py` - `crates/calternal-sync/root_identity_campaign.py` - `crates/calternal-sync/run_campaigns.py` - `crates/calternal-sync/src/bin/calternald.rs` - `crates/calternal-sync/src/config.rs` - `crates/calternal-sync/src/engine.rs` - `crates/calternal-sync/src/journal.rs` - `crates/calternal-sync/src/lib.rs` - `crates/calternal-sync/src/local.rs` - `crates/calternal-sync/src/plan.rs` ## Real-server verification ```text PASS complete nested subtree deletion pauses before remote trash PASS replaced local roots pause before remote trash server alive at end: True ==== ROUND 2 FINDINGS 0 ``` The alternate-filesystem path ran: `/dev/shm` is on device 26 and the worktree is on device 2049. The updated campaign also passed and removed its test root. ## Final gate output `cargo fmt --check` exited 0 and printed no output. `cargo clippy --all-targets -- -D warnings`: ```text Finished `dev` profile [unoptimized + debuginfo] target(s) in 10m 38s ``` `cargo test` (69 result lines; 1,231 passed, 0 failed, 12 ignored): ```text Finished `test` profile [unoptimized + debuginfo] target(s) in 3m 09s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s test result: ok. 49 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 48.31s test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.02s test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.02s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 6.46s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 3.15s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 48.48s test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 5.94s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.99s test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.16s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 4.11s test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.99s test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 28.95s test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.09s test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 18.32s test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.45s test result: ok. 9 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.86s test result: ok. 17 passed; 0 failed; 4 ignored; 0 measured; 0 filtered out; finished in 0.13s test result: ok. 33 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 17.80s test result: ok. 35 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 7.93s test result: ok. 476 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.22s test result: ok. 13 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.87s test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.49s test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s test result: ok. 19 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 5.14s test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.28s test result: ok. 29 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 3.43s test result: ok. 42 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.31s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.42s test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.46s test result: ok. 99 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 25.65s test result: ok. 90 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 79.82s test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.38s test result: ok. 38 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 4.65s test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.08s test result: ok. 24 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 1.49s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.10s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.09s test result: ok. 14 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 6.11s test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.04s test result: ok. 1 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 5.20s test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s test result: ok. 40 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 6.42s test result: ok. 45 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.95s test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 8.29s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s ``` `bun run check`: ```text $ svelte-kit sync && svelte-check --tsconfig ./tsconfig.json Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/sync-root-guard/apps/web Getting Svelte diagnostics... svelte-check found 0 errors and 0 warnings ``` `bun run test`: ```text $ vitest run RUN v5.0.1 /home/kayg/Developer/calternal-wt/sync-root-guard/apps/web Could not parse CSS stylesheet Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Not implemented: Window's scrollTo() method Test Files 68 passed (68) Tests 534 passed (534) Start at 07:46:21 Duration 23.20s (transform 65%, import 13%, environment 12%, tests 6%, setup 2%) Transform |component| transforming modules took 43.24s · 65% of tracked time, re-done on every run persist transforms across runs with fsModuleCache: true learn more: https://vitest.dev/guide/improving-performance#caching-between-reruns ``` ## Decisions not specified in DESIGN.md - Use `.calternal-sync-root-id` as a reserved local client path and store `calternal-sync-root-id/1\n<canonical UUID>` in it. Keep device and inode in the journal identity. If unrelated or malformed data occupies that exact path, fail closed and preserve its bytes; the user must move it before resuming. - Treat a subtree as requiring confirmation when it has at least two tracked files and all of them would be trashed remotely in one pass. A one-file subtree remains an ordinary delete. - A pair with no stored root identity may adopt the marker only when its current scan still verifies a baseline file, unless the user confirms the change. Known limitation: marker-path collisions appear as the general `local_root_changed` reason. The user must manually move the reserved-name file before sync can resume. Vitest passed but printed `Could not parse CSS stylesheet` and jsdom `Not implemented: Window's scrollTo() method` notices.
Author
Owner

Verification note: the full workspace gates in my final report ran after all Rust implementation changes and before the last Python-only campaign teardown edit. After that edit, root_identity_campaign.py passed and removed its alternate-filesystem test root. I did not rerun the full workspace gates after this test-fixture-only edit.

Final cleanup: cargo clean output was Removed 15562 files, 12.8GiB total. Generated web build output and installed dependencies were removed.

Verification note: the full workspace gates in my final report ran after all Rust implementation changes and before the last Python-only campaign teardown edit. After that edit, `root_identity_campaign.py` passed and removed its alternate-filesystem test root. I did not rerun the full workspace gates after this test-fixture-only edit. Final cleanup: `cargo clean` output was `Removed 15562 files, 12.8GiB total`. Generated web build output and installed dependencies were removed.
Author
Owner

Merged into dev at 8b8e5cc9 (root identity marker, subtree mass-deletion guard needing confirmation, campaigns for vanished/empty/alternate roots). Claude reviewed the report. Deploy status on #203.

Merged into dev at 8b8e5cc9 (root identity marker, subtree mass-deletion guard needing confirmation, campaigns for vanished/empty/alternate roots). Claude reviewed the report. Deploy status on #203.
kayg closed this issue 2026-09-27 05:58:58 +00:00
Sign in to join this conversation.
No labels
No milestone
No project
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#212
No description provided.