Transaction formats¶
Scope¶
Choose a format based on the kind of edit you need.
patchdiff-tx/1(Primary Typed Match Transaction): A structured JSON format passed directly through MCP tool arguments (patchdiff_submit_edits), stored as canonical JSON inappend_only_pdcp_logs.pdcp_contentunder runtime profilepatchdiff-mvp-0.2.2. Delimiter-collision free, with minimal mandatory prose overhead (151 Bwire /206 Bpersisted for a 1-edit transaction;259 Bwire /314 Bpersisted for 5 edits).patchdiff-tx/2(Guarded Match & Span Operations): Canonical JSON passed throughpatchdiff_submit_operationswith requiredintent, required state guard, and orderedoperations[]carrying explicitop: "match"orop: "span". Span records requirestart,end,expected_text, andreplace, use sequential canonical Unicode code-point coordinates, and allow explicit empty-span insertion (start == end,expected_text: ""). Optionalexpected_before/expected_afterverify exact adjacent text; records using them execute under immutable runtime profilepatchdiff-mvp-0.5.0(otherwisepatchdiff-mvp-0.3.0).patchdiff-tx/3(Guarded Sole Whole-Target Replacement): Canonical JSON passed throughpatchdiff_submit_replacementwith requiredintent, required state guard, and a single{"op": "replace_all", "replace": "..."}operation under immutable runtime profilepatchdiff-mvp-0.4.0(232 BMCPargumentsJSON /290 Bpersisted record /341 Bcompacttools/callline for a 3-byte whole-target replacement).
None of these formats is executable code. All lower into the same internal ParsedPDCP representation and execute through the same deterministic applier (apply_pdcp_transaction).
Architecture decisions: modernization architecture. As-built record: product change record.
1. Typed Match Transaction (patchdiff-tx/1, Primary)¶
Wire Representation (MCP Tool Arguments)¶
Submitted over MCP via patchdiff_submit_edits:
{
"target_file_id": "11111111-1111-4111-8111-111111111111",
"expected_target_version": 0,
"expected_current_hash": null,
"intent": "Update user settings label and default value.",
"edits": [
{
"match": "theme: dark",
"replace": "theme: system",
"change_id": "chg_001",
"rationale": "Align default theme with OS preferences.",
"risk": "low"
},
{
"match": "notifications: true",
"replace": "notifications: false",
"occurrence_index": 2
}
],
"client_request_id": "req-settings-v1"
}
Top-Level Properties¶
target_file_id(string, required): UUID of target document.intent(string, required nonblank): High-level transaction intent for audit logs and ledger.edits(array, required,minItems: 1): Ordered list of edits executed sequentially.expected_target_version(integer 0..2^63-1 | null): Version precondition guard.expected_current_hash(string | null): Exactly 64 lowercase hex SHA-256 target hash precondition guard.- Guard requirement: At least one of
expected_target_versionorexpected_current_hashmust be non-null. client_request_id(string | null, optional): 1..128 printable ASCII characters without whitespace for request deduplication.parent_pdcp_log_id(string | null, optional): UUID of rejected parent attempt when retrying.attempt(integer 1..2^31-1 | null, optional): Retry attempt counter.
Edit Item Schema (TYPED_EDIT_JSON_SCHEMA)¶
match(string, required non-empty): Exact target text to match.replace(string, required): Replacement text. Empty string""denotes deletion.change_id(string | null, optional): A complete 1ā64-character identifier containing only ASCII letters, digits, underscore, period, colon, or hyphen; trailing newlines reject. Defaults tochg_001,chg_002, ... by 1-based index if omitted ornull. Duplicate IDs reject withFailureReason.MANIFEST_INVALID.rationale(string | null, optional): Nonblank per-edit explanation when supplied.risk("low" | "medium" | "high" | null, optional): Advisory risk classification.occurrence_index(integer >= 1 | null, optional): 1-based index over exact matches in the sequential buffer.anchor_before(string | null, optional): Exact substring that must precede candidate span.anchor_after(string | null, optional): Exact substring that must succeed candidate span.- Selectors are conjunctive constraints even when there is only one match (
patchdiff-mvp-0.2.2). An index beyond the occurrence count or an excluding anchor rejects (disambiguation_invalid); combined selectors must all hold. Anchors need not be adjacent. additionalProperties: false: Unknown fields reject withFailureReason.MANIFEST_INVALID.
Canonical Persisted Record¶
Stored in append_only_pdcp_logs.pdcp_content with protocol_version = "patchdiff-tx/1" and runtime_version = "patchdiff-mvp-0.2.2", normalized via json.dumps(record, ensure_ascii=False, separators=(",", ":"), sort_keys=True).
Known-target invalid Unicode (unpaired surrogates or NUL) is rejected before normal hashing/storage with target_encoding_invalid at PROTOCOL_VALIDATION. Its log uses a reversible ASCII-escaped patchdiff-invalid-input/1 evidence envelope in the same TEXT column with a safe JSONB diagnostic marker; that rejected envelope is never applied or decoded as accepted history.
2. Guarded Match & Span Operations (patchdiff-tx/2)¶
Wire Representation (MCP Tool Arguments)¶
Submitted over MCP via patchdiff_submit_operations when the caller holds exact Unicode code-point offsets from patchdiff_read_target, patchdiff_read_lines, or patchdiff_find_occurrences:
{
"target_file_id": "11111111-1111-4111-8111-111111111111",
"expected_target_version": 2,
"expected_current_hash": null,
"intent": "Insert header note and rename setting.",
"operations": [
{
"op": "span",
"start": 0,
"end": 0,
"expected_text": "",
"expected_after": "# Settings\n",
"replace": "Note: Managed by PatchDIFF\n"
},
{
"op": "match",
"match": "alpha",
"replace": "beta"
}
],
"client_request_id": "req-ops-v2"
}
Operation Rules & Runtime Profiles¶
- Requires top-level
intentand at least one state guard (expected_target_versionand/orexpected_current_hash). op: "match"carries the same fields and selector rules aspatchdiff-tx/1edit items.op: "span"requires non-boolean integerstartandend(0 <= start <= end <= len(buffer)in sequential canonical LF code points), exactexpected_text(buffer[start:end] == canonicalize_text(expected_text)), andreplace.- Empty-span insertion uses
start == endwithexpected_text: "". - Optional
expected_beforeandexpected_afterverify exact adjacent text (buffer[start-len(expected_before):start]andbuffer[end:end+len(expected_after)]) so off-by-N insertion offsets reject deterministically (span_expected_text_mismatch). - Runtime profile selection:
patchdiff-mvp-0.3.0when no span carriesexpected_beforeorexpected_after.patchdiff-mvp-0.5.0when any span carriesexpected_beforeorexpected_after.- See MCP tool contracts for complete field schemas.
3. Guarded Sole Whole-Target Replacement (patchdiff-tx/3)¶
Wire Representation & Canonical Record¶
Submitted over MCP via patchdiff_submit_replacement for explicit whole-document replacement under mandatory state guards. Stored as canonical JSON under immutable runtime profile patchdiff-mvp-0.4.0:
{
"expected_current_hash": null,
"expected_target_version": 0,
"format": "patchdiff-tx/3",
"intent": "Explicit whole-target replacement",
"operations": [
{
"op": "replace_all",
"replace": "new text\n"
}
],
"target_file_id": "11111111-1111-4111-8111-111111111111"
}
Rules & Service Admission Boundary¶
- Exactly one operation with
{"op": "replace_all", "replace": "..."}is permitted. Nomatch,span, selectors, or mixed operations are allowed inpatchdiff-tx/3(andpatchdiff-tx/2continues to refusereplace_all). - At least one valid state guard (
expected_target_versionand/orexpected_current_hash) is required; both top-level guard keys are persisted with raw values (includingnull) through the same canonical JSON identity rule. - Default
change_idischg_001; defaultriskis"low"; one typed ledger row (operation: "replace_all") covers the full original span[0, len(buffer)). - Fresh service admission limit: Fresh
patchdiff-tx/3replacement submissions are bounded to<= 1,000,000Unicode code points and<= 1,048,576UTF-8 bytes (both raw input and canonical LF result). Replay bypasses fresh admission limits so historical accepted logs always replay.
4. Typed Format Decision Matrix & Measured Payload Overhead¶
Choosing the Right Typed Format¶
| Format | MCP Tool | Runtime Profile | Primary Use Case | Guard Requirement | Typical Wire Overhead (Excluding Payload) |
|---|---|---|---|---|---|
patchdiff-tx/1 |
patchdiff_submit_edits |
patchdiff-mvp-0.2.2 |
Default exact MATCH/REPLACE edits with optional selectors (occurrence_index, anchor_before, anchor_after) |
Required (version and/or hash) |
~140 B (151 B total for 1 tiny edit; 259 B for 5 small edits) |
patchdiff-tx/2 |
patchdiff_submit_operations |
patchdiff-mvp-0.3.0 (0.5.0 with span context) |
Coordinate-known span replacements, descending multi-span edits from a single read, and verified insertions (expected_before/expected_after) |
Required (version and/or hash) |
~165 B |
patchdiff-tx/3 |
patchdiff_submit_replacement |
patchdiff-mvp-0.4.0 |
Explicit guarded whole-document replacement (replace_all, up to 1,048,576 UTF-8 bytes / 1,000,000 code points, raw and canonical) |
Required (version and/or hash) |
229 B MCP arguments JSON framing (232 B arguments / 290 B persisted record / 341 B complete tools/call line for a 3-byte replacement) |
Deterministic Byte Accounting (backend/tests/verification/offline_eval.py)¶
Measured UTF-8 byte sizes across standard typed editing scenarios (PYTHONPATH=backend python backend/tests/verification/offline_eval.py):
| Scenario | patchdiff-tx/1 Minimal (typed_min, Wire / Persisted) | patchdiff-tx/1 Matched Prose (typed_match, Wire / Persisted) | Verdict & Delimiter Safety |
| --- | --- | --- | --- | --- | --- |
| 1 tiny edit | 151 B / 206 B | 327 B / 382 B | accepted |
| 5 small edits | 259 B / 314 B | 1,139 B / 1,194 B | accepted |
| 1 edit, 2 KB span | 4,441 B / 4,496 B | 4,617 B / 4,672 B | accepted |
| Quotes + backslashes + Unicode | 172 B / 227 B | 348 B / 403 B | accepted |
| Payload has a delimiter line (=======) | 162 B / 217 B | 338 B / 393 B | accepted (eliminates .pdcp delimiter-line collisions; JSON syntax remains protocol-validated) |
Measured patchdiff-tx/3 (patchdiff_submit_replacement) UTF-8 byte sizes:
patchdiff-tx/3 Case |
Raw / Canonical Replacement (UTF-8 B) |
MCP Tool arguments JSON (UTF-8 B) |
Persisted Record Bytes (pdcp_content) |
Complete JSON-RPC tools/call Line Bytes |
Verdict |
|---|---|---|---|---|---|
Guarded whole-target rewrite ("old" -> "new") |
3 B / 3 B |
232 B |
290 B |
341 B |
accepted |
Empty target replacement ("" -> "new") |
3 B / 3 B |
232 B |
290 B |
341 B |
accepted |
Empty replacement (clear target, "old" -> "") |
0 B / 0 B |
229 B |
287 B |
338 B |
accepted |
Unicode, quotes, CRLF normalization ('say "hi" \\\r\nš') |
16 B raw / 15 B LF |
250 B |
308 B |
359 B |
accepted |
Whole-target 2 KB rewrite (40 lines, 39 \n chars) |
2,109 B / 2,109 B |
2,377 B |
2,435 B |
2,486 B |
accepted |
5. Shared Lowering and Applier Semantics¶
The structural batch tool mode wraps ordered tx/2 operation groups in the internal
durable format patchdiff-observed-batch/1, runtime patchdiff-observed-batch-r1.
It is not another public tool or operation type. Each group lowers through the
same tx/2 parser and ParsedPDCP and executes the existing applier against its
own original slice. The canonical envelope additionally records target/bundle,
both guards, intent, editing cycle and immutable authoritative scope evidence.
Original operation order, group/operation indices, scope hashes/bounds and IDs
support deterministic replay without discovery. Complete batch specification.
All supported transaction formats lower into ParsedPDCP:
@dataclass(frozen=True, slots=True)
class ParsedPDCP:
header: PDCPHeader
changes: tuple[PDCPChangeBlock, ...]
The applier apply_pdcp_transaction(target_content, parsed):
1. Normalizes target content and payloads to LF.
2. Checks header preconditions (if present).
3. Simulates each change sequentially against the in-memory buffer:
- For match operations: locates all exact spans (overlapping spans counted in patchdiff-mvp-0.2.2), resolves disambiguation to exactly one span, slices the buffer, and splices replace immediately.
- For tx/2 span operations: verifies 0 <= start <= end <= len(buffer), buffer[start:end] == expected_text, and optional expected_before / expected_after adjacency guards before splicing replace.
- For the tx/3 sole replace_all operation: replaces [0, len(buffer)) in one step.
- Computes authoritative change_facts (span_start, span_end, char counts, SHA-256 digests).
4. Computes final after_hash.
5. Emits AcceptedCoreResult on full success or RejectedCoreResult on any failure without mutating the target.