Implement life event cancel / POST /v1/events/{id}/cancel #75
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
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?
Context
docs/technical/cli-api.md's command-to-route table documents:life event cancel ID --etag ETAG --input -Neither half exists today.
EventSubcommandshas onlyAddandUpdate(solife event cancelis a clap usage error), and the router registers no/v1/events/{id}/cancelroute (grep -c 'events/.*cancel' src/api/mod.rs→ 0).Found during the #32 audit (PR #74), which moved the command to the skill's not-implemented table so the package stopped over-claiming — honest, but the contract still advertises it.
Why it matters
Cancellation is the one event transition with a documented non-obvious semantic:
docs/technical/cli-api.mdsays cancellation setsSTATUS:CANCELLED, notDELETE— a distinct operation from removal, and the same distinction#12draws for tasks (cancelis notcomplete). Without it, an agent told "cancel that block" has no correct command and would reasonably reach for an update patch carrying a status, which is exactly the free-form status edit the domain model avoids.What to build
EventSubcommands::Cancel { id, etag, input, operation_id }andPOST /v1/events/{id}/cancel, following#12's task-transition shape and#11's preflight: mandatory quoted strongIf-Match(missing/weak/unquoted/*/multi-tag refused before any dispatch),Idempotency-Keyrequired,deny_unknown_fieldsbody carrying an optionalreasonand optionaloccurred_at.STATUS:CANCELLEDon the target component and must notDELETEthe resource. ACANCELLEDevent still occupies no capacity (consumes_capacityis already false for cancelled events), so the agenda/planning paths need no change — assert that instead of assuming it.#12: a no-op on an already-CANCELLEDevent is a recordedappliedoperation withchanged:false, 0 PUTs and no duplicate timeline fact; the reverse transition (un-cancelling) is out of scope unless explicitly specified later.before_json/after_json, and on a stale ETag returnetag_conflict(412 / exit 4) withoperations.state='failed'and no retry — never a refreshed-ETag overwrite.#15's scoping rule: a bare cancellation of a recurring item asks for scope rather than guessing (cancelling the series and cancelling one occurrence are different operations with the same name).skills/life/references/commands.mdto move the row back to the implemented table.Acceptance criteria
life event cancel ID --etag '"v1"' --input - --jsoncancels the event:STATUS:CANCELLEDin the resource, resource still present (no DELETE), envelope exit 0.*/multi-tag ETag → 400invalid_input/ exit 2 with zero backend requests and no journal row.etag_conflict/ exit 4,operations.state='failed', no retry with a refreshed ETag.applied,changed:false, 0 PUTs, no duplicate fact).needs_clarification(exit 3) with the series/occurrence choice.before_json/after_jsonrecorded in one transaction with the operation result.tests/skill_package_tests.rspasses.Notes
checked >= 1, which accepted both the old count (2) and the new one (1) — so it did not establish thatlife event cancelleft the not-implemented section, which is the whole point of the criterion. It now asserts the parsed command-path set equals exactly{sync}, via the existingvalidate_stricthelper that also validates each row against the real clap tree; the author falsified it both ways by hand (re-adding the row fails that row's own exit assertion; emptying the section fails the set equality).X-properties andVALARMpreserved, all six unusable ETag forms refused before dispatch with zero requests and zero journal rows, stale ETag → 412 / exit 4 /failed/ no refreshed-ETag retry, the recorded no-op, the agenda +consumes_capacityclaim asserted through the existing predicate with a positive control, write-scope enforcement (a read credential is refused before the handler), reuse of#15's clarification payload, and the journal boundary. It called the SQLite-atomicity caveat accurate.test resultlines).Blocked by
None — #11 (preflight/updates), #12 (lifecycle discipline) and #15 (recurring scope) are all merged.
Related
Refs #32 (where the gap was found), #11 (event update), #12 (task cancellation semantics).