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
|
* 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
|
* mutates only its own section against a FRESH load, so ThreadController and
|
||||||
* AttributionController never clobber each other. After the mutation, anchors
|
* AttributionController never clobber each other. After the mutation, anchors
|
||||||
* referenced by neither threads nor attributions are pruned (proposals are
|
* referenced by no thread, attribution, OR proposal are pruned (threads,
|
||||||
* still empty in F3 — revisit in F4). `mutate` MUST be synchronous: the
|
* 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.
|
* read-modify-write (and the self-write mark) completes within this call.
|
||||||
*/
|
*/
|
||||||
update(docPath: string, mutate: (artifact: Artifact) => void): Artifact {
|
update(docPath: string, mutate: (artifact: Artifact) => void): Artifact {
|
||||||
@@ -71,6 +71,7 @@ export class CoauthorStore {
|
|||||||
const referenced = new Set<string>([
|
const referenced = new Set<string>([
|
||||||
...artifact.threads.map((t) => t.anchorId),
|
...artifact.threads.map((t) => t.anchorId),
|
||||||
...artifact.attributions.map((a) => a.anchorId),
|
...artifact.attributions.map((a) => a.anchorId),
|
||||||
|
...artifact.proposals.map((p) => p.anchorId),
|
||||||
]);
|
]);
|
||||||
for (const id of Object.keys(artifact.anchors)) {
|
for (const id of Object.keys(artifact.anchors)) {
|
||||||
if (!referenced.has(id)) delete artifact.anchors[id];
|
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 { describe, expect, it } from "vitest";
|
||||||
import {
|
import {
|
||||||
emptyArtifact,
|
emptyArtifact,
|
||||||
@@ -5,6 +8,7 @@ import {
|
|||||||
type Artifact,
|
type Artifact,
|
||||||
type Proposal,
|
type Proposal,
|
||||||
} from "../src/model";
|
} from "../src/model";
|
||||||
|
import { CoauthorStore } from "../src/store";
|
||||||
|
|
||||||
const agent = {
|
const agent = {
|
||||||
kind: "agent" as const,
|
kind: "agent" as const,
|
||||||
@@ -44,3 +48,28 @@ describe("Proposal model (spec §6.3, INV-13)", () => {
|
|||||||
expect(serializeArtifact(again)).toBe(serializeArtifact(a));
|
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