Startup: serve available data with an incomplete flag instead of 503 #1605

Open
opened 2026-10-12 07:08:48 +00:00 by kayg · 13 comments
Owner

While indexes are still building after a start, Calendar and Notes answer 200 with what exists plus an incomplete flag; the app shows a quiet Still updating… status and fills in when ready (lazy, max performance). Replaces the 503 + Retry-After for these data routes. Decision Q-ST1 (a).

Owner decision 2026-10-12 (answers to the UI question list). Follow CLAUDE.md, docs/DESIGN.md (update the relevant section to record this decision) and the shared UI brief.

While indexes are still building after a start, Calendar and Notes answer 200 with what exists plus an incomplete flag; the app shows a quiet Still updating… status and fills in when ready (lazy, max performance). Replaces the 503 + Retry-After for these data routes. Decision Q-ST1 (a). Owner decision 2026-10-12 (answers to the UI question list). Follow CLAUDE.md, docs/DESIGN.md (update the relevant section to record this decision) and the shared UI brief.
Author
Owner

Starting #1605 on branch job/partial-1605, based on dev at 8e840962979acd2bdf58141888618ab5560f728e. I am mapping the Calendar and Notes startup read routes and their shared readiness gate before implementing the incomplete-response lifecycle.

Starting #1605 on branch `job/partial-1605`, based on `dev` at `8e840962979acd2bdf58141888618ab5560f728e`. I am mapping the Calendar and Notes startup read routes and their shared readiness gate before implementing the incomplete-response lifecycle.
Author
Owner

Finding: the live router is installed and /readyz becomes successful before the deferred startup pipeline runs. StartupProgress reports background phases, but the Calendar and Notes data envelopes do not carry that state. Their Index-backed reads already use the read pool, while generic SQLite transient failures remain separate 503 responses. I will add a cheap shared startup-projection flag to the existing Calendar range/item/year and Notes list envelopes; the existing Notes and Files event feeds will trigger refreshes after reconciliation. Writes and the unrelated Search/collaboration contracts stay unchanged.

Finding: the live router is installed and `/readyz` becomes successful before the deferred startup pipeline runs. `StartupProgress` reports background phases, but the Calendar and Notes data envelopes do not carry that state. Their Index-backed reads already use the read pool, while generic SQLite transient failures remain separate 503 responses. I will add a cheap shared startup-projection flag to the existing Calendar range/item/year and Notes list envelopes; the existing Notes and Files event feeds will trigger refreshes after reconciliation. Writes and the unrelated Search/collaboration contracts stay unchanged.
Author
Owner

Finding: cargo test -p calternal-server ran 279 tests successfully and 12 were ignored, but wire::tests::live_apps_run_in_separate_processes failed in its nested full_app_setup_session_config_and_backup case. The five-second wait for the existing User deletion worker expired at crates/calternal-server/src/wire.rs:12114-12123; the failure was a timeout, not an assertion or response mismatch. The direct live_router_install_marks_health_ready_before_background_work test passed. I kept the existing timing expectation unchanged and will include the exact gate output in the final report.

Finding: `cargo test -p calternal-server` ran 279 tests successfully and 12 were ignored, but `wire::tests::live_apps_run_in_separate_processes` failed in its nested `full_app_setup_session_config_and_backup` case. The five-second wait for the existing User deletion worker expired at `crates/calternal-server/src/wire.rs:12114-12123`; the failure was a timeout, not an assertion or response mismatch. The direct `live_router_install_marks_health_ready_before_background_work` test passed. I kept the existing timing expectation unchanged and will include the exact gate output in the final report.
Author
Owner

Performance guard finding: scripts/perf-lint --check first reported two unresolved call sites for the existing calternal_db::is_sqlite_transient classifier after the Calendar and Notes routes began using it for startup partial reads. I added one exact pure call binding backed by saturated_or_closed_sqlite_pools_are_transient_service_errors; the guard now reports perf-lint: PASS; 0 violations; 20350 scoped exceptions. I refreshed exact live pins and removed 23 resolved/stale exception sites, lowering the ratchet by 23; no ratchet count increased.

Performance guard finding: `scripts/perf-lint --check` first reported two unresolved call sites for the existing `calternal_db::is_sqlite_transient` classifier after the Calendar and Notes routes began using it for startup partial reads. I added one exact `pure` call binding backed by `saturated_or_closed_sqlite_pools_are_transient_service_errors`; the guard now reports `perf-lint: PASS; 0 violations; 20350 scoped exceptions`. I refreshed exact live pins and removed 23 resolved/stale exception sites, lowering the ratchet by 23; no ratchet count increased.
Author
Owner

Calendar multi-Home read finding: if a transient SQLite error occurs after an earlier owner chunk returns rows, those rows are not a globally ordered prefix. Returning a keyset cursor could skip rows from later Homes during the next page. The partial response now keeps available rows, omits the cursor, and relies on the bounded completion refetch. The Calendar crate passed clippy and its tests after this fix, including the new Events list startup lifecycle test for whole-list and dated reads.

Calendar multi-Home read finding: if a transient SQLite error occurs after an earlier owner chunk returns rows, those rows are not a globally ordered prefix. Returning a keyset cursor could skip rows from later Homes during the next page. The partial response now keeps available rows, omits the cursor, and relies on the bounded completion refetch. The Calendar crate passed clippy and its tests after this fix, including the new Events list startup lifecycle test for whole-list and dated reads.
Author
Owner

Startup verification passed on the candidate server. After /readyz, all five affected Calendar/Notes read routes returned 200 with incomplete: true; they later cleared to false. The 40-Note/40-File startup storm converged to every seeded and acknowledged row, and the second User retained private Notes without cross-User Calendar leakage. The campaign also completed its existing Files/Notes/Money and Search checks.

Local startup profile (load average 13.27, shared host):

  • Notes list: p50/p95 12.84/17.97 ms; baseline 1.3/3.1 ms; peak RSS 244,699,136 B; average CPU 164.56%.
  • Calendar range: 22.95/33.20 ms; baseline 1.7/4.0 ms; peak RSS 253,829,120 B; average CPU 124.54%.
  • Calendar items: 24.20/34.33 ms; no existing baseline; peak RSS 263,364,608 B; average CPU 108.29%.
  • Calendar year: 26.85/44.00 ms; no existing baseline; peak RSS 270,024,704 B; average CPU 94.91%.
  • Calendar Events: 11.21/17.97 ms; baseline 1.4/3.9 ms; peak RSS 274,419,712 B; average CPU 109.05%.

Each route also completed a 16-request burst at concurrency 8. These numbers are local measurements during startup work on a busy host; they are not a quiet-host latency comparison. I am proceeding with the production screenshot set.

Startup verification passed on the candidate server. After `/readyz`, all five affected Calendar/Notes read routes returned 200 with `incomplete: true`; they later cleared to false. The 40-Note/40-File startup storm converged to every seeded and acknowledged row, and the second User retained private Notes without cross-User Calendar leakage. The campaign also completed its existing Files/Notes/Money and Search checks. Local startup profile (load average 13.27, shared host): - Notes list: p50/p95 12.84/17.97 ms; baseline 1.3/3.1 ms; peak RSS 244,699,136 B; average CPU 164.56%. - Calendar range: 22.95/33.20 ms; baseline 1.7/4.0 ms; peak RSS 253,829,120 B; average CPU 124.54%. - Calendar items: 24.20/34.33 ms; no existing baseline; peak RSS 263,364,608 B; average CPU 108.29%. - Calendar year: 26.85/44.00 ms; no existing baseline; peak RSS 270,024,704 B; average CPU 94.91%. - Calendar Events: 11.21/17.97 ms; baseline 1.4/3.9 ms; peak RSS 274,419,712 B; average CPU 109.05%. Each route also completed a 16-request burst at concurrency 8. These numbers are local measurements during startup work on a busy host; they are not a quiet-host latency comparison. I am proceeding with the production screenshot set.
Author
Owner

Forgejo #1605 — complete

Branch: job/partial-1605
Head: 51155968a9a7c40b430a13db72b84b6f27db8d30

Built

  • Calendar range, year, items and Events reads, plus the Notes list, return HTTP 200 with the shared incomplete response field while startup projections can still add rows. A transient Index read failure during that window returns the rows already available or an empty envelope with incomplete: true; it does not make the request wait for startup work. Writes keep their existing safety checks.
  • The server clears the flag only after each ordered startup projection pass succeeds. A failed pass keeps it true while the existing repair or retry proceeds.
  • Calendar and Notes use the shared StatusPill for an accessible, quiet “Still updating…” status. Calendar retains the flag with its cached snapshot and refreshes from the Files stream; Notes coalesces Files stream refreshes. The flag clears when a complete response arrives.
  • OpenAPI, generated packages/api-client, and DESIGN §59 document the contract. Search and collaboration keep their existing contracts because they do not share this readiness gate.
  • Calendar omits a page cursor when a failed owner chunk could leave a non-global row prefix. The next bounded refresh then replaces that partial page without skipping another Home’s rows.

Files for #1605: crates/calternal-plugin/src/lib.rs; crates/calternal-server/src/main.rs, crates/calternal-server/src/wire.rs; crates/plugins/calendar/src/items.rs, routes.rs, view.rs; crates/plugins/notes/src/lib.rs; apps/web/src/lib/calendar/data.ts, data.test.ts; apps/web/src/routes/calendar/[view]/[date]/+page.svelte, apps/web/src/routes/notes/+page.svelte; contracts/openapi.json; packages/api-client/src/generated.ts; docs/DESIGN.md; tests/adversarial/merge40_startup.py; bench/startup-projections-1605.py; and the exact performance ledgers.

Checks

cargo fmt --check
(exit 0; no output)

cargo clippy -p calternal-plugin --all-targets -- -D warnings
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.60s
cargo test -p calternal-plugin
test result: ok. 64 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 10.05s

cargo clippy -p calternal-plugin-calendar --all-targets -- -D warnings
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.78s
cargo test -p calternal-plugin-calendar
test result: ok. 112 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 15.82s
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.39s
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.11s

cargo clippy -p calternal-plugin-notes --all-targets -- -D warnings
Finished `dev` profile [unoptimized + debuginfo] target(s) in 43.08s
cargo test -p calternal-plugin-notes
test result: ok. 343 passed; 0 failed; 5 ignored; 0 measured; 0 filtered out; finished in 175.23s
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.08s

cargo clippy -p calternal-server --all-targets -- -D warnings
Finished `dev` profile [unoptimized + debuginfo] target(s) in 1m 01s
cargo test -p calternal-server
test result: ok. 280 passed; 0 failed; 12 ignored; 0 measured; 0 filtered out; finished in 113.49s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 27.37s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.17s

bun run check
perf-lint: PASS; 0 violations; 20329 scoped exceptions
svelte-check found 0 errors and 0 warnings

bunx vitest run src/lib/calendar/data.test.ts --maxWorkers=2
Test Files  1 passed (1)
Tests  50 passed (50)

scripts/perf-lint --check
perf-lint: PASS; 0 violations; 20329 scoped exceptions

The production web build completed and wrote apps/web/build. cargo clean completed with Removed 23407 files, 21.0GiB total; apps/web/build and .svelte-kit/output were then removed.

The startup storm probe passed: PASS startup storm: ready=3.579s; writer handover=one-at-a-time (candidate readyz held at 503); acknowledged Files/Notes/Money=8/8/8; Search reads=16; cross-User startup isolation=1/1; documented 503 responses=96; cleanup retry=60.101s with 24 concurrent Files/Notes/Money batches; post-recovery injection phase=connect-index; Notes 0039/0040 and Calendar 0007 applied. The 96 documented 503 responses are existing unrelated startup traffic; each affected Calendar and Notes read after /readyz returned 200, reported true then false, converged to seeded data, and kept Users isolated.

Performance

The request adds one O(1) atomic status read and one response boolean. It adds no startup wait or database work. Existing route bounds remain in force: Notes pages up to 100 rows, Calendar Items up to 1,000 rows, Calendar ranges up to 42 days, and a bounded year view.

The local profile used 40 Notes, 40 Files, 50 serial samples and 16-request bursts at concurrency 8. The host was busy (load averages 13.2724609375, 18.57666015625, 17.078125), so these measurements are not quiet-host comparisons.

Route Baseline p50/p95 ms Measured p50/p95 ms Average CPU Peak RSS
Notes 1.3 / 3.1 12.84 / 17.97 164.56% 244,699,136 B
Calendar range 1.7 / 4.0 22.95 / 33.20 124.54% 253,829,120 B
Calendar Items no baseline 24.20 / 34.33 108.29% 263,364,608 B
Calendar year no baseline 26.85 / 44.00 94.91% 270,024,704 B
Calendar Events 1.4 / 3.9 11.21 / 17.97 109.05% 274,419,712 B

The merged performance ledger has 20,329 scoped exceptions versus 20,352 on origin/dev; no rule cap increased. Calendar migration numbering is still 0007 and Notes is 0040 on origin/dev; this change adds no migration.

UX gaps and visual evidence

The shared status remains screen-reader announced and does not add a spinner or reserve layout space. The 12 fresh, post-merge production captures use Chromium with macOS platform emulation, real API data, and no pointer hover. They are in artifacts/partial-1605/: Calendar and Notes at 390, 820 and 1440 px, in light and dark themes.

Forgejo rejected the fresh attachment POST with HTTP 401 without accepting an upload. The issue retains the earlier eight attached phone and desktop captures; the fresh tablet captures and refreshed set remain local in the artifact directory.

Existing attached Calendar evidence: 390 light, 390 dark, 1440 light, 1440 dark.
Existing attached Notes evidence: 390 light, 390 dark, 1440 light, 1440 dark.

UX gaps closed: pointer, touch and keyboard users get the same status; assistive technology gets a polite live status; data stays visible while the Index catches up; refreshes happen from the existing stream.
UX gaps left: the refreshed captures could not be uploaded through the Forgejo attachment API in this session. No implementation gap remains known.

Decisions and merge-round work

  • Owner decision Q-ST1 (a) sets the response field name incomplete and the partial-read behavior. Where DESIGN had no client refresh detail, I used the existing Files event stream and coalesced Notes refreshes by 300 ms.
  • The origin/dev sync is recorded in merge commit 51155968a; no push, deploy or merge to dev was done.
  • Full web tests are deferred by the shared verification policy. For the merge round, run cd apps/web && bun run test -- --maxWorkers=2 and confirm the full web suite passes.
# Forgejo #1605 — complete **Branch:** `job/partial-1605` **Head:** `51155968a9a7c40b430a13db72b84b6f27db8d30` ## Built - Calendar range, year, items and Events reads, plus the Notes list, return HTTP 200 with the shared `incomplete` response field while startup projections can still add rows. A transient Index read failure during that window returns the rows already available or an empty envelope with `incomplete: true`; it does not make the request wait for startup work. Writes keep their existing safety checks. - The server clears the flag only after each ordered startup projection pass succeeds. A failed pass keeps it true while the existing repair or retry proceeds. - Calendar and Notes use the shared `StatusPill` for an accessible, quiet “Still updating…” status. Calendar retains the flag with its cached snapshot and refreshes from the Files stream; Notes coalesces Files stream refreshes. The flag clears when a complete response arrives. - OpenAPI, generated `packages/api-client`, and DESIGN §59 document the contract. Search and collaboration keep their existing contracts because they do not share this readiness gate. - Calendar omits a page cursor when a failed owner chunk could leave a non-global row prefix. The next bounded refresh then replaces that partial page without skipping another Home’s rows. **Files for #1605:** `crates/calternal-plugin/src/lib.rs`; `crates/calternal-server/src/main.rs`, `crates/calternal-server/src/wire.rs`; `crates/plugins/calendar/src/items.rs`, `routes.rs`, `view.rs`; `crates/plugins/notes/src/lib.rs`; `apps/web/src/lib/calendar/data.ts`, `data.test.ts`; `apps/web/src/routes/calendar/[view]/[date]/+page.svelte`, `apps/web/src/routes/notes/+page.svelte`; `contracts/openapi.json`; `packages/api-client/src/generated.ts`; `docs/DESIGN.md`; `tests/adversarial/merge40_startup.py`; `bench/startup-projections-1605.py`; and the exact performance ledgers. ## Checks ```text cargo fmt --check (exit 0; no output) cargo clippy -p calternal-plugin --all-targets -- -D warnings Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.60s cargo test -p calternal-plugin test result: ok. 64 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 10.05s cargo clippy -p calternal-plugin-calendar --all-targets -- -D warnings Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.78s cargo test -p calternal-plugin-calendar test result: ok. 112 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 15.82s test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.39s test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.11s cargo clippy -p calternal-plugin-notes --all-targets -- -D warnings Finished `dev` profile [unoptimized + debuginfo] target(s) in 43.08s cargo test -p calternal-plugin-notes test result: ok. 343 passed; 0 failed; 5 ignored; 0 measured; 0 filtered out; finished in 175.23s test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.08s cargo clippy -p calternal-server --all-targets -- -D warnings Finished `dev` profile [unoptimized + debuginfo] target(s) in 1m 01s cargo test -p calternal-server test result: ok. 280 passed; 0 failed; 12 ignored; 0 measured; 0 filtered out; finished in 113.49s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 27.37s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.17s bun run check perf-lint: PASS; 0 violations; 20329 scoped exceptions svelte-check found 0 errors and 0 warnings bunx vitest run src/lib/calendar/data.test.ts --maxWorkers=2 Test Files 1 passed (1) Tests 50 passed (50) scripts/perf-lint --check perf-lint: PASS; 0 violations; 20329 scoped exceptions ``` The production web build completed and wrote `apps/web/build`. `cargo clean` completed with `Removed 23407 files, 21.0GiB total`; `apps/web/build` and `.svelte-kit/output` were then removed. The startup storm probe passed: `PASS startup storm: ready=3.579s; writer handover=one-at-a-time (candidate readyz held at 503); acknowledged Files/Notes/Money=8/8/8; Search reads=16; cross-User startup isolation=1/1; documented 503 responses=96; cleanup retry=60.101s with 24 concurrent Files/Notes/Money batches; post-recovery injection phase=connect-index; Notes 0039/0040 and Calendar 0007 applied`. The 96 documented 503 responses are existing unrelated startup traffic; each affected Calendar and Notes read after `/readyz` returned 200, reported true then false, converged to seeded data, and kept Users isolated. ## Performance The request adds one O(1) atomic status read and one response boolean. It adds no startup wait or database work. Existing route bounds remain in force: Notes pages up to 100 rows, Calendar Items up to 1,000 rows, Calendar ranges up to 42 days, and a bounded year view. The local profile used 40 Notes, 40 Files, 50 serial samples and 16-request bursts at concurrency 8. The host was busy (load averages `13.2724609375, 18.57666015625, 17.078125`), so these measurements are not quiet-host comparisons. | Route | Baseline p50/p95 ms | Measured p50/p95 ms | Average CPU | Peak RSS | | --- | ---: | ---: | ---: | ---: | | Notes | 1.3 / 3.1 | 12.84 / 17.97 | 164.56% | 244,699,136 B | | Calendar range | 1.7 / 4.0 | 22.95 / 33.20 | 124.54% | 253,829,120 B | | Calendar Items | no baseline | 24.20 / 34.33 | 108.29% | 263,364,608 B | | Calendar year | no baseline | 26.85 / 44.00 | 94.91% | 270,024,704 B | | Calendar Events | 1.4 / 3.9 | 11.21 / 17.97 | 109.05% | 274,419,712 B | The merged performance ledger has 20,329 scoped exceptions versus 20,352 on `origin/dev`; no rule cap increased. Calendar migration numbering is still 0007 and Notes is 0040 on `origin/dev`; this change adds no migration. ## UX gaps and visual evidence The shared status remains screen-reader announced and does not add a spinner or reserve layout space. The 12 fresh, post-merge production captures use Chromium with macOS platform emulation, real API data, and no pointer hover. They are in `artifacts/partial-1605/`: Calendar and Notes at 390, 820 and 1440 px, in light and dark themes. Forgejo rejected the fresh attachment POST with HTTP 401 without accepting an upload. The issue retains the earlier eight attached phone and desktop captures; the fresh tablet captures and refreshed set remain local in the artifact directory. Existing attached Calendar evidence: [390 light](https://git.kayg.org/attachments/47aba603-9eaf-40de-b0ca-bb613dc52e67), [390 dark](https://git.kayg.org/attachments/7e943932-2bcf-4439-af3a-ea729aeef80b), [1440 light](https://git.kayg.org/attachments/1fe126f2-43e2-473e-ab49-b5ed19fa8d16), [1440 dark](https://git.kayg.org/attachments/f4c8032b-4a1d-4443-8e95-6833d37e034b). Existing attached Notes evidence: [390 light](https://git.kayg.org/attachments/6d758efc-e778-4dea-a093-79f382fdf549), [390 dark](https://git.kayg.org/attachments/9bb0db6a-e4fd-424a-837e-a4a2cc94e87d), [1440 light](https://git.kayg.org/attachments/aac35ec3-28f5-4544-8dd5-dd49b122ff36), [1440 dark](https://git.kayg.org/attachments/60e56ae9-5b27-4e36-9895-f3cce26b48b4). **UX gaps closed:** pointer, touch and keyboard users get the same status; assistive technology gets a polite live status; data stays visible while the Index catches up; refreshes happen from the existing stream. **UX gaps left:** the refreshed captures could not be uploaded through the Forgejo attachment API in this session. No implementation gap remains known. ## Decisions and merge-round work - Owner decision Q-ST1 (a) sets the response field name `incomplete` and the partial-read behavior. Where DESIGN had no client refresh detail, I used the existing Files event stream and coalesced Notes refreshes by 300 ms. - The origin/dev sync is recorded in merge commit `51155968a`; no push, deploy or merge to `dev` was done. - Full web tests are deferred by the shared verification policy. For the merge round, run `cd apps/web && bun run test -- --maxWorkers=2` and confirm the full web suite passes.
Author
Owner

Starting verification on branch job/partial-1605 at 51155968a9a7c40b430a13db72b84b6f27db8d30. The branch fork point is 80bc0362a982797efae8bbf4ac353bfaa56f8c01; current origin/dev is a612c71a6a3eb1343feca043a75c0c12de073539. I am reviewing the five partial-read routes, startup flag lifetime, pagination and User scoping, then will run the locked perf A/B and the requested gates after the final origin/dev merge.

Starting verification on branch `job/partial-1605` at `51155968a9a7c40b430a13db72b84b6f27db8d30`. The branch fork point is `80bc0362a982797efae8bbf4ac353bfaa56f8c01`; current `origin/dev` is `a612c71a6a3eb1343feca043a75c0c12de073539`. I am reviewing the five partial-read routes, startup flag lifetime, pagination and User scoping, then will run the locked perf A/B and the requested gates after the final `origin/dev` merge.
Author
Owner

Correctness finding: crates/plugins/notes/src/lib.rs::list treated every 503 from note_summary as an Index startup miss. note_summary can return 503 for transient filesystem pressure (ApiError::from(calternal_fs::Error::Io)), while DESIGN §59 permits partial success only for transient Index reads. Under file descriptor or syscall pressure this could silently omit a Note and return incomplete: true. I am narrowing that fallback and adding a regression test.

Correctness finding: `crates/plugins/notes/src/lib.rs::list` treated every `503` from `note_summary` as an Index startup miss. `note_summary` can return `503` for transient filesystem pressure (`ApiError::from(calternal_fs::Error::Io)`), while DESIGN §59 permits partial success only for transient Index reads. Under file descriptor or syscall pressure this could silently omit a Note and return `incomplete: true`. I am narrowing that fallback and adding a regression test.
Author
Owner

Correctness finding: GET /api/v1/calendar/items reads active Share roots before its paged Index batches. calendar_share_roots distinguishes transient Index contention as FilesIndexReadError::Busy, but this route mapped every error to Failure::internal(). A busy Index therefore returned 500 during startup instead of a safe empty 200 with incomplete: true; after completion it also returned the wrong status. I am adding the startup fallback and a route-level regression test.

Correctness finding: `GET /api/v1/calendar/items` reads active Share roots before its paged Index batches. `calendar_share_roots` distinguishes transient Index contention as `FilesIndexReadError::Busy`, but this route mapped every error to `Failure::internal()`. A busy Index therefore returned `500` during startup instead of a safe empty `200` with `incomplete: true`; after completion it also returned the wrong status. I am adding the startup fallback and a route-level regression test.
Author
Owner

Calendar paging review found an incomplete-page race: the route returned a non-null next_cursor, and the web client followed it while startup could add earlier-sorted rows. Regression evidence: the Rust test returned incomplete: true with a cursor; the web test followed a fixed incomplete cursor for all 20 allowed pages and repeated agenda.txt. The first server fix also exposed a page-bound issue: the no-cursor branch returned limit + 1 rows. The route now truncates before cursor selection, returns no cursor for incomplete pages, and the web reader stops on incomplete: true. Focused checks pass: cargo fmt --check; cargo test -p calternal-plugin-calendar items_are_placed_in_time_order_and_paginated -- --nocapture; bunx vitest run src/lib/calendar/data.test.ts --maxWorkers=2 (51 tests passed).

Calendar paging review found an incomplete-page race: the route returned a non-null `next_cursor`, and the web client followed it while startup could add earlier-sorted rows. Regression evidence: the Rust test returned `incomplete: true` with a cursor; the web test followed a fixed incomplete cursor for all 20 allowed pages and repeated `agenda.txt`. The first server fix also exposed a page-bound issue: the no-cursor branch returned `limit + 1` rows. The route now truncates before cursor selection, returns no cursor for incomplete pages, and the web reader stops on `incomplete: true`. Focused checks pass: `cargo fmt --check`; `cargo test -p calternal-plugin-calendar items_are_placed_in_time_order_and_paginated -- --nocapture`; `bunx vitest run src/lib/calendar/data.test.ts --maxWorkers=2` (51 tests passed).
Author
Owner

Resuming verification on branch job/partial-1605 at 2f8fafc99b. Prior commits already fixed Notes Index-only fallback, Calendar share-root startup fallback, and incomplete-page cursors. Remaining: compare steady-state routes against current origin/dev and the named 51155968a candidate, finish lifecycle/User-isolation verification, then run final gates after the single origin/dev sync.

Resuming verification on branch job/partial-1605 at 2f8fafc99bff2078d1718274e10aaf4985be414c. Prior commits already fixed Notes Index-only fallback, Calendar share-root startup fallback, and incomplete-page cursors. Remaining: compare steady-state routes against current origin/dev and the named 51155968a candidate, finish lifecycle/User-isolation verification, then run final gates after the single origin/dev sync.
Author
Owner

Benchmark setup finding: the A/B helper polled /readyz, but the server exposes /healthz (crates/calternal-server/src/main.rs:339, 828, 1653); /readyz returned an empty 503 during setup and the helper attempted to decode it as JSON. No route samples were taken. I corrected the ignored benchmark helper to poll /healthz and will run the locked comparison once with the corrected harness.

Benchmark setup finding: the A/B helper polled /readyz, but the server exposes /healthz (crates/calternal-server/src/main.rs:339, 828, 1653); /readyz returned an empty 503 during setup and the helper attempted to decode it as JSON. No route samples were taken. I corrected the ignored benchmark helper to poll /healthz and will run the locked comparison once with the corrected harness.
Commenting is not possible because the repository is archived.
No labels
No milestone
No project
No assignees
1 participant
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-v0#1605
No description provided.