Mail P1b: search save-log token and paged reconcile #995

Closed
kayg wants to merge 1 commit from t3/paged-search-reconcile into dev
Owner

Design after the 2026-11-07 generation-fence ruling

P1b stays a shared, backend-only search change. Notes remains the current provider; Mail can use the same cursor and page API later. Notes search, save behavior, result order, and UI stay unchanged. The indexer owns the fence: it will not index a source older than a notes.saved receipt or checkpoint past that receipt.

Fence and restart rules

History::install_executor already returns the accepted per-note version. Append that value as source_version to each new notes.saved event (at most 37 serialized bytes including its comma). Publish the same version with the accepted Notes source and expose it to search. Existing events without the field are unknown: read the item again from a fresh pinned Notes generation, compare its source stamp, and validate the snapshot before checkpointing.

When a journal page touches an item, compare every saved version in that page with the published source version. If the source is behind, drop the private page stage, keep the previous cursor, and park the entry. Subscribe to the existing MetaDb.populated watch before pinning the provider snapshot; its publication notification retries the same page without a polling loop. If the watch produces no notification, allow five bounded exponential-backoff rereads (10, 20, 40, 80, 160 ms), then wait on the watch. A still-pending entry never advances the cursor.

Every commit covers a contiguous run of verified entries, with documents, use scores, and the page token in one SQLite transaction. On startup, validate the cursor and re-read the final journal page before trusting it. Store that page's starting token beside each new cursor. For an older index without a starting token, scan forward in 256-entry pages to locate the final page; memory stays bounded. If re-verification finds a source behind its receipt, rebuild from a fresh snapshot and keep the old cursor untrusted until a complete verified publish succeeds.

A missing or expired cursor does a full paged reconcile. Before publishing its head token, scan the retained journal through that head and stage the latest per-item fence in SQLite's temporary file. Compare each provider page against those fences; a pending source drops the whole private reconcile. A restored or truncated log is detected only through the engine contract: P1b uses Engine::position and Engine::journal; #928 remains the only owner of signed token proof and high-water restore detection. P1b does not read the high-water file or add/parse token formats. If #928 lands first, rebase and keep this API boundary.

Sizes and cost

Work Bound Cost
Incremental catch-up 256 journal entries; 1,024 related IDs; 64 document payloads O(K log K + R + D); memory O(256 + R + 64)
Cursor-tail recheck 256 entries; one provider page O(256 + D) time and memory, except one-time legacy cursor lookup O(K) time in 256-entry pages and O(256) memory
Full reconcile 256 provider rows; 64 document payloads; 512 compare metadata records; fence state in SQLite temp_store=FILE, 2,000 KiB writer cache O(K + N log M + M) for K journal entries, N provider rows, M indexed rows; RAM independent of Home size; temp disk O(S) for changed rows and per-item fences
Version metadata ≤37 serialized bytes per event and accepted-source row At most 37 MB raw for 1M fully versioned rows, before SQLite page overhead; no extra fsync or save-path read

No page uses OFFSET or retains a Home-sized vector. The version is returned by the existing history install; Notes does no extra work on the notes-save path beyond encoding the appended field.

Failure table

Failure Effect Detection Recovery Test
notes.saved is visible before source publication The old source cannot enter the index and the cursor stays before the receipt Published source version is below the event version Drop the page, wait on the existing publication watch, retry the same page Real Notes executor delayed-publication race, incremental and missing-index cases
Crash after receipt, before source publication Receipt remains; cursor cannot pass it Restart replays the durable executor record and rechecks the receipt version Recovery publishes source; index resumes from the prior cursor Crash/restart between journal receipt and publication
Crash or kill at a page boundary Private stage is discarded; committed documents and cursor agree Reopen index and inspect the saved cursor Replay the uncommitted page kill_restart_at_every_provider_page_boundary_converges
Old build already checkpointed a raced receipt Its cursor is treated as untrusted until the tail page is checked Startup re-reads the last page and compares source version/stamp Reconcile from a fresh generation and publish a verified cursor Legacy cursor restart regression
Cursor came from a restored older backup Engine cannot prove it against the restored log Engine::position returns SyncTokenExpired Full paged reconcile; never construct a token locally Restored-backup cursor test; #928 owns token-proof coverage
Cursor is ahead or behind Ahead/expired cursor cannot be used; retained behind cursor needs only its tail pages position result plus page sequence checks Full reconcile if expired; otherwise apply bounded pages ahead_cursor_reindexes_from_current_head, valid_behind_cursor_uses_pages_without_listing
Save log is truncated or index is missing No trusted checkpoint remains position/page read expires, or cursor is absent Full paged reconcile with disk-backed fences and one atomic publish truncated_log_reindexes_from_pages, missing_index_rebuilds_in_pages
Publication notification is absent or the snapshot changes No unverified page becomes visible Bounded fallback retry; provider snapshot validation Retry after a notification/backoff or discard and start a fresh reconcile Watch and snapshot-change cases in the generation property test

Tests

  • Real Notes race: pause EventRecorder::record_once after the durable receipt and before it returns to BatchPublished; run incremental catch-up and missing-index reconcile; prove neither indexes the old text nor passes the cursor; release publication, restart the indexer, and prove the new text is indexed.
  • Crash before publication: stop after the receipt, reopen Notes so executor recovery replays it, and prove the cursor never skips the entry.
  • Reopen after every journal/provider page boundary and compare the final index to a clean build; include a legacy cursor with no page-start token.
  • Property test over save versions, delayed/reordered publication notifications, duplicate touches, arbitrary page cuts, and restarts; assert one indexed row per item, latest text, and a cursor no later than the contiguous verified prefix.
  • Restored-older-backup cursor, cursor ahead/behind, truncated log, missing index, and 1M-entry paged reconcile with a 256-row maximum and bounded memory.

Gates

Gate Exact command or run Pass condition
Format CARGO_BUILD_JOBS=4 ~/bin/slot /nix/var/nix/profiles/default/bin/nix develop -c bash -c 'cd /home/claude/Developer/Worktrees/mail-p1b-test-e0fc && cargo fmt --check' Pass
Clippy CARGO_BUILD_JOBS=4 ~/bin/slot /nix/var/nix/profiles/default/bin/nix develop -c bash -c 'cd /home/claude/Developer/Worktrees/mail-p1b-test-e0fc && cargo clippy --workspace --all-targets -- -D warnings' Pass
Rust tests CARGO_BUILD_JOBS=4 ~/bin/slot /nix/var/nix/profiles/default/bin/nix develop -c bash -c 'cd /home/claude/Developer/Worktrees/mail-p1b-test-e0fc && cargo test --workspace --no-fail-fast' Pass
Crash-typing flake cargo test -p calternal --test notes_crash_typing kill_nine_while_people_type_loses_and_doubles_nothing -- --exact 20 candidate and 20 dev runs interleaved; report both pass rates
Chromium Notes search/save, 1440 and 390 ~/bin/slot /nix/var/nix/profiles/default/bin/nix develop -c bash -c 'cd /home/claude/Developer/Worktrees/mail-p1b-test-e0fc/apps/web && CALTERNAL_BIN=/home/claude/Developer/Worktrees/mail-p1b-test-e0fc/target/debug/calternal bunx playwright test e2e/search-palette.spec.ts e2e/search-corpus.spec.ts e2e/editor.spec.ts e2e/list-catch-up.spec.ts --project=chromium-1440 --project=chromium-390 --workers=1' Pass
WebKit Notes search/save, 1440 and 390 ~/bin/slot /nix/var/nix/profiles/default/bin/nix develop -c bash -c 'cd /home/cua/Developer/Worktrees/mail-p1b-webkit-b822165c6/apps/web && CALTERNAL_BIN=/home/cua/Developer/Worktrees/mail-p1b-webkit-b822165c6/target/debug/calternal bunx playwright test e2e/search-palette.spec.ts e2e/search-corpus.spec.ts e2e/editor.spec.ts e2e/list-catch-up.spec.ts --project=webkit-1440 --project=webkit-390 --workers=1' Pass
Perf Hold ~/bin/perf-lock on perf-01 then perf-02 for the complete same-epoch session. Run dev-vs-dev control and candidate/dev ABAB ×5; each sample runs calternal-perf --bin "$SERVER_BIN" --work "$WORK" --data-dir "$DATA" --state-dir "$STATE" --budgets perf/budgets.json --sizes 100k,1000000 --runs 1 --cold-runs 1 --idle-secs 1 --drop-caches --json "$OUT/run.json" --md "$OUT/run.md" and storage-bench save --dir "$SAVE_DIR" --saves 200. Compare first query, query, catch-up, notes-save p50/p99, typing, and startup at 100k/1M beyond the dev-vs-dev spread. Every path is flat or better. The 1M reconcile must beat today's full path or the PR records the measured reason it does not.

Any regression beyond the control spread blocks qualification. Post commands, rates, all metric deltas, and the same-epoch control in PR comments. Never merge.

## Design after the 2026-11-07 generation-fence ruling P1b stays a shared, backend-only search change. Notes remains the current provider; Mail can use the same cursor and page API later. Notes search, save behavior, result order, and UI stay unchanged. The indexer owns the fence: it will not index a source older than a `notes.saved` receipt or checkpoint past that receipt. ### Fence and restart rules `History::install_executor` already returns the accepted per-note version. Append that value as `source_version` to each new `notes.saved` event (at most 37 serialized bytes including its comma). Publish the same version with the accepted Notes source and expose it to search. Existing events without the field are `unknown`: read the item again from a fresh pinned Notes generation, compare its source stamp, and validate the snapshot before checkpointing. When a journal page touches an item, compare every saved version in that page with the published source version. If the source is behind, drop the private page stage, keep the previous cursor, and park the entry. Subscribe to the existing `MetaDb.populated` watch before pinning the provider snapshot; its publication notification retries the same page without a polling loop. If the watch produces no notification, allow five bounded exponential-backoff rereads (10, 20, 40, 80, 160 ms), then wait on the watch. A still-pending entry never advances the cursor. Every commit covers a contiguous run of verified entries, with documents, use scores, and the page token in one SQLite transaction. On startup, validate the cursor and re-read the final journal page before trusting it. Store that page's starting token beside each new cursor. For an older index without a starting token, scan forward in 256-entry pages to locate the final page; memory stays bounded. If re-verification finds a source behind its receipt, rebuild from a fresh snapshot and keep the old cursor untrusted until a complete verified publish succeeds. A missing or expired cursor does a full paged reconcile. Before publishing its head token, scan the retained journal through that head and stage the latest per-item fence in SQLite's temporary file. Compare each provider page against those fences; a pending source drops the whole private reconcile. A restored or truncated log is detected only through the engine contract: P1b uses `Engine::position` and `Engine::journal`; #928 remains the only owner of signed token proof and high-water restore detection. P1b does not read the high-water file or add/parse token formats. If #928 lands first, rebase and keep this API boundary. ### Sizes and cost | Work | Bound | Cost | |---|---:|---| | Incremental catch-up | 256 journal entries; 1,024 related IDs; 64 document payloads | `O(K log K + R + D)`; memory `O(256 + R + 64)` | | Cursor-tail recheck | 256 entries; one provider page | `O(256 + D)` time and memory, except one-time legacy cursor lookup `O(K)` time in 256-entry pages and `O(256)` memory | | Full reconcile | 256 provider rows; 64 document payloads; 512 compare metadata records; fence state in SQLite `temp_store=FILE`, 2,000 KiB writer cache | `O(K + N log M + M)` for `K` journal entries, `N` provider rows, `M` indexed rows; RAM independent of Home size; temp disk `O(S)` for changed rows and per-item fences | | Version metadata | ≤37 serialized bytes per event and accepted-source row | At most 37 MB raw for 1M fully versioned rows, before SQLite page overhead; no extra fsync or save-path read | No page uses `OFFSET` or retains a Home-sized vector. The version is returned by the existing history install; Notes does no extra work on the notes-save path beyond encoding the appended field. ### Failure table | Failure | Effect | Detection | Recovery | Test | |---|---|---|---|---| | `notes.saved` is visible before source publication | The old source cannot enter the index and the cursor stays before the receipt | Published source version is below the event version | Drop the page, wait on the existing publication watch, retry the same page | Real Notes executor delayed-publication race, incremental and missing-index cases | | Crash after receipt, before source publication | Receipt remains; cursor cannot pass it | Restart replays the durable executor record and rechecks the receipt version | Recovery publishes source; index resumes from the prior cursor | Crash/restart between journal receipt and publication | | Crash or kill at a page boundary | Private stage is discarded; committed documents and cursor agree | Reopen index and inspect the saved cursor | Replay the uncommitted page | `kill_restart_at_every_provider_page_boundary_converges` | | Old build already checkpointed a raced receipt | Its cursor is treated as untrusted until the tail page is checked | Startup re-reads the last page and compares source version/stamp | Reconcile from a fresh generation and publish a verified cursor | Legacy cursor restart regression | | Cursor came from a restored older backup | Engine cannot prove it against the restored log | `Engine::position` returns `SyncTokenExpired` | Full paged reconcile; never construct a token locally | Restored-backup cursor test; #928 owns token-proof coverage | | Cursor is ahead or behind | Ahead/expired cursor cannot be used; retained behind cursor needs only its tail pages | `position` result plus page sequence checks | Full reconcile if expired; otherwise apply bounded pages | `ahead_cursor_reindexes_from_current_head`, `valid_behind_cursor_uses_pages_without_listing` | | Save log is truncated or index is missing | No trusted checkpoint remains | `position`/page read expires, or cursor is absent | Full paged reconcile with disk-backed fences and one atomic publish | `truncated_log_reindexes_from_pages`, `missing_index_rebuilds_in_pages` | | Publication notification is absent or the snapshot changes | No unverified page becomes visible | Bounded fallback retry; provider snapshot validation | Retry after a notification/backoff or discard and start a fresh reconcile | Watch and snapshot-change cases in the generation property test | ### Tests - Real Notes race: pause `EventRecorder::record_once` after the durable receipt and before it returns to `BatchPublished`; run incremental catch-up and missing-index reconcile; prove neither indexes the old text nor passes the cursor; release publication, restart the indexer, and prove the new text is indexed. - Crash before publication: stop after the receipt, reopen Notes so executor recovery replays it, and prove the cursor never skips the entry. - Reopen after every journal/provider page boundary and compare the final index to a clean build; include a legacy cursor with no page-start token. - Property test over save versions, delayed/reordered publication notifications, duplicate touches, arbitrary page cuts, and restarts; assert one indexed row per item, latest text, and a cursor no later than the contiguous verified prefix. - Restored-older-backup cursor, cursor ahead/behind, truncated log, missing index, and 1M-entry paged reconcile with a 256-row maximum and bounded memory. ### Gates | Gate | Exact command or run | Pass condition | |---|---|---| | Format | `CARGO_BUILD_JOBS=4 ~/bin/slot /nix/var/nix/profiles/default/bin/nix develop -c bash -c 'cd /home/claude/Developer/Worktrees/mail-p1b-test-e0fc && cargo fmt --check'` | Pass | | Clippy | `CARGO_BUILD_JOBS=4 ~/bin/slot /nix/var/nix/profiles/default/bin/nix develop -c bash -c 'cd /home/claude/Developer/Worktrees/mail-p1b-test-e0fc && cargo clippy --workspace --all-targets -- -D warnings'` | Pass | | Rust tests | `CARGO_BUILD_JOBS=4 ~/bin/slot /nix/var/nix/profiles/default/bin/nix develop -c bash -c 'cd /home/claude/Developer/Worktrees/mail-p1b-test-e0fc && cargo test --workspace --no-fail-fast'` | Pass | | Crash-typing flake | `cargo test -p calternal --test notes_crash_typing kill_nine_while_people_type_loses_and_doubles_nothing -- --exact` | 20 candidate and 20 dev runs interleaved; report both pass rates | | Chromium Notes search/save, 1440 and 390 | `~/bin/slot /nix/var/nix/profiles/default/bin/nix develop -c bash -c 'cd /home/claude/Developer/Worktrees/mail-p1b-test-e0fc/apps/web && CALTERNAL_BIN=/home/claude/Developer/Worktrees/mail-p1b-test-e0fc/target/debug/calternal bunx playwright test e2e/search-palette.spec.ts e2e/search-corpus.spec.ts e2e/editor.spec.ts e2e/list-catch-up.spec.ts --project=chromium-1440 --project=chromium-390 --workers=1'` | Pass | | WebKit Notes search/save, 1440 and 390 | `~/bin/slot /nix/var/nix/profiles/default/bin/nix develop -c bash -c 'cd /home/cua/Developer/Worktrees/mail-p1b-webkit-b822165c6/apps/web && CALTERNAL_BIN=/home/cua/Developer/Worktrees/mail-p1b-webkit-b822165c6/target/debug/calternal bunx playwright test e2e/search-palette.spec.ts e2e/search-corpus.spec.ts e2e/editor.spec.ts e2e/list-catch-up.spec.ts --project=webkit-1440 --project=webkit-390 --workers=1'` | Pass | | Perf | Hold `~/bin/perf-lock` on perf-01 then perf-02 for the complete same-epoch session. Run dev-vs-dev control and candidate/dev ABAB ×5; each sample runs `calternal-perf --bin "$SERVER_BIN" --work "$WORK" --data-dir "$DATA" --state-dir "$STATE" --budgets perf/budgets.json --sizes 100k,1000000 --runs 1 --cold-runs 1 --idle-secs 1 --drop-caches --json "$OUT/run.json" --md "$OUT/run.md"` and `storage-bench save --dir "$SAVE_DIR" --saves 200`. | Compare first query, query, catch-up, notes-save p50/p99, typing, and startup at 100k/1M beyond the dev-vs-dev spread. Every path is flat or better. The 1M reconcile must beat today's full path or the PR records the measured reason it does not. | Any regression beyond the control spread blocks qualification. Post commands, rates, all metric deltas, and the same-epoch control in PR comments. Never merge.
docs(search): design paged save-log reconcile
Some checks failed
CI / server (pull_request) Has been cancelled
CI / rust (pull_request) Has been cancelled
CI / web (pull_request) Has been cancelled
Markdown extraction contract / markdown-native-contract (pull_request) Has been cancelled
Markdown extraction contract / markdown-editor-contract (pull_request) Has been cancelled
d875cc809f
Author
Owner

Qualification snapshot for head e2d6c98c891b332c3a92fac5aac9a791682a960b

This PR is not merge-ready. The source-publication race recorded at the top invalidates the cursor correctness assumption. Stop perf qualification until the publication fence is redesigned and its regression tests pass.

Rust gates ran on the candidate source at c090d830; later commits through this head are documentation-only. Browser gates ran on source at b71f8cd8; later commits through this head are documentation-only.

Gate Exact command Result
Format cargo fmt --check PASS
Clippy cargo clippy --workspace --all-targets -- -D warnings PASS
Workspace tests cargo test --workspace --no-fail-fast PASS, including doctests
Chromium Notes search/save, 1440 + 390 ~/bin/slot /nix/var/nix/profiles/default/bin/nix develop -c bash -c 'cd /home/claude/Developer/Worktrees/mail-p1b-test-e0fc/apps/web && CALTERNAL_BIN=/home/claude/Developer/Worktrees/mail-p1b-test-e0fc/target/debug/calternal bunx playwright test e2e/search-palette.spec.ts e2e/search-corpus.spec.ts e2e/editor.spec.ts e2e/list-catch-up.spec.ts --project=chromium-1440 --project=chromium-390 --workers=1 --output=/home/claude/mail-p1b-chromium-final-b71f-output --reporter=line' PASS: 55 passed, 19 skipped
WebKit Notes search/save, 1440 + 390 ~/bin/slot /nix/var/nix/profiles/default/bin/nix develop -c bash -c 'cd /home/cua/Developer/Worktrees/mail-p1b-webkit-b822165c6/apps/web && CALTERNAL_BIN=/home/cua/Developer/Worktrees/mail-p1b-webkit-b822165c6/target/debug/calternal bunx playwright test e2e/search-palette.spec.ts e2e/search-corpus.spec.ts e2e/editor.spec.ts e2e/list-catch-up.spec.ts --project=webkit-1440 --project=webkit-390 --workers=1 --output=/home/cua/mail-p1b-webkit-final-20261107-output --reporter=line' PASS: 48 passed, 26 skipped
Perf ABAB ×5 plus same-epoch dev-vs-dev control, both shared locks calternal-perf --bin "$SERVER_BIN" --work "$WORK" --data-dir "$DATA" --state-dir "$STATE" --budgets perf/budgets.json --sizes 100k,1000000 --runs 1 --cold-runs 1 --idle-secs 1 --drop-caches --json "$OUT/run.json" --md "$OUT/run.md"; each sample also runs storage-bench save --dir "$SAVE_DIR" --saves 200 NOT RUN; stop until the cursor/source-publication design is fixed
Perf metric 100k 1M Status
Notes search latency (search.first_query, search.query, search.catch_up) Not measured Not measured Pending
notes_save p50 / p99 Not measured Not measured Pending
Typing (load.typing_cpu_pct) Not measured Not measured Pending
Startup (startup.healthz, cold.startup) Not measured Not measured Pending
1M reconcile vs current full path — Not measured Pending

No perf conclusion is claimed. The required same-epoch ABAB ×5 and dev-vs-dev control must run after the design correction, with both locks held together.

Additional test observation: an earlier standalone candidate run of kill_nine_while_people_type_loses_and_doubles_nothing timed out after 30 seconds with a retryable save response. Ten later candidate repetitions passed, and the subsequent workspace suite passed. No equivalent dev/train repeat comparison is recorded, so this observation is not cleared by the repository's flaky-test rule.

## Qualification snapshot for head `e2d6c98c891b332c3a92fac5aac9a791682a960b` This PR is not merge-ready. The source-publication race recorded at the top invalidates the cursor correctness assumption. Stop perf qualification until the publication fence is redesigned and its regression tests pass. Rust gates ran on the candidate source at `c090d830`; later commits through this head are documentation-only. Browser gates ran on source at `b71f8cd8`; later commits through this head are documentation-only. | Gate | Exact command | Result | |---|---|---| | Format | `cargo fmt --check` | PASS | | Clippy | `cargo clippy --workspace --all-targets -- -D warnings` | PASS | | Workspace tests | `cargo test --workspace --no-fail-fast` | PASS, including doctests | | Chromium Notes search/save, 1440 + 390 | `~/bin/slot /nix/var/nix/profiles/default/bin/nix develop -c bash -c 'cd /home/claude/Developer/Worktrees/mail-p1b-test-e0fc/apps/web && CALTERNAL_BIN=/home/claude/Developer/Worktrees/mail-p1b-test-e0fc/target/debug/calternal bunx playwright test e2e/search-palette.spec.ts e2e/search-corpus.spec.ts e2e/editor.spec.ts e2e/list-catch-up.spec.ts --project=chromium-1440 --project=chromium-390 --workers=1 --output=/home/claude/mail-p1b-chromium-final-b71f-output --reporter=line'` | PASS: 55 passed, 19 skipped | | WebKit Notes search/save, 1440 + 390 | `~/bin/slot /nix/var/nix/profiles/default/bin/nix develop -c bash -c 'cd /home/cua/Developer/Worktrees/mail-p1b-webkit-b822165c6/apps/web && CALTERNAL_BIN=/home/cua/Developer/Worktrees/mail-p1b-webkit-b822165c6/target/debug/calternal bunx playwright test e2e/search-palette.spec.ts e2e/search-corpus.spec.ts e2e/editor.spec.ts e2e/list-catch-up.spec.ts --project=webkit-1440 --project=webkit-390 --workers=1 --output=/home/cua/mail-p1b-webkit-final-20261107-output --reporter=line'` | PASS: 48 passed, 26 skipped | | Perf ABAB ×5 plus same-epoch dev-vs-dev control, both shared locks | `calternal-perf --bin "$SERVER_BIN" --work "$WORK" --data-dir "$DATA" --state-dir "$STATE" --budgets perf/budgets.json --sizes 100k,1000000 --runs 1 --cold-runs 1 --idle-secs 1 --drop-caches --json "$OUT/run.json" --md "$OUT/run.md"`; each sample also runs `storage-bench save --dir "$SAVE_DIR" --saves 200` | NOT RUN; stop until the cursor/source-publication design is fixed | | Perf metric | 100k | 1M | Status | |---|---:|---:|---| | Notes search latency (`search.first_query`, `search.query`, `search.catch_up`) | Not measured | Not measured | Pending | | `notes_save` p50 / p99 | Not measured | Not measured | Pending | | Typing (`load.typing_cpu_pct`) | Not measured | Not measured | Pending | | Startup (`startup.healthz`, `cold.startup`) | Not measured | Not measured | Pending | | 1M reconcile vs current full path | — | Not measured | Pending | No perf conclusion is claimed. The required same-epoch ABAB ×5 and dev-vs-dev control must run after the design correction, with both locks held together. Additional test observation: an earlier standalone candidate run of `kill_nine_while_people_type_loses_and_doubles_nothing` timed out after 30 seconds with a retryable save response. Ten later candidate repetitions passed, and the subsequent workspace suite passed. No equivalent dev/train repeat comparison is recorded, so this observation is not cleared by the repository's flaky-test rule.
Author
Owner

Superseded by #1011. Forgejo retained this PR's original design-only diff after the implementation branch advanced; #1011 points at the same head 000873f49e and exposes all 28 changed files. The design and review history remain here for reference.

Superseded by https://git.kayg.org/kayg/calternal/pulls/1011. Forgejo retained this PR's original design-only diff after the implementation branch advanced; #1011 points at the same head 000873f49e174c80961a3504ad95a2762f54f4d0 and exposes all 28 changed files. The design and review history remain here for reference.
kayg closed this pull request 2026-11-07 18:46:03 +00:00
Some checks failed
CI / server (pull_request) Has been cancelled
CI / rust (pull_request) Has been cancelled
CI / web (pull_request) Has been cancelled
Markdown extraction contract / markdown-native-contract (pull_request) Has been cancelled
Markdown extraction contract / markdown-editor-contract (pull_request) Has been cancelled

Pull request closed

Sign in to join this conversation.
No reviewers
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!995
No description provided.