Q30: documents keep every version

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude Opus 5.5 committed 2026-10-08 06:10:25 -04:00
1 parent 794fa372c9
commit 4c475e5ceb
9 files changed
+183 -23

No files matched your search

+15 -4
View File
@@ -62,21 +62,32 @@ describe("agent document tools", () => {
const docId = /doc_id=(doc_\w+)/.exec(created)![1];
h.run("create_document", { title: "Q3 Report", doc_type: "report", content: "Revenue grew." });
expect(h.run("list_documents", { query: "booking" })).toMatch(new RegExp(`^Booking PRD \\(doc_id=${docId}, prd, draft, \\d+ chars`));
expect(h.run("list_documents", { query: "booking" })).toMatch(new RegExp(`^Booking PRD \\(doc_id=${docId}, prd, draft, v1, \\d+ chars`));
expect(h.run("list_documents", { doc_type: "report" })).toMatch(/^Q3 Report \(doc_id=doc_\w+, report/);
expect(h.run("list_documents", { query: "kubernetes" })).toBe('No documents match "kubernetes".');
const page = h.run("read_document", { doc_id: docId });
expect(page.startsWith("Booking PRD (prd, draft)\n\n[lines 1-")).toBe(true);
expect(page.startsWith("Booking PRD (prd, draft, v1 of 1)\n\n[lines 1-")).toBe(true);
expect(page).toContain("[more: pass start_line=");
expect(h.run("read_document", { doc_id: docId, search: "Requirement 2999:" })).toContain("2999: Requirement 2999: booking flow");
expect(h.run("update_document", { doc_id: docId, content: "Requirement 1: rebooking", reason: "Scope cut" })).toBe(`Updated document "Booking PRD" (doc_id=${docId}).`);
expect(h.run("read_document", { doc_id: docId })).toBe("Booking PRD (prd, draft)\n\nRequirement 1: rebooking");
expect(h.run("update_document", { doc_id: docId, content: "Requirement 1: rebooking", reason: "Scope cut" })).toBe(`Updated document "Booking PRD" to v2 (doc_id=${docId}).`);
expect(h.run("read_document", { doc_id: docId })).toBe("Booking PRD (prd, draft, v2 of 2)\n\nRequirement 1: rebooking");
expect(h.run("read_document", { doc_id: docId, version: 1, search: "Requirement 2999:" })).toContain("v1 of 2");
expect(h.run("read_document", { doc_id: docId, version: 1, search: "Requirement 2999:" })).toContain("2999: Requirement 2999: booking flow");
expect(h.run("read_document", { doc_id: docId, history: true })).toMatch(/^v1 .* by agent agt_\w+ — created \(\d+ chars\)\nv2 .* by agent agt_\w+ — Scope cut \(24 chars\)$/);
expect(h.run("read_document", { doc_id: docId, version: 3 })).toBe('ERROR: "version" must be a whole number from 1 to 2.');
expect(h.run("read_document", { doc_id: docId, history: "yes" })).toBe('ERROR: "history" must be true or false.');
expect(h.run("read_document", { doc_id: docId, history: true, version: 0 })).toBe("ERROR: history=true lists versions; it takes no version or paging arguments.");
expect(h.run("read_document", { doc_id: docId, history: true, search: 123 })).toBe('ERROR: "search" is required.');
expect(h.run("update_document", { doc_id: docId, content: "x" })).toBe('ERROR: "reason" is required.');
expect(h.run("read_document", { doc_id: "doc_missing" })).toBe('ERROR: No document with doc_id "doc_missing".');
expect(h.run("read_document", { doc_id: docId, search: 123 })).toBe('ERROR: "search" is required.');
expect(h.run("list_documents", { doc_type: "novel" })).toBe('ERROR: "doc_type" must be one of: prd, spec, memo, report, runbook, proposal, policy, guide.');
h.connection.db.prepare("delete from documents where id = ?").run(docId);
expect(h.run("read_document", { doc_id: docId, history: true }).split("\n")).toHaveLength(2);
expect(h.run("read_document", { doc_id: docId, version: 1, line_count: 1 })).toMatch(/^Booking PRD \(deleted, draft, v1 of 2\)/);
h.connection.close();
});
+48
View File
@@ -0,0 +1,48 @@
import { randomUUID } from "node:crypto";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { DatabaseSync } from "node:sqlite";
import { describe, expect, test } from "vitest";
import { DatabaseConnection } from "../../src/main/database/connection";
import { OrganizationService } from "../../src/main/services/organization-service";
import { DocumentService } from "../../src/main/services/document-service";
import { documentVersionsSchema } from "../../src/main/database/migrations/065-document-versions";
describe("document versions", () => {
test("every update is a new version; earlier versions stay intact, even after deletion", () => {
const connection = new DatabaseConnection(join(tmpdir(), `ac-docver-${randomUUID()}.db`));
let orgId = "";
connection.transaction(() => {
orgId = new OrganizationService(connection.db).createOrganization({ organizationName: "V", employeeName: "Y", mission: "m", goal: "g" });
});
const documents = new DocumentService(connection.db);
const id = documents.create({ orgId, title: "Spec", docType: "spec", content: "v1 body", authorAgentId: null, reviewerAgentId: null, projectId: null, goalId: null, taskId: null });
expect(documents.update({ orgId, documentId: id, title: "Spec", content: "v2 body", status: "review", reason: "Added API", changedByType: "employee", changedById: null })).toBe(2);
expect(() => documents.update({ orgId, documentId: id, title: "Spec", content: "v3", status: "review", reason: " ", changedByType: "employee", changedById: null }))
.toThrow("A document update needs a reason.");
expect(documents.versions(orgId, id).map((v) => [v.version, v.content, v.status, v.reason])).toEqual([
[1, "v1 body", "draft", "created"],
[2, "v2 body", "review", "Added API"],
]);
expect(connection.db.prepare("select version, content from documents where id = ?").get(id)).toEqual({ version: 2, content: "v2 body" });
documents.delete({ orgId, documentId: id });
expect(documents.versions(orgId, id)).toHaveLength(2);
connection.close();
});
test("the migration records each existing document as version 1", () => {
const db = new DatabaseSync(join(tmpdir(), `ac-docmig-${randomUUID()}.db`));
db.exec(`
create table organizations(id text primary key);
create table schema_migrations(version integer primary key, name text not null, applied_at text not null);
create table documents(id text primary key, org_id text, title text, content text, status text, author_agent_id text, created_at text);
insert into organizations values ('org_1');
insert into documents values ('doc_1', 'org_1', 'T', 'C', 'approved', 'agt_1', '2026-01-01T00:00:00.000Z');
`);
db.exec(documentVersionsSchema);
expect(db.prepare("select document_id, version, content, status, changed_by_type, changed_by_id, reason from document_versions").all())
.toEqual([{ document_id: "doc_1", version: 1, content: "C", status: "approved", changed_by_type: "agent", changed_by_id: "agt_1", reason: "created" }]);
expect(db.prepare("select version from documents").get()).toEqual({ version: 1 });
db.close();
});
});
+1 -1
View File
@@ -24,7 +24,7 @@ describe("domain services", () => {
const connection = createConnection();
const rows = connection.db.prepare("select version from schema_migrations order by version asc").all() as Array<{ version: number }>;
expect(rows.map((row) => row.version)).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64]);
expect(rows.map((row) => row.version)).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65]);
connection.close();
});