Skip to content

feat(items): idempotency keys for comments, notes and decisions; idempotency_target_gone is structured (TASK-3585) - #2068

Merged
xarmian merged 3 commits into
mainfrom
rook/task-3585-append
Oct 11, 2026
Merged

xarmian merged 3 commits into
mainfrom
rook/task-3585-append

Conversation

@xarmian

@xarmian xarmian commented Oct 11, 2026

Copy link
Copy Markdown
Collaborator

TASK-3585, appends: idempotency keys for comment create and the note and decision appends. This also folds in the lead's ask from #2065: idempotency_target_gone is now a structured code.

What changes

  • idempotency_target_gone (410) is allow-listed and mapped on both MCP transports:
    • Remote: a new 410 arm in errors.go.
    • Stdio: the CLI's top-level marker, via IsPatchRefusal.
    • The hint is shared between internal/cli and internal/mcp, and the parity test covers it.
  • Comment create (POST .../items/{ref}/comments): accepts op_key in the body or an Idempotency-Key header.
    • The answer (the comment id) is recorded inside CreateCommentWithActivity's transaction (CommentCreate.AfterWriteTx).
    • A retry returns the comment, not a twin.
    • A refused comment releases its key.
    • A replay of a comment deleted since then returns 410.
  • Note and decision appends (PATCH .../items/{ref}): a key is accepted only on an append-only update, meaning append_implementation_note and/or append_decision, plus source.
    • Any other member alongside a key is refused 400. A key there would promise a replay this handler can't give.
    • The answer (the item id plus the minted entries) is recorded in the update's own transaction (ItemUpdate.AfterWriteTx).
    • A replay returns the item as it stands now and echoes the first entries in appended. It is enriched with the caller's visible collections, like the update's own response.
    • The built-in and patch callers (b != nil) already carry their own key and are skipped.
  • CLI: pad item comment|note|decide --op-key, with a fresh key per invocation by default. Each retries once with the same key on a 5xx or transport error, only when the server advertises idempotency_keys.
  • MCP: comment, note and decide forward op_key on both transports. ToolSurfaceVersion goes from 0.91 to 0.92 (additive). The golden files, README, instructions.md and docs/api.md are updated.

Tests

  • Server, comment keys: replay with no twin, a refusal releases the key, a deleted comment returns 410. These go red with the store hook disabled.
  • Server, append keys: replay echoes the first entry with no twin note (red with AfterWriteTx unset). A key on a non-append update is refused and writes nothing. Unkeyed appends still append each time.
  • CLI: comment and note retry once with the same key, and don't retry without the capability. Comment keys are either the one given or a fresh one each time.
  • MCP: comment and append op_key tests go red with forwarding removed. A create replay of a deleted item returns the structured 410 on remote and stdio, red with the 410 arm removed.
  • Local: full go test ./... (41 packages) and lint are green before the rebase. After it, cmd/pad, internal/cli, internal/mcp, internal/models and the server/store guard and TASK-3585 tests are green.

https://claude.ai/code/session_01Q9WssCjFuHjSfLABoW4tmS

…orts (TASK-3585)

A replayed create whose item has since been deleted answers 410
idempotency_target_gone. Remote MCP had no 410 arm, and the CLI wrote no
stdio marker for it, so it reached agents as a generic error. It is now
allow-listed, mapped on the remote 410 arm, and marked on stdio, with a
hint shared between internal/cli and internal/mcp.

Claude-Session: https://claude.ai/code/session_01Q9WssCjFuHjSfLABoW4tmS
POST .../items/{ref}/comments accepts op_key (body) or Idempotency-Key.
The key's answer (the comment id) is recorded inside
CreateCommentWithActivity's transaction, so a retry whose first answer
was lost returns that comment instead of a twin. A refused comment
releases its key; a replay of a comment deleted since answers 410
idempotency_target_gone. `pad item comment --op-key` (a fresh key per
invocation by default) retries once behind the idempotency_keys
capability; MCP comment forwards op_key on both transports.

Claude-Session: https://claude.ai/code/session_01Q9WssCjFuHjSfLABoW4tmS
PATCH /workspaces/{ws}/items/{ref} accepts op_key (body) or
Idempotency-Key, but only on an append-only update
(append_implementation_note and/or append_decision, plus source); a key
with any other member is refused 400. The answer (the item id and the
minted entries) is recorded in the update's own transaction, so a retry
whose first answer was lost echoes the first entries in `appended`
instead of appending twice. The built-in and patch callers, which reach
this handler with their own key, are untouched.

`pad item note|decide --op-key` (fresh per invocation by default) retry
once behind the idempotency_keys capability; MCP note/decide forward
op_key. ToolSurfaceVersion 0.91 -> 0.92 (additive; also records the 410
now being structured). docs/api.md covers create, comment and append keys.

Claude-Session: https://claude.ai/code/session_01Q9WssCjFuHjSfLABoW4tmS
@xarmian
xarmian merged commit 493498c into main Oct 11, 2026
12 checks passed
@xarmian
xarmian deleted the rook/task-3585-append branch October 11, 2026 18:38
xarmian added a commit that referenced this pull request Oct 12, 2026
… hard-coded current version

docs/mcp.md said "currently 0.90" in two places, while #2065 and #2068
moved the constant to 0.92 without a changelog entry. The entries are added
from version.go's own notes, and the "currently" figures (catalog,
ToolSurfaceVersion, CmdhelpVersion) now point at the constants in
internal/mcp/version.go, so they cannot go stale again.

Claude-Session: https://claude.ai/code/session_01FAVXA5mJ5BNhKmv95jxQkQ
xarmian added a commit that referenced this pull request Oct 12, 2026
… hard-coded current version

docs/mcp.md said "currently 0.90" in two places, while #2065 and #2068
moved the constant to 0.92 without a changelog entry. The entries are added
from version.go's own notes, and the "currently" figures (catalog,
ToolSurfaceVersion, CmdhelpVersion) now point at the constants in
internal/mcp/version.go, so they cannot go stale again.

Claude-Session: https://claude.ai/code/session_01FAVXA5mJ5BNhKmv95jxQkQ
xarmian added a commit that referenced this pull request Oct 12, 2026
… hard-coded current version

docs/mcp.md said "currently 0.90" in two places, while #2065 and #2068
moved the constant to 0.92 without a changelog entry. The entries are added
from version.go's own notes, and the "currently" figures (catalog,
ToolSurfaceVersion, CmdhelpVersion) now point at the constants in
internal/mcp/version.go, so they cannot go stale again.

Claude-Session: https://claude.ai/code/session_01FAVXA5mJ5BNhKmv95jxQkQ
xarmian added a commit that referenced this pull request Oct 12, 2026
… hard-coded current version (#2095)

docs/mcp.md said "currently 0.90" in two places, while #2065 and #2068
moved the constant to 0.92 without a changelog entry. The entries are added
from version.go's own notes, and the "currently" figures (catalog,
ToolSurfaceVersion, CmdhelpVersion) now point at the constants in
internal/mcp/version.go, so they cannot go stale again.

Claude-Session: https://claude.ai/code/session_01FAVXA5mJ5BNhKmv95jxQkQ
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.

1 participant