perf(skills): verify bundled releases with current inventory digests (#17652)

Use current scoped database inventory fingerprints and the current on-disk expectation before reusing bounded snapshot verification. Preserve full validation, seeding and access checks when content, expectation or row existence changes.

Verified177 skill and authorization tests, workspace typechecking, server build, all green CI and exact-head Apex5/5. Prior staging release measured and saved Legacy data fingerprints preserved.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Devin FoleyandPaperclip authored and GitHub committed 2026-10-25 13:37:11 -07:00
1 parent 67aa87a93b
commit f0b597172b
5 files changed
+240 -24

No files matched your search

+7
View File
@@ -185,6 +185,13 @@ queries after any older request. Follow-up reads belong to the current mounted
scope and stop when it changes or unmounts. The first open and reconnect still
reconcile missed events.
Bundled skill release checks read the current stored version ID and a SHA-256
fingerprint of its file inventory. A per-database memo keeps at most 64 verified
snapshots. It reuses a verification only when the current database fingerprint
and current on-disk inventory hash both match. A changed or missing snapshot
still follows full validation or seeding. Company access and local source
reconciliation still run for each request; authorization is not cached.
Connection-assignment checks select profile bindings for the current company or
agent and read rules that can match the selected connection's catalog rows.
All five selector kinds, null application matching, exclusions, and profile
@@ -16,6 +16,7 @@ import {
projectWorkspaces,
} from "@paperclipai/db";
import { parseFrontmatterMarkdown } from "@paperclipai/shared";
import { createDatabaseResponseCapture, installDatabaseResponseCapture } from "@paperclipai/db/response-capture";
import {
getEmbeddedPostgresTestSupport,
startEmbeddedPostgresTestDatabase,
@@ -722,6 +723,73 @@ describeEmbeddedPostgres("companySkillService.list", () => {
expect(refreshed.filter((skill) => skill.key === key)).toHaveLength(1);
});
it("validates warm bundled releases without returning their stored file bodies", async () => {
const companyId = randomUUID();
await db.insert(companies).values({ id: companyId, name: "Release read checks", issuePrefix: "RRC" });
await svc.list(companyId);
const expected = await svc.list(companyId);
const before = await db.select().from(companySkillVersions).where(eq(companySkillVersions.companyId, companyId))
.orderBy(companySkillVersions.id);
expect(before.length).toBeGreaterThan(0);
const events: Array<Record<string, unknown>> = [], now = Date.now();
const capture = createDatabaseResponseCapture({ start: now - 100, end: now + 59_000 }, event => events.push(event));
installDatabaseResponseCapture(db, { companySkillVersions }, capture);
try { expect(await companySkillService(db).list(companyId)).toEqual(expected); }
finally { capture.flush(); }
const reads = events.filter(event => event.event === "paperclip.db_response_capture.entry.v1"
&& event.table === "company_skill_versions" && event.operation === "select");
expect(reads.reduce((sum, event) => sum + Number(event.calls), 0)).toBeGreaterThan(0);
expect(reads.reduce((sum, event) => sum + Number(event.responseValueBytes), 0)).toBeLessThan(4096);
expect(await db.select().from(companySkillVersions).where(eq(companySkillVersions.companyId, companyId))
.orderBy(companySkillVersions.id)).toEqual(before);
});
it.each(["agent", "board"] as const)("checks %s company access before using a warm release validation", async type => {
const companyId = randomUUID(), otherCompanyId = randomUUID();
await db.insert(companies).values({ id: companyId, name: "Release access checks", issuePrefix: "RAC" });
await svc.list(companyId);
await svc.list(companyId);
const actor = type === "agent"
? { type, companyId: otherCompanyId, agentId: randomUUID() }
: { type, companyIds: [otherCompanyId] };
const events: Array<Record<string, unknown>> = [], now = Date.now();
const capture = createDatabaseResponseCapture({ start: now - 100, end: now + 59_000 }, event => events.push(event));
installDatabaseResponseCapture(db, { companySkillVersions }, capture);
try { expect(await companySkillService(db, { actor }).list(companyId)).toEqual([]); }
finally { capture.flush(); }
expect(events.find(event => event.event === "paperclip.db_response_capture.summary.v1")?.completedQueries).toBe(0);
});
it("restores a deleted release after warm validation", async () => {
const companyId = randomUUID();
await db.insert(companies).values({ id: companyId, name: "Release deletion checks", issuePrefix: "RDC" });
await svc.list(companyId);
await svc.list(companyId);
const before = await db.select().from(companySkillVersions).where(eq(companySkillVersions.companyId, companyId));
const original = before[0]!;
await db.delete(companySkillVersions).where(eq(companySkillVersions.id, original.id));
await companySkillService(db).list(companyId);
const after = await db.select().from(companySkillVersions).where(eq(companySkillVersions.companyId, companyId));
expect(after).toHaveLength(before.length);
expect(after.find(version => version.releaseId === original.releaseId)).toMatchObject({
companyId, companySkillId: original.companySkillId, fileInventory: original.fileInventory,
});
expect(after.some(version => version.id === original.id)).toBe(false);
});
it("rejects a changed bundled snapshot after warm validation without overwriting it", async () => {
const companyId = randomUUID();
await db.insert(companies).values({ id: companyId, name: "Release integrity checks", issuePrefix: "RIC" });
await svc.list(companyId);
await svc.list(companyId);
const [version] = await db.select().from(companySkillVersions).where(eq(companySkillVersions.companyId, companyId));
const changed = [{ path: "SKILL.md", kind: "skill" as const, content: "Changed stored snapshot" }];
await db.update(companySkillVersions).set({ fileInventory: changed }).where(eq(companySkillVersions.id, version!.id));
await expect(companySkillService(db).list(companyId)).rejects.toThrow("does not match its seeded snapshot");
expect((await db.select().from(companySkillVersions).where(eq(companySkillVersions.id, version!.id)))[0]!.fileInventory)
.toEqual(changed);
});
it("seeds bundled skill releases idempotently and materializes the frozen champion snapshot", async () => {
const companyId = randomUUID();
await db.insert(companies).values({
@@ -0,0 +1,103 @@
import { createHash, randomUUID } from "node:crypto";
import { and, eq } from "drizzle-orm";
import { afterAll, beforeAll, describe, expect, it, vi } from "vitest";
import { companies, companySkills, companySkillVersions, createDb } from "@paperclipai/db";
import { getEmbeddedPostgresTestSupport, startEmbeddedPostgresTestDatabase } from "../__tests__/helpers/embedded-postgres.js";
import { verifyExistingBundledSkillRelease } from "./bundled-skill-release-validation.js";
const support = await getEmbeddedPostgresTestSupport();
const hashInventory = (files: Array<{ path: string; content: string }>) => createHash("sha256")
.update(JSON.stringify(files.map(file => [file.path, file.content]))).digest("hex");
(support.supported ? describe : describe.skip)("bundled release validation with current database fingerprints", () => {
let db: ReturnType<typeof createDb>;
let temporary: Awaited<ReturnType<typeof startEmbeddedPostgresTestDatabase>>;
beforeAll(async () => {
temporary = await startEmbeddedPostgresTestDatabase("bundled-release-validation-");
db = createDb(temporary.connectionString);
}, 30_000);
afterAll(async () => { await db?.$client.end(); await temporary?.cleanup(); });
async function seed(content = "saved release content") {
const companyId = randomUUID(), skillId = randomUUID();
await db.insert(companies).values({ id: companyId, name: "Release validation", issuePrefix: companyId.slice(0, 8) });
await db.insert(companySkills).values({ id: skillId, companyId, key: "fixture/release", slug: "release",
name: "Release", markdown: "# Release", sourceType: "local_path" });
const files = [{ path: "SKILL.md", kind: "skill" as const, content }];
const [version] = await db.insert(companySkillVersions).values({ companyId, companySkillId: skillId,
revisionNumber: 1, releaseId: "release-1", fileInventory: files }).returning();
const expectedInventoryHash = hashInventory(files);
const validateInventory = vi.fn((row: typeof companySkillVersions.$inferSelect) => {
if (hashInventory(row.fileInventory) !== expectedInventoryHash) throw new Error("snapshot mismatch");
});
return { version: version!, input: { db, companyId, skillId, releaseId: "release-1", expectedInventoryHash, validateInventory } };
}
it("reuses proof only within current company, skill and release bindings", async () => {
const f = await seed();
expect(await verifyExistingBundledSkillRelease(f.input)).toBe(true);
expect(await verifyExistingBundledSkillRelease(f.input)).toBe(true);
expect(f.input.validateInventory).toHaveBeenCalledTimes(1);
expect(await verifyExistingBundledSkillRelease({ ...f.input, companyId: randomUUID() })).toBe(false);
expect(await verifyExistingBundledSkillRelease({ ...f.input, skillId: randomUUID() })).toBe(false);
expect(await verifyExistingBundledSkillRelease({ ...f.input, releaseId: "other-release" })).toBe(false);
expect(f.input.validateInventory).toHaveBeenCalledTimes(1);
expect(await verifyExistingBundledSkillRelease(f.input)).toBe(true);
});
it("revalidates when the expected on-disk inventory changes without poisoning valid proof", async () => {
const f = await seed();
await verifyExistingBundledSkillRelease(f.input);
const changedHash = hashInventory([{ path: "SKILL.md", content: "new on-disk content" }]);
const validateChanged = vi.fn((row: typeof companySkillVersions.$inferSelect) => {
if (hashInventory(row.fileInventory) !== changedHash) throw new Error("snapshot mismatch");
});
await expect(verifyExistingBundledSkillRelease({ ...f.input, expectedInventoryHash: changedHash,
validateInventory: validateChanged })).rejects.toThrow("snapshot mismatch");
expect(validateChanged).toHaveBeenCalledOnce();
expect(await verifyExistingBundledSkillRelease(f.input)).toBe(true);
expect(f.input.validateInventory).toHaveBeenCalledOnce();
});
it("rechecks current content after a change overlaps an earlier validation", async () => {
const f = await seed();
let entered!: () => void, release!: () => void;
const validating = new Promise<void>(resolve => { entered = resolve; });
const held = new Promise<void>(resolve => { release = resolve; });
const first = verifyExistingBundledSkillRelease({ ...f.input, validateInventory: async row => {
f.input.validateInventory(row);
entered();
await held;
} });
await validating;
try {
await db.update(companySkillVersions).set({ fileInventory: [{ path: "SKILL.md", kind: "skill", content: "changed" }] })
.where(and(eq(companySkillVersions.companyId, f.input.companyId), eq(companySkillVersions.id, f.version.id)));
} finally { release(); }
expect(await first).toBe(true);
await expect(verifyExistingBundledSkillRelease(f.input)).rejects.toThrow("snapshot mismatch");
expect(f.input.validateInventory).toHaveBeenCalledTimes(2);
});
it("does not accept a deleted row from earlier proof", async () => {
const f = await seed();
await verifyExistingBundledSkillRelease(f.input);
await db.delete(companySkillVersions).where(eq(companySkillVersions.id, f.version.id));
expect(await verifyExistingBundledSkillRelease(f.input)).toBe(false);
expect(f.input.validateInventory).toHaveBeenCalledOnce();
});
it("bounds retained proof and safely revalidates an evicted release", async () => {
const f = await seed();
await verifyExistingBundledSkillRelease(f.input);
for (let revisionNumber = 2; revisionNumber <= 66; revisionNumber++) {
const releaseId = `release-${revisionNumber}`;
await db.insert(companySkillVersions).values({ companyId: f.input.companyId, companySkillId: f.input.skillId,
revisionNumber, releaseId, fileInventory: f.version.fileInventory });
expect(await verifyExistingBundledSkillRelease({ ...f.input, releaseId })).toBe(true);
}
const before = f.input.validateInventory.mock.calls.length;
expect(await verifyExistingBundledSkillRelease(f.input)).toBe(true);
expect(f.input.validateInventory).toHaveBeenCalledTimes(before + 1);
});
});
@@ -0,0 +1,45 @@
import { companySkillVersions, type Db } from "@paperclipai/db";
import { and, eq, getTableColumns, sql } from "drizzle-orm";
type Version = typeof companySkillVersions.$inferSelect;
type Verification = { inventorySha256: string; expectedInventoryHash: string };
const verifiedInventories = new WeakMap<Db, Map<string, Verification>>();
const inventorySha256 = sql<string>`encode(sha256(convert_to(${companySkillVersions.fileInventory}::text, 'UTF8')), 'hex')`;
export async function verifyExistingBundledSkillRelease(input: {
db: Db;
companyId: string;
skillId: string;
releaseId: string;
expectedInventoryHash: string;
validateInventory: (version: Version) => void | Promise<void>;
}): Promise<boolean> {
const scope = and(eq(companySkillVersions.companyId, input.companyId),
eq(companySkillVersions.companySkillId, input.skillId), eq(companySkillVersions.releaseId, input.releaseId));
const [current] = await input.db.select({ id: companySkillVersions.id, inventorySha256 })
.from(companySkillVersions).where(scope).limit(1);
if (!current) return false;
let verified = verifiedInventories.get(input.db);
if (!verified) {
verified = new Map();
verifiedInventories.set(input.db, verified);
}
const previous = verified.get(current.id);
if (previous?.inventorySha256 === current.inventorySha256
&& previous.expectedInventoryHash === input.expectedInventoryHash) {
verified.delete(current.id);
verified.set(current.id, previous);
return true;
}
const [version] = await input.db.select({ ...getTableColumns(companySkillVersions), inventorySha256 })
.from(companySkillVersions).where(scope).limit(1);
if (!version) return false;
await input.validateInventory(version);
verified.delete(version.id);
verified.set(version.id, { inventorySha256: version.inventorySha256, expectedInventoryHash: input.expectedInventoryHash });
while (verified.size > 64) {
const oldest = verified.keys().next().value;
if (oldest !== undefined) verified.delete(oldest);
}
return true;
}
+17 -24
View File
@@ -1,4 +1,5 @@
import { ensureSkillCacheDirectory, touchSkillVersionCache, withSkillVersionCacheLock } from "./skill-version-cache.js";
import { verifyExistingBundledSkillRelease } from "./bundled-skill-release-validation.js";
import { retainProjectSkillAccess, skillAccessService } from "./skill-access.js";
import type { AuthorizationActor } from "./authorization.js";
import { updateAgentConfigurationInTransaction } from "./agent-configuration-transaction.js";
@@ -3094,30 +3095,22 @@ export function companySkillService(db: Db, options: { actor?: AuthorizationActo
const fileInventory = serializeVersionFileInventory(
await collectVersionFileInventoryFromDirectory(release.releaseDir),
);
const existing = await db
.select()
.from(companySkillVersions)
.where(and(
eq(companySkillVersions.companyId, companyId),
eq(companySkillVersions.companySkillId, paperclipSkill.id),
eq(companySkillVersions.releaseId, release.id),
))
.then((rows) => rows[0] ?? null);
if (existing) {
const existingInventory = toCompanySkillVersion(existing).fileInventory;
const existingHash = buildInventoryContentHash(existingInventory.map((entry) => ({
path: entry.path,
sha256: sha256Buffer(entry.content),
})));
const registryHash = buildInventoryContentHash(fileInventory.map((entry) => ({
path: entry.path,
sha256: sha256Buffer(entry.content),
})));
if (existingHash !== registryHash) {
throw new Error(`Bundled skill release ${release.id} does not match its seeded snapshot.`);
}
continue;
}
const registryHash = buildInventoryContentHash(fileInventory.map((entry) => ({
path: entry.path,
sha256: sha256Buffer(entry.content),
})));
if (await verifyExistingBundledSkillRelease({ db, companyId, skillId: paperclipSkill.id, releaseId: release.id,
expectedInventoryHash: registryHash, validateInventory: existing => {
const existingInventory = toCompanySkillVersion(existing).fileInventory;
const existingHash = buildInventoryContentHash(existingInventory.map((entry) => ({
path: entry.path,
sha256: sha256Buffer(entry.content),
})));
if (existingHash !== registryHash) {
throw new Error(`Bundled skill release ${release.id} does not match its seeded snapshot.`);
}
},
})) continue;
const releasedAt = new Date(release.releasedAt);
if (Number.isNaN(releasedAt.getTime())) {
throw new Error(`Invalid bundled skill release date: ${release.releasedAt}`);