Skip to content

feat(tasks): compare revisions on the task being changed - #168

Merged
ChesterRa merged 4 commits into
ChesterRa:mainfrom
chriscoveries:feat/task-revision-v2
Oct 23, 2026
Merged

ChesterRa merged 4 commits into
ChesterRa:mainfrom
chriscoveries:feat/task-revision-v2

Conversation

@chriscoveries

@chriscoveries chriscoveries commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

An unrelated agent-state write invalidates the whole-Context if_version token, even when the task being changed is untouched. Callers cannot distinguish that case from another caller changing the same task.

Tasks now expose an unsigned rev: older task files without one read as 0, and new tasks start at 1. Existing-task operations accept if_task_rev. A mismatch rejects the batch with task_conflict, naming the task and its expected and actual revisions. The existing whole-Context guard remains independent; callers may use either guard or both.

Each semantic task change increments its revision. An identical patch retains the revision and derived timestamp. Operations in a batch see preceding in-memory revisions; a later conflict commits none of the batch's task or Context changes. Targetless creates reject if_task_rev, and callers cannot assign rev themselves. Existing task-ID non-reuse protects against a deleted task being mistaken for a later incarnation. Direct YAML editors must follow the same revision protocol; API guards cannot fence writes that bypass the store.

How we built it. Revision checks and advancement share the common Context operation boundary under its existing lock. Advancement compares all tasks after each operation, including indirectly changed tasks; legacy migration participates too. Read-only loading supplies revision zero without rewriting old files. Full/summary Context reads, individual tasks, task pages, the compact index and MCP bootstrap previews retain the revision. The daemon exposes the structured conflict. MCP preserves a guard once when one call expands into update plus move, and the schema and Context/IPC contracts document it.

How we tested it.

  • Eight added core, daemon and MCP tests use projections of three recorded task files. They cover unrelated writes, stale same-task writes, dry runs, late-conflict atomic rejection, sequential guards, identical patches, create/restore/delete, malformed and exhausted revisions, legacy migration, wrapper expansion and read surfaces. Private titles, notes and assignees are omitted from the fixture; test mutations are identified as mutations rather than historical records.
  • GitHub Actions CI on the fork at exact PR head 3969f7970f742760dc3b255ec58c611078c480ce passed all six jobs, including rust-linux: run 39777285737. The test-only reference run 39763090179, based on upstream main a212cb04 before it advanced to f1d27838, fails exactly the eight added tests while the other 2,715 pass; it is historical baseline evidence, not a test of current main.
  • Real-daemon CI probes use source-pinned CI builds, launch each daemon in a fresh private home, and retain raw Unix-socket requests/responses plus source, binary and fixture hashes. Stock 0.4.44 and upstream main each fail six revision checks; their existing whole-Context guard and ordinary update controls pass. The candidate passes all eight, including named conflicts, no stale-guard writes and atomic batch rejection. Every owned daemon is stopped. Setup failures cannot count as a negative reference.
  • Non-author source review passes the feature head. The separate evidence branch is not part of this PR.

Tested on: GitHub-hosted Ubuntu 24.04 x86_64 runners, 4 vCPUs, Rust 1.88.0 (daemon probes); fork CI also passes Windows smoke.

Main's retry-safe task creation (client_request_id, parent_ref,
blocked_by_refs) meets the revision tokens: the revision check still runs
before each task op and the revision advance after it, with main's
task.create path (task id and replayed in the change) in between; the
stored task drops both sides' op-only keys, and the MCP task create keys
are the union.

Main's new storage test compares a loaded task with the seeded map; a
loaded task now carries its revision, so the test expects the untouched
task at rev 0 (the failed batch published nothing).
@ChesterRa
ChesterRa merged commit 4d17ecc into ChesterRa:main Oct 23, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants