BLOCKER: make acknowledged Security state survive power loss (#663) #824

Open
opened 2026-10-02 13:18:09 +00:00 by kayg · 17 comments
Owner

Context: SQLite architecture audit #663. DESIGN §2 says Security state exists only in the Index and cannot be rebuilt from files. Losing an acknowledged revocation can restore access. This finding concerns power loss/hard reset, not normal process restart.

Evidence at c4a61e8cf0:

  • crates/calternal-db/src/db.rs:53 explicitly sets synchronous=NORMAL on the sole writer.
  • crates/calternal-server/src/wire.rs:1233 constructs SqliteAuthStore from that writer and the separate readers.
  • crates/calternal-auth/src/store.rs:2080–2098 revokes a session in a writer transaction and returns after commit. Many authority changes share this boundary.
  • crates/calternal-db/tests/queue.rs:86 asserts the NORMAL setting. This audit does not change that expectation.
  • Queued merge-round-7a 2f4482ded retains NORMAL. Queued job/perf-mut-667 52d2b17f80 explicitly documents process-restart durability only in mutations.rs:11–12. That comment does not establish a power-loss policy for Security state.

Reasoned impact: SQLite documents that WAL/NORMAL can roll back committed transactions after power loss or hard reset. A recent session/App Password revoke, Role change, Share revoke or account change can therefore disappear despite the server's acknowledgement. Derived data can be rebuilt; Security state cannot. This is a security/data-loss blocker under the owner rule. No power-loss injection or restored-access incident was observed in this read-only audit. SQLite does not predict corruption from NORMAL under its documented assumptions; the issue is loss of acknowledged state.

Primary source: https://www.sqlite.org/pragma.html#pragma_synchronous and https://www.sqlite.org/wal.html section 2.3. FULL syncs the WAL at each commit; NORMAL does not guarantee power-loss durability. The hardware/filesystem must also honor sync.

Concrete fix: give authority changes a power-loss durability boundary before ACK. The smallest safe first change is a FULL writer policy for the shared Index. If measured ingest latency warrants a narrower policy, reserve the sole writer connection, set FULL for the complete authority transaction, commit, and restore its normal policy on every success/error/cancellation path; ensure no unrelated caller can borrow a weaker writer for authority changes. A separate durable Security state file is a larger design choice, not required to start. Do not weaken any existing status or test assertion. Record the selected policy in DESIGN; do not call a process restart a power-loss test.

Tests: verify the effective writer setting and which writer every authority mutation uses, including rollback/error and queued receipts. Use a fault-injection VFS or equivalent controlled sync-boundary test to show the acknowledgement follows the required sync; assert revoked authority stays revoked after recovery. Preserve ordinary process-restart and read-your-writes tests. Measure the durable mutation latency on the locked HDD perf VM; checkpoint scheduling is a separate performance issue.

Duplicate check: searched all issue titles through #800 for durability, power, WAL, fsync and Security state. #728 is filesystem permissions, #429/#476 concern content-file fsync, #667 explicitly covers process-restart receipts. No matching Security state power-loss issue was found. No product edit was made.

Context: SQLite architecture audit #663. DESIGN §2 says Security state exists only in the Index and cannot be rebuilt from files. Losing an acknowledged revocation can restore access. This finding concerns power loss/hard reset, not normal process restart. Evidence at c4a61e8cf090170f35b1bed3350d9de20c83ecd5: - crates/calternal-db/src/db.rs:53 explicitly sets synchronous=NORMAL on the sole writer. - crates/calternal-server/src/wire.rs:1233 constructs SqliteAuthStore from that writer and the separate readers. - crates/calternal-auth/src/store.rs:2080–2098 revokes a session in a writer transaction and returns after commit. Many authority changes share this boundary. - crates/calternal-db/tests/queue.rs:86 asserts the NORMAL setting. This audit does not change that expectation. - Queued merge-round-7a 2f4482ded retains NORMAL. Queued job/perf-mut-667 52d2b17f805072cd0304d7a05fe0534523cc7bc3 explicitly documents process-restart durability only in mutations.rs:11–12. That comment does not establish a power-loss policy for Security state. Reasoned impact: SQLite documents that WAL/NORMAL can roll back committed transactions after power loss or hard reset. A recent session/App Password revoke, Role change, Share revoke or account change can therefore disappear despite the server's acknowledgement. Derived data can be rebuilt; Security state cannot. This is a security/data-loss blocker under the owner rule. No power-loss injection or restored-access incident was observed in this read-only audit. SQLite does not predict corruption from NORMAL under its documented assumptions; the issue is loss of acknowledged state. Primary source: https://www.sqlite.org/pragma.html#pragma_synchronous and https://www.sqlite.org/wal.html section 2.3. FULL syncs the WAL at each commit; NORMAL does not guarantee power-loss durability. The hardware/filesystem must also honor sync. Concrete fix: give authority changes a power-loss durability boundary before ACK. The smallest safe first change is a FULL writer policy for the shared Index. If measured ingest latency warrants a narrower policy, reserve the sole writer connection, set FULL for the complete authority transaction, commit, and restore its normal policy on every success/error/cancellation path; ensure no unrelated caller can borrow a weaker writer for authority changes. A separate durable Security state file is a larger design choice, not required to start. Do not weaken any existing status or test assertion. Record the selected policy in DESIGN; do not call a process restart a power-loss test. Tests: verify the effective writer setting and which writer every authority mutation uses, including rollback/error and queued receipts. Use a fault-injection VFS or equivalent controlled sync-boundary test to show the acknowledgement follows the required sync; assert revoked authority stays revoked after recovery. Preserve ordinary process-restart and read-your-writes tests. Measure the durable mutation latency on the locked HDD perf VM; checkpoint scheduling is a separate performance issue. Duplicate check: searched all issue titles through #800 for durability, power, WAL, fsync and Security state. #728 is filesystem permissions, #429/#476 concern content-file fsync, #667 explicitly covers process-restart receipts. No matching Security state power-loss issue was found. No product edit was made.
Author
Owner

Started #824 and companion #823 on job/wal-824, base c4a61e8cf090170f35b1bed3350d9de20c83ecd5. Reading audit evidence and tracing shared database connections. No pushes, deploys or issue closure. Will verify durable commit and move checkpoint work to maintenance.

Started #824 and companion #823 on `job/wal-824`, base `c4a61e8cf090170f35b1bed3350d9de20c83ecd5`. Reading audit evidence and tracing shared database connections. No pushes, deploys or issue closure. Will verify durable commit and move checkpoint work to maintenance.
Author
Owner

Finding: production wire.rs:1233 builds SqliteAuthStore from Db::writer_pool and Db::reader_pool. The shared writer currently sets NORMAL; its connection options do not override SQLite auto-checkpoint (default 1,000 pages). cargo search sqlx --limit 1 confirms 0.9.0; dependencies are unchanged.

Decision for #824: FULL on the shared writer, including replacement connections, because Security state and Derived data share the Index and raw writer pool. Per-transaction switching would require a broader authority-write refactor to be cancellation safe. macOS fullfsync is enabled; sync-honoring storage remains required.

Decision for #823: independent writable maintenance connection, PASSIVE each second, no commit hook, 16 MiB retained-WAL target, progress/error counters and a best-effort shutdown pass. A pinned read snapshot can exceed the retention target: journal_size_limit is not a hard active-WAL cap. A hard bound needs reader expiry or writer admission policy; this limitation will remain explicit.

Finding: production `wire.rs:1233` builds `SqliteAuthStore` from `Db::writer_pool` and `Db::reader_pool`. The shared writer currently sets NORMAL; its connection options do not override SQLite auto-checkpoint (default 1,000 pages). `cargo search sqlx --limit 1` confirms 0.9.0; dependencies are unchanged. Decision for #824: FULL on the shared writer, including replacement connections, because Security state and Derived data share the Index and raw writer pool. Per-transaction switching would require a broader authority-write refactor to be cancellation safe. macOS fullfsync is enabled; sync-honoring storage remains required. Decision for #823: independent writable maintenance connection, PASSIVE each second, no commit hook, 16 MiB retained-WAL target, progress/error counters and a best-effort shutdown pass. A pinned read snapshot can exceed the retention target: `journal_size_limit` is not a hard active-WAL cap. A hard bound needs reader expiry or writer admission policy; this limitation will remain explicit.
Author
Owner

Durability slice committed: 33f460ec3b79d7e0c091a658bcc7b0251a9ebddd. Effective shared writer setting is FULL; macOS fullfsync is enabled. Stronger expectation 1→2 is required by #824, not a weakened test.

cargo fmt --check passed with no output.

Clippy:

    Blocking waiting for file lock on package cache
    Blocking waiting for file lock on package cache
    Blocking waiting for file lock on package cache
    Checking calternal-db v0.1.0 (/home/kayg/Developer/calternal-wt/wal-824/crates/calternal-db)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 23.86s

Tests:

   Compiling zerofrom v0.1.8
   Compiling num-traits v0.2.19
   Compiling tokio v1.53.1
   Compiling tracing v0.1.44
   Compiling yoke v0.8.3
   Compiling serde v1.0.229
   Compiling zerovec v0.11.8
   Compiling zerotrie v0.2.5
   Compiling tinystr v0.8.4
   Compiling icu_locale_core v2.3.0
   Compiling potential_utf v0.1.6
   Compiling icu_collections v2.3.0
   Compiling icu_provider v2.3.1
   Compiling thiserror v2.0.21
   Compiling libsqlite3-sys v0.37.0
   Compiling icu_normalizer v2.3.0
   Compiling icu_properties v2.3.0
   Compiling chrono v0.4.45
   Compiling phf_macros v0.11.3
   Compiling idna_adapter v1.2.2
   Compiling idna v1.1.0
   Compiling url v2.5.8
   Compiling atoi v2.0.0
   Compiling blake3 v1.8.7
   Compiling chrono-tz v0.10.4
   Compiling phf v0.11.3
   Compiling cron v0.17.0
   Compiling tokio-stream v0.1.19
   Compiling sqlx-core v0.9.0
   Compiling sqlx-sqlite v0.9.0
   Compiling sqlx v0.9.0
   Compiling calternal-db v0.1.0 (/home/kayg/Developer/calternal-wt/wal-824/crates/calternal-db)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 3m 46s
     Running unittests src/lib.rs (/mnt/hdd/targets/jobs/wal-824/debug/deps/calternal_db-6ce6e6e1ec45335b)

running 10 tests
test cron::tests::zoned_cron_keeps_its_wall_clock_time_across_daylight_saving ... ok
test sqlite::tests::saturated_or_closed_sqlite_pools_are_transient_service_errors ... ok
test sqlite::tests::wrapped_sqlite_contention_retries_the_whole_operation ... ok
test sqlite::tests::persistent_wrapped_sqlite_contention_stops_at_the_attempt_limit ... ok
test secrets::tests::secret_survives_database_reopen ... ok
test secrets::tests::rejects_invalid_names_and_empty_candidates ... ok
test secrets::tests::named_secret_can_be_replaced_and_cleared_without_reading_it_for_status ... ok
test secrets::tests::named_secret_is_stable_and_first_candidate_wins ... ok
test worker::tests::disabled_handler_keeps_jobs_pending_and_finishes_active_work ... ok
test jobs::contention_tests::background_queue_write_waits_past_five_seconds_for_the_writer ... ok

test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 11.73s

     Running tests/queue.rs (/mnt/hdd/targets/jobs/wal-824/debug/deps/queue-ca91ae55984806aa)

running 17 tests
test enqueue_lease_throughput_microbenchmark ... ignored, manual enqueue plus lease throughput measurement
test opens_wal_database_with_required_pragmas ... ok
test plugin_migrations_share_namespaces_and_check_applied_sql ... ok
test queue_change_subscribers_receive_a_hint_after_state_changes ... ok
test deduplicates_pending_and_leased_jobs ... ok
test failure_backoff_uses_fake_clock_and_dead_letters_at_limit ... ok
test controlled_worker_observes_stop_at_handler_checkpoint ... ok
test job_list_summaries_do_not_read_handler_payloads ... ok
test cron_enqueues_each_occurrence_once_and_keeps_one_active_job ... ok
test expired_lease_is_recovered_and_old_owner_loses_lease ... ok
test owned_job_progress_is_private_and_cancellation_finishes_at_checkpoint ... ok
test heartbeat_does_not_deadlock_a_handler_inside_a_write_transaction ... ok
test worker_publishes_registered_kind_metadata_to_the_shared_queue ... ok
test queue_pause_is_idempotent_persistent_and_blocks_new_leases ... ok
test queue_retry_run_now_and_clear_only_change_failed_jobs_of_one_kind ... ok
test snapshot_restores_as_a_readable_database ... ok
test workers_do_not_execute_a_job_twice ... ok

test result: ok. 16 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 22.93s

   Doc-tests calternal_db

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

Sync-image fault injection:

NORMAL negative control: acknowledged state lost
FULL simulated power loss: recovered: revoked session, revoked App Password, changed password, enabled 2FA
sync failure: no ACK

Scope: the fault injection uses the real bundled SQLite VFS and real shared Db opener with synthetic authority rows. It tests the common commit boundary, not real Auth HTTP endpoints. Production wire.rs passes this same writer pool to SqliteAuthStore. Checkpoint work and its HDD comparison are next.

Durability slice committed: `33f460ec3b79d7e0c091a658bcc7b0251a9ebddd`. Effective shared writer setting is FULL; macOS fullfsync is enabled. Stronger expectation 1→2 is required by #824, not a weakened test. `cargo fmt --check` passed with no output. Clippy: ``` Blocking waiting for file lock on package cache Blocking waiting for file lock on package cache Blocking waiting for file lock on package cache Checking calternal-db v0.1.0 (/home/kayg/Developer/calternal-wt/wal-824/crates/calternal-db) Finished `dev` profile [unoptimized + debuginfo] target(s) in 23.86s ``` Tests: ``` Compiling zerofrom v0.1.8 Compiling num-traits v0.2.19 Compiling tokio v1.53.1 Compiling tracing v0.1.44 Compiling yoke v0.8.3 Compiling serde v1.0.229 Compiling zerovec v0.11.8 Compiling zerotrie v0.2.5 Compiling tinystr v0.8.4 Compiling icu_locale_core v2.3.0 Compiling potential_utf v0.1.6 Compiling icu_collections v2.3.0 Compiling icu_provider v2.3.1 Compiling thiserror v2.0.21 Compiling libsqlite3-sys v0.37.0 Compiling icu_normalizer v2.3.0 Compiling icu_properties v2.3.0 Compiling chrono v0.4.45 Compiling phf_macros v0.11.3 Compiling idna_adapter v1.2.2 Compiling idna v1.1.0 Compiling url v2.5.8 Compiling atoi v2.0.0 Compiling blake3 v1.8.7 Compiling chrono-tz v0.10.4 Compiling phf v0.11.3 Compiling cron v0.17.0 Compiling tokio-stream v0.1.19 Compiling sqlx-core v0.9.0 Compiling sqlx-sqlite v0.9.0 Compiling sqlx v0.9.0 Compiling calternal-db v0.1.0 (/home/kayg/Developer/calternal-wt/wal-824/crates/calternal-db) Finished `test` profile [unoptimized + debuginfo] target(s) in 3m 46s Running unittests src/lib.rs (/mnt/hdd/targets/jobs/wal-824/debug/deps/calternal_db-6ce6e6e1ec45335b) running 10 tests test cron::tests::zoned_cron_keeps_its_wall_clock_time_across_daylight_saving ... ok test sqlite::tests::saturated_or_closed_sqlite_pools_are_transient_service_errors ... ok test sqlite::tests::wrapped_sqlite_contention_retries_the_whole_operation ... ok test sqlite::tests::persistent_wrapped_sqlite_contention_stops_at_the_attempt_limit ... ok test secrets::tests::secret_survives_database_reopen ... ok test secrets::tests::rejects_invalid_names_and_empty_candidates ... ok test secrets::tests::named_secret_can_be_replaced_and_cleared_without_reading_it_for_status ... ok test secrets::tests::named_secret_is_stable_and_first_candidate_wins ... ok test worker::tests::disabled_handler_keeps_jobs_pending_and_finishes_active_work ... ok test jobs::contention_tests::background_queue_write_waits_past_five_seconds_for_the_writer ... ok test result: ok. 10 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 11.73s Running tests/queue.rs (/mnt/hdd/targets/jobs/wal-824/debug/deps/queue-ca91ae55984806aa) running 17 tests test enqueue_lease_throughput_microbenchmark ... ignored, manual enqueue plus lease throughput measurement test opens_wal_database_with_required_pragmas ... ok test plugin_migrations_share_namespaces_and_check_applied_sql ... ok test queue_change_subscribers_receive_a_hint_after_state_changes ... ok test deduplicates_pending_and_leased_jobs ... ok test failure_backoff_uses_fake_clock_and_dead_letters_at_limit ... ok test controlled_worker_observes_stop_at_handler_checkpoint ... ok test job_list_summaries_do_not_read_handler_payloads ... ok test cron_enqueues_each_occurrence_once_and_keeps_one_active_job ... ok test expired_lease_is_recovered_and_old_owner_loses_lease ... ok test owned_job_progress_is_private_and_cancellation_finishes_at_checkpoint ... ok test heartbeat_does_not_deadlock_a_handler_inside_a_write_transaction ... ok test worker_publishes_registered_kind_metadata_to_the_shared_queue ... ok test queue_pause_is_idempotent_persistent_and_blocks_new_leases ... ok test queue_retry_run_now_and_clear_only_change_failed_jobs_of_one_kind ... ok test snapshot_restores_as_a_readable_database ... ok test workers_do_not_execute_a_job_twice ... ok test result: ok. 16 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 22.93s Doc-tests calternal_db running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s ``` Sync-image fault injection: ``` NORMAL negative control: acknowledged state lost FULL simulated power loss: recovered: revoked session, revoked App Password, changed password, enabled 2FA sync failure: no ACK ``` Scope: the fault injection uses the real bundled SQLite VFS and real shared Db opener with synthetic authority rows. It tests the common commit boundary, not real Auth HTTP endpoints. Production wire.rs passes this same writer pool to SqliteAuthStore. Checkpoint work and its HDD comparison are next.
Author
Owner

wal-824 final report

Head: fa68c3d41d3fa064b41156baa28408cba8466c9e on job/wal-824. No push, deploy or outgoing merge. The required one fetch and merge of origin/dev completed as 466d3baa8; it changed only media sandbox scripts. The worktree is clean. No issue is closed.

Built

  • #824: the shared Index writer uses FULL and macOS fullfsync. All core and Plugin repositories using Db inherit the policy, including replacement connections. Production SqliteAuthStore receives this same writer pool. No temporary synchronous downgrade is introduced. The stronger NORMAL→FULL test expectation is required by this issue.
  • #823: automatic writer checkpoints are off. One maintenance connection runs PASSIVE each second, records content-free frame/duration/error diagnostics, retries incomplete work and makes a final best-effort pass. Db clones share its lifetime. Close remains joinable after cancellation.
  • Regression coverage: pinned readers, >16 MiB active WAL, writer liveness, completion after release, retention after restart, rollback/error/cancelled transactions, replacement writer, failed maintenance, retry, close cancellation and reopen.
  • Linux sync-boundary harness: successful fsync/fdatasync images only, SIGKILL immediately after ACK, main/WAL recovery from a durable initial WAL baseline. The NORMAL negative control loses the acknowledged change, FULL preserves all four synthetic authority fields, and failed sync receives no ACK.

Files

  • crates/calternal-db/src/db.rs
  • crates/calternal-db/src/checkpoint.rs
  • crates/calternal-db/src/lib.rs
  • crates/calternal-db/Cargo.toml
  • Cargo.lock
  • crates/calternal-db/tests/queue.rs
  • crates/calternal-db/tests/checkpoint.rs
  • crates/calternal-db/examples/wal_probe.rs
  • tests/adversarial/sqlite/durability.py
  • tests/adversarial/sqlite/sync_image.c
  • bench/sqlite-wal.sh
  • docs/DESIGN.md
  • docs/perf/2026-10-02-wal-824.md

Measurements

Locked HDD VM, release builds, SQLite 3.51.3, 100k synthetic 1 KiB rows, 1,000 accepted changed-row commits and a 32-request burst per phase. Lock released between phases. HDD emulation used direct I/O, ext4, 8 ms delays and 200 IOPS. Load before phases: 0.08/0.73/0.66 and 0.00/0.03/0.24.

Metric Before After
Mutation p50 ms 0.179 54.897
Mutation p95 ms 0.363 103.992
Mutation max ms 391.877 578.404
Ingest p95 ms 0.870 110.972
Ingest max ms 1077.680 636.519
Burst p95 ms 4.151 1894.848
Probe seconds 20.178 141.508
CPU user + system seconds 0.88 1.62
Average CPU % (time) 4 1
Peak RSS KiB 8132 8700

The old mutation commits were not power-loss durable. docs/perf/baseline.json has no corresponding SQLite profile; the recorded old-opener run is the comparison. The latency increase is filed as #853. Peak RSS increased about 7%, below the 15% threshold. Full results and repeat commands: docs/perf/2026-10-02-wal-824.md.

Gate output, verbatim

cargo fmt --check: passed, no output (including a final check after documentation edits).

cargo clippy -p calternal-db --all-targets -- -D warnings:

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 27.08s

cargo test -p calternal-db:

    Finished `test` profile [unoptimized + debuginfo] target(s) in 20.35s
     Running unittests src/lib.rs (/mnt/hdd/targets/jobs/wal-824/debug/deps/calternal_db-fc6d78b9df826065)

running 12 tests
test cron::tests::zoned_cron_keeps_its_wall_clock_time_across_daylight_saving ... ok
test sqlite::tests::saturated_or_closed_sqlite_pools_are_transient_service_errors ... ok
test checkpoint::tests::failed_pass_counts_error_and_a_later_pass_recovers ... ok
test sqlite::tests::wrapped_sqlite_contention_retries_the_whole_operation ... ok
test sqlite::tests::persistent_wrapped_sqlite_contention_stops_at_the_attempt_limit ... ok
test checkpoint::tests::cancelled_close_keeps_the_final_pass_joinable ... ok
test secrets::tests::rejects_invalid_names_and_empty_candidates ... ok
test secrets::tests::named_secret_is_stable_and_first_candidate_wins ... ok
test secrets::tests::named_secret_can_be_replaced_and_cleared_without_reading_it_for_status ... ok
test secrets::tests::secret_survives_database_reopen ... ok
test worker::tests::disabled_handler_keeps_jobs_pending_and_finishes_active_work ... ok
test jobs::contention_tests::background_queue_write_waits_past_five_seconds_for_the_writer ... ok

test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 12.44s

     Running tests/checkpoint.rs (/mnt/hdd/targets/jobs/wal-824/debug/deps/checkpoint-0fd30139b93d0bfc)

running 3 tests
test durability_policy_survives_errors_and_replacement_connections ... ok
test wal_retention_target_and_reuse_are_configured ... ok
test pinned_reader_reports_remaining_frames_and_retries_after_release ... ok

test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 18.63s

     Running tests/queue.rs (/mnt/hdd/targets/jobs/wal-824/debug/deps/queue-37225b1abc3ce8b0)

running 17 tests
test enqueue_lease_throughput_microbenchmark ... ignored, manual enqueue plus lease throughput measurement
test opens_wal_database_with_required_pragmas ... ok
test queue_change_subscribers_receive_a_hint_after_state_changes ... ok
test job_list_summaries_do_not_read_handler_payloads ... ok
test deduplicates_pending_and_leased_jobs ... ok
test plugin_migrations_share_namespaces_and_check_applied_sql ... ok
test cron_enqueues_each_occurrence_once_and_keeps_one_active_job ... ok
test expired_lease_is_recovered_and_old_owner_loses_lease ... ok
test controlled_worker_observes_stop_at_handler_checkpoint ... ok
test failure_backoff_uses_fake_clock_and_dead_letters_at_limit ... ok
test heartbeat_does_not_deadlock_a_handler_inside_a_write_transaction ... ok
test owned_job_progress_is_private_and_cancellation_finishes_at_checkpoint ... ok
test queue_pause_is_idempotent_persistent_and_blocks_new_leases ... ok
test worker_publishes_registered_kind_metadata_to_the_shared_queue ... ok
test queue_retry_run_now_and_clear_only_change_failed_jobs_of_one_kind ... ok
test snapshot_restores_as_a_readable_database ... ok
test workers_do_not_execute_a_job_twice ... ok

test result: ok. 16 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 19.46s

   Doc-tests calternal_db

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s

Final sync-image adversarial round:

NORMAL negative control: acknowledged state lost
FULL simulated power loss: recovered: revoked session, revoked App Password, changed password, enabled 2FA
sync failure: no ACK

C shim compilation with -Wall -Wextra -Werror, Python byte compilation, bash syntax and git diff checks passed with no output. No route or external API contract changed, so no server or web gate was run. No real-server hostile request round was run. The adversarial evidence here is the real SQLite fault harness and maintenance regressions. No migration was added.

Cleanup:

     Removed 3171 files, 1.2GiB total

No web build output was created. Review artifacts and saved probe binaries remain ignored under artifacts/. No screenshots or other review files were committed.

Decisions

Use FULL for the shared Index because Security state and Derived data share an exposed writer pool. A narrower boundary needs a cancellation-safe authority lease or separate Security state Index; Auth-only coverage would miss Shares, Roles and Plugin state. The measured HDD increase is recorded for that follow-up rather than weakening durability.

Use one-second PASSIVE maintenance, a 16 MiB retained-WAL target and a best-effort final pass. A commit does not invoke or await a checkpoint.

Known gaps

  • #823 remains partial: there is no hard active-WAL growth bound while a reader pins frames or a transaction grows. Enforcing one needs transaction limits and reader lifetime or writer admission policy outside the current raw-pool API. The >16 MiB test explicitly proves the distinction from retained-WAL trimming. Keep #823 open.
  • The fault harness uses synthetic authority rows through the real shared opener/VFS. It does not exercise actual Auth HTTP mutations or queued receipt recovery. Physical power loss and torn sectors were not tested. Durability assumes storage honors sync, as required by SQLite.
  • Standalone stores outside Db are not changed. The production Security state Index and Db-opened Plugin Index files receive the shared policy.

UX gaps closed / UX gaps left

Not applicable: no UI changed. No screenshot or device-width evidence is required for this backend change.

# wal-824 final report Head: `fa68c3d41d3fa064b41156baa28408cba8466c9e` on `job/wal-824`. No push, deploy or outgoing merge. The required one fetch and merge of origin/dev completed as `466d3baa8`; it changed only media sandbox scripts. The worktree is clean. No issue is closed. ## Built - #824: the shared Index writer uses FULL and macOS fullfsync. All core and Plugin repositories using Db inherit the policy, including replacement connections. Production SqliteAuthStore receives this same writer pool. No temporary synchronous downgrade is introduced. The stronger NORMAL→FULL test expectation is required by this issue. - #823: automatic writer checkpoints are off. One maintenance connection runs PASSIVE each second, records content-free frame/duration/error diagnostics, retries incomplete work and makes a final best-effort pass. Db clones share its lifetime. Close remains joinable after cancellation. - Regression coverage: pinned readers, >16 MiB active WAL, writer liveness, completion after release, retention after restart, rollback/error/cancelled transactions, replacement writer, failed maintenance, retry, close cancellation and reopen. - Linux sync-boundary harness: successful fsync/fdatasync images only, SIGKILL immediately after ACK, main/WAL recovery from a durable initial WAL baseline. The NORMAL negative control loses the acknowledged change, FULL preserves all four synthetic authority fields, and failed sync receives no ACK. ## Files - `crates/calternal-db/src/db.rs` - `crates/calternal-db/src/checkpoint.rs` - `crates/calternal-db/src/lib.rs` - `crates/calternal-db/Cargo.toml` - `Cargo.lock` - `crates/calternal-db/tests/queue.rs` - `crates/calternal-db/tests/checkpoint.rs` - `crates/calternal-db/examples/wal_probe.rs` - `tests/adversarial/sqlite/durability.py` - `tests/adversarial/sqlite/sync_image.c` - `bench/sqlite-wal.sh` - `docs/DESIGN.md` - `docs/perf/2026-10-02-wal-824.md` ## Measurements Locked HDD VM, release builds, SQLite 3.51.3, 100k synthetic 1 KiB rows, 1,000 accepted changed-row commits and a 32-request burst per phase. Lock released between phases. HDD emulation used direct I/O, ext4, 8 ms delays and 200 IOPS. Load before phases: 0.08/0.73/0.66 and 0.00/0.03/0.24. | Metric | Before | After | | --- | ---: | ---: | | Mutation p50 ms | 0.179 | 54.897 | | Mutation p95 ms | 0.363 | 103.992 | | Mutation max ms | 391.877 | 578.404 | | Ingest p95 ms | 0.870 | 110.972 | | Ingest max ms | 1077.680 | 636.519 | | Burst p95 ms | 4.151 | 1894.848 | | Probe seconds | 20.178 | 141.508 | | CPU user + system seconds | 0.88 | 1.62 | | Average CPU % (time) | 4 | 1 | | Peak RSS KiB | 8132 | 8700 | The old mutation commits were not power-loss durable. docs/perf/baseline.json has no corresponding SQLite profile; the recorded old-opener run is the comparison. The latency increase is filed as [#853](https://git.kayg.org/kayg/calternal/issues/853). Peak RSS increased about 7%, below the 15% threshold. Full results and repeat commands: `docs/perf/2026-10-02-wal-824.md`. ## Gate output, verbatim `cargo fmt --check`: passed, no output (including a final check after documentation edits). `cargo clippy -p calternal-db --all-targets -- -D warnings`: ``` Finished `dev` profile [unoptimized + debuginfo] target(s) in 27.08s ``` `cargo test -p calternal-db`: ``` Finished `test` profile [unoptimized + debuginfo] target(s) in 20.35s Running unittests src/lib.rs (/mnt/hdd/targets/jobs/wal-824/debug/deps/calternal_db-fc6d78b9df826065) running 12 tests test cron::tests::zoned_cron_keeps_its_wall_clock_time_across_daylight_saving ... ok test sqlite::tests::saturated_or_closed_sqlite_pools_are_transient_service_errors ... ok test checkpoint::tests::failed_pass_counts_error_and_a_later_pass_recovers ... ok test sqlite::tests::wrapped_sqlite_contention_retries_the_whole_operation ... ok test sqlite::tests::persistent_wrapped_sqlite_contention_stops_at_the_attempt_limit ... ok test checkpoint::tests::cancelled_close_keeps_the_final_pass_joinable ... ok test secrets::tests::rejects_invalid_names_and_empty_candidates ... ok test secrets::tests::named_secret_is_stable_and_first_candidate_wins ... ok test secrets::tests::named_secret_can_be_replaced_and_cleared_without_reading_it_for_status ... ok test secrets::tests::secret_survives_database_reopen ... ok test worker::tests::disabled_handler_keeps_jobs_pending_and_finishes_active_work ... ok test jobs::contention_tests::background_queue_write_waits_past_five_seconds_for_the_writer ... ok test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 12.44s Running tests/checkpoint.rs (/mnt/hdd/targets/jobs/wal-824/debug/deps/checkpoint-0fd30139b93d0bfc) running 3 tests test durability_policy_survives_errors_and_replacement_connections ... ok test wal_retention_target_and_reuse_are_configured ... ok test pinned_reader_reports_remaining_frames_and_retries_after_release ... ok test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 18.63s Running tests/queue.rs (/mnt/hdd/targets/jobs/wal-824/debug/deps/queue-37225b1abc3ce8b0) running 17 tests test enqueue_lease_throughput_microbenchmark ... ignored, manual enqueue plus lease throughput measurement test opens_wal_database_with_required_pragmas ... ok test queue_change_subscribers_receive_a_hint_after_state_changes ... ok test job_list_summaries_do_not_read_handler_payloads ... ok test deduplicates_pending_and_leased_jobs ... ok test plugin_migrations_share_namespaces_and_check_applied_sql ... ok test cron_enqueues_each_occurrence_once_and_keeps_one_active_job ... ok test expired_lease_is_recovered_and_old_owner_loses_lease ... ok test controlled_worker_observes_stop_at_handler_checkpoint ... ok test failure_backoff_uses_fake_clock_and_dead_letters_at_limit ... ok test heartbeat_does_not_deadlock_a_handler_inside_a_write_transaction ... ok test owned_job_progress_is_private_and_cancellation_finishes_at_checkpoint ... ok test queue_pause_is_idempotent_persistent_and_blocks_new_leases ... ok test worker_publishes_registered_kind_metadata_to_the_shared_queue ... ok test queue_retry_run_now_and_clear_only_change_failed_jobs_of_one_kind ... ok test snapshot_restores_as_a_readable_database ... ok test workers_do_not_execute_a_job_twice ... ok test result: ok. 16 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 19.46s Doc-tests calternal_db running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.01s ``` Final sync-image adversarial round: ``` NORMAL negative control: acknowledged state lost FULL simulated power loss: recovered: revoked session, revoked App Password, changed password, enabled 2FA sync failure: no ACK ``` C shim compilation with -Wall -Wextra -Werror, Python byte compilation, bash syntax and git diff checks passed with no output. No route or external API contract changed, so no server or web gate was run. No real-server hostile request round was run. The adversarial evidence here is the real SQLite fault harness and maintenance regressions. No migration was added. Cleanup: ``` Removed 3171 files, 1.2GiB total ``` No web build output was created. Review artifacts and saved probe binaries remain ignored under artifacts/. No screenshots or other review files were committed. ## Decisions Use FULL for the shared Index because Security state and Derived data share an exposed writer pool. A narrower boundary needs a cancellation-safe authority lease or separate Security state Index; Auth-only coverage would miss Shares, Roles and Plugin state. The measured HDD increase is recorded for that follow-up rather than weakening durability. Use one-second PASSIVE maintenance, a 16 MiB retained-WAL target and a best-effort final pass. A commit does not invoke or await a checkpoint. ## Known gaps - #823 remains partial: there is no hard active-WAL growth bound while a reader pins frames or a transaction grows. Enforcing one needs transaction limits and reader lifetime or writer admission policy outside the current raw-pool API. The >16 MiB test explicitly proves the distinction from retained-WAL trimming. Keep #823 open. - The fault harness uses synthetic authority rows through the real shared opener/VFS. It does not exercise actual Auth HTTP mutations or queued receipt recovery. Physical power loss and torn sectors were not tested. Durability assumes storage honors sync, as required by SQLite. - Standalone stores outside Db are not changed. The production Security state Index and Db-opened Plugin Index files receive the shared policy. ## UX gaps closed / UX gaps left Not applicable: no UI changed. No screenshot or device-width evidence is required for this backend change.
Author
Owner

Round 2 started on job/wal-824 at fa68c3d41d. Trace confirms the shared opener sets FULL on all writes. Plan: dedicated FULL authority connection; ordinary WAL/NORMAL; audit Auth, Shares, public links, feed tokens, Plugin access and user deletion; nonblocking oversized-WAL truncation; endpoint sync-image tests and locked HDD measurements. No UI changes.

Round 2 started on job/wal-824 at fa68c3d41d3fa064b41156baa28408cba8466c9e. Trace confirms the shared opener sets FULL on all writes. Plan: dedicated FULL authority connection; ordinary WAL/NORMAL; audit Auth, Shares, public links, feed tokens, Plugin access and user deletion; nonblocking oversized-WAL truncation; endpoint sync-image tests and locked HDD measurements. No UI changes.
Author
Owner

Round 2 authority audit: production Auth receives a dedicated FULL connection. Explicit access writers also include Shares, public-link grants/passwords/download admission, calendar feed tokens, Plugin enablement, protocol access flags, and the cross-crate User deletion transaction. Files deletion/move transactions can remove or rewrite grants; these must stay atomic on FULL while ordinary indexing remains NORMAL. No per-transaction PRAGMA switching is used, so cancellation cannot leak NORMAL into authority work.

The actual Auth revocation endpoints return 204, not 200. Auth is passkey-only: no account-password mutation endpoint or hash exists. The fault round will use actual session revoke, App Password revoke, and sign-out HTTP endpoints, plus the existing synthetic password/2FA test. This is a documented coverage gap, not a new password API.

WAL reclamation adds nonblocking TRUNCATE above the 16 MiB threshold after a complete PASSIVE pass. Pinned readers/in-flight writes can delay truncation. The test checks that releasing a reader reclaims WAL without a subsequent write. This does not impose an absolute byte cap while readers are pinned.

Round 2 authority audit: production Auth receives a dedicated FULL connection. Explicit access writers also include Shares, public-link grants/passwords/download admission, calendar feed tokens, Plugin enablement, protocol access flags, and the cross-crate User deletion transaction. Files deletion/move transactions can remove or rewrite grants; these must stay atomic on FULL while ordinary indexing remains NORMAL. No per-transaction PRAGMA switching is used, so cancellation cannot leak NORMAL into authority work. The actual Auth revocation endpoints return 204, not 200. Auth is passkey-only: no account-password mutation endpoint or hash exists. The fault round will use actual session revoke, App Password revoke, and sign-out HTTP endpoints, plus the existing synthetic password/2FA test. This is a documented coverage gap, not a new password API. WAL reclamation adds nonblocking TRUNCATE above the 16 MiB threshold after a complete PASSIVE pass. Pinned readers/in-flight writes can delay truncation. The test checks that releasing a reader reclaims WAL without a subsequent write. This does not impose an absolute byte cap while readers are pinned.
Author
Owner

Read-only review found additional nonrebuildable state in calternal-db instance secrets, AI provider credentials, and Calendar/Mail Connected Account credentials and access toggles. These now use the FULL authority pool. Sync timestamps and cached messages/events retain NORMAL.

Two policy connections add a SQLite stale-snapshot risk for SELECT-first authority transactions. They now use BEGIN IMMEDIATE, which reserves the write lock before reads. Added mixed-policy transaction checks. Review also identified a pre-existing Plugin cache cancellation concern: cancellation during COMMIT can skip the following in-memory cache update. No successful ACK occurs on that cancelled request; this is separate from power-loss durability and is recorded for follow-up.

DESIGN decision commit: separate immutable FULL/NORMAL connection policies and nonblocking oversized-WAL reclamation. Compilation remains active on the busy shared host; restarted this job without the shared sccache wrapper after basic dependency requests spent minutes waiting. No other job processes were changed.

Read-only review found additional nonrebuildable state in calternal-db instance secrets, AI provider credentials, and Calendar/Mail Connected Account credentials and access toggles. These now use the FULL authority pool. Sync timestamps and cached messages/events retain NORMAL. Two policy connections add a SQLite stale-snapshot risk for SELECT-first authority transactions. They now use BEGIN IMMEDIATE, which reserves the write lock before reads. Added mixed-policy transaction checks. Review also identified a pre-existing Plugin cache cancellation concern: cancellation during COMMIT can skip the following in-memory cache update. No successful ACK occurs on that cancelled request; this is separate from power-loss durability and is recorded for follow-up. DESIGN decision commit: separate immutable FULL/NORMAL connection policies and nonblocking oversized-WAL reclamation. Compilation remains active on the busy shared host; restarted this job without the shared sccache wrapper after basic dependency requests spent minutes waiting. No other job processes were changed.
Author
Owner

The authority audit found that AI token bindings must be durable alongside their Auth sessions. If a binding is lost but the Auth session survives, startup cannot identify the session to revoke it. Bindings now commit on FULL before driver dispatch. Completion uses the existing revoke helper to revoke Auth first, then remove the binding; a failed revoke keeps the binding. Startup stops with recovery handles intact if any revoke fails. A new regression test injects an Auth revocation failure, checks the binding remains, removes the failure, and checks the old session is refused after recovery.

This is required to keep the dedicated authority policy complete, rather than only fixing Auth's direct caller. AI turn output and file undo indexes remain NORMAL.

The authority audit found that AI token bindings must be durable alongside their Auth sessions. If a binding is lost but the Auth session survives, startup cannot identify the session to revoke it. Bindings now commit on FULL before driver dispatch. Completion uses the existing revoke helper to revoke Auth first, then remove the binding; a failed revoke keeps the binding. Startup stops with recovery handles intact if any revoke fails. A new regression test injects an Auth revocation failure, checks the binding remains, removes the failure, and checks the old session is refused after recovery. This is required to keep the dedicated authority policy complete, rather than only fixing Auth's direct caller. AI turn output and file undo indexes remain NORMAL.
Author
Owner

Round 2 HDD profile completed under /root/perf.lock: ordinary mutation p95 0.401852 ms (round 1 FULL: 103.992 ms; original NORMAL: 0.363 ms), authority commit p95 117.234803 ms. CPU 1.35 s, peak RSS 6,772 KiB; load inside lock 5.42/2.54/2.62. The small +10.70% old-baseline difference exceeds the 10% reporting threshold and is tracked in #855; workload and host load differ, so it is not a proven causal regression. Ordinary performance is back to the requested approximately 0.4 ms.

One bounded fault round passed: real Auth session revocation, App Password revocation, and sign-out return 204 and the old credential is refused after recovery from only synced bytes. NORMAL loses the revocation; injected sync failure never receives a successful acknowledgement. The synthetic password/2FA check also passes. DESIGN §7 defines passkey-only accounts, so there is no real account-password-change endpoint to test. Full numbers and fault output are in docs/perf/2026-10-02-wal-824.md.

Round 2 HDD profile completed under `/root/perf.lock`: ordinary mutation p95 **0.401852 ms** (round 1 FULL: 103.992 ms; original NORMAL: 0.363 ms), authority commit p95 **117.234803 ms**. CPU 1.35 s, peak RSS 6,772 KiB; load inside lock 5.42/2.54/2.62. The small +10.70% old-baseline difference exceeds the 10% reporting threshold and is tracked in #855; workload and host load differ, so it is not a proven causal regression. Ordinary performance is back to the requested approximately 0.4 ms. One bounded fault round passed: real Auth session revocation, App Password revocation, and sign-out return 204 and the old credential is refused after recovery from only synced bytes. NORMAL loses the revocation; injected sync failure never receives a successful acknowledgement. The synthetic password/2FA check also passes. DESIGN §7 defines passkey-only accounts, so there is no real account-password-change endpoint to test. Full numbers and fault output are in `docs/perf/2026-10-02-wal-824.md`.
Author
Owner

Read-only review started on job/rev2-wal-824, base 440e19dce2. Review target: 8e8718003, compared with origin/dev. I will inspect source and test history only. No builds, tests, servers or product edits.

Read-only review started on job/rev2-wal-824, base 440e19dce23040ac8ebaae88f0469b6535b1afcb. Review target: 8e8718003, compared with origin/dev. I will inspect source and test history only. No builds, tests, servers or product edits.
Author
Owner

Review of job/wal-824 — #824

Result

Request changes. One P1 finding blocks merge. One P3 finding does not block
merge. Both findings are in the reviewed change and belong to #824. No
separate issue was filed.

Target: 8e8718003. Comparison base:
440e19dce23040ac8ebaae88f0469b6535b1afcb (origin/dev at review start).
Command: git diff origin/dev...8e8718003.
The review used the Git objects in this worktree. The author's worktree and
report were not used as evidence.

F1 — P1: move all production Security state writes to FULL

The ordinary pool uses NORMAL at crates/calternal-db/src/db.rs:64.
The new FULL pool at line 83 protects only callers that select it.
Production callers still select NORMAL:

Location at target Security state write
crates/calternal-server/src/wire.rs:1233 The production Auth store receives writer_pool(). Sessions, App Passwords, Roles and account changes use that pool.
crates/calternal-server/src/wire.rs:2146 Protocol access changes and their audit event use the ordinary pool.
crates/calternal-server/src/wire.rs:4260 User deletion and Files grant revocation use a caller-owned ordinary transaction. Commit at line 4273 precedes Home staging.
crates/plugins/files/src/shares.rs:266 Share revoke commits on the ordinary pool before a 204 response.
crates/plugins/files/src/public.rs:580 Public-link access and password changes use the ordinary pool.
crates/plugins/calendar/src/feeds/publication.rs:506 Feed credential revoke uses the ordinary pool before a 204 response. Rotation at line 475 uses it too.

This leaves the power-loss failure from #824 possible after acknowledgement.
A later checkpoint or FULL commit can sync prior writes, but neither must run
before these responses. SQLite distinguishes consistency from power-loss
durability for WAL/NORMAL. This finding does not claim corruption or a measured
restored-access incident. See the SQLite sync policy.

DESIGN §2 requires durable Security state. The branch's new policy also names
Auth, Roles, Shares, public links, feed credentials, protocol access and User
deletion. crates/calternal-auth/src/store.rs:971 says the server supplies
FULL, but the production constructor at wire.rs:1233 contradicts it.

Concrete fix: pass db.authority_pool().clone() to the production Auth store.
Move all authority writes and caller-owned authority transactions to that
pool. Use BEGIN IMMEDIATE for transactions that read before they write.
Check other adapters that select the ordinary pool through Storage::writer.
Keep derived data and ordinary queue work on NORMAL. Preserve all ownership
checks and response codes. Correct the policy comments with the code.

Test needed: cover the production server constructor and the authority
repositories. Assert their effective write policy and that a failed sync
cannot produce a successful response. Apply the existing recovery checks to
the production wiring. Include caller-owned deletion transactions, Shares,
public links and feed credentials. Keep cross-User denial checks.

The new crates/calternal-auth/examples/durability_server.rs:28 selects FULL
directly. It exercises the Auth router, but bypasses the server constructor.
It can pass while production uses NORMAL. The synthetic WAL probe also
selects the pool directly. These probes test the FULL boundary, not complete
production adoption of that boundary.

F2 — P3: make checkpoint diagnostics match their contract

crates/calternal-db/src/checkpoint.rs:127 sets status.busy = busy != 0.
Its field comment at line 32 includes a pinned reader or active writer.
An incomplete PASSIVE pass can return a zero first column while
log_frames > checkpointed_frames. The status then reports busy=false.
Frame counts still show the incomplete pass, and the next timer tick retries.
See SQLite checkpoint results.

Concrete fix: include log >= 0 && checkpointed < log in the flag, or narrow
the comment and expose a separate incomplete flag. Extend
crates/calternal-db/tests/checkpoint.rs:52 to assert the chosen contract
while the reader pins frames and after it releases them. This is a diagnostic
defect, not a durability or maintenance-liveness blocker.

Other checks

  • Reuse: git grep found one new background checkpointer. Existing manual
    checkpoints serve migration or test boundaries. The revoke refactor shares
    one implementation. No duplicate production helper was found.
  • Concurrency: authority transactions reserve the lock before reads. The
    recovery helper checks the User on a live credential and leaves public
    revocation strict. No new route or removal of an ownership filter was found.
    F1 remains an authorization risk after power loss.
  • Error handling: Auth retains its existing error mapping. Failed maintenance
    passes count errors and retry. No new User-facing error text was introduced.
  • Checkpoint lifetime: Db clones share a worker without a Db reference cycle.
    Close retains its join handle across cancellation. FULL durability does not
    depend on the final pass. TRUNCATE uses a zero busy timeout.
  • Test history: inspected git log -p origin/dev..8e8718003 for the changed
    Rust tests. The queue sync assertion changed from 1 to 2 in 33f460ec3,
    then back to 1 in 687985ac5. Its final value matches the base. The new
    durability test was moved from the ordinary pool to the authority pool.
    No existing response-code or cross-User expectation was weakened. F1 shows
    why pool-level tests alone are insufficient.
  • Performance: automatic checkpoints are disabled on both application write
    pools. Maintenance uses a separate connection. Shared disk I/O can still
    affect requests, as the module comment states. The profile covers ordinary
    and authority latency, ingest and bursts. No measurement was run or inferred
    from the author's report. DESIGN §58 is absent from both the base and target;
    this review used §2 and the performance rules in CLAUDE.md.

Validation and limits

Only source, Git history, issue search and SQLite documentation were read.
No Cargo or Bun gate, server, browser, benchmark or adversarial round was run.
There is no gate output to quote. The LIGHT job expressly prohibits these
commands. No build output was created, so no cleanup was needed.
No product code, dependency, migration, test or expectation was changed.
No push, deploy or merge was performed. The target and comparison base stayed
fixed for reproducible source references.

Decisions

No product design decision was made. Related omissions share one fix and are
grouped as F1 on #824. F2 is a separate, non-blocking comment on the same
branch. Searches for durability, checkpoint and pinned reader found the
existing #824/#823 work. No outside-branch defect required a new issue.

UX gaps closed and left

No UI was changed. No UX gap was closed. Runtime and UI checks remain outside
this read-only review.

Review artifacts and completion

Built: the source review and its audit log only.
Files: review-wal-824.md, audit-findings.md.
Review branch: job/rev2-wal-824.
Review head: c4892d0b50dac88b6de28e6b467701cdcb8ef838.
Reviewed product head: 8e8718003.

git diff --check completed with no output before the report commit.
Cargo and Bun gates: not run; prohibited by the LIGHT review prompt.
Gate output: none. No pass result is claimed.
Known gaps: no runtime, sync-fault, performance or UI verification.
Decisions: no product decisions; related routing omissions are grouped under #824.
Issue list: #824 (findings); #823 (existing checkpoint work). No new issue.

# Review of job/wal-824 — #824 ## Result Request changes. One P1 finding blocks merge. One P3 finding does not block merge. Both findings are in the reviewed change and belong to #824. No separate issue was filed. Target: `8e8718003`. Comparison base: `440e19dce23040ac8ebaae88f0469b6535b1afcb` (`origin/dev` at review start). Command: `git diff origin/dev...8e8718003`. The review used the Git objects in this worktree. The author's worktree and report were not used as evidence. ## F1 — P1: move all production Security state writes to FULL The ordinary pool uses NORMAL at `crates/calternal-db/src/db.rs:64`. The new FULL pool at line 83 protects only callers that select it. Production callers still select NORMAL: | Location at target | Security state write | | --- | --- | | `crates/calternal-server/src/wire.rs:1233` | The production Auth store receives `writer_pool()`. Sessions, App Passwords, Roles and account changes use that pool. | | `crates/calternal-server/src/wire.rs:2146` | Protocol access changes and their audit event use the ordinary pool. | | `crates/calternal-server/src/wire.rs:4260` | User deletion and Files grant revocation use a caller-owned ordinary transaction. Commit at line 4273 precedes Home staging. | | `crates/plugins/files/src/shares.rs:266` | Share revoke commits on the ordinary pool before a 204 response. | | `crates/plugins/files/src/public.rs:580` | Public-link access and password changes use the ordinary pool. | | `crates/plugins/calendar/src/feeds/publication.rs:506` | Feed credential revoke uses the ordinary pool before a 204 response. Rotation at line 475 uses it too. | This leaves the power-loss failure from #824 possible after acknowledgement. A later checkpoint or FULL commit can sync prior writes, but neither must run before these responses. SQLite distinguishes consistency from power-loss durability for WAL/NORMAL. This finding does not claim corruption or a measured restored-access incident. See the [SQLite sync policy](https://www.sqlite.org/pragma.html#pragma_synchronous). DESIGN §2 requires durable Security state. The branch's new policy also names Auth, Roles, Shares, public links, feed credentials, protocol access and User deletion. `crates/calternal-auth/src/store.rs:971` says the server supplies FULL, but the production constructor at `wire.rs:1233` contradicts it. Concrete fix: pass `db.authority_pool().clone()` to the production Auth store. Move all authority writes and caller-owned authority transactions to that pool. Use `BEGIN IMMEDIATE` for transactions that read before they write. Check other adapters that select the ordinary pool through `Storage::writer`. Keep derived data and ordinary queue work on NORMAL. Preserve all ownership checks and response codes. Correct the policy comments with the code. Test needed: cover the production server constructor and the authority repositories. Assert their effective write policy and that a failed sync cannot produce a successful response. Apply the existing recovery checks to the production wiring. Include caller-owned deletion transactions, Shares, public links and feed credentials. Keep cross-User denial checks. The new `crates/calternal-auth/examples/durability_server.rs:28` selects FULL directly. It exercises the Auth router, but bypasses the server constructor. It can pass while production uses NORMAL. The synthetic WAL probe also selects the pool directly. These probes test the FULL boundary, not complete production adoption of that boundary. ## F2 — P3: make checkpoint diagnostics match their contract `crates/calternal-db/src/checkpoint.rs:127` sets `status.busy = busy != 0`. Its field comment at line 32 includes a pinned reader or active writer. An incomplete PASSIVE pass can return a zero first column while `log_frames > checkpointed_frames`. The status then reports `busy=false`. Frame counts still show the incomplete pass, and the next timer tick retries. See [SQLite checkpoint results](https://www.sqlite.org/pragma.html#pragma_wal_checkpoint). Concrete fix: include `log >= 0 && checkpointed < log` in the flag, or narrow the comment and expose a separate incomplete flag. Extend `crates/calternal-db/tests/checkpoint.rs:52` to assert the chosen contract while the reader pins frames and after it releases them. This is a diagnostic defect, not a durability or maintenance-liveness blocker. ## Other checks - Reuse: `git grep` found one new background checkpointer. Existing manual checkpoints serve migration or test boundaries. The revoke refactor shares one implementation. No duplicate production helper was found. - Concurrency: authority transactions reserve the lock before reads. The recovery helper checks the User on a live credential and leaves public revocation strict. No new route or removal of an ownership filter was found. F1 remains an authorization risk after power loss. - Error handling: Auth retains its existing error mapping. Failed maintenance passes count errors and retry. No new User-facing error text was introduced. - Checkpoint lifetime: Db clones share a worker without a Db reference cycle. Close retains its join handle across cancellation. FULL durability does not depend on the final pass. TRUNCATE uses a zero busy timeout. - Test history: inspected `git log -p origin/dev..8e8718003` for the changed Rust tests. The queue sync assertion changed from 1 to 2 in `33f460ec3`, then back to 1 in `687985ac5`. Its final value matches the base. The new durability test was moved from the ordinary pool to the authority pool. No existing response-code or cross-User expectation was weakened. F1 shows why pool-level tests alone are insufficient. - Performance: automatic checkpoints are disabled on both application write pools. Maintenance uses a separate connection. Shared disk I/O can still affect requests, as the module comment states. The profile covers ordinary and authority latency, ingest and bursts. No measurement was run or inferred from the author's report. DESIGN §58 is absent from both the base and target; this review used §2 and the performance rules in CLAUDE.md. ## Validation and limits Only source, Git history, issue search and SQLite documentation were read. No Cargo or Bun gate, server, browser, benchmark or adversarial round was run. There is no gate output to quote. The LIGHT job expressly prohibits these commands. No build output was created, so no cleanup was needed. No product code, dependency, migration, test or expectation was changed. No push, deploy or merge was performed. The target and comparison base stayed fixed for reproducible source references. ## Decisions No product design decision was made. Related omissions share one fix and are grouped as F1 on #824. F2 is a separate, non-blocking comment on the same branch. Searches for `durability`, `checkpoint` and `pinned reader` found the existing #824/#823 work. No outside-branch defect required a new issue. ## UX gaps closed and left No UI was changed. No UX gap was closed. Runtime and UI checks remain outside this read-only review. ## Review artifacts and completion Built: the source review and its audit log only. Files: `review-wal-824.md`, `audit-findings.md`. Review branch: `job/rev2-wal-824`. Review head: `c4892d0b50dac88b6de28e6b467701cdcb8ef838`. Reviewed product head: `8e8718003`. `git diff --check` completed with no output before the report commit. Cargo and Bun gates: not run; prohibited by the LIGHT review prompt. Gate output: none. No pass result is claimed. Known gaps: no runtime, sync-fault, performance or UI verification. Decisions: no product decisions; related routing omissions are grouped under #824. Issue list: #824 (findings); #823 (existing checkpoint work). No new issue.
Author
Owner

A final hot-path audit found SqliteAuthStore::record_app_password_use on the FULL pool. The server awaits it on each DAV and App Password bearer request (wire.rs calls at the Basic and bearer branches). It changes only last-used time/protocol/coarse IP, not grant authority. Routing that telemetry to NORMAL removes an avoidable per-request sync while the conditional UPDATE still refuses revoked/expired credentials. The production store will get a distinct ordinary activity pool; credential creation/revocation and session expiry changes stay FULL. A regression holds the ordinary connection, revokes on FULL, then requires the queued telemetry update to refuse the revoked credential.

The first Files test run returned 145 passed, 1 failed, 1 ignored. The failure is the existing 1,000-write reconcile storm deadline: writes, reconcile scans, and watcher adoption complete within five minutes: Elapsed(()) at lib.rs:5112. Its later entry not found panic followed timeout cancellation and temporary-directory cleanup. No expectation is changed. One rerun with reduced test concurrency will check this under the shared host load.

A final hot-path audit found `SqliteAuthStore::record_app_password_use` on the FULL pool. The server awaits it on each DAV and App Password bearer request (`wire.rs` calls at the Basic and bearer branches). It changes only last-used time/protocol/coarse IP, not grant authority. Routing that telemetry to NORMAL removes an avoidable per-request sync while the conditional UPDATE still refuses revoked/expired credentials. The production store will get a distinct ordinary activity pool; credential creation/revocation and session expiry changes stay FULL. A regression holds the ordinary connection, revokes on FULL, then requires the queued telemetry update to refuse the revoked credential. The first Files test run returned 145 passed, 1 failed, 1 ignored. The failure is the existing 1,000-write reconcile storm deadline: `writes, reconcile scans, and watcher adoption complete within five minutes: Elapsed(())` at `lib.rs:5112`. Its later `entry not found` panic followed timeout cancellation and temporary-directory cleanup. No expectation is changed. One rerun with reduced test concurrency will check this under the shared host load.
Author
Owner

Files final gate evidence (no assertions or deadlines changed):

The full cargo test -p calternal-plugin-files run failed only internal_temp_paths_never_enter_index_during_atomic_write_reconcile_storm: 145 passed, 1 failed, 1 ignored. A single retry with -- --test-threads=2 failed at the same existing 300-second deadline.

writes, reconcile scans, and watcher adoption complete within five minutes: Elapsed(())
test result: FAILED. 145 passed; 1 failed; 1 ignored; 0 measured; 0 filtered out; finished in 552.16s

After the timeout aborted the tasks, the write task also reported atomic write 753 failed: entry not found. This run does not establish that Files passes. The shared host is busy; no further retry or timeout change is planned. Calendar, Mail, AI and server gates are still running.

Files final gate evidence (no assertions or deadlines changed): The full `cargo test -p calternal-plugin-files` run failed only `internal_temp_paths_never_enter_index_during_atomic_write_reconcile_storm`: 145 passed, 1 failed, 1 ignored. A single retry with `-- --test-threads=2` failed at the same existing 300-second deadline. ```text writes, reconcile scans, and watcher adoption complete within five minutes: Elapsed(()) test result: FAILED. 145 passed; 1 failed; 1 ignored; 0 measured; 0 filtered out; finished in 552.16s ``` After the timeout aborted the tasks, the write task also reported `atomic write 753 failed: entry not found`. This run does not establish that Files passes. The shared host is busy; no further retry or timeout change is planned. Calendar, Mail, AI and server gates are still running.
Author
Owner

Round 2 report. Head: 1b6c287040ada1741c867cf008905f5b8efdeb1f. Branch: job/wal-824.

Status: partial. The production authority boundary still has uncommitted adapters. Do not merge this branch yet. The Files full test gate failed at its existing storm deadline; remaining gate status is quoted below. Work stops at the job time limit. Pending code remains in the worktree and in artifacts/round2/pending-authority-boundary.patch. #824, #823 and #853 remain open.

Built: dedicated immutable FULL authority connection; ordinary WAL/NORMAL writes; BEGIN IMMEDIATE authority transactions; nonblocking oversized-WAL reclamation; Auth recovery retries; NORMAL App Password telemetry and nonblocking session refresh; durable Plugin, Calendar and Mail authority adapters; real Auth endpoint power-loss harness; separate ordinary and authority HDD measurements. Files, AI and server adapters are prepared in the worktree where listed below.

Files changed since round 1:

  • bench/sqlite-wal.sh
  • crates/calternal-auth/examples/durability_server.rs
  • crates/calternal-auth/src/store.rs
  • crates/calternal-db/examples/wal_probe.rs
  • crates/calternal-db/src/checkpoint.rs
  • crates/calternal-db/src/db.rs
  • crates/calternal-db/src/lib.rs
  • crates/calternal-db/src/secrets.rs
  • crates/calternal-db/tests/checkpoint.rs
  • crates/calternal-db/tests/queue.rs
  • crates/calternal-plugin/src/state.rs
  • crates/calternal-server/src/wire.rs
  • crates/plugins/ai/src/lib.rs
  • crates/plugins/ai/src/routes.rs
  • crates/plugins/ai/src/store.rs
  • crates/plugins/ai/src/turns.rs
  • crates/plugins/calendar/src/cache/store.rs
  • crates/plugins/calendar/src/feeds/publication.rs
  • crates/plugins/files/src/index.rs
  • crates/plugins/files/src/public.rs
  • crates/plugins/files/src/shares.rs
  • crates/plugins/mail/src/cache/store.rs
  • docs/DESIGN.md
  • docs/perf/2026-10-02-wal-824.md
  • tests/adversarial/sqlite/auth_durability.py

Uncommitted files:

  • crates/calternal-server/src/wire.rs
  • crates/plugins/ai/src/lib.rs
  • crates/plugins/ai/src/routes.rs
  • crates/plugins/ai/src/store.rs
  • crates/plugins/ai/src/turns.rs
  • crates/plugins/files/src/index.rs
  • crates/plugins/files/src/public.rs
  • crates/plugins/files/src/shares.rs

Performance: one locked perf-VM HDD run; 100k synthetic 1 KiB rows in 100-row batches, 1,000 ordinary mutations, 1,000 authority commits and separate 32-request bursts. SQLite 3.51.3; ext4 direct I/O, 8 ms delay, 200 IOPS; no VM compilation. Load inside the lock: 5.42 / 2.54 / 2.62. The lock was released after the run.

Metric Old NORMAL Round 1 FULL Round 2
Ordinary p50 ms 0.179 54.897 0.248825
Ordinary p95 ms 0.363 103.992 0.401852
Ordinary burst p95 ms 4.151 1894.848 3.974247
Authority p50 ms not durable 54.897 68.30549
Authority p95 ms not durable 103.992 117.234803
Authority burst p95 ms not durable 1894.848 1742.509606
CPU user + system s 0.88 1.62 1.35
Peak RSS KiB 8132 8700 6772

The ordinary p95 meets the requested approximately 0.4 ms target. The +10.70% comparison against the old 0.363 ms sample crosses the 10% reporting threshold; follow-up #855 records it. Workload and load differ, so this comparison alone does not establish a code regression. docs/perf/baseline.json has no matching SQLite profile. Full method and results are in docs/perf/2026-10-02-wal-824.md.

Fault checks used the real Auth router on a loopback server. Real revoke endpoints return 204, not 200. The harness killed the server after the response, restored synced main/WAL bytes, and checked the old credential with the production verifier. The same checks passed again after the Auth telemetry change. Output, verbatim:

Auth session: NORMAL negative control loses revocation
Auth session: 204 survives simulated power loss; old credential refused
Auth session: sync failure has no successful ACK
Auth app: 204 survives simulated power loss; old credential refused
Auth app: sync failure has no successful ACK
Auth signout: 204 survives simulated power loss; old credential refused
Auth signout: sync failure has no successful ACK

Synthetic test output, verbatim:

NORMAL negative control: acknowledged state lost
FULL simulated power loss: recovered: revoked session, revoked App Password, changed password, enabled 2FA
sync failure: no ACK

Gates: per crate only. cargo fmt --check passed with no output. The lines below are verbatim command output; crate and gate labels identify each log. No test expectations or timeout were changed to pass a gate. The Files storm hit its existing 300-second deadline in the original run and one retry with two test threads. The retry also reported an entry-not-found error after the timeout aborted its tasks.

cargo fmt --check: passed (no output).

calternal-db
clippy:
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1m 56s
test:
    Finished `test` profile [unoptimized + debuginfo] target(s) in 1m 42s
test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 14.49s
test result: ok. 6 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 6.40s
test result: ok. 16 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 16.49s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.39s

calternal-auth
clippy:
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 4m 30s
test:
    Finished `test` profile [unoptimized + debuginfo] target(s) in 3m 07s
test result: ok. 67 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 94.94s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

calternal-plugin
clippy:
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 5m 50s
test:
    Finished `test` profile [unoptimized + debuginfo] target(s) in 2m 43s
test result: ok. 25 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 207.35s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

calternal-plugin-files
clippy:
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 14m 18s
test:
    Finished `test` profile [unoptimized + debuginfo] target(s) in 5m 02s
test result: FAILED. 145 passed; 1 failed; 1 ignored; 0 measured; 0 filtered out; finished in 552.16s
error: test failed, to rerun pass `-p calternal-plugin-files --lib`

calternal-plugin-calendar
clippy:
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 5m 00s
test:
    Finished `test` profile [unoptimized + debuginfo] target(s) in 9m 34s
test result: ok. 83 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 42.11s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.27s
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.28s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

calternal-plugin-mail
clippy:
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 3m 31s
test:
    Finished `test` profile [unoptimized + debuginfo] target(s) in 3m 21s
test result: ok. 45 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 5.96s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

calternal-plugin-ai
clippy:
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 3m 36s
test:
not completed

calternal-server
clippy:
not run
test:
not run

Before final gates: one git fetch origin and git merge origin/dev, already up to date at 440e19dce23040ac8ebaae88f0469b6535b1afcb. No migrations or dependencies changed. No pushes, deploys or merges into another branch.

Known gaps: unfinished production adapters and gates as listed above; Files storm deadline failure; no real account-password-change endpoint under passkeys-only DESIGN §7; real passkey-enrolment fault flow not added; sync-image recovery does not cover physical outages, torn sectors or devices that ignore sync. A pinned reader or one in-flight transaction can exceed 16 MiB until it finishes; reclamation is tested after reader release without another write. The pre-existing Plugin cache update cancellation window has no successful ACK and is outside acknowledged-commit durability.

Decisions: use a dedicated immutable FULL pool instead of changing PRAGMA around transactions; reserve authority locks before reads; keep mixed grant/Index changes atomic on FULL; use a 16 MiB journal limit and reclaim threshold with nonblocking TRUNCATE after complete PASSIVE; retain strict public revoke semantics and add an idempotent internal recovery retry; put only App Password metadata on NORMAL; skip expiry refresh while the ordinary writer is occupied.

UX gaps closed: remove per-request App Password telemetry sync and keep live-session reads available during ordinary transactions. UX gaps left: no UI flow changed. Device screenshots and web gates do not apply.

Cleanup output, verbatim:

Removed 17843 files, 8.6GiB total

No web build output was created.

Round 2 report. Head: `1b6c287040ada1741c867cf008905f5b8efdeb1f`. Branch: `job/wal-824`. Status: partial. The production authority boundary still has uncommitted adapters. Do not merge this branch yet. The Files full test gate failed at its existing storm deadline; remaining gate status is quoted below. Work stops at the job time limit. Pending code remains in the worktree and in `artifacts/round2/pending-authority-boundary.patch`. #824, #823 and #853 remain open. Built: dedicated immutable FULL authority connection; ordinary WAL/NORMAL writes; BEGIN IMMEDIATE authority transactions; nonblocking oversized-WAL reclamation; Auth recovery retries; NORMAL App Password telemetry and nonblocking session refresh; durable Plugin, Calendar and Mail authority adapters; real Auth endpoint power-loss harness; separate ordinary and authority HDD measurements. Files, AI and server adapters are prepared in the worktree where listed below. Files changed since round 1: - `bench/sqlite-wal.sh` - `crates/calternal-auth/examples/durability_server.rs` - `crates/calternal-auth/src/store.rs` - `crates/calternal-db/examples/wal_probe.rs` - `crates/calternal-db/src/checkpoint.rs` - `crates/calternal-db/src/db.rs` - `crates/calternal-db/src/lib.rs` - `crates/calternal-db/src/secrets.rs` - `crates/calternal-db/tests/checkpoint.rs` - `crates/calternal-db/tests/queue.rs` - `crates/calternal-plugin/src/state.rs` - `crates/calternal-server/src/wire.rs` - `crates/plugins/ai/src/lib.rs` - `crates/plugins/ai/src/routes.rs` - `crates/plugins/ai/src/store.rs` - `crates/plugins/ai/src/turns.rs` - `crates/plugins/calendar/src/cache/store.rs` - `crates/plugins/calendar/src/feeds/publication.rs` - `crates/plugins/files/src/index.rs` - `crates/plugins/files/src/public.rs` - `crates/plugins/files/src/shares.rs` - `crates/plugins/mail/src/cache/store.rs` - `docs/DESIGN.md` - `docs/perf/2026-10-02-wal-824.md` - `tests/adversarial/sqlite/auth_durability.py` Uncommitted files: - `crates/calternal-server/src/wire.rs` - `crates/plugins/ai/src/lib.rs` - `crates/plugins/ai/src/routes.rs` - `crates/plugins/ai/src/store.rs` - `crates/plugins/ai/src/turns.rs` - `crates/plugins/files/src/index.rs` - `crates/plugins/files/src/public.rs` - `crates/plugins/files/src/shares.rs` Performance: one locked perf-VM HDD run; 100k synthetic 1 KiB rows in 100-row batches, 1,000 ordinary mutations, 1,000 authority commits and separate 32-request bursts. SQLite 3.51.3; ext4 direct I/O, 8 ms delay, 200 IOPS; no VM compilation. Load inside the lock: 5.42 / 2.54 / 2.62. The lock was released after the run. | Metric | Old NORMAL | Round 1 FULL | Round 2 | | --- | ---: | ---: | ---: | | Ordinary p50 ms | 0.179 | 54.897 | 0.248825 | | Ordinary p95 ms | 0.363 | 103.992 | 0.401852 | | Ordinary burst p95 ms | 4.151 | 1894.848 | 3.974247 | | Authority p50 ms | not durable | 54.897 | 68.30549 | | Authority p95 ms | not durable | 103.992 | 117.234803 | | Authority burst p95 ms | not durable | 1894.848 | 1742.509606 | | CPU user + system s | 0.88 | 1.62 | 1.35 | | Peak RSS KiB | 8132 | 8700 | 6772 | The ordinary p95 meets the requested approximately 0.4 ms target. The +10.70% comparison against the old 0.363 ms sample crosses the 10% reporting threshold; follow-up #855 records it. Workload and load differ, so this comparison alone does not establish a code regression. `docs/perf/baseline.json` has no matching SQLite profile. Full method and results are in `docs/perf/2026-10-02-wal-824.md`. Fault checks used the real Auth router on a loopback server. Real revoke endpoints return 204, not 200. The harness killed the server after the response, restored synced main/WAL bytes, and checked the old credential with the production verifier. The same checks passed again after the Auth telemetry change. Output, verbatim: ```text Auth session: NORMAL negative control loses revocation Auth session: 204 survives simulated power loss; old credential refused Auth session: sync failure has no successful ACK Auth app: 204 survives simulated power loss; old credential refused Auth app: sync failure has no successful ACK Auth signout: 204 survives simulated power loss; old credential refused Auth signout: sync failure has no successful ACK ``` Synthetic test output, verbatim: ```text NORMAL negative control: acknowledged state lost FULL simulated power loss: recovered: revoked session, revoked App Password, changed password, enabled 2FA sync failure: no ACK ``` Gates: per crate only. `cargo fmt --check` passed with no output. The lines below are verbatim command output; crate and gate labels identify each log. No test expectations or timeout were changed to pass a gate. The Files storm hit its existing 300-second deadline in the original run and one retry with two test threads. The retry also reported an entry-not-found error after the timeout aborted its tasks. ```text cargo fmt --check: passed (no output). calternal-db clippy: Finished `dev` profile [unoptimized + debuginfo] target(s) in 1m 56s test: Finished `test` profile [unoptimized + debuginfo] target(s) in 1m 42s test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 14.49s test result: ok. 6 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 6.40s test result: ok. 16 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 16.49s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.39s calternal-auth clippy: Finished `dev` profile [unoptimized + debuginfo] target(s) in 4m 30s test: Finished `test` profile [unoptimized + debuginfo] target(s) in 3m 07s test result: ok. 67 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 94.94s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s calternal-plugin clippy: Finished `dev` profile [unoptimized + debuginfo] target(s) in 5m 50s test: Finished `test` profile [unoptimized + debuginfo] target(s) in 2m 43s test result: ok. 25 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 207.35s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s calternal-plugin-files clippy: Finished `dev` profile [unoptimized + debuginfo] target(s) in 14m 18s test: Finished `test` profile [unoptimized + debuginfo] target(s) in 5m 02s test result: FAILED. 145 passed; 1 failed; 1 ignored; 0 measured; 0 filtered out; finished in 552.16s error: test failed, to rerun pass `-p calternal-plugin-files --lib` calternal-plugin-calendar clippy: Finished `dev` profile [unoptimized + debuginfo] target(s) in 5m 00s test: Finished `test` profile [unoptimized + debuginfo] target(s) in 9m 34s test result: ok. 83 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 42.11s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.27s test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.28s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s calternal-plugin-mail clippy: Finished `dev` profile [unoptimized + debuginfo] target(s) in 3m 31s test: Finished `test` profile [unoptimized + debuginfo] target(s) in 3m 21s test result: ok. 45 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 5.96s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s calternal-plugin-ai clippy: Finished `dev` profile [unoptimized + debuginfo] target(s) in 3m 36s test: not completed calternal-server clippy: not run test: not run ``` Before final gates: one `git fetch origin` and `git merge origin/dev`, already up to date at `440e19dce23040ac8ebaae88f0469b6535b1afcb`. No migrations or dependencies changed. No pushes, deploys or merges into another branch. Known gaps: unfinished production adapters and gates as listed above; Files storm deadline failure; no real account-password-change endpoint under passkeys-only DESIGN §7; real passkey-enrolment fault flow not added; sync-image recovery does not cover physical outages, torn sectors or devices that ignore sync. A pinned reader or one in-flight transaction can exceed 16 MiB until it finishes; reclamation is tested after reader release without another write. The pre-existing Plugin cache update cancellation window has no successful ACK and is outside acknowledged-commit durability. Decisions: use a dedicated immutable FULL pool instead of changing PRAGMA around transactions; reserve authority locks before reads; keep mixed grant/Index changes atomic on FULL; use a 16 MiB journal limit and reclaim threshold with nonblocking TRUNCATE after complete PASSIVE; retain strict public revoke semantics and add an idempotent internal recovery retry; put only App Password metadata on NORMAL; skip expiry refresh while the ordinary writer is occupied. UX gaps closed: remove per-request App Password telemetry sync and keep live-session reads available during ordinary transactions. UX gaps left: no UI flow changed. Device screenshots and web gates do not apply. Cleanup output, verbatim: ```text Removed 17843 files, 8.6GiB total ``` No web build output was created.
Author
Owner

Round 3 report (wal-824). Branch job/wal-824, head b8c5fd288c2012ecf84f934621cd50c42a7de9cb. The branch merges cleanly with origin/dev (c4faf184d, one docs commit ahead).

Status: ready for the merge round. Review P1 is fixed and proven through production pool selection. Review P3 is fixed. All requested gates pass.

What changed

The unverified WIP commit 242dd4f26 was split into these commits, finished and verified:

  • 40dd99ab5 Checkpoint diagnostics: a PASSIVE pass that copies back fewer frames than the WAL holds now reports busy=true (review P3). The pinned-reader test asserts this.
  • 8f966773f Db::write_change_counts(): total_changes() for each of the two one-connection write pools. Tests use the difference across one operation to see which policy committed it.
  • 29d0e545a SqliteAuthStore::for_index(&Db) is now the only mapping from Index pools to Auth (FULL authority, readers, NORMAL activity). ensure_durable_authority() refuses a store whose authority connection reports synchronous < FULL. The server, the power-loss harness (durability_server) and the tests all use for_index.
  • 2c06321e6 Server: build_live_app uses for_index and does not start without FULL. Protocol access changes and the User deletion intent with Files grant revocation use BEGIN IMMEDIATE on the authority pool.
  • afcbd9c2b Files: Share create/revoke, public link create/update/revoke, public edit credentials, grant-removing deletes (remove, folder descendants, forget Trash name, empty Trash) and replacing moves commit on FULL. These stay on NORMAL: plain moves (grants stay bound to the item ID, and an access check never trusts a path alone), view/download statistics on unlimited links, rate limits and removal of expired edit sessions. A download on a link with a download limit spends its quota on FULL.
  • 06ff0279b AI: provider credential save/delete and new Agent token bindings use FULL. A binding is removed only after its revocation commits (revoke_session_for_recovery). A failed revoke stops startup and keeps all bindings. Binding cleanup after a synced revoke stays on NORMAL: the WAL keeps commit order, and a lost unbind only repeats an idempotent revoke.
  • d7171a493 Calendar: test for feed create/rotate/revoke on FULL.
  • 40eb48f6a DESIGN §2: lists which writes need FULL and which stay ordinary, and records the startup refusal.
  • b8c5fd288 Fixed a race in the Auth test app_password_activity_uses_ordinary_pool_and_cannot_revive_revocation. It failed 2 of 3 solo runs: SQLx returns a released connection asynchronously, and the session refresh only uses try_acquire by design. The test now retries with a 5 s limit and still fails if no refresh is saved. The assertion is not weaker.

Proof for review P1 (production pool selection, no test-only pool)

  • Auth production_store_commits_security_state_on_full: builds the store with for_index (the server's call), then issues and revokes a session, creates and revokes an App Password, and disables a User. It asserts authority >= 5, ordinary == 0. Mutation check: with for_index changed to pass the ordinary writer, the test fails (startup check panic). The original file was restored. normal_authority_pool_is_refused_at_startup is the negative control.
  • Server live app (full_app_setup_session_config_and_backup, run by live_apps_run_in_separate_processes): the real build_live_app runs the FULL startup check. Real HTTP DELETE /api/v1/admin/users/{id} must change at least 5 rows on the authority connection; on the ordinary writer at most 3 are possible. Real PUT /api/v1/admin/apps/surfaces must change at least 2. This server threshold has no mutation check; one server rebuild takes about 20 minutes on this host.
  • Files grant_changes_commit_on_full_and_ordinary_work_on_normal: real routes on a production Db. Share and link create, update and revoke, and a limited download, each change rows on FULL. An unlimited download is exactly (ordinary 1, authority 0). A plain move is authority 0. The revoked link then returns 404.
  • Calendar feed_capability_changes_commit_on_full, AI restart_keeps_binding_until_auth_revocation_succeeds (now uses for_index; a binding is exactly 1 authority row).
  • Power-loss harness through the real Auth router, now built with for_index. Output, verbatim:
Auth session: NORMAL negative control loses revocation
Auth session: 204 survives simulated power loss; old credential refused
Auth session: sync failure has no successful ACK
Auth app: 204 survives simulated power loss; old credential refused
Auth app: sync failure has no successful ACK
Auth signout: 204 survives simulated power loss; old credential refused
Auth signout: sync failure has no successful ACK

Gates (verbatim, per crate, at the heads shown)

cargo fmt --check: exit 0, no output (at b8c5fd288).

calternal-db (40eb48f6a)
clippy:    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1m 46s
test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 5.59s
test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 4.11s
test result: ok. 16 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.37s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

calternal-auth (b8c5fd288)
clippy:    Finished `dev` profile [unoptimized + debuginfo] target(s) in 17.27s
test result: ok. 69 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 21.00s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

calternal-plugin-ai
clippy:    Finished `dev` profile [unoptimized + debuginfo] target(s) in 14m 25s
test result: ok. 13 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.25s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

calternal-plugin-calendar
clippy:    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1m 00s
test result: ok. 84 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.74s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.06s
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.09s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

calternal-plugin-mail
clippy:    Finished `dev` profile [unoptimized + debuginfo] target(s) in 2m 26s
test result: ok. 45 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 1.79s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

calternal-plugin-files
clippy:    Finished `dev` profile [unoptimized + debuginfo] target(s) in 2m 48s
test result: ok. 147 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 78.14s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

calternal-server
clippy:    Finished `dev` profile [unoptimized + debuginfo] target(s) in 25m 59s
test result: ok. 107 passed; 0 failed; 3 ignored; 0 measured; 0 filtered out; finished in 6.33s

The plugin and server gates ran at 40eb48f6a. b8c5fd288 changes only an Auth test. The Files storm test that hit its deadline in round 2 passed in this run. Host load was about 27 to 31 during all gates.

Known gaps (none block a merge)

  • Moving an item to Trash (not emptying Trash) stays on NORMAL. Grants remain bound to the item ID, so after a lost commit the identity check still guards access. This was not changed.
  • A download limit added after a request has read the link applies from the next download on.
  • The HDD measurement was not repeated. The extra FULL commits are on Files grant changes, limited downloads and Agent token binding only. Ordinary writes, plain moves and unlimited downloads use the same NORMAL path as the round 2 run (ordinary p95 0.402 ms).
  • No push, deploy or merge. Web gates do not apply (no UI change). The web build exists in the worktree only for the server crate.
Round 3 report (wal-824). Branch `job/wal-824`, head `b8c5fd288c2012ecf84f934621cd50c42a7de9cb`. The branch merges cleanly with `origin/dev` (`c4faf184d`, one docs commit ahead). Status: ready for the merge round. Review P1 is fixed and proven through production pool selection. Review P3 is fixed. All requested gates pass. ## What changed The unverified WIP commit `242dd4f26` was split into these commits, finished and verified: - `40dd99ab5` Checkpoint diagnostics: a PASSIVE pass that copies back fewer frames than the WAL holds now reports `busy=true` (review P3). The pinned-reader test asserts this. - `8f966773f` `Db::write_change_counts()`: `total_changes()` for each of the two one-connection write pools. Tests use the difference across one operation to see which policy committed it. - `29d0e545a` `SqliteAuthStore::for_index(&Db)` is now the only mapping from Index pools to Auth (FULL authority, readers, NORMAL activity). `ensure_durable_authority()` refuses a store whose authority connection reports `synchronous` < FULL. The server, the power-loss harness (`durability_server`) and the tests all use `for_index`. - `2c06321e6` Server: `build_live_app` uses `for_index` and does not start without FULL. Protocol access changes and the User deletion intent with Files grant revocation use BEGIN IMMEDIATE on the authority pool. - `afcbd9c2b` Files: Share create/revoke, public link create/update/revoke, public edit credentials, grant-removing deletes (remove, folder descendants, forget Trash name, empty Trash) and replacing moves commit on FULL. These stay on NORMAL: plain moves (grants stay bound to the item ID, and an access check never trusts a path alone), view/download statistics on unlimited links, rate limits and removal of expired edit sessions. **A download on a link with a download limit spends its quota on FULL.** - `06ff0279b` AI: provider credential save/delete and new Agent token bindings use FULL. A binding is removed only after its revocation commits (`revoke_session_for_recovery`). A failed revoke stops startup and keeps all bindings. Binding cleanup after a synced revoke stays on NORMAL: the WAL keeps commit order, and a lost unbind only repeats an idempotent revoke. - `d7171a493` Calendar: test for feed create/rotate/revoke on FULL. - `40eb48f6a` DESIGN §2: lists which writes need FULL and which stay ordinary, and records the startup refusal. - `b8c5fd288` Fixed a race in the Auth test `app_password_activity_uses_ordinary_pool_and_cannot_revive_revocation`. It failed 2 of 3 solo runs: SQLx returns a released connection asynchronously, and the session refresh only uses `try_acquire` by design. The test now retries with a 5 s limit and still fails if no refresh is saved. The assertion is not weaker. ## Proof for review P1 (production pool selection, no test-only pool) - Auth `production_store_commits_security_state_on_full`: builds the store with `for_index` (the server's call), then issues and revokes a session, creates and revokes an App Password, and disables a User. It asserts `authority >= 5`, `ordinary == 0`. **Mutation check:** with `for_index` changed to pass the ordinary writer, the test fails (startup check panic). The original file was restored. `normal_authority_pool_is_refused_at_startup` is the negative control. - Server live app (`full_app_setup_session_config_and_backup`, run by `live_apps_run_in_separate_processes`): the real `build_live_app` runs the FULL startup check. Real HTTP `DELETE /api/v1/admin/users/{id}` must change at least 5 rows on the authority connection; on the ordinary writer at most 3 are possible. Real `PUT /api/v1/admin/apps/surfaces` must change at least 2. This server threshold has no mutation check; one server rebuild takes about 20 minutes on this host. - Files `grant_changes_commit_on_full_and_ordinary_work_on_normal`: real routes on a production `Db`. Share and link create, update and revoke, and a limited download, each change rows on FULL. An unlimited download is exactly `(ordinary 1, authority 0)`. A plain move is `authority 0`. The revoked link then returns 404. - Calendar `feed_capability_changes_commit_on_full`, AI `restart_keeps_binding_until_auth_revocation_succeeds` (now uses `for_index`; a binding is exactly 1 authority row). - Power-loss harness through the real Auth router, now built with `for_index`. Output, verbatim: ```text Auth session: NORMAL negative control loses revocation Auth session: 204 survives simulated power loss; old credential refused Auth session: sync failure has no successful ACK Auth app: 204 survives simulated power loss; old credential refused Auth app: sync failure has no successful ACK Auth signout: 204 survives simulated power loss; old credential refused Auth signout: sync failure has no successful ACK ``` ## Gates (verbatim, per crate, at the heads shown) `cargo fmt --check`: exit 0, no output (at `b8c5fd288`). ```text calternal-db (40eb48f6a) clippy: Finished `dev` profile [unoptimized + debuginfo] target(s) in 1m 46s test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 5.59s test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 4.11s test result: ok. 16 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.37s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s calternal-auth (b8c5fd288) clippy: Finished `dev` profile [unoptimized + debuginfo] target(s) in 17.27s test result: ok. 69 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 21.00s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s calternal-plugin-ai clippy: Finished `dev` profile [unoptimized + debuginfo] target(s) in 14m 25s test result: ok. 13 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.25s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s calternal-plugin-calendar clippy: Finished `dev` profile [unoptimized + debuginfo] target(s) in 1m 00s test result: ok. 84 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.74s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.06s test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.09s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s calternal-plugin-mail clippy: Finished `dev` profile [unoptimized + debuginfo] target(s) in 2m 26s test result: ok. 45 passed; 0 failed; 2 ignored; 0 measured; 0 filtered out; finished in 1.79s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s calternal-plugin-files clippy: Finished `dev` profile [unoptimized + debuginfo] target(s) in 2m 48s test result: ok. 147 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 78.14s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s calternal-server clippy: Finished `dev` profile [unoptimized + debuginfo] target(s) in 25m 59s test result: ok. 107 passed; 0 failed; 3 ignored; 0 measured; 0 filtered out; finished in 6.33s ``` The plugin and server gates ran at `40eb48f6a`. `b8c5fd288` changes only an Auth test. The Files storm test that hit its deadline in round 2 passed in this run. Host load was about 27 to 31 during all gates. ## Known gaps (none block a merge) - Moving an item to Trash (not emptying Trash) stays on NORMAL. Grants remain bound to the item ID, so after a lost commit the identity check still guards access. This was not changed. - A download limit added after a request has read the link applies from the next download on. - The HDD measurement was not repeated. The extra FULL commits are on Files grant changes, limited downloads and Agent token binding only. Ordinary writes, plain moves and unlimited downloads use the same NORMAL path as the round 2 run (ordinary p95 0.402 ms). - No push, deploy or merge. Web gates do not apply (no UI change). The web build exists in the worktree only for the server crate.
Author
Owner

7b integration finding: wal-824 at b8c5fd288 predates the Connected Account repositories carried by 7a. crates/calternal-db/src/integrations.rs create_integration_account/set_integration_services/delete_integration_account and crates/calternal-server/src/integrations.rs migrate_legacy_account/create_account/update_account/remove_account still selected writer_pool (NORMAL) for credentials and service access. This violates DESIGN §2 and #824's production selection.

The assembly fix routes these writes through authority_pool (FULL), with BEGIN IMMEDIATE for mixed read/write transactions. Migration scans and status telemetry keep NORMAL. The repository regression checks per-connection row-change counts around account creation, service changes and deletion, requiring zero ordinary writes and at least three authority writes. Production startup also retains the #512 persisted verification key through a constructor that delegates to #824's for_index. Validation is in progress; no pass is claimed yet.

7b integration finding: wal-824 at b8c5fd288 predates the Connected Account repositories carried by 7a. crates/calternal-db/src/integrations.rs create_integration_account/set_integration_services/delete_integration_account and crates/calternal-server/src/integrations.rs migrate_legacy_account/create_account/update_account/remove_account still selected writer_pool (NORMAL) for credentials and service access. This violates DESIGN §2 and #824's production selection. The assembly fix routes these writes through authority_pool (FULL), with BEGIN IMMEDIATE for mixed read/write transactions. Migration scans and status telemetry keep NORMAL. The repository regression checks per-connection row-change counts around account creation, service changes and deletion, requiring zero ordinary writes and at least three authority writes. Production startup also retains the #512 persisted verification key through a constructor that delegates to #824's for_index. Validation is in progress; no pass is claimed yet.
Author
Owner

The Connected Account FULL-pool regression passed on the assembled Batch A code. Fix commit: see local job/merge-round-7b2 commit c18c97aa7f (head is recorded in #867's final report). No existing assertion was weakened.

Verbatim focused output:

running 1 test
test integrations::tests::account_services_and_credentials_are_scoped_to_the_owner ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 27 filtered out; finished in 1.17s
The Connected Account FULL-pool regression passed on the assembled Batch A code. Fix commit: see local job/merge-round-7b2 commit c18c97aa7f18f2c31047fc85edf6d5cdc90daddc (head is recorded in #867's final report). No existing assertion was weakened. Verbatim focused output: ``` running 1 test test integrations::tests::account_services_and_credentials_are_scoped_to_the_owner ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 27 filtered out; finished in 1.17s ```
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#824
No description provided.