F4 SLICE-5: host E2E (propose/accept/reject/persist/stale/coexist) + seam fix: event-level net-effect matching survives host word-diff splitting (INV-9) (#12)
The accept E2E exposed a latent F3 limitation: VS Code word-diffs one applied WorkspaceEdit into several minimal hunks when old/new share interior tokens, so the registry's per-hunk exact match missed and seam edits fell through as fragmented human spans. PendingEditRegistry.match is replaced by matchEvent (all hunks inside the registered full range + equal net delta — one applyEdit is one change event). Plan AMENDMENT 1. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+37
-10
@@ -8,26 +8,53 @@ const AGENT: Provenance = {
|
||||
};
|
||||
|
||||
describe("PendingEditRegistry (INV-9)", () => {
|
||||
it("matches and consumes an exact registration", () => {
|
||||
it("matches and consumes a single-hunk event equal to the registration", () => {
|
||||
const reg = new PendingEditRegistry();
|
||||
reg.register({ docPath: "d.md", start: 3, end: 7, newText: "new", provenance: AGENT, turnId: "t1" });
|
||||
const hit = reg.match("d.md", { start: 3, end: 7, text: "new" });
|
||||
reg.register({
|
||||
docPath: "d.md", start: 3, end: 7, newText: "new", provenance: AGENT, turnId: "t1",
|
||||
full: { start: 3, end: 7, newLength: 3 },
|
||||
});
|
||||
const hit = reg.matchEvent("d.md", [{ start: 3, end: 7, newLength: 3 }]);
|
||||
expect(hit?.turnId).toBe("t1");
|
||||
expect(reg.match("d.md", { start: 3, end: 7, text: "new" })).toBeNull();
|
||||
expect(reg.matchEvent("d.md", [{ start: 3, end: 7, newLength: 3 }])).toBeNull();
|
||||
});
|
||||
it("does not match a different doc, range, or text (human typing stays human)", () => {
|
||||
it("matches a HOST-SPLIT event: several hunks inside the full range with the same net delta", () => {
|
||||
// The host word-diffs one applied replace into multiple minimal hunks
|
||||
// (observed on F4 accept) — per-hunk equality misses; net-effect matches.
|
||||
const reg = new PendingEditRegistry();
|
||||
reg.register({
|
||||
docPath: "d.md", start: 10, end: 50, newText: "x".repeat(45), provenance: AGENT, turnId: "t2",
|
||||
full: { start: 10, end: 50, newLength: 45 },
|
||||
});
|
||||
const hit = reg.matchEvent("d.md", [
|
||||
{ start: 12, end: 20, newLength: 9 },
|
||||
{ start: 25, end: 30, newLength: 6 },
|
||||
{ start: 40, end: 48, newLength: 11 },
|
||||
]);
|
||||
expect(hit?.turnId).toBe("t2");
|
||||
});
|
||||
it("does not match a different doc, hunks outside the full range, or a different delta", () => {
|
||||
const reg = new PendingEditRegistry();
|
||||
reg.register({
|
||||
docPath: "d.md", start: 3, end: 7, newText: "new", provenance: AGENT,
|
||||
full: { start: 3, end: 7, newLength: 3 },
|
||||
});
|
||||
expect(reg.matchEvent("other.md", [{ start: 3, end: 7, newLength: 3 }])).toBeNull();
|
||||
expect(reg.matchEvent("d.md", [{ start: 3, end: 8, newLength: 4 }])).toBeNull();
|
||||
expect(reg.matchEvent("d.md", [{ start: 3, end: 7, newLength: 4 }])).toBeNull();
|
||||
expect(reg.matchEvent("d.md", [])).toBeNull();
|
||||
});
|
||||
it("falls back to the minimized range when no full extent was registered", () => {
|
||||
const reg = new PendingEditRegistry();
|
||||
reg.register({ docPath: "d.md", start: 3, end: 7, newText: "new", provenance: AGENT });
|
||||
expect(reg.match("other.md", { start: 3, end: 7, text: "new" })).toBeNull();
|
||||
expect(reg.match("d.md", { start: 3, end: 8, text: "new" })).toBeNull();
|
||||
expect(reg.match("d.md", { start: 3, end: 7, text: "neww" })).toBeNull();
|
||||
expect(reg.matchEvent("d.md", [{ start: 3, end: 7, newLength: 3 }])).not.toBeNull();
|
||||
});
|
||||
it("unregister removes a failed application", () => {
|
||||
const reg = new PendingEditRegistry();
|
||||
const p = { docPath: "d.md", start: 0, end: 0, newText: "x", provenance: AGENT };
|
||||
reg.register(p);
|
||||
reg.unregister(p);
|
||||
expect(reg.match("d.md", { start: 0, end: 0, text: "x" })).toBeNull();
|
||||
expect(reg.matchEvent("d.md", [{ start: 0, end: 0, newLength: 1 }])).toBeNull();
|
||||
});
|
||||
it("unregister reports whether the registration was still pending", () => {
|
||||
const reg = new PendingEditRegistry();
|
||||
@@ -35,7 +62,7 @@ describe("PendingEditRegistry (INV-9)", () => {
|
||||
reg.register(p);
|
||||
expect(reg.unregister(p)).toBe(true);
|
||||
reg.register(p);
|
||||
reg.match("d.md", { start: 0, end: 0, text: "x" });
|
||||
reg.matchEvent("d.md", [{ start: 0, end: 0, newLength: 1 }]);
|
||||
expect(reg.unregister(p)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user