F4 SLICE-1: anchor prune counts proposal anchorIds (closes store.ts F4 TODO) (#12)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+3
-2
@@ -61,8 +61,8 @@ export class CoauthorStore {
|
||||
* Read-modify-write for sidecar co-ownership (spec F3 §6.2): each controller
|
||||
* mutates only its own section against a FRESH load, so ThreadController and
|
||||
* AttributionController never clobber each other. After the mutation, anchors
|
||||
* referenced by neither threads nor attributions are pruned (proposals are
|
||||
* still empty in F3 — revisit in F4). `mutate` MUST be synchronous: the
|
||||
* referenced by no thread, attribution, OR proposal are pruned (threads,
|
||||
* attributions, and proposals all keep their anchors — F4). `mutate` MUST be synchronous: the
|
||||
* read-modify-write (and the self-write mark) completes within this call.
|
||||
*/
|
||||
update(docPath: string, mutate: (artifact: Artifact) => void): Artifact {
|
||||
@@ -71,6 +71,7 @@ export class CoauthorStore {
|
||||
const referenced = new Set<string>([
|
||||
...artifact.threads.map((t) => t.anchorId),
|
||||
...artifact.attributions.map((a) => a.anchorId),
|
||||
...artifact.proposals.map((p) => p.anchorId),
|
||||
]);
|
||||
for (const id of Object.keys(artifact.anchors)) {
|
||||
if (!referenced.has(id)) delete artifact.anchors[id];
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import { mkdtempSync, rmSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
emptyArtifact,
|
||||
@@ -5,6 +8,7 @@ import {
|
||||
type Artifact,
|
||||
type Proposal,
|
||||
} from "../src/model";
|
||||
import { CoauthorStore } from "../src/store";
|
||||
|
||||
const agent = {
|
||||
kind: "agent" as const,
|
||||
@@ -44,3 +48,28 @@ describe("Proposal model (spec §6.3, INV-13)", () => {
|
||||
expect(serializeArtifact(again)).toBe(serializeArtifact(a));
|
||||
});
|
||||
});
|
||||
|
||||
describe("CoauthorStore prune with proposals (spec §6.3)", () => {
|
||||
it("retains anchors referenced only by proposals; prunes them after removal", () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), "cowriting-prune-"));
|
||||
try {
|
||||
const store = new CoauthorStore(dir);
|
||||
store.update("docs/x.md", (a) => {
|
||||
a.anchors["a_p"] = {
|
||||
fingerprint: { text: "t", before: "", after: "", lineHint: 0 },
|
||||
};
|
||||
a.proposals.push({
|
||||
id: "pr_1", anchorId: "a_p", replacement: "r", author: agent,
|
||||
createdAt: "2026-06-10T00:00:00.000Z",
|
||||
});
|
||||
});
|
||||
expect(store.load("docs/x.md")!.anchors["a_p"]).toBeDefined();
|
||||
store.update("docs/x.md", (a) => {
|
||||
a.proposals = a.proposals.filter((p) => p.id !== "pr_1");
|
||||
});
|
||||
expect(store.load("docs/x.md")!.anchors["a_p"]).toBeUndefined();
|
||||
} finally {
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user