Q126: a file write and its record succeed or fail together
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
11cdc4af20
commit
5799a3561f
7 files changed
+119
-48
No files matched your search
@@ -66,4 +66,19 @@ describe("files shared in a private conversation", () => {
|
||||
rmSync(huge);
|
||||
connection.close();
|
||||
});
|
||||
|
||||
test("an import whose record fails leaves no stored copy behind", async () => {
|
||||
const connection = new DatabaseConnection(join(tmpdir(), `ac-import-fail-${randomUUID()}.db`));
|
||||
let orgId = "";
|
||||
connection.transaction(() => {
|
||||
orgId = new OrganizationService(connection.db).createOrganization({ organizationName: "Acme", employeeName: "Yang", mission: "m", goal: "g" });
|
||||
});
|
||||
const source = join(tmpdir(), `ac-import-fail-${randomUUID()}.md`);
|
||||
writeFileSync(source, "notes");
|
||||
connection.db.exec("create trigger registry_down before insert on file_objects begin select raise(abort, 'registry down'); end;");
|
||||
const artifacts = join(tmpdir(), `ac-import-fail-art-${randomUUID()}`);
|
||||
await expect(new FileService(connection.db, artifacts).importPath(orgId, source, null)).rejects.toThrow("registry down");
|
||||
expect(readdirSync(artifacts, { recursive: true }).filter((entry) => String(entry).endsWith(".md"))).toEqual([]);
|
||||
connection.close();
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,5 @@
|
||||
import { createHash, randomUUID } from "node:crypto";
|
||||
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { describe, expect, test } from "vitest";
|
||||
@@ -8,6 +8,7 @@ import { OrganizationService } from "../../src/main/services/organization-servic
|
||||
import { preserveRecordedFiles, recordWorkspaceFile, rescanLegacyFiles } from "../../src/main/services/files/file-object-store";
|
||||
import { ChatService } from "../../src/main/services/chat-service";
|
||||
import { readAttachmentTool } from "../../src/main/services/agent-attachment-tools";
|
||||
import { dispatchAgentTool } from "../../src/main/services/agent-tools";
|
||||
|
||||
describe("recorded workspace files", () => {
|
||||
test("a file id keeps the bytes it was recorded with after the workspace file is rewritten", () => {
|
||||
@@ -106,3 +107,46 @@ describe("files scanned before whole-file scanning", () => {
|
||||
connection.close();
|
||||
});
|
||||
});
|
||||
|
||||
describe("a file write whose record fails", () => {
|
||||
test("leaves the workspace and the artifact store as they were", async () => {
|
||||
const connection = new DatabaseConnection(join(tmpdir(), `ac-partial-${randomUUID()}.db`));
|
||||
const root = join(tmpdir(), `ac-partial-ws-${randomUUID()}`);
|
||||
mkdirSync(root, { recursive: true });
|
||||
let orgId = "";
|
||||
let ceoId = "";
|
||||
connection.transaction(() => {
|
||||
const organization = new OrganizationService(connection.db);
|
||||
orgId = organization.createOrganization({ organizationName: "Acme", employeeName: "Dana", mission: "m", goal: "g" });
|
||||
ceoId = organization.createAgent({ orgId, name: "CEO", role: "CEO", department: "Executive", modelProviderId: null, modelId: null, budgetCents: 1000, managerAgentId: null, roleLevel: "c_suite" });
|
||||
});
|
||||
writeFileSync(join(root, "kept.md"), "old");
|
||||
connection.db.exec("create trigger registry_down before insert on file_objects begin select raise(abort, 'registry down'); end;");
|
||||
const deps = { db: connection.db, organization: new OrganizationService(connection.db), chat: new ChatService(connection.db) };
|
||||
const ctx = { orgId, agent: { id: ceoId, modelProviderId: null, modelId: null }, taskProjectId: null, taskGoalId: null, taskId: null, sourceThreadId: null, workspace: { id: "wsp", localPath: root, writeAccess: true } };
|
||||
expect(dispatchAgentTool(deps, ctx, { id: "c1", name: "write_deliverable", arguments: { path: "kept.md", content: "new" } })).toBe("ERROR: registry down");
|
||||
expect(dispatchAgentTool(deps, ctx, { id: "c2", name: "write_deliverable", arguments: { path: "fresh.md", content: "new" } })).toBe("ERROR: registry down");
|
||||
expect(readFileSync(join(root, "kept.md"), "utf8")).toBe("old");
|
||||
expect(existsSync(join(root, "fresh.md"))).toBe(false);
|
||||
// No content copy outlives the record that failed.
|
||||
expect(existsSync(join(root, ".agentcompany", "files")) ? readdirSync(join(root, ".agentcompany", "files")) : []).toEqual([]);
|
||||
connection.close();
|
||||
});
|
||||
|
||||
test("a damaged content copy is replaced, never reused", () => {
|
||||
const connection = new DatabaseConnection(join(tmpdir(), `ac-damaged-${randomUUID()}.db`));
|
||||
const root = join(tmpdir(), `ac-damaged-ws-${randomUUID()}`);
|
||||
mkdirSync(join(root, ".agentcompany", "files"), { recursive: true });
|
||||
let orgId = "";
|
||||
connection.transaction(() => {
|
||||
orgId = new OrganizationService(connection.db).createOrganization({ organizationName: "Acme", employeeName: "Dana", mission: "m", goal: "g" });
|
||||
});
|
||||
writeFileSync(join(root, "plan.md"), "new");
|
||||
const sha = createHash("sha256").update("new").digest("hex");
|
||||
writeFileSync(join(root, ".agentcompany", "files", `${sha}.md`), "n");
|
||||
const fileId = recordWorkspaceFile(connection.db, { orgId, ownerType: "agent", ownerId: null, workspaceRoot: root, relativePath: "plan.md" });
|
||||
const stored = (connection.db.prepare("select storage_path from file_objects where id = ?").get(fileId) as { storage_path: string }).storage_path;
|
||||
expect(readFileSync(stored, "utf8")).toBe("new");
|
||||
connection.close();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user