Skip to content

Commit d993db5

Browse files
committed
docs: align HPATCH contracts with current grammar
Update documentation and tests for destination-based `add` mutations, EOF appends, and target-based terminology. Remove obsolete HPATCH/1 state, compatibility, and recovery payload references.
1 parent dc2cdff commit d993db5

13 files changed

Lines changed: 38 additions & 73 deletions

doc/architecture/index.md

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,7 @@ The workspace owner retains each touched file's invocation-original identity and
3333
current logical path, pending lifecycle action, and ordered pending splices. Returning to
3434
a file reuses those values. A successful script orders the splices once and renders one
3535
original-to-final content value. No command materializes an intermediate baseline, and
36-
the engine owns no selection, cursor, clipboard, generation, mutable shadow buffer, or
37-
script-level commit state.
36+
all targets continue to resolve against the invocation-original content.
3837

3938
One shared pure verified-row owner computes and renders `LINE:HASH` identity for routed
4039
reads under `REQ-READ-001`, target validation, repair context, and final-state previews.
@@ -97,10 +96,9 @@ and moved paths, last-edit summaries, net file counts, empty-file rows, and cont
9796
before any external effect. Apply and translation paths share this projection.
9897

9998
The projection borrows completed editor content during rendering and retains no additional
100-
original or final content copy, routed-read history, translated patch, word diff, selection,
101-
cursor, clipboard, target, generation, or resume state. Its rows describe only successful
102-
completed state. The router consumes the rendered report and does not compute coordinates,
103-
hashes, command extents, or formatting adjustments.
99+
original or final content copy. Its rows describe only successful completed state. The router
100+
consumes the rendered report and does not compute coordinates, hashes, command extents, or
101+
formatting adjustments.
104102

105103
## CTR-BOUNDARY-001 — Filesystem and output boundary
106104

doc/brief.md

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,8 @@
33
## Problem
44

55
Agents describe edits with line-oriented diffs that repeat old content and unchanged
6-
context. HPATCH/1 reduces that repetition, but its separate selector, cursor, clipboard,
7-
and generation state makes simple mutations multi-command programs. A failed selector or
8-
state precondition discards the complete atomic script and can consume more output and
9-
model turns than the successful encoding saves.
6+
context. Repeating that context increases output, while ambiguous mutation boundaries can
7+
discard a complete atomic script and consume extra model turns.
108

119
## Outcome
1210

@@ -60,8 +58,8 @@ wall time must remain close to control.
6058
- Private hgrep commands accept familiar ripgrep matching, context, and file-selection arguments
6159
and emit complete UTF-8 result rows as copyable path-and-`LINE:HASH` results.
6260
- Mutation-owned complete-line, inclusive line-range, and anchored literal targets.
63-
- Replacement, insertion immediately before or after a target, and deletion without a
64-
separate selection, cursor, or clipboard protocol.
61+
- Replacement, insertion immediately before a line or text destination, EOF append, and
62+
deletion.
6563
- Optional positive multiplicity for repeated anchored literal mutations.
6664
- File creation, movement, and deletion.
6765
- One immutable baseline per touched existing file for the complete script; overlapping
@@ -114,10 +112,6 @@ wall time must remain close to control.
114112

115113
## Non-goals
116114

117-
- Compatibility aliases or legacy support for `tsel`, `rsel`, `copy`, `cut`, `paste`, or
118-
script-level `commit`.
119-
- A persistent selection, cursor, clipboard, mutable shadow buffer, undo history, or
120-
resume protocol.
121115
- Content movement without re-emitting the moved content; `mv` moves complete files only.
122116
- Selecting content introduced earlier in the same script. Dependent edits use a later
123117
inspected invocation.
@@ -139,7 +133,6 @@ wall time must remain close to control.
139133

140134
## Constraints
141135

142-
- The HPATCH/2 grammar replaces HPATCH/1; compatibility is not required.
143136
- A routed row reference combines a positive one-based logical-line hint with a lowercase
144137
four-digit content hash. The hash verifies complete logical-line bytes, including
145138
indentation and excluding its terminator. A shifted row resolves only when the hash is unique.

doc/codex-router-e2e.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ upgrade before changing routing assumptions.
1010
- A Codex session started in a directory inside this Git repository declares the enclosing repository root as its base directory. A nested `-C` directory does not create another hpatch base directory.
1111
- In the Codex CLI 0.145.0 observation, `--add-dir` added a sandbox-writable root but did not add that directory to the `x-codex-turn-metadata` `workspaces` map. This metadata observation predates directory-based translation and must be rerun after Codex or router changes; it is not evidence of router confinement.
1212
- A standalone non-repository `/tmp` workdir, even with `--skip-git-repo-check`, supplied no usable base directory in that observation. The router now forwards such a turn; without a selected directory, absolute hpatch operands work and relative operands reject without falling back to the router process CWD.
13-
- Hpatch has no base-directory selection command. Scripts contain ordinary hpatch commands only; do not introduce `workspace_id`, workspace lists, dynamic workspace developer messages, patch rebasing, or multi-directory routing unless a real Codex request demonstrates that requirement.
13+
- Hpatch receives its base directory outside the script. Do not introduce `workspace_id`, workspace lists, dynamic workspace developer messages, patch rebasing, or multi-directory routing unless a real Codex request demonstrates that requirement.
1414

1515
The router validates and canonicalizes the optional declared base-directory string for normal server-side translation. It does not open a pinned engine root, impose filesystem confinement, or check directory identity before and after evaluation. Relative, `..`, symlink, and absolute operands use ordinary host path resolution when a directory is selected. Without one, only absolute operands are valid and router cwd is never used. Current Codex emits zero or one workspace entry. Codex remains responsible for permission checks when it executes the generated patch carrier.
1616

doc/spec/comparison.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ The comparison program emulates at least these equivalent edit scenarios:
77
1. Replace a small expression on a long line.
88
2. Delete the last literal occurrence on a line.
99
3. Duplicate a multi-line implementation block.
10-
4. Apply independent edits whose selectors retain original baseline coordinates.
10+
4. Apply independent edits whose targets retain original baseline coordinates.
1111
5. Create a file with one complete-content write.
1212
6. Edit and move a file, and delete another file, in one script.
1313

doc/spec/interface.md

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -576,9 +576,8 @@ line range text-single text-multiple
576576
Targetless `type VALUE` initialization has no target counter. Anchored and unanchored text
577577
targets use the same counters. A text target with omitted count or count one is
578578
`text-single`; an explicit count intended to exceed one is
579-
`text-multiple`, including an invalid multiple count. Unsupported HPATCH/1 commands and
580-
unknown future commands are syntax failures but do not receive supported-command or
581-
target attribution.
579+
`text-multiple`, including an invalid multiple count. Unknown commands are syntax failures
580+
but do not receive supported-command or target attribution.
582581

583582
Terminal command errors carry stable internal reason identifiers grouped as:
584583

@@ -650,8 +649,7 @@ Acceptance:
650649
5. The input-overhead table has no plugin child rows. Net added input includes the signed difference
651650
between current and stock tool-result estimates.
652651
6. The six supported hpatch command counters and four target counters reconcile with
653-
aggregate command attempts and errors. No selector, clipboard, editor-generation, or
654-
script-level commit counter remains.
652+
aggregate command attempts and errors.
655653
7. Every definition-bearing request increments the definition-request counter, while the exact
656654
installed tool collection, its reconciling per-tool breakdown, and the displaced baseline
657655
definition accumulate only once per distinct session. An absent session or definition
@@ -680,8 +678,8 @@ Acceptance:
680678

681679
## REQ-SCRIPT-001 — HPATCH/2 script grammar
682680

683-
HPATCH/2 replaces HPATCH/1. There are no compatibility aliases. Outside a heredoc body,
684-
blank lines are ignored and every other physical line begins exactly one command:
681+
Outside a heredoc body, blank lines are ignored and every other physical line begins exactly
682+
one command:
685683

686684
```text
687685
in PATH
@@ -765,8 +763,7 @@ commands are invalid.
765763

766764
Acceptance:
767765

768-
1. Every accepted nonblank command is one of the six public commands; `type-`, `type+`,
769-
`tsel`, `rsel`, `copy`, `cut`, `paste`, `del`, and script-level `commit` are syntax errors.
766+
1. Every accepted nonblank command is one of the six public commands.
770767
2. Line, range, anchored text, and unanchored text targets parse without a separate selection command, and inline
771768
replacement values remain distinguishable from a text target's quoted literal.
772769
3. Anchored and unanchored text targets accept JSON-escaped LF and exact multiline or
@@ -794,7 +791,7 @@ denote a different target from the retained command. Recovery
794791
has no operation keyword and cannot change operations, values, heredoc bodies, command count or
795792
order, or file context. Target parsing and script rebuilding preserve the public target literal's
796793
exact decoded bytes, including escaped LF, and enforce the same empty, CR, and control
797-
exclusions. There are no compatibility aliases, sentinel lines, or `accept` operation.
794+
exclusions.
798795

799796
The router owns recovery grammar, parsing, handle resolution, ancestry, worktree isolation,
800797
dispatch, replay, diagnostics, and reevaluation. Every command handle resolves against the latest
@@ -842,11 +839,10 @@ Acceptance:
842839
7. Ordinary mutation-leading hpatch scripts are never detected as recovery.
843840
8. Per-attempt telemetry remains individual, while persistent metrics count every chain payload and one
844841
final or failed comparator.
845-
9. Removed `target`, `drop`, operation, value, value-row, and structural forms are syntax errors, not compatibility paths.
846-
10. One payload can correct multiple distinct command targets atomically without changing any other command field.
847-
11. A target correction can retarget an anchored or unanchored mutation to exact multiline
842+
9. One payload can correct multiple distinct command targets atomically without changing any other command field.
843+
10. A target correction can retarget an anchored or unanchored mutation to exact multiline
848844
text with escaped LF; rebuilding preserves the target bytes and public control exclusions.
849-
12. A target correction that denotes the retained target rejects before root reevaluation,
845+
11. A target correction that denotes the retained target rejects before root reevaluation,
850846
including an explicit default occurrence count, an equivalent quoted escape spelling, or a
851847
range whose two endpoints are the retained single row; the retained baseline and handles remain usable.
852848

@@ -932,8 +928,7 @@ still stop at their authoritative boundary.
932928

933929
Resolution produces one nonempty baseline span for a line or range and one or more
934930
nonempty spans for a text target. A mutation over multiple spans validates and registers
935-
all of them or none. There is no persistent selection, cursor, clipboard, shadow buffer,
936-
generation, or resume state.
931+
all of them or none.
937932

938933
Acceptance:
939934

@@ -981,8 +976,8 @@ Content movement requires emitting the destination content; `mv` moves whole fil
981976

982977
Acceptance:
983978

984-
1. Replacement, before insertion, after insertion, and deletion produce the specified
985-
result directly from their targets without a selection command.
979+
1. Replacement, deletion, insertion before a line or text destination, and EOF append
980+
produce the specified result directly from their targets or destination.
986981
2. Multi-match text mutation applies the same action to every requested match or none.
987982
3. Disjoint edits are script-order independent except for deliberate insertions at the
988983
same boundary, which retain script order.
@@ -1271,8 +1266,7 @@ Persistent guidance teaches this workflow:
12711266

12721267
Acceptance:
12731268

1274-
1. A model can choose and encode every HPATCH/2 operation from the persistent guidance without learning
1275-
HPATCH/1 state concepts.
1269+
1. A model can choose and encode every HPATCH/2 operation from the persistent guidance.
12761270
2. The installed prompt contains the central guidance exactly once and omits the pinned stock
12771271
apply_patch, rg, and exec_command instructions.
12781272
3. A configured legacy or marked customized prompt retains content before and after the owned

hooks_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -370,8 +370,8 @@ func TestErrorAndOutcomeHooksReceiveAttemptMetadata(t *testing.T) {
370370
}
371371

372372
evaluatedScript := "new note.txt\ntype \"ok\"\n"
373-
recoveryPayload := "C2:abcd target 2:bbbb"
374-
delta := "C2:abcd target: 1:aaaa -> 2:bbbb"
373+
recoveryPayload := "C2:abcd 2:bbbb"
374+
delta := "C2:abcd: 1:aaaa -> 2:bbbb"
375375
recoveryMetadata := AttemptMetadata{
376376
SessionID: "session-1",
377377
CorrelationID: "chain-1",

internal/router/dashboard_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ import (
77
"testing"
88
)
99

10-
func TestDashboardGainStateHasNoRemovedBlockTableReference(t *testing.T) {
10+
func TestDashboardGainStateHasNoBlockTableReference(t *testing.T) {
1111
if strings.Contains(string(dashboardHTML), "blocks:blocks.tbody") {
12-
t.Fatal("dashboard gain state references the removed block selector table")
12+
t.Fatal("dashboard gain state references an unsupported block table")
1313
}
1414
}
1515

internal/router/hpatch_exact_evidence_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ func TestHPatchExactEvidenceMalformedRecoveryIsRouterRejected(t *testing.T) {
120120
directory := t.TempDir()
121121
t.Setenv(hpatchExactEvidenceEnvironment, directory)
122122
transform, _, _, _ := newHPatchTestTransform(t, testTranslator(t, new(int)))
123-
payload := "C1:ffff drop\n"
123+
payload := "C1:ffff not-a-target\n"
124124
history, err := transform.translateRecovery("call-malformed", payload, nil)
125125
if err != nil {
126126
t.Fatal(err)

internal/router/hpatch_proxy_test.go

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -407,22 +407,6 @@ func TestHPatchPrepareRequestExposesEditToolsAndShell(t *testing.T) {
407407
if _, exists := request.fields["instructions"]; exists {
408408
t.Fatalf("prepareRequest added instructions: %s", request.fields["instructions"])
409409
}
410-
for _, obsoleteGuidance := range []string{
411-
"Repairing a rejected script:",
412-
"INDEX: COMMAND",
413-
`INDEX.ROW: "VALUE"`,
414-
": accept",
415-
} {
416-
if strings.Contains(exposed, obsoleteGuidance) {
417-
t.Fatalf("standalone hpatch description includes obsolete recovery guidance %q: %q", obsoleteGuidance, exposed)
418-
}
419-
}
420-
if strings.Contains(exposed, "type <<PATCH replacement or insertion consumes") {
421-
t.Fatalf("standalone hpatch description retains obsolete indexed recovery framing: %q", exposed)
422-
}
423-
if strings.Contains(exposed, "workspace_id") {
424-
t.Fatalf("standalone hpatch description retains workspace selection: %q", exposed)
425-
}
426410
if string(topTools[0]["future"]) != "true" {
427411
t.Fatalf("unrelated top-level tool changed: %#v", topTools[0])
428412
}
@@ -2067,7 +2051,7 @@ func TestHPatchHistoryDoesNotCrossWorkspacesSharingSessionIdentity(t *testing.T)
20672051
t.Fatalf("cross-workspace replay restored input %q", input)
20682052
}
20692053

2070-
history, err := second.translateRecovery("call-recovery", "C1:ffff drop", nil)
2054+
history, err := second.translateRecovery("call-recovery", "C1:ffff not-a-target", nil)
20712055
if err != nil {
20722056
t.Fatal(err)
20732057
}
@@ -2450,7 +2434,7 @@ func TestHPatchNonEvaluatorFailureDoesNotBecomeRecoveryBaseline(t *testing.T) {
24502434
if first.evaluatorRejected || strings.Contains(first.translationError, "Use hpatch without `in`") {
24512435
t.Fatalf("non-evaluator failure exposed recovery guidance: %+v", first)
24522436
}
2453-
second, err := transform.translateRecovery("call-2", "C1:ffff drop", nil)
2437+
second, err := transform.translateRecovery("call-2", "C1:ffff not-a-target", nil)
24542438
if err != nil {
24552439
t.Fatal(err)
24562440
}
@@ -2475,7 +2459,7 @@ func TestHPatchUnevaluatedRecoveryRunsOutcomeHookOnce(t *testing.T) {
24752459
t,
24762460
newManagedHPatchProxyWithDataDirectory(t, newInProcessHPatchTranslator(dataDirectory), dataDirectory),
24772461
)
2478-
payload := "C1:ffff drop"
2462+
payload := "C1:ffff not-a-target"
24792463
history, err := transform.translateRecovery("call-recovery", payload, nil)
24802464
if err != nil {
24812465
t.Fatal(err)

internal/router/hpatch_recovery_protocol_test.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,17 +107,16 @@ func TestRecoverScriptRetargetsMultilineLiteral(t *testing.T) {
107107
}
108108
}
109109

110-
func TestRecoverScriptRejectsNonTargetDuplicateStaleAndRemovedForms(t *testing.T) {
110+
func TestRecoverScriptRejectsNonTargetDuplicateStaleAndMalformedPayloads(t *testing.T) {
111111
script := "in file.go\n" + `type 1:aaaa "bad"` + "\n"
112112
commands := recoveryCommands(script)
113113
handle := commands[1].handle
114114
for _, payload := range []string{
115115
commands[0].handle + " 2:bbbb",
116116
"C2:ffff 2:bbbb",
117117
handle + " 2:bbbb\n" + handle + " 3:cccc",
118-
handle + " target 2:bbbb",
119-
handle + ` value "fixed"`,
120-
handle + " drop",
118+
handle + " not-a-target",
119+
handle + " 2:bbbb trailing",
121120
handle + " 2:bbbb",
122121
handle + "\t2:bbbb",
123122
handle + ` 2:bbbb "literal"`,

0 commit comments

Comments
 (0)