feat(tasks): delete, promote and duplicate (B3) #798
Closed
kayg
wants to merge 1 commit from
feat/tasks-delete-promote-duplicate into dev
pull from: feat/tasks-delete-promote-duplicate
merge into: kayg:dev
kayg:main
kayg:feat/style-sheet-fixes
kayg:storage/wal-checkpoint-off-save-1051
kayg:t3/paged-search-reconcile-final
kayg:feat/pass8
kayg:review/calendar-2
kayg:fix/shared-row-swipes
kayg:feat/contrast
kayg:dev
kayg:feat/collections-db
kayg:fix/instant-keys-1056
kayg:feat/money-round
kayg:perf/stalls-1054
kayg:wip/tiering-v3-cold4
kayg:wip/tiering-v3-cold5
kayg:port/rows-actions
kayg:port/rows-drop
kayg:port/hidden-blocks
kayg:port/rows
kayg:port/port-tables
kayg:port/port-tables-2
kayg:t3/mail-1011-liveness
kayg:port/devices
kayg:port/port-attachments
kayg:port/port-attachments-2
kayg:port/port-attachments-3
kayg:docs/design-consistency
kayg:docs/engineering-standards
kayg:fix/rusqlite-0.41-rustsec-2026-0459
kayg:feat/iphone-fixes
kayg:shared/restore-and-stale-918-919
kayg:fix/phone-top-band-contrast
kayg:feat/notes-approve
kayg:perf/soak-max-gate
kayg:feat/files-s1-identity
kayg:fix/lifecycle-event-id
kayg:feat/quickfix
kayg:wip/tiering-v3-cold
kayg:perf/web-mockup-frame-fixes
kayg:port/contacts-merge
kayg:port/contacts-edit
kayg:test/shared-room-no-deleted-text
kayg:perf/first-paint-credits
kayg:fix/shared-history-after-grant
kayg:fix/phone-swipe-regressions
kayg:feat/contrast-r1
kayg:review/sharing-pr3-review
kayg:feat/tiering-v3-s2-tags
kayg:feat/folder-drop
kayg:fix/deskpills-lazy-share
kayg:feat/sharing-s1-pr3
kayg:tmp/money-round-before
kayg:fix/phone-pane-backdrop-roots
kayg:fix/palette-preload-chips
kayg:fix/invite-defaults
kayg:port/tasks-matrix
kayg:fix/restore-green-gates
kayg:fix/sharing-fast-path-hardening
kayg:t3/photos-s1
kayg:feat/tiering-v3-s1-namespace
kayg:notes-archive-hide
kayg:feat/tasks-actions
kayg:t3/photos-plan-slice-one
kayg:feat/whiteboard-s1
kayg:t3/paged-search-reconcile
kayg:t3/smooth-pane-transitions
kayg:t3/index-position-startup-scan
kayg:fix/private-staging-759b-r2
kayg:feat/dav-transport
kayg:feat/imap-notes-fetch
kayg:feat/imap-notes-projection
kayg:feat/imap-notes-readonly
kayg:feat/imap-notes-auth
kayg:feat/imap-notes-server-foundation
kayg:docs/undo-block-ids-plan
kayg:fix/undo-768
kayg:proto/block-identity-v7d
kayg:issue/carddav-read-perf-fix-r2
kayg:issue/carddav-read-r2
kayg:docs/imap-766-plan-v2
kayg:docs/imap-notes-plan
kayg:docs/quota-plan-2
kayg:fix/parallel-test-flakes
kayg:feat/contacts-companion-pure
kayg:docs/calendar-backend-v2-plan
kayg:feat/calendar-ical-converter
kayg:docs/tasks-writes-v2-plan
kayg:docs/carddav-writes-v2-plan
kayg:docs/contacts-import-v2-plan
kayg:issue/610-pm-midnight
kayg:feat/calendar-v2-s1-containment
kayg:docs/dav-recorder-v2-plan
kayg:plan/previews-865
kayg:plan/save-log-durable-entry
kayg:fix/e2e-data-on-ram
kayg:feat/appwindow3
kayg:feat/appwindow2
kayg:feat/appwindow
kayg:feat/money-p1
kayg:feat/keys-sheet
kayg:review/calendar-2-themes
kayg:feat/smooth-cursor
kayg:fix/float-style
kayg:review/calendar-2-float
kayg:release/2026-11-06-hotfix
kayg:archive/port/rows-drop-code
kayg:archive/port/rows-actions
kayg:archive/port/rows
kayg:scratch/tasks-894-ui-gates
kayg:review/calendar-2-night-wip
kayg:release/2026-11-05
kayg:archive/feat/tiering-v3-s1-namespace
kayg:archive/flaky/rows-drop-fixed
kayg:archive/flaky/rows-actions
kayg:archive/feat/tiering-v3-reader
kayg:archive/feat/tiering-v3-roots
kayg:archive/flaky/rows
kayg:sim/bm15-final
kayg:sim/bm15-fix
kayg:sim/bm15-bis-932
kayg:sim/bm15-bis-909
kayg:sim/bm15
kayg:sim/bm15-bis-887
kayg:perf/tiering-option-b
kayg:port/style-3
kayg:port/style-2
kayg:port/style
kayg:fix/sw-lazy-precache
kayg:fix/vim-w-waits-for-save
kayg:fix/signout-test-races
kayg:fp-scratch/all
kayg:sim/bm15-first-paint
kayg:archive/bm15/rows-drop
kayg:archive/bm15/rows-actions
kayg:fix/palette-snippet-contrast
kayg:archive/bm15/rows
kayg:archive/bm15/hidden
kayg:archive/plan/save-log-durable-entry
kayg:mock/money-fix2
kayg:archive/fix/e2e-data-on-ram
kayg:mock/appearance-all
kayg:mock/pill-press
kayg:archive/docs/design-consistency
kayg:mock/auto-swipes
kayg:mock/mail-actions
kayg:archive/feat/mail-p1
kayg:mock/money-static
kayg:archive/review/standards-917
kayg:archive/docs/engineering-standards
kayg:perf/tiering-761-fix
kayg:mock/type-audit
kayg:mock/third-pane
kayg:feat/contacts-import-v2-s1
kayg:perf/executor-hot-path
kayg:archive/port/hidden-blocks
kayg:archive/port/devices
kayg:scratch/executor-on-894
kayg:scratch/executor-spans2
kayg:scratch/executor-spans
kayg:train/2026-11-03-bm14c
kayg:archive/port/rows-drop
kayg:feat/tiering-761-d-scale
kayg:feat/tiering-761-c-index
kayg:feat/tiering-761-b-reader
kayg:archive/review/sharing-pr3-review-9fec3d1f
kayg:train/2026-11-03-bm14b
kayg:perf/ports-lazy-budgets
kayg:feat/tiering-761-a-migration
kayg:train/2026-11-03-bm14
kayg:train/2026-11-03-bm12
kayg:archive/review/sharing-pr3-review
kayg:archive/feat/sharing-s1-pr3
kayg:train/2026-11-03-bm11
kayg:archive/feat/imap-notes-fetch
kayg:train/2026-11-03-bm10
kayg:archive/review/sharing-pr3-review-code
kayg:archive/plan/previews-865
kayg:train/2026-11-02-bm9
kayg:archive/port/port-attachments-3
kayg:archive/port/contacts-merge
kayg:fix/motion-audit-859
kayg:train/2026-11-02-bm8
kayg:archive/scratch/bm8-prep
kayg:archive/port/port-pickers
kayg:archive/port/port-tables-2
kayg:archive/port/port-tables
kayg:archive/feat/sharing-s1-pr3-code
kayg:archive/scratch/bm7-fixed
kayg:train/2026-11-02-bm7
kayg:archive/fix/parallel-test-flakes-code-minipc01
kayg:train/2026-11-02-bm6
kayg:docs/imap-766-readonly-slice
kayg:feat/dav-recorder-v3
kayg:research/788-diagnosis
kayg:archive/fix/parallel-test-flakes
kayg:archive/perf/pr792-cold-index-edit
kayg:docs/sharing-slice-1
kayg:archive/docs/imap-766-plan-v2
kayg:feat/dav-recorder-v2
kayg:archive/docs/dav-recorder-v2-plan
kayg:spike/imap-766-s0-cases
kayg:archive/fix/undo-768
kayg:feat/dav-recorder
kayg:archive/docs/contacts-import-v2-plan
kayg:archive/feat/calendar-ical-converter
kayg:feat/contacts-import
kayg:perf/cold-growth-on-764
kayg:perf/cold-growth-on-tiering
kayg:archive/docs/tasks-writes-v2-plan
kayg:archive/docs/calendar-backend-v2-plan
kayg:archive/feat/contacts-companion-pure
kayg:feat/tasks-set-status
kayg:feat/tasks-move-reorder
kayg:archive/docs/carddav-writes-v2-plan
kayg:archive/feat/calendar-v2-s1-containment
kayg:archive/feat/dav-transport
kayg:archive/fix/carddav-rebuild-r1
kayg:archive/fix/carddav-retention-r1
kayg:feat/carddav-writes
kayg:perf/quota-on-tiering
kayg:perf/quota-simple
kayg:perf/cold-growth-on-quota-stack
kayg:feat/calendar-identity
kayg:feat/calendar-event-parser
kayg:feat/calendar-props-maps
kayg:port/glass-when-ring
kayg:feat/tiering-761-s1
kayg:wip/10by32-01-impl-759b-scratch-20261030
kayg:wip/6by32-01-bm3-webkit-pause-20261030
kayg:wip/10by32-01-bm3-chromium-pause-20261030
kayg:wip/6by32-01-tier-761-s1-pause-20261030
kayg:spike/imap-766-s0
kayg:wip/6by32-02-calternal-pause-20261030
kayg:wip/8by64-01-review-688-pause-20261030
kayg:wip/6by32-01-verify-744-pause-20261030
kayg:wip/6by32-01-verify-742-pause-20261030
kayg:fix/private-staging-759b
kayg:wip/6by32-01-review-748-pause-20261030
kayg:wip/6by32-01-review-733c-pause-20261030
kayg:wip/6by32-01-review-733b-pause-20261030
kayg:wip/6by32-01-perf-idle-8-pause-20261030
kayg:wip/6by32-01-perf767-base-pause-20261030
kayg:wip/6by32-01-perf767b-767-pause-20261030
kayg:wip/6by32-01-perf767b-764-pause-20261030
kayg:wip/6by32-01-perf767-767-pause-20261030
kayg:wip/6by32-01-perf767-764-pause-20261030
kayg:wip/6by32-01-lists-b-pause-20261030
kayg:wip/6by32-01-ids-verify5-pause-20261030
kayg:wip/6by32-01-ids-verify4-pause-20261030
kayg:review/calendar-month
kayg:wip/10by32-02-undo-768-20261030
kayg:test/ids-verify3
kayg:wip/10by32-02-bm3-chromium-20261030
kayg:wip/10by32-01-review-753-20261030
kayg:wip/10by32-01-perf-idle-2-20261030
kayg:wip/10by32-01-bm2-webkit2-20261030
kayg:plan/759b-staging
kayg:train/2026-10-30-bm2
kayg:review/758b-tests
kayg:review/758-tests
kayg:review/764d-tests
kayg:review/767b-tests
kayg:review/preview-2026-10-29
kayg:review/767-tests
kayg:docs/tiering-731-plan
kayg:wip/c6b02-when2
kayg:wip/c6b02-tasks-create-fix5
kayg:wip/c6b02-settings-frost-mock
kayg:wip/c6b02-settings-frost-base
kayg:wip/c6b02-settings-frost
kayg:wip/c6b02-ring
kayg:wip/c6b02-review-733d
kayg:wip/c6b02-bm1-full-a
kayg:wip/compute02-when-field
kayg:wip/compute02-webkit-lists-repro
kayg:wip/compute02-train-bl2-dev
kayg:wip/compute02-dev-green-pw164
kayg:docs/deploy-plan
kayg:review/764c-tests
kayg:review/764b-tests
kayg:review/748g-tests
kayg:review/764-tests
kayg:review/759b-tests
kayg:fix/storage-owner-liveness-642
kayg:fix/storage-fix-7
kayg:wip/compute02-storage-fix-7
kayg:review/759-tests
kayg:feat/tiering-731
kayg:wip/tiering-731-first-attempt
kayg:wip/compute02-storage-fix-1
kayg:review/754g-tests
kayg:perf/idle-honest-2
kayg:review/754f-tests
kayg:review/754e-tests
kayg:review/755-tests
kayg:review/754d-tests
kayg:review/754c-tests
kayg:review/754b-tests
kayg:review/754-tests
kayg:fix/storage-private-tmp-642-dev
kayg:wip/compute02-verify-750
kayg:test/ids-verify5
kayg:test/ids-verify4
kayg:fix/storage-exact-file-quarantine-642
kayg:wip/compute02-verify-740
kayg:perf/one-flush-save
kayg:review/calendar-static
kayg:wip/compute02-verify-737
kayg:review/indexing-ring
kayg:review/when-field-2
kayg:wip/compute02-review-733
kayg:proto/block-identity-2-review
kayg:proto/block-identity-review
kayg:wip/c6b02-tc-design
kayg:review/when-field
kayg:issue/tasks-create-ids
kayg:issue/tasks-create
kayg:review/combined
kayg:wip/compute02-review-combined
kayg:review/task-polish
kayg:review/top-buttons
kayg:review/q1-q5
kayg:train/2026-10-26-bl2
kayg:archive/wt-398h
kayg:archive/train-bm-proptest-seed
kayg:archive/integ325-293
kayg:archive/integ325-ci
kayg:archive/client-auth-review-baseline
kayg:archive/wt-sim-4853
kayg:archive/wt-sim-4811
kayg:archive/wt-448m
kayg:archive/wt-398a-allocator
kayg:issue/calendar-adr
kayg:archive/old/train/2026-10-23bk2
kayg:archive/old/train/2026-10-23bk
kayg:issue/469-mockup-9a
kayg:issue/smart-tasks-mockup
kayg:issue/469-menus
kayg:issue/652-signout-flake
kayg:issue/652-history-fix7
kayg:issue/652-history-fix6
kayg:archive/old/issue/spring-check-perf
kayg:issue/spring-check-perf
kayg:issue/659-rename-fast
kayg:archive/old/train/2026-10-21ar
kayg:issue/search-hardening-3
kayg:issue/652-history-fix5
kayg:issue/660-port-sheets-glass
kayg:issue/652-history-fix4
kayg:issue/652-history-fix3
kayg:issue/652-history-fix2
kayg:issue/652-history-flake
kayg:train/2026-10-21an
kayg:issue/settings-admin-port
kayg:issue/search-port
kayg:train/2026-10-20am
kayg:issue/storage-split-fix2
kayg:issue/storage-split
kayg:archive/old/issue/folder-ids-fix
kayg:train/2026-10-20al
kayg:archive/old/issue/folder-ids
kayg:train/2026-10-20ak
kayg:issue/tables-mockup
kayg:train/2026-10-19aj
kayg:train/2026-10-19ai
kayg:issue/469-merge-fixes
kayg:issue/sidebar-band
kayg:archive/old/issue/608-settings-spec
kayg:train/2026-10-19ah
kayg:issue/swipe-offscreen
kayg:train/2026-10-19ag
kayg:archive/old/issue/590-nlp-adr
kayg:archive/old/issue/587-event-after-write-2
kayg:train/2026-10-19af
kayg:archive/old/issue/587-event-after-write
kayg:issue/469-pill-room-c
kayg:issue/469-pill-room
kayg:train/2026-10-19ae
kayg:issue/469-narrow-handle
kayg:issue/397-ios-swipe-2
kayg:archive/old/issue/adrs-photos-files-tasks
kayg:issue/469-desk-fixes
kayg:archive/old/issue/auto-update-adr
kayg:issue/469-filter-gap
kayg:issue/469-port-1b
kayg:issue/442-opacity-flake
kayg:archive/397-ios-swipe-2
kayg:issue/327-web-fast-3
kayg:archive/old/issue/469-port-1
kayg:issue/469-port-1
kayg:issue/327-web-fast-2
kayg:archive/old/issue/327-web-fast
kayg:archive/old/issue/425-stt-bench-vq1
kayg:archive/old/issue/445-no-bleed
kayg:archive/old/issue/400-swipe-mockup-2
kayg:issue/426-client-review
kayg:issue/426-client-2
kayg:issue/426-client
kayg:archive/old/issue/476-safe-area-lab
kayg:archive/445-top-light-2
kayg:archive/old/issue/445-top-light
kayg:issue/471-blocks-mockup
kayg:archive/old/issue/448-links-app-2
kayg:archive/old/issue/448-links-app
kayg:archive/old/issue/398-cold-links
kayg:issue/434-442-small-2
kayg:issue/434-442-small
kayg:archive/old/issue/398-unload
kayg:archive/old/issue/445-bottom-band
kayg:issue/445-bottom-band
kayg:issue/351-rem-3
kayg:archive/312-views-mockup
kayg:issue/351-rem-2
kayg:issue/351-rem
kayg:archive/old/issue/327-first-note
kayg:issue/395-width-phone
kayg:archive/old/issue/327-typing
kayg:issue/396-ios-edges
kayg:issue/397-ios-swipe
kayg:archive/398-allocator
kayg:archive/old/issue/327-notes-index
kayg:issue/345-347-contrast-empty-2
kayg:archive/old/issue/44-arenas
kayg:issue/44-arenas
kayg:archive/integ325
kayg:archive/old/issue/327-perf-harness
kayg:archive/old/issue/44-thp
kayg:issue/44-thp
kayg:archive/old/issue/285-sticky-pills
kayg:archive/old/issue/44-trim-3
kayg:issue/44-trim-3
kayg:archive/old/issue/165-cards
kayg:archive/old/issue/165-picker-2
kayg:archive/old/issue/212-multi-select
No reviewers
Labels
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
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!798
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/tasks-delete-promote-duplicate"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
round 2 final fix
Shared actor byte/kind validation guards task splices and Trash; structural child markers cannot hide owners, and promotion scans the full block extent before claiming a task. Both reported P1 probes are permanent regressions. Final gates pass: 598 package tests, 23 task-operation tests, fmt, clippy, contract and action types. Never merged; ready for final independent review.
Full status, exact commands, decisions and limits: round 2 fix report.
Tasks B3 implementation plan
Base:
origin/dev; no B1/B2 stack required. Backend only.Expected size: L, at most 1,500 application lines plus tests. Planning scans backlink candidate notes; current bound is O(items × candidate bytes + items²) because ownership checks reuse the existing link reader. Restore searches parsed block identities Home-wide, O(removed spans × Home bytes). No performance result is claimed. Each job holds one Home job lock and uses existing room/version and step receipts.
Gates (dev-01, slot, Nix):
cargo fmt --all -- --check;cargo clippy -j4 -p calternal-tasks -p calternal-notes -p calternal --all-targets -- -D warnings;cargo test -j4 -p calternal-tasks -p calternal-notes -p calternal; contract tests from the repository runner. No merge.Implementation decisions:
pulldown-cmarkparser used by the task index, and thenotes::propswriter. The web editor uses Lezer; no JavaScript parser runtime is added to the Rust backend.Validation on dev-01 (
tasks-b3-gates3, exit 0):cargo clippy -j4 -p calternal-tasks -p calternal-notes -p calternal --all-targets -- -D warningspassed.cargo test -j4 -p calternal-tasks -p calternal-notes -p calternalpassed: 556 tests, 10 existing ignored tests, no failures.cargo test -j4 -p calternal contractpassed: one contract test.node apps/web/scripts/action-types.mjs --checkpassed.cargo fmt --all -- --checkpassed.The corpus covers accepted metadata preservation, refused unusual YAML, code spans/fences/comments, BOM/CRLF and no final newline. Job crash tests kill and restart at each planned step; restore tests cover moved anchors, renamed/deleted sources and deletion generations. No latency measurement or independent review is claimed.
Result: registers
tasks.delete,tasks.promoteandtasks.duplicatewith durable job receipts andnotes.undo. Promotion retains parent prose and person links, appends a block marker when needed for its backlink, and adds a child link. Delete sends the note to Trash with exact removed-line records. Duplicate creates a fresh note and owning-line identity, stripping copied block IDs and upstream authority.Limits: YAML support is intentionally narrower than valid YAML, including refusal of nested maps. Markdown handling reuses the existing Rust task-index parser (
pulldown-cmark); it does not embed the web editor's Lezer parser. Ambiguous unmatched backticks refuse the action. Planning and restoration scan existing notes as described above; no performance measurement was run. Review pending; never merge as part of this task.WIP: tasks delete, promote and duplicate (B3)to feat(tasks): delete, promote and duplicate (B3)Independent review, round 1
Verdict: fix first. Reviewed head
7e063cf02afd61a1f3d762752bd3b6559d60b64cagainst the PR plan, ADRs 0066/0056/0068/0067 (including the → amendment), and the tasks decision memory. No P0 findings. Four P1 findings and one P2 finding below are reproduced by independent probes.P1: delete removes ordinary reference bullets, not just owners and session relationships.
crates/notes/src/task_ops.rs:173–177. A note containing# Reference readingfollowed by a top-level- [Task](<Task.md>)loses that bullet when Task is deleted. There is no owning checkbox or event/session parent. The non-checkbox branch checks only the link target, so unrelated lists are edited. Fix: classify the parent/container and remove standalone relationship bullets only under an owning checkbox or a supported session; preserve ordinary references. Probe:review_r1_delete_keeps_ordinary_reference_bullets.P1: duplicate reuses block IDs on paragraph continuation lines.
crates/notes/src/task_blocks.rs:111–121, consumed bycrates/notes/src/task_ops.rs:516–530. A task bodyfirst line\nlast line ^bbbbbbbbbbbbbbbb\nis copied with that exact ID still present.marker_blocksexamines only each parsed block's first physical line, so the copy filter misses this marker and creates a second Home-wide identity. The same omission affects hidden-block filtering. Fix: recognize marker positions throughout supported block ranges with the shared marker rules, and remove/re-key copied identities; otherwise refuse unsupported marker layouts before creating anything. Probe:review_r1_duplicate_multiline_block_id_is_fresh.P1: a stale promotion still mutates the source and reports success.
crates/notes/src/task_ops.rs:100–105,122–129;crates/notes/src/bulk/steps.rs:108–125. Pause atsplice-before-turn, change- [ ] Task ^bbbbbbbbbbbbbbbbto- [ ] Changed ^bbbbbbbbbbbbbbbb, then resume. The job returnsstate: doneand inserts the old[Task]relationship beneath Changed. For an existing marker the splice is insertion-only, so the destructive-undo guard has no deleted span to validate; the original source hash/version is never rechecked at mutation time. Fix: enforce the source block/version precondition inside the actor turn for insertion-only promotion too, propagate refusal rather than a successful kept result, and ensure a failed source claim cannot publish an orphan owner. Probe:review_r1_promote_stale_source_must_not_report_success.P1: delete Undo is not byte-exact even without intervening edits.
crates/notes/src/task_ops/restore.rs:202–222. Starting with- [Task](Task.md), delete thennotes.undorestores- [Task](<Task.md>). Every bound link is unconditionally regenerated despite unchanged source/target paths, so the stored exact span is not actually restored. Fix: reuse the saved raw link spelling when it still resolves to the retained UUID; rewrite only destinations that must change after relocation/path reuse, preserving other bytes. Probe:review_r1_delete_undo_preserves_link_spelling.P2: the YAML refusal guard still has the plain-list interior-quote bypass.
crates/notes/src/task_ops.rs:575–578.custom:\n - has "quote" within\npassessimple_frontmatter; the scalar branch rejects interior quotes, but the list branch merely compares the existing writer's rendering. That writer also emits interior quotes bare, so round-trip equality is not a safety proof. This contradicts the plan's refusal policy and leaves the specifically requested calendar-regression bypass open. Fix: apply lexical checks to each raw plain list item too, including quote/comment interactions; accept properly quoted supported scalars or refuse before planning. Probe:review_r1_yaml_list_quotes_must_refuse.Validation
All commands ran on dev-02 in an isolated, uncommitted worktree, using Nix and
~/bin/slot; no code was pushed.cargo test -j4 -p calternal-notes task_ops::tests -- --skip review_r1: 4 passed, including kill-9/replay, deletion generations, renamed/deleted sources and moved anchors.cargo test -j4 -p calternal-tasks mutation_tests: 7 passed, including the existing YAML/code/comment corpus and BOM/CRLF/exact-undo fixture.cargo test -j4 -p calternal-notes review_r1 -- --nocapture: 2 passed, 5 failed. The passing independent cases cover exact restore around surviving children and promotion with an existing descriptive → child, including a second request returning the same owning task.Probe source:
cua@calternal-dev-02-std:~/Developer/Worktrees/review-798-r1/crates/notes/src/task_ops/tests.rs(uncommitted). Logs:~/review-798-r1-finalprobes.log,~/review-798-r1-existing.log,~/review-798-r1-actions.logon that VM. Run from the worktree withexport PATH=/nix/var/nix/profiles/default/bin:$PATH; nix develop -c bash -c '~/bin/slot cargo test -j4 -p calternal-notes review_r1 -- --nocapture'. Full lint/workspace gates were not rerun for this review.round 1 fixes
head
0968bbff9751862b4fad4de41e59bcd580636d42; addresses review 71163. never merged.review_r1_delete_keeps_ordinary_reference_bullets, continuation/child and session casesreview_r1_duplicate_multiline_block_id_is_fresh, hidden/code casereview_r1_promote_stale_source_must_not_report_success, strengthened to check unchanged source prose and no orphan ownerreview_r1_delete_undo_preserves_link_spelling, renamed/deleted-source and moved-anchor casesreview_r1_yaml_list_quotes_must_refuseThe exact reviewer probe set was reproduced first: 2 passed, 5 failed. All seven probes are retained. The final task-operation run passes 14/14, including existing crash/replay, source rename/deletion and deletion-generation tests, plus continuation/nested-child, hidden/code and open/legacy session cases. The added nested-child and legacy-session probes were also observed failing before their fixes.
Decisions: settle pending server title/metadata before pinning snapshots, then refuse subsequent source changes. Whole-source preconditions deliberately also refuse unrelated edits. Promotion/duplication claim the source before creating an owner, retaining durable replay order. Move the existing daily log reader unchanged into notes and re-export its API from daily, so session classification shares all supported clock forms without a dependency cycle.
cmpconfirmed the moved reader is byte-identical. Full design/failure notes are indocs/plans/tasks-b3.md.All final commands ran on dev-01, through
~/bin/slot, with the Nix dev shell. The temporary flake files were not committed.cargo test -j4 -p calternal-notes task_ops::tests -- --nocapturecargo clippy -j4 -p calternal-tasks -p calternal-notes -p calternal -p calternal-daily --all-targets -- -D warningscargo test -j4 -p calternal-tasks -p calternal-notes -p calternal -p calternal-dailycargo test -j4 -p calternal contractnode apps/web/scripts/action-types.mjs --checkcargo fmt --all -- --checkFinal log:
claude@calternal-dev-01-compute02:~/tasks-b3-r1-shared-final.log(GATES_PASS). Baseline probe log:~/tasks-b3-r1-red.log.Limits: intentionally strict YAML/source refusal; existing ambiguous-backtick refusal remains; no performance claim. Independent re-review is pending. This is the response to review round 1 of the maximum 3; no merge was performed.
Independent review, round 2
verdict: fix first. reviewed
0968bbff9751862b4fad4de41e59bcd580636d42. all five round 1 findings are verified fixed for their original reproductions. two new P1 findings remain; no P0 findings.round 1 verification
review_r1_delete_keeps_ordinary_reference_bulletscompares exact source bytes; continuation/nested-child and shared-session tests also pass.review_r1_duplicate_multiline_block_id_is_freshexercises real duplication and rejects the original ID; the hidden-continuation/code fixture also passes.review_r1_promote_stale_source_must_not_report_successchanges the source atsplice-before-turn, verifies the changed prose remains, no old relationship or orphan task appears, and success is refused. this does not cover the separate Trash write path below.review_r1_delete_undo_preserves_link_spellingcompares the whole source before/after Undo, including the bare destination spelling. rename/deleted-source/moved-anchor tests also pass.review_r1_yaml_list_quotes_must_refusetests single/double interior quotes and the quote/comment interaction. independent expanded refusal probes also pass.new findings
P1: delete skips the exact-source guard on the task note being trashed.
crates/notes/src/task_ops.rs:211–217,crates/notes/src/bulk/steps.rs:203. only the backlink sources receive guarded splices;Step::Trashcarries just the task UUID. pause deletion at the source'ssplice-before-turn, change the task fromstatus: todotostatus: did-not-happen, ingest the outside change into its room, and verify the new status is present before resuming. deletion still removes the owning source lines, trashes the now non-task note, and reportsstate: done. the originalrequire_taskdecision is stale, and the changed note is destructively acted on without a source precondition. fix: retain the task snapshot in the durable plan and enforce its exact text/identity precondition inside the Trash actor turn before committing deletion; propagate refusal and make partial-job recovery/Undo preserve intervening edits. probe:review_r2_delete_changed_task_must_refuse.P1: a block marker on the task-link child hides the existing owner and promotion creates a second one.
crates/notes/src/task_blocks.rs:82–91. start with:where
Task.mdhasstatus: todo.relationship()compares the entire child text with the raw link without removing its trailing block marker, soowner()misses the existing task. promoting the checkbox succeeds with a fresh task UUID and inserts another task-link child while retaining the first one. the task index already recognizes the original child target (crates/tasks/src/found.rs:295–307), so this also disagrees with the read model. ADR 0068 O01 requires structural tokens to be removed before standalone-relationship classification; the one-owner invariant must hold. fix: use shared structural/marker parsing when classifying relationships, or refuse unsupported child layouts before making a new owner. apply the same classification to delete/session relationships. probe:review_r2_relationship_marker_retains_owner.validation and scope
all execution was on a dev VM, through Nix and
~/bin/slot; no code was pushed. the final uncommitted probe worktree isclaude@calternal-dev-01-compute02:~/Developer/Worktrees/review-798-r2. dev-02's queued runs were stopped before moving verification to dev-01.cargo test -j4 -p calternal-notes task_ops::tests -- --nocapture: 18 passed, 2 failed, exactly the two new P1 probes above. all 14 existing task-operation tests, including round 1 regressions and kill-9/replay, passed. final log:~/review-798-r2-confirm.log.cargo test -j4 -p calternal-tasks mutation_tests: 7 passed, including BOM/CRLF exact Undo and action-level YAML/code/comment corpus. log:~/review-798-r2-all.log.reproduction environment:
export CARGO_TARGET_DIR=/home/claude/Developer/Worktrees/tasks-b3-r1-fix/target; nix develop -c bash -c '~/bin/slot cargo test -j4 -p calternal-notes task_ops::tests -- --nocapture'from the final worktree. no full workspace/lint/performance gate rerun is claimed. this is round 2 of the maximum 3; never merged.round 2 implementation plan: retain the reviewer probes; centralize exact-text and parsed task/non-task validation in
verify_target(note, expected_source_bytes, expected_kind), called inside the actor after outside ingestion, immediately before guarded splices and Trash moves. Persist the Trash snapshot; force guarded Trash through the actor even for closed notes. Ownership reads the owner marker from its own head line only; strip structural child markers for relationships, and refuse promotion when any other task link exists in the block extent. B1 move/set-status are absent from this branch; their shared mutation boundary is the guarded splice actor. Expected delta: M, roughly 200 application lines, linear byte comparison plus existing parsed block/link scan; stored snapshots cost O(source bytes).Gates: fmt, clippy with -D warnings, tasks/notes/calternal/daily tests, contract, action-types. No merge.
round 2 fixes, ready for final review
head
f1600ed05109566241d1daa19c91be1d2b81ed1c. addresses review 71308. never merged.review_r2_delete_changed_task_must_refuse, strengthened to Undo the partial job and retain the edited taskreview_r2_relationship_marker_retains_ownerverify_target(note, expected_source_bytes, expected_kind)used by guarded splices and Trash; existing daily-note duplicate placement also uses a guarded spliceThe baseline reviewer probes were reproduced on dev-01: 4 passed, 2 failed, exactly the two reported P1s (
~/tasks-b3-r2-red.log). The final task-operation suite passes 23/23. An initial green invocation reused the baseline binary from the shared Cargo target; it was discarded, and the corrected run explicitly rebuilt the current notes crate and confirmed all 23 tests. The validated VM source tree matches the pushed head exactly; temporary Nix flake files are excluded and were not committed.All final gates ran on dev-01, using Nix and
~/bin/slot:cargo fmt --all -- --checkcargo test -j4 -p calternal-notes task_ops::tests -- --nocapturecargo clippy -j4 -p calternal-tasks -p calternal-notes -p calternal -p calternal-daily --all-targets -- -D warningscargo test -j4 -p calternal-tasks -p calternal-notes -p calternal -p calternal-dailycargo test -j4 -p calternal contractnode apps/web/scripts/action-types.mjs --checkFinal log:
claude@calternal-dev-01-compute02:~/tasks-b3-r2-gates.log, endingGATES_PASS.Decisions / limits: move and set-status are not present on this B3 branch; the shared actor splice guard is available for their integration. A duplicate's original task is a read-only captured snapshot, and intervening edits to that original remain intact. New identities have no existing bytes to validate; every B3 write to existing text uses the shared guard. A late Trash refusal can leave earlier relationship removals applied; the job fails, and Undo restores them without replacing the intervening task edit. Existing strict YAML/ambiguous-Markdown refusals remain. No performance claim. Final independent review is pending; no merge.
Independent review, round 3 (final)
verdict: stop and simplify. reviewed
f1600ed05109566241d1daa19c91be1d2b81ed1c. both round-2 reproductions pass, but a new P1 remains in failed-delete recovery. no P0 findings. this is the final round under the three-round rule; do not start another patch/review loop on this design.status
review_r2_delete_changed_task_must_refusepasses, including Undo preserving the intervening task edit; guarded Trash enters the room even for closed notesreview_r2_relationship_marker_retains_ownerpasses; relationship classification strips the child markerSpliceandTaskRemovesupply expected bytes; roomundo_edit_retainedcalls the shared guard; task Trash supplies expected bytes and explicitly requires Taskroom/save.rshas no shared task guard; its outside-file conflict loop ingests and retries. the new post-guard flush probe returns done after ingesting a changed sourcebulk/undo.rsconstructs inverse Splice withexpected: None; the actor consequently skipsverify_target. the new source-kind-change probe confirms the write proceedstask_ops/restore.rs:238supplies a fresh destination snapshot to the shared guard, but restores planned removals without checking whether their removal actually applied or whether the block now has another owner; new P1 belowshared API for B1 #801 / B2 #799:
crate::task_ops::verify_target(note, expected_source_bytes, expected_kind)atcrates/notes/src/task_ops.rs:475, withTargetKindat line 458. both arepub(crate), reusable by helpers insidecalternal-notes; they are not an exported API callable directly fromcalternal-tasks. the name/location are clear, but the assertion that every existing-text B3 write uses it is false. do not copy the optional-expected Undo bypass into B1/B2.new findings
P1: Undo of a refused deletion restores a removal that never happened and creates two task owners.
crates/notes/src/task_ops/restore.rs:72–83,137–156,230–257;crates/notes/src/bulk/undo.rs:138–143.remove()persists every planned row before any source splice succeeds. Undo includes TaskRestore even when TaskRemove failed, and restore treats all those planned rows as removed. reproduction: Task.md and Other.md are both task notes; Source contains- [ ] Task ^bbbbbbbbbbbbbbbbwith child[Task](<Task.md>). pause delete atsplice-before-turn, change that child to[Other](<Other.md>), ingest/save it, then release. delete correctly refuses and changes no source bytes. now Undo reportsstate: doneand inserts the old[Task](<Task.md>)child before[Other](<Other.md>). one checkbox now has two owning task targets. the fresh byte guard passes because it snapshots the already-retargeted destination; it does not validate recovery provenance or ownership. independent probe:review_r3_refused_delete_undo_does_not_add_second_relationship. a separate moved-marker probe also reproduces duplicate relationship insertion after an existing relationship's label changes, showing raw prefix equality is not semantic deduplication. required design property: restore only removals proven applied by durable receipts, and refuse/reconcile a conflicting current owner before inserting any relationship. the author's late-refusal recovery claim does not cover this case.P2: shared-guard coverage stops before several requested write paths.
crates/notes/src/bulk/undo.rs:105–115;crates/notes/src/room/delete.rs:104–124;crates/notes/src/room/save.rs:232–237. inverse task splices drop expected bytes entirely; only the older span guard runs.review_r3_undo_promote_source_kind_changedpromotes inside a task note, changes that source's status todid-not-happen, then confirms Undo still removes the relationship and reports done. the new status itself is preserved; this probe demonstrates the requested guard-policy gap, not loss of that status. separately,review_r3_promote_flush_revalidates_outside_changepauses the post-apply save at BeforeWrite, changes the on-disk checkbox title Task to Changed, and resumes. the save ingests/retries and promotion reports done with Changed plus the old Task relationship. generic recovery publication also has noverify_targetcall. required design property: define one task mutation boundary covering apply, inverse, and publication/recovery, with explicit preconditions and receipt semantics; generic save/CRDT preservation is not evidence that this shared guard ran. no additional data-loss severity is inferred from these two probes.concrete simpler design
Replace the nested TaskRemove loop plus all-planned-rows recovery sidecar with one ordinary guarded top-level Splice per source in the existing durable bulk plan. Retain each source's exact removed bytes, bindings, and before/after state with that step's durable receipt. Undo must use those receipts, including a receipt committed just before a crash, to select only edits that actually applied. Route forward and inverse writes through one mandatory task guard boundary.
For v1 restoration, automatically restore only an unchanged, unambiguously owned destination. If a block moved or its binding changed, retain the pinned recovery span and report a conflict for explicit restoration instead of guessing placement or adding an owner. If moved-block reuse is retained, resolve its current owner by UUID first: same owner means no new relationship; another owner or ambiguous structure means refuse without consuming the recovery record. This removes speculative all-row restoration and raw-prefix ownership guesses while retaining the existing bulk engine and exact recovery evidence.
validation
all execution ran on dev-01, through Nix and
~/bin/slot; no builds/tests ran on the control host and no code was pushed. isolated uncommitted probe worktree:claude@calternal-dev-01-compute02:~/Developer/Worktrees/review-798-r3, based on the reviewed head, with only probe tests and temporary Nix flake files changed.Exact final command from that worktree:
23 existing tests passed; 4 independent probes failed: refused-delete Undo, moved-owner relationship reuse, Undo source-kind guard, and post-guard flush. final log:
~/review-798-r3-confirm.logon dev-01. full lint/workspace/action gates and performance were not rerun. never merged.on hold pending the shared Tasks writes v2 plan, PR #808, branch
docs/tasks-writes-v2-plan, head8d32f656049061a195653e950c9db097d10285c7.docs/plans/0066-tasks-writes-v2.mdcovers all task writes: pure exact-byte splice planners plus ONE journaled executor for apply, recovery and recorded inverse undo, reusing calendar v2’s parsed view and frontmatter writer. Undo requires a committed change and the exact recorded post-image; refused removals cannot be restored. Executor first, then B1, B2, B3. Keep existing work/probes preserved; no further patch/review loop or merge of this held implementation pending the plan. Docs only; no implementation started.Pointer: #894 (
feat/tasks-actions) overlaps this PR. The review that compares the two is here: #894 (comment)Recommendation (c): keep #894's API,
version, reorder and status-only move. Rebuild delete/duplicate (and later any line move) as v2 planners on the shared executor (#808). From this PR: owner/relationship classification, the full-extent block-ID re-key, the YAML refusal corpus and allreview_r1/r2/r3probes. #894's own duplicate repeats r1 P1-2 and r2 P1-2, and its delete repeats r2 P1-1.After those pieces are ported to the v2 rebuild, close this PR. Keep the branch until then.
kayg referenced this pull request2026-11-05 23:21:11 +00:00
Keeper proposal after reading #798, #799, #894 and all their review comments: gate #894 (
cac156dd68c222da099343628a75abfa97a75504), the replacement following review 76844, rather than revive the held bulk/room implementations. #798 round 3 explicitly requires stop-and-simplify; #799 is held for the same executor design. Propose closing #798/#799 as superseded only after recording the missing promotion/physical-line-move scope and preserving/porting their review probes. Do not close #894 as a duplicate: it carries the reviewed replacement API and the executor rework. Neither earlier PR is approved for merge. No PR closed or merged. I will merge current dev into #894, run fresh gates, add crash/retry regressions and report remaining scope explicitly.housekeeping #1009: needs-decision.
Explicit three-round hold; Tasks writes v2 plan #808 and executor work #894 replace the action direction. Preserve promotion and physical-line scope regressions.
original head:
f1600ed05109566241d1daa19c91be1d2b81ed1c. against devcd7f84432febfc63b788fbb9b9f9950452982d74, git cherry reports 11 unmatched and 0 equivalent patches. the landing audit lists each candidate missing commit and stable patch match inpr-798.md; unmatched patches can include superseded behavior.left open, source branch unchanged. no rebase or new test claim for this decision lane.
Superseded by #808/#894 (Tasks writes v2). The branch and original tips are archived under
archive/(see #1009), so nothing is lost; useful regression vectors are noted there for the active lanes. Closing.Pull request closed