import fs from "node:fs/promises"; import path from "node:path"; import { afterEach, describe, expect, test } from "vitest"; import { appendArtifactRequest, materializeArtifactRequest, requestArtifactStorage } from "../src/artifacts/append.js"; import { buildArtifactCatalog, getArtifactBody, getArtifactCatalog } from "../src/artifacts/catalog.js"; import { ARTIFACT_EVENT_TYPE, ARTIFACT_MAX_IMAGE_BYTES, ARTIFACT_MAX_TEXT_BYTES, ARTIFACT_REQUEST_EVENT_TYPE } from "../src/artifacts/types.js"; import { createDefaultRegistry } from "../src/events/registry.js"; import { JazzThoughtStore } from "../src/jazz/store.js"; import { renderArtifactMarkdown, startInspectorServer } from "../src/web/inspector.js"; import { temporaryProject, testStore } from "./helpers.js"; const stores: JazzThoughtStore[] = []; const roots: string[] = []; const servers: import("node:http").Server[] = []; afterEach(async () => { await Promise.all(servers.splice(0).map((server) => new Promise((resolve) => { server.closeAllConnections(); server.close(() => resolve()); }))); await Promise.all(stores.splice(0).map((store) => store.close())); await Promise.all(roots.splice(0).map((root) => fs.rm(root, { recursive: true, force: true }))); }); async function fixture(name = "artifact.md", bytes: string | Buffer = "# Artifact\n") { const project = await temporaryProject("artifact-store-"); const workspace = await temporaryProject("artifact-workspace-"); roots.push(project, workspace); const store = await testStore(project); stores.push(store); await fs.writeFile(path.join(workspace, name), bytes); return { project, workspace, store }; } const input = { requestId: "req-1", workspaceLeaseId: "lease-1", workspaceRootLabel: "exe workspace lease", relativeFilePath: "artifact.md", artifactId: "skill:test", artifactVersion: 1, kind: "skill" as const, title: "Test", summary: "Private test artifact", mediaType: "text/markdown" as const, provenance: { source: "agent-workspace", label: "selected skill file" }, visibility: "private" as const, requestingAgentId: "agent-test", requestingRunId: "run-test", sourceEventId: "evt-source" }; const png = Buffer.from([137,80,78,71,13,10,26,10,0,0,0,0]); describe("durable private artifact storage", () => { test("request evidence is private, bounded, path-dark, and has no authority", async () => { const { workspace, store } = await fixture(); const request = await appendArtifactRequest({ store, ...input }); expect(request.event.type).toBe(ARTIFACT_REQUEST_EVENT_TYPE); expect(request.event.privacy).toBe("private"); expect(request.event.payload.publicationEligible).toBe(false); expect(request.event.payload.publicationAuthority).toBe(false); expect(JSON.stringify(request.event)).not.toContain(workspace); }); test("schema rejects absolute request paths and provenance labels", () => { const registry = createDefaultRegistry(); const payload = { ...input, publicationEligible: false, publicationAuthority: false, status: "pending" }; expect(() => registry.validateEvent(ARTIFACT_REQUEST_EVENT_TYPE, 1, "private", { ...payload, relativeFilePath: "/etc/passwd" } as never)).toThrow(); expect(() => registry.validateEvent(ARTIFACT_REQUEST_EVENT_TYPE, 1, "private", { ...payload, provenance: { source: "x", label: "/secret" } } as never)).toThrow(); }); test("materializes exact bytes once with strong request lineage and no inline bytes", async () => { const { workspace, store } = await fixture(); const result = await requestArtifactStorage({ store, workspaceRoot: workspace, ...input }); expect(result.event.type).toBe(ARTIFACT_EVENT_TYPE); expect(result.event.payload.publicationEligible).toBe(false); expect(result.event.payload.relations).toContainEqual({ type: "materialized-from-request", eventId: result.requestEvent!.id }); expect(result.event.payload.blob).toMatchObject({ algorithm: "sha256", byteCount: 11 }); expect(result.event.payload.body).toBeUndefined(); expect(result.event.payload.bodySha256).toBeUndefined(); const detail = await getArtifactBody(store, result.event.id); expect(detail?.text).toBe("# Artifact\n"); }); test("same identity and bytes is idempotent; changed bytes conflict", async () => { const { workspace, store } = await fixture(); const first = await requestArtifactStorage({ store, workspaceRoot: workspace, ...input }); const replay = await requestArtifactStorage({ store, workspaceRoot: workspace, ...input }); expect(first.inserted).toBe(true); expect(replay.inserted).toBe(false); await fs.writeFile(path.join(workspace, "artifact.md"), "changed"); await expect(requestArtifactStorage({ store, workspaceRoot: workspace, ...input })).rejects.toThrow("Idempotency key reused with different payload"); }); test("request lease identity must match materializer authority", async () => { const { workspace, store } = await fixture(); const request = await appendArtifactRequest({ store, ...input }); await expect(materializeArtifactRequest({ store, requestEventId: request.event.id, workspaceRoot: workspace, workspaceLeaseId: "other" })).rejects.toThrow("lease identity"); }); test("rejects workspace escape", async () => { const { workspace, store } = await fixture(); await expect(requestArtifactStorage({ store, workspaceRoot: workspace, ...input, relativeFilePath: "../outside.md" })).rejects.toThrow("normalized relative path"); }); test("rejects file symlink", async () => { const { workspace, store } = await fixture(); await fs.symlink("artifact.md", path.join(workspace, "alias.md")); await expect(requestArtifactStorage({ store, workspaceRoot: workspace, ...input, relativeFilePath: "alias.md" })).rejects.toThrow("symbolic links"); }); test("rejects parent symlink", async () => { const { workspace, store } = await fixture(); await fs.mkdir(path.join(workspace, "real")); await fs.writeFile(path.join(workspace, "real", "x.md"), "x"); await fs.symlink("real", path.join(workspace, "alias")); await expect(requestArtifactStorage({ store, workspaceRoot: workspace, ...input, relativeFilePath: "alias/x.md" })).rejects.toThrow("symbolic links"); }); test("rejects extension and MIME mismatch", async () => { const { workspace, store } = await fixture("artifact.txt", "x"); await expect(requestArtifactStorage({ store, workspaceRoot: workspace, ...input, relativeFilePath: "artifact.txt" })).rejects.toThrow("extension and media type"); }); test("rejects PNG and JPEG magic mismatch", async () => { const a = await fixture("artifact.png", "not png"); await expect(requestArtifactStorage({ store: a.store, workspaceRoot: a.workspace, ...input, relativeFilePath: "artifact.png", mediaType: "image/png", kind: "image" })).rejects.toThrow("PNG magic"); const b = await fixture("artifact.jpg", "not jpeg"); await expect(requestArtifactStorage({ store: b.store, workspaceRoot: b.workspace, ...input, relativeFilePath: "artifact.jpg", mediaType: "image/jpeg", kind: "image" })).rejects.toThrow("JPEG magic"); }); test("validates JSON and YAML structure", async () => { const a = await fixture("artifact.json", "{"); await expect(requestArtifactStorage({ store: a.store, workspaceRoot: a.workspace, ...input, relativeFilePath: "artifact.json", mediaType: "application/json" })).rejects.toThrow(); const b = await fixture("artifact.yaml", "a: ["); await expect(requestArtifactStorage({ store: b.store, workspaceRoot: b.workspace, ...input, relativeFilePath: "artifact.yaml", mediaType: "application/yaml" })).rejects.toThrow(); }); test("enforces exact text and image caps", async () => { const a = await fixture("artifact.txt", Buffer.alloc(ARTIFACT_MAX_TEXT_BYTES + 1, 1)); await expect(requestArtifactStorage({ store: a.store, workspaceRoot: a.workspace, ...input, relativeFilePath: "artifact.txt", mediaType: "text/plain" })).rejects.toThrow("exceeds"); const b = await fixture("artifact.png", Buffer.alloc(ARTIFACT_MAX_IMAGE_BYTES + 1, 1)); await expect(requestArtifactStorage({ store: b.store, workspaceRoot: b.workspace, ...input, relativeFilePath: "artifact.png", mediaType: "image/png", kind: "image" })).rejects.toThrow("exceeds"); }); test("stores blobs with canonical path and private modes", async () => { const { workspace, store } = await fixture(); const result = await requestArtifactStorage({ store, workspaceRoot: workspace, ...input }); const blob = result.event.payload.blob as { relativePath: string }; const rootStat = await fs.stat(store.getArtifactRoot()); const fileStat = await fs.stat(path.join(store.getArtifactRoot(), ...blob.relativePath.split("/"))); expect(rootStat.mode & 0o777).toBe(0o700); expect(fileStat.mode & 0o777).toBe(0o600); expect(blob.relativePath).toMatch(/^sha256\/[a-f0-9]{2}\/[a-f0-9]{64}$/); }); test("blob tamper fails on read", async () => { const { workspace, store } = await fixture(); const result = await requestArtifactStorage({ store, workspaceRoot: workspace, ...input }); const blob = result.event.payload.blob as { relativePath: string }; await fs.writeFile(path.join(store.getArtifactRoot(), ...blob.relativePath.split("/")), "tamper"); await expect(getArtifactBody(store, result.event.id)).rejects.toThrow("integrity check failed"); }); test("catalog is projected, metadata-only, and path/credential/body dark", async () => { const { workspace, store } = await fixture("artifact.md", "PRIVATE-BODY credential=secret-token"); const result = await requestArtifactStorage({ store, workspaceRoot: workspace, ...input }); const catalog = await getArtifactCatalog(store); const projection = await store.getProjection("artifact-catalog"); const serialized = JSON.stringify(catalog); expect(catalog.entries[0]?.eventId).toBe(result.event.id); expect(projection?.projectionVersion).toBe(1); expect(projection?.payload).toEqual(JSON.parse(serialized)); expect(serialized).not.toContain("PRIVATE-BODY"); expect(serialized).not.toContain("secret-token"); expect(serialized).not.toContain(workspace); }); test("private inspector returns safe text detail and rejects mutations", async () => { const { workspace, store } = await fixture(); const result = await requestArtifactStorage({ store, workspaceRoot: workspace, ...input }); const { base } = await inspector(store); const catalog = await (await fetch(`${base}/api/artifacts`)).text(); expect(catalog).not.toContain("# Artifact"); const detail = await (await fetch(`${base}/api/artifacts/${result.event.id}`)).json() as { text: string; body?: string }; expect(detail.text).toBe("# Artifact\n"); expect(detail.body).toBeUndefined(); expect((await fetch(`${base}/api/artifacts`, { method: "POST" })).status).toBe(405); }); test("artifact UI renders content first, collapses metadata, and opens one focused detail view", async () => { const { store } = await fixture(); const { base } = await inspector(store); const page = await (await fetch(base)).text(); const renderer = page.slice(page.indexOf("function renderArtifact"), page.indexOf("function bindArtifactBack")); expect(page).toMatch(/main\.detail-selected #list-pane\s*\{\s*display:none\s*\}/); expect(page).toContain("data.renderedHtml"); expect(page).toContain("Artifact details"); expect(renderer).not.toContain("JSON.stringify(item"); expect(renderer).not.toContain("canonical envelope"); expect(renderer.indexOf("+content+metadata")).toBeGreaterThan(-1); }); test("Markdown rendering strips frontmatter, preserves document structure, and escapes active content", () => { const rendered = renderArtifactMarkdown("---\nid: secret\n---\n# Heading\n\n- one\n- **two**\n\n***both*** `code` [site](https://example.com) [bad](javascript:alert(2))\n"); expect(rendered).toContain("

Heading

"); expect(rendered).toContain(""); expect(rendered).toContain("both"); expect(rendered).toContain("code"); expect(rendered).toContain('site'); expect(rendered).toContain("<script>alert(1)</script>"); expect(rendered).toContain("[bad](javascript:alert(2))"); expect(rendered).not.toContain("id: secret"); }); test("suggestions UI is human-first, precedes review, and never renders proposal JSON", async () => { const { store } = await fixture(); const { base } = await inspector(store); const queue = await (await fetch(`${base}/api/proposals`)).json() as { count: number; items: unknown[] }; expect(queue).toEqual({ count: 0, items: [] }); const page = await (await fetch(base)).text(); expect(page.indexOf('id="suggestions-tab"')).toBeLessThan(page.indexOf('id="reviews-tab"')); const renderer = page.slice(page.indexOf("function renderSuggestion"), page.indexOf("function renderReview")); expect(renderer).toContain("Memory suggestion"); expect(renderer).toContain("Original delivered reply"); expect(renderer).toContain("Technical details"); expect(renderer).not.toContain("JSON.stringify(item"); expect(renderer).not.toContain("raw payload"); }); test("generated inspector client JavaScript parses", async () => { const { store } = await fixture(); const { base } = await inspector(store); const page = await (await fetch(base)).text(); const script = page.match(/