Notes: duplicate parsed Task anchors return 500 after the Home write #531

Closed
opened 2026-09-30 15:22:25 +00:00 by kayg · 4 comments
Owner

Notes API returns 500 after writing a checklist with colliding parsed anchors

Found on the real local #393 performance server, source 56eb9ec003. A Notes API POST with 96 recurring checkbox lines and distinct dashed block anchors returned 500 (Notes operation failed). The Home file was written; its Task Index update failed. The watcher then logged repeated Markdown reconciliation failures. No process crash or cross-User access was observed.

Evidence: Task line.rs accepts alphanumeric anchor characters only, so all anchors with the same dashed prefix parse as the same identity. tasks_store inserts the resulting duplicate Task IDs into task_items. The API writes the Home before that Index failure. The current Notes API does not reject the duplicate projected identities before its write.

The benchmark now uses distinct alphanumeric anchors. This fixes the benchmark input; it does not fix the server's 500 or partial-write behavior. Needs input validation before writes and a regression for duplicate parsed Task identities, with preservation or explicit handling of dashed anchors according to the block-ID grammar. No existing test expectation was changed. Raw local evidence remains in ignored artifacts/mac393-round3/perf-lab-v2 and perf-run-v2.log. #393 remains NO-GO while acceptance is unfinished.

# Notes API returns 500 after writing a checklist with colliding parsed anchors Found on the real local #393 performance server, source 56eb9ec003da27466cc7812ff60f8a35fdc2fa5d. A Notes API POST with 96 recurring checkbox lines and distinct dashed block anchors returned 500 (Notes operation failed). The Home file was written; its Task Index update failed. The watcher then logged repeated Markdown reconciliation failures. No process crash or cross-User access was observed. Evidence: Task line.rs accepts alphanumeric anchor characters only, so all anchors with the same dashed prefix parse as the same identity. tasks_store inserts the resulting duplicate Task IDs into task_items. The API writes the Home before that Index failure. The current Notes API does not reject the duplicate projected identities before its write. The benchmark now uses distinct alphanumeric anchors. This fixes the benchmark input; it does not fix the server's 500 or partial-write behavior. Needs input validation before writes and a regression for duplicate parsed Task identities, with preservation or explicit handling of dashed anchors according to the block-ID grammar. No existing test expectation was changed. Raw local evidence remains in ignored artifacts/mac393-round3/perf-lab-v2 and perf-run-v2.log. #393 remains NO-GO while acceptance is unfinished.
Author
Owner

Started #531 on branch job/tasks-500-531, based on origin/dev at 7c6fd3ce99651123f078c781d22e13ff5b5fd743. I am tracing the Task anchor parser and the Notes write/index ordering before changing the validation.

Started #531 on branch `job/tasks-500-531`, based on `origin/dev` at `7c6fd3ce99651123f078c781d22e13ff5b5fd743`. I am tracing the Task anchor parser and the Notes write/index ordering before changing the validation.
Author
Owner

Finding for #531: markdown::lift_block_id and the link scanner accept ASCII alphanumeric, - and _ IDs, but tasks::line::parse_task_line consumed only alphanumeric characters. Distinct anchors such as ^weekly-check-01 and ^weekly-check-02 therefore projected the same Task ID. Both Notes and Task write helpers persist the Home file before Task indexing, where task_items rejects that collision. I am aligning the parser with the existing Markdown grammar and adding pre-write uniqueness validation; index_source already replaces all projections for its source in one transaction, which makes an indexing retry idempotent.

Finding for #531: `markdown::lift_block_id` and the link scanner accept ASCII alphanumeric, `-` and `_` IDs, but `tasks::line::parse_task_line` consumed only alphanumeric characters. Distinct anchors such as `^weekly-check-01` and `^weekly-check-02` therefore projected the same Task ID. Both Notes and Task write helpers persist the Home file before Task indexing, where `task_items` rejects that collision. I am aligning the parser with the existing Markdown grammar and adding pre-write uniqueness validation; `index_source` already replaces all projections for its source in one transaction, which makes an indexing retry idempotent.
Author
Owner

#531 complete

The Notes Task create path no longer loses dashed block IDs. Markdown anchors use the shared ASCII block-ID grammar (letters, digits, - and _), so each of the 96 recurring checklist children keeps its own stable Task ID. Notes and Task write boundaries now detect duplicate projected Task IDs before writing Home content and return 400. Re-projecting the same source replaces its Index rows, so retry does not duplicate them.

Regression coverage checks a 96-child create returns 201 with 97 unique Task rows, re-index retry preserves those IDs, and a repeated anchor returns 400 before a file is created. The real-server adversarial probe also reconciles the created source and verifies its Task IDs remain stable; the rejected duplicate leaves Task rows unchanged.

Files

  • crates/calternal-notes-core/src/markdown.rs
  • crates/calternal-notes-core/src/tasks/{extract.rs,line.rs,mod.rs}
  • crates/plugins/notes/src/{lib.rs,store.rs,tasks_store.rs}
  • tests/adversarial/attack.py
  • bench/task-anchors-531.py, bench/{record.py,run.sh,test_record.py}
  • docs/perf/{README.md,baseline.json}

Gates (after merging origin/dev)

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

git diff --check
(no output; exit 0)

cargo clippy -p calternal-notes-core --all-targets -- -D warnings
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 11.18s

cargo test -p calternal-notes-core
test result: ok. 510 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.79s
test result: ok. 13 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 10.60s
test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.12s
test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.63s
test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.02s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

cargo clippy -p calternal-plugin-notes --all-targets -- -D warnings
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 1m 27s

cargo test -p calternal-plugin-notes
test result: ok. 132 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 92.49s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.58s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

cargo clippy -p calternal-server --all-targets -- -D warnings
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 12m 39s

cargo test -p calternal-server
running 96 tests
test result: ok. 93 passed; 0 failed; 3 ignored; 0 measured; 0 filtered out; finished in 45.98s

bun run --cwd apps/web check
Text sizes and UI shape values use shared role tokens.
UI transitions and animation options use shared motion tokens or documented exceptions.
svelte-check found 0 errors and 0 warnings

bun run --cwd apps/web test
 Test Files  140 passed (140)
      Tests  914 passed (914)
   Duration  154.41s (transform 57%, environment 17%, import 13%, tests 9%, setup 3%)

cargo clean
     Removed 0 files

Performance

Added the first Task-anchor baseline in docs/perf/baseline.json. Local busy-host run, 96 child Tasks per request: 10 sequential creates p50 1151.66 ms / p95 3896.05 ms, server CPU 11.43 s (62.68%), mean RSS 210,964,075 bytes and peak RSS 332,005,376 bytes. A 24-request burst measured p50 12781.56 ms / p95 15748.3 ms, server CPU 5.25 s (33.1%), mean RSS 353,143,879 bytes and peak RSS 362,692,608 bytes. Load average was 21.05/22.86/25.57 before and 22.04/22.90/25.49 after. No prior Task-anchor baseline exists for comparison.

Adversarial findings and gaps

The full local real-server campaign exited 1 with 56 findings, many marked SLOW under high host load. The #531 probes produced no findings. Non-Task follow-ups were filed: #551 (background Tus size/type validation) and #552 (Appearance assignment timeouts/missing values during a 16-request contention probe). Evidence was also added to #502 (Appearance location probe/schema mismatch), #48 (completed VTODO fields missing on GET), and #41 (DAV update probe expects 201 but received 204, then reuses a stale ETag). These are not included in this fix.

Decision

DESIGN.md does not state how duplicate parsed Task anchors are handled. I used the existing Markdown block-ID grammar and reject true collisions with 400 before the Home write; this keeps the Index unique constraint from failing after durable content changes.

Head: d869e3f34a9238cf83e70f96291fe4f7282b6243. No push, deploy or merge to dev was performed. cargo clean completed and generated web build output was removed.

## #531 complete The Notes Task create path no longer loses dashed block IDs. Markdown anchors use the shared ASCII block-ID grammar (letters, digits, `-` and `_`), so each of the 96 recurring checklist children keeps its own stable Task ID. Notes and Task write boundaries now detect duplicate projected Task IDs before writing Home content and return 400. Re-projecting the same source replaces its Index rows, so retry does not duplicate them. Regression coverage checks a 96-child create returns 201 with 97 unique Task rows, re-index retry preserves those IDs, and a repeated anchor returns 400 before a file is created. The real-server adversarial probe also reconciles the created source and verifies its Task IDs remain stable; the rejected duplicate leaves Task rows unchanged. ### Files - `crates/calternal-notes-core/src/markdown.rs` - `crates/calternal-notes-core/src/tasks/{extract.rs,line.rs,mod.rs}` - `crates/plugins/notes/src/{lib.rs,store.rs,tasks_store.rs}` - `tests/adversarial/attack.py` - `bench/task-anchors-531.py`, `bench/{record.py,run.sh,test_record.py}` - `docs/perf/{README.md,baseline.json}` ### Gates (after merging `origin/dev`) ```text cargo fmt --check (no output; exit 0) git diff --check (no output; exit 0) cargo clippy -p calternal-notes-core --all-targets -- -D warnings Finished `dev` profile [unoptimized + debuginfo] target(s) in 11.18s cargo test -p calternal-notes-core test result: ok. 510 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.79s test result: ok. 13 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 10.60s test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.12s test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.63s test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.02s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s cargo clippy -p calternal-plugin-notes --all-targets -- -D warnings Finished `dev` profile [unoptimized + debuginfo] target(s) in 1m 27s cargo test -p calternal-plugin-notes test result: ok. 132 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 92.49s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.58s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s cargo clippy -p calternal-server --all-targets -- -D warnings Finished `dev` profile [unoptimized + debuginfo] target(s) in 12m 39s cargo test -p calternal-server running 96 tests test result: ok. 93 passed; 0 failed; 3 ignored; 0 measured; 0 filtered out; finished in 45.98s bun run --cwd apps/web check Text sizes and UI shape values use shared role tokens. UI transitions and animation options use shared motion tokens or documented exceptions. svelte-check found 0 errors and 0 warnings bun run --cwd apps/web test Test Files 140 passed (140) Tests 914 passed (914) Duration 154.41s (transform 57%, environment 17%, import 13%, tests 9%, setup 3%) cargo clean Removed 0 files ``` ### Performance Added the first Task-anchor baseline in `docs/perf/baseline.json`. Local busy-host run, 96 child Tasks per request: 10 sequential creates p50 1151.66 ms / p95 3896.05 ms, server CPU 11.43 s (62.68%), mean RSS 210,964,075 bytes and peak RSS 332,005,376 bytes. A 24-request burst measured p50 12781.56 ms / p95 15748.3 ms, server CPU 5.25 s (33.1%), mean RSS 353,143,879 bytes and peak RSS 362,692,608 bytes. Load average was 21.05/22.86/25.57 before and 22.04/22.90/25.49 after. No prior Task-anchor baseline exists for comparison. ### Adversarial findings and gaps The full local real-server campaign exited 1 with 56 findings, many marked SLOW under high host load. The #531 probes produced no findings. Non-Task follow-ups were filed: #551 (background Tus size/type validation) and #552 (Appearance assignment timeouts/missing values during a 16-request contention probe). Evidence was also added to #502 (Appearance location probe/schema mismatch), #48 (completed VTODO fields missing on GET), and #41 (DAV update probe expects 201 but received 204, then reuses a stale ETag). These are not included in this fix. ### Decision `DESIGN.md` does not state how duplicate parsed Task anchors are handled. I used the existing Markdown block-ID grammar and reject true collisions with 400 before the Home write; this keeps the Index unique constraint from failing after durable content changes. Head: `d869e3f34a9238cf83e70f96291fe4f7282b6243`. No push, deploy or merge to `dev` was performed. `cargo clean` completed and generated web build output was removed.
Author
Owner

Shipped in merge round 4, deployed to calternal.cloud in 1af8ead26 (healthy).

Shipped in merge round 4, deployed to calternal.cloud in 1af8ead26 (healthy).
kayg closed this issue 2026-10-01 09:17:53 +00:00
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kayg/calternal#531
No description provided.