From 4df2d3a8d733c8880ebe620bcae26786454abb18 Mon Sep 17 00:00:00 2001 From: Dotta Date: Sun, 27 Sep 2026 18:17:12 -0500 Subject: [PATCH] test: seed instruction drift fixture without versioned writer bypass Co-Authored-By: Paperclip --- .../rerun-survival.integration.test.ts | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/server/src/__tests__/rerun-survival.integration.test.ts b/server/src/__tests__/rerun-survival.integration.test.ts index 1708febeadf..aa3d5cb48c4 100644 --- a/server/src/__tests__/rerun-survival.integration.test.ts +++ b/server/src/__tests__/rerun-survival.integration.test.ts @@ -9,7 +9,7 @@ import { writeEnvFileAtomicallyIfChanged, } from "@paperclipai/shared/env-file"; import { resourceStatus, stockHash } from "../services/managed-resource-drift.js"; -import { agentInstructionsService } from "../services/agent-instructions.js"; +import { agentInstructionsService, resolveManagedInstructionsRoot } from "../services/agent-instructions.js"; /** * Cross-cutting setup/sync rerun survival test. @@ -23,7 +23,7 @@ import { agentInstructionsService } from "../services/agent-instructions.js"; * - .env -> updateEnvFileContents + writeEnvFileAtomicallyIfChanged * - managed sandbox env -> resourceStatus/stockHash drift gate (the `shouldWrite` * classifier the boot reconciler and built-in sync share) - * - managed instructions -> agentInstructionsService materialize + the drift gate that + * - managed instructions -> agentInstructionsService export + the drift gate that * makes built-in/plugin/portability rebuilds skip operator edits * * The DB-backed end-to-end variants of the sandbox-row, skills-assignment, and @@ -31,7 +31,7 @@ import { agentInstructionsService } from "../services/agent-instructions.js"; * server/src/__tests__/company-skills-service.test.ts (skills add/remove/replace) * server/src/services/managed-environments.test.ts (sandbox row boot reconcile) * server/src/__tests__/environment-service.test.ts (sandbox skip + stock update) - * server/src/__tests__/agent-instructions-service.test.ts (managed instruction drift) + * server/src/__tests__/agent-instruction-revisions.test.ts (guarded bundle materialization) */ const cleanupDirs = new Set(); @@ -147,7 +147,7 @@ describe("setup/sync rerun survival — cross-cutting", () => { ).toBe("stock_current"); }); - it("keeps operator edits and additions in a managed instructions tree across a re-materialize", async () => { + it("keeps operator edits and additions when managed instructions reconciliation detects drift", async () => { const home = await tmp("pap16587-instr-home-"); process.env.PAPERCLIP_HOME = home; process.env.PAPERCLIP_INSTANCE_ID = "test-instance"; @@ -156,18 +156,25 @@ describe("setup/sync rerun survival — cross-cutting", () => { const agent = { id: "agent-1", companyId: "company-1", name: "Agent 1", adapterConfig: {} }; const stockFiles = { "AGENTS.md": "# stock\n", "docs/TOOLS.md": "## stock tools\n" }; - const first = await svc.materializeManagedBundle(agent, stockFiles, { entryFile: "AGENTS.md" }); - const root = first.bundle.managedRootPath!; + // Initial files are fixture setup. Bundle materialization now requires a + // real database so that it cannot bypass a canonical instruction revision. + const root = resolveManagedInstructionsRoot(agent); + for (const [name, content] of Object.entries(stockFiles)) { + await fs.mkdir(path.dirname(path.join(root, name)), { recursive: true }); + await fs.writeFile(path.join(root, name), content, "utf8"); + } + const managedAgent = { ...agent, adapterConfig: { + instructionsBundleMode: "managed", instructionsRootPath: root, + instructionsEntryFile: "AGENTS.md", instructionsFilePath: path.join(root, "AGENTS.md"), + } }; // Operator hand-edits a managed file and adds a brand-new operator-only file. await fs.writeFile(path.join(root, "AGENTS.md"), "# stock\n\noperator note\n", "utf8"); await fs.writeFile(path.join(root, "OPERATOR.md"), "operator-added\n", "utf8"); // The drift gate the built-in/plugin/portability rebuilds share: classify the tree. - const currentFiles = { - "AGENTS.md": await fs.readFile(path.join(root, "AGENTS.md"), "utf8"), - "docs/TOOLS.md": await fs.readFile(path.join(root, "docs", "TOOLS.md"), "utf8"), - }; + const currentFiles = (await svc.exportFiles(managedAgent)).files; + expect(currentFiles["OPERATOR.md"]).toBe("operator-added\n"); const status = resourceStatus({ resourceId: agent.id, currentHash: stockHash(currentFiles),