Skip to content

Add idempotent mutation transaction identities - #486

Merged
JSv4 merged 6 commits into
mainfrom
agent/issue-449-transaction-identity
Aug 15, 2026
Merged

Add idempotent mutation transaction identities#486
JSv4 merged 6 commits into
mainfrom
agent/issue-449-transaction-identity

Conversation

@JSv4

@JSv4 JSv4 commented Aug 14, 2026

Copy link
Copy Markdown
Owner

Closes #449

Summary

  • adds an optional caller-supplied root transactionId for applying docxodus_mutations batches, scoped to the lifetime of one open session
  • fingerprints a deterministic canonical request with SHA-256: object key order and equivalent JSON escapes normalize, step/array order and payload content remain exact, omitted mode normalizes only to the actual atomic default, and duplicate keys fail before reservation
  • retains the original identity-attached serialized batch result byte-for-byte so response-loss retries return the same versions, operation summaries, affected anchors, semantic deltas, rollback state, generated ids/timestamps, and package hash without reapplying or rechecking preconditions
  • returns typed conflicts for the same id with different content and typed expiration after bounded FIFO retention (128 full responses followed by 1,024 fingerprint tombstones)
  • composes replay with atomic rollback, explicit best-effort partial success, optimistic preconditions, undo/redo, save, close/reopen, and independently scoped concurrent sessions; preview/dry-run and direct or nested-step identities fail closed
  • serializes every document-bound MCP action through a per-session gate while preserving cross-session parallelism, close/save ordering, handle lifetime, and an enforced non-reentrant callback lock contract
  • shares one explicit Unicode White_Space definition between runtime validation and the advertised JSON Schema, counts the 256-character limit in Unicode scalar values, and keeps error-code parity across .NET, npm, and Python
  • keeps [Agent] Add idempotent transaction identities and receipts #449 ownership limited to in-session identity, replay, and conflict detection; the portable hash-addressed delivery artifact remains owned by [Verify] Generate a hash-addressed delivery change receipt #458

Validation

  • authoritative fresh Release full solution suite on final stacked ancestry: 3,733 passed, 3 intentionally skipped, 0 failed (3,736 total)
  • final transaction-identity suite: 25/25
  • final MCP dispatcher + atomic batch + isolated preview matrix: 136/136
  • full Python suite: 89/89
  • npm locked install and full typecheck: passed
  • solution build: 0 errors
  • deterministic fault-injection coverage for post-commit clock failure, eviction-time clock failure, journal-factory failure, lifecycle lock ordering, active/deferred flowed child work, and cross-session parallelism
  • two independent acceptance/concurrency/parity audits: clean after all findings were resolved
  • final worktree, ancestry, and diff checks: clean

Stack

Base: agent/issue-452-content-controls / PR #484. This is the final child issue in epic #435.

JSv4 added 5 commits August 14, 2026 07:03
…ncomplete ids

Review follow-up on the mutation transaction identity epic (#449).

Retention was bounded by count only. A retained entry is a complete serialized
MutationBatchResult, which emits every step's results twice plus patch.markdown
and the semantic delta sets — a measured ~3.2 KB even for a one-step batch on a
blank document — so 128 responses per session is not a memory bound. Adds a
32 MiB per-session byte budget alongside the count cap, evicting oldest-first
until both hold. A response larger than the whole budget evicts itself rather
than raising the ceiling; the identity stays bound and answers
transaction_result_evicted. Byte accounting is decremented on every path a
retained response leaves the record map.

A reservation that never recorded a terminal response returned
transaction_conflict quoting the RETRY's own fingerprint — the exact opposite of
what happened — and could never be reclaimed. It now has its own decision kind
and EditErrorCode.TransactionIncomplete ("outcome unknown"), the Dispatcher
abandons any reservation it cannot complete, an abandoned reservation becomes an
outcome-unknown tombstone, and uncompleted reservations are FIFO-bounded like
completed ones.

Also:
- CHANGELOG [Unreleased] entry covering the new transactionId field, all four
  error codes, and the retention bound.
- HttpTransport's doc comment said SessionStore assumes single-threaded access,
  which this epic made false. Corrected to state that per-session dispatch is
  forward-looking: Handle runs inline on the serial accept loop, so the lock is
  not what serializes requests, and making concurrency real means moving
  handling off the accept loop AND dropping the lock — a deliberate behaviour
  change, not a comment fix. Notes the same for the lifecycle gate around open.
- ToolCatalog and the schema description interpolate MaxTransactionIdLength
  instead of repeating 256.
- Document the retention cost, the validation-failure-burns-the-id rule, the
  tombstone-expiry re-apply hazard, and that idempotency is MCP-only.

Tests: five new MCP449 facts (byte-budget eviction incl. the oversized-response
boundary, measured real retained cost, incomplete-vs-conflict truthfulness and
evictability, reservation FIFO bound, Dispatcher mapping), plus wire-string
parity assertions on both client surfaces.
@JSv4
JSv4 changed the base branch from agent/issue-452-content-controls to main August 15, 2026 04:34
@JSv4
JSv4 marked this pull request as ready for review August 15, 2026 04:34
Brings the branch up to date with main, which had advanced by 20
commits (PRs #479, #481, #477, #480, #482, #485 and #484).

CHANGELOG.md was the only conflict: both sides appended to the same
[Unreleased] / ### Added region. Resolved by keeping every entry from
both sides, with this branch's idempotent mutation transaction entry
ahead of main's accumulated entries. Main's block is otherwise
unchanged, and the historic "[Unreleased] - .NET 8" heading further
down the file is untouched.

EditErrorCode gained four transaction codes on this branch and image
and revision codes on main; the merged enum retains all of them, and
the npm and Python mirrors carry this branch's four. The enum has no
explicit ordinals and is serialized by name, so the reordering #485
introduced does not affect the wire.

This PR remains MCP-transport-only: DocxSessionOps, DocxSessionJson
and the WASM bridge are byte-identical to main after the merge.
@JSv4
JSv4 merged commit fd849c3 into main Aug 15, 2026
13 of 14 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.

[Agent] Add idempotent transaction identities and receipts

1 participant