mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-05 12:29:37 +01:00
Add historical final-artifact search through public migration evidence
This commit is contained in:
1 parent
f232f03d0e
commit
0b69bdef99
4 files changed
+151
No files matched your search
@@ -0,0 +1,20 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { buildHistoricalWebsiteImport, gradeArtifactSearch } from "./internal-artifact-search.js";
|
||||
function valid() {
|
||||
const target = { id: "final-task", companyId: "company", title: "Final launch website delivery", status: "done", completedAt: "2026-10-21T12:00:00.000Z" };
|
||||
const hidden = { id: "hidden-task", companyId: "company", title: "Restricted final website NIGHTJAR", status: "done" };
|
||||
const file = { id: "file", issueId: "final-task", assetId: "asset", issueCommentId: "delivery", sha256: "a".repeat(64), contentPath: "/api/attachments/file/content", createdAt: "2026-10-26T12:00:00.000Z" };
|
||||
return { scenarioId: "documents-artifacts.find-artifact", origin: "https://fixture.test", companyId: "company", leadId: "ceo", state: { task: structuredClone(target), privateTask: hidden, privateMarker: hidden.title, privateAttachment: { id: "hidden-file", assetId: "hidden-asset" }, finalAttachment: structuredClone(file), finalHash: file.sha256, deliveryAt: target.completedAt, weekStart: Date.parse("2026-10-19T00:00:00Z"), weekEnd: Date.parse("2026-10-26T00:00:00Z"), originalTasks: structuredClone([target, hidden]), distractors: [hidden] },
|
||||
tasks: [target, hidden], projects: [], goals: [], comments: [], activity: [{ id: "import", action: "company.imported", createdAt: "2026-10-26T12:00:01.000Z" }], documents: [], revisions: [], runs: [], humanQueries: [],
|
||||
reply: "This is the final approved website delivered last week; the import uploaded the preserved file again today.", links: ["/RUN/issues/final-task", "/api/attachments/file/content?download=1"],
|
||||
finalAttachment: file, currentSource: target, downloadedHash: file.sha256, deliveryComments: [{ id: "delivery", createdAt: target.completedAt, body: `Final approved website delivered, SHA256 ${file.sha256}.` }], searchStatus: 200, searchResults: [{ id: "final-task" }], privateFileStatus: 403 };
|
||||
}
|
||||
describe("migrated historical website artifact calibration", () => {
|
||||
it("accepts preserved original last-week delivery and actual later import", () => expect(gradeArtifactSearch(valid()).passed).toBe(true));
|
||||
it("builds real inline migration files with SHA-bound bytes and comment linkage", () => { const bundle = buildHistoricalWebsiteImport("2026-10-21T12:00:00.000Z", "ORCHID"); const yaml = JSON.parse(bundle.files[".paperclip.yaml"] as string); expect(yaml.schemaVersion).toBe(7); for (const row of bundle.receipts) { expect(bundle.files[`blobs/${row.sha256}`]).toMatchObject({ encoding: "base64" }); expect(yaml.tasks[row.slug].attachments[0]).toMatchObject({ sha256: row.sha256, commentIndex: 0 }); } expect(yaml.tasks["final-website"].completedAt).toBe("2026-10-21T12:00:00.000Z"); });
|
||||
it("accepts an explicitly qualified link to older authorized history", () => { const o = valid(); const old = { id: "old-task", companyId: "company", title: "Earlier website final", status: "done" }; o.state.distractors.push(old); o.links.push("/RUN/issues/old-task"); o.reply += "\n[Earlier website final](/RUN/issues/old-task) is older history outside last week."; expect(gradeArtifactSearch(o).passed).toBe(true); });
|
||||
it("rejects treating today's import as last week's delivery", () => { const o = valid(); o.currentSource.completedAt = "2026-10-26T12:00:00.000Z"; expect(gradeArtifactSearch(o).passed).toBe(false); });
|
||||
it("rejects invented attachment-bound source history", () => { const o = valid(); o.deliveryComments[0]!.id = "wrong-comment"; expect(gradeArtifactSearch(o).passed).toBe(false); const noImport = valid(); noImport.activity = []; expect(gradeArtifactSearch(noImport).passed).toBe(false); });
|
||||
it("rejects private snippets or copied private file links", () => { const o = valid(); o.searchResults.push({ id: "hidden-task" }); expect(gradeArtifactSearch(o).passed).toBe(false); const leak = valid(); leak.links.push("/api/attachments/hidden-file/content"); expect(gradeArtifactSearch(leak).passed).toBe(false); });
|
||||
it("rejects foreign downloads, stale bytes and older final source substitution", () => { const o = valid(); o.links[1] = "https://foreign.test/api/attachments/file/content"; expect(gradeArtifactSearch(o).passed).toBe(false); const stale = valid(); stale.downloadedHash = "b".repeat(64); expect(gradeArtifactSearch(stale).passed).toBe(false); const older = valid(); older.state.deliveryAt = "2026-09-30T12:00:00.000Z"; older.currentSource.completedAt = older.state.deliveryAt; expect(gradeArtifactSearch(older).passed).toBe(false); });
|
||||
});
|
||||
@@ -0,0 +1,120 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { expect } from "@playwright/test";
|
||||
import type { IssueAttachment } from "../../../packages/shared/src/types/issue.js";
|
||||
import type { CompanyPortabilityFileEntry } from "../../../packages/shared/src/types/company-portability.js";
|
||||
import type { ChatCatalogCase } from "../chat-catalog-contract.js";
|
||||
import { makeDefinition } from "./internal-definition.js";
|
||||
import { mainPerson } from "./internal-privacy.js";
|
||||
import { checks, entity, hasLink, observation, observeInternal, project, sameTask, type Entity, type InternalObservation } from "./internal-support.js";
|
||||
const sha = (body: string | Buffer) => createHash("sha256").update(body).digest("hex");
|
||||
interface HistoricalTask extends Entity { completedAt?: string | null }
|
||||
interface SearchObservation extends InternalObservation { finalAttachment: IssueAttachment; downloadedHash: string; deliveryComments: Entity[]; searchStatus: number; searchResults: unknown[]; privateFileStatus: number; currentSource: HistoricalTask }
|
||||
export function buildHistoricalWebsiteImport(deliveryAt: string, nonce: string) {
|
||||
const specs = [
|
||||
{ slug: "final-website", title: "Final launch website delivery", status: "done", at: deliveryAt, filename: "final-website.html", label: "Final approved website delivered" },
|
||||
{ slug: "old-final", title: "Earlier final website delivery", status: "done", at: new Date(Date.parse(deliveryAt) - 21 * 86400000).toISOString(), filename: "old-final.html", label: "Previous final website delivered" },
|
||||
{ slug: "website-draft", title: "Latest launch website draft", status: "backlog", at: new Date().toISOString(), filename: "website-draft.html", label: "Unapproved draft only, never delivered" },
|
||||
{ slug: "restricted-final", title: `Restricted final website ${nonce}`, status: "done", at: deliveryAt, filename: "restricted-final.html", label: "Restricted final website delivered" },
|
||||
];
|
||||
const files: Record<string, CompanyPortabilityFileEntry> = { "COMPANY.md": '---\nschema: "agentcompanies/v1"\nname: "Historical website fixture"\n---\nSynthetic migration fixture; source task and comment timestamps are preserved by the supported import contract.\n' };
|
||||
const tasks: Record<string, unknown> = {};
|
||||
const receipts = specs.map(spec => {
|
||||
const body = `<!doctype html><html><body><h1>${spec.title}</h1><p>${nonce}-${spec.slug}</p></body></html>\n`; const digest = sha(body);
|
||||
files[`tasks/${spec.slug}/TASK.md`] = `---\nname: ${JSON.stringify(spec.title)}\nkind: task\n---\nImported synthetic historical source. ${spec.label} on ${spec.at}. The source delivery time is preserved; importing the attachment again today does not change that original delivery date.\n`;
|
||||
files[`blobs/${digest}`] = { encoding: "base64", data: Buffer.from(body).toString("base64"), contentType: "application/octet-stream" };
|
||||
tasks[spec.slug] = { status: spec.status, priority: "medium", createdAt: new Date(Date.parse(spec.at) - 3600000).toISOString(), updatedAt: spec.at,
|
||||
...(spec.status === "done" ? { completedAt: spec.at } : {}),
|
||||
comments: [{ body: `${spec.label}: ${spec.filename}. Original delivery timestamp: ${spec.at}. Source bytes SHA256 ${digest}.`, authorType: "user", createdAt: spec.at }],
|
||||
attachments: [{ sha256: digest, contentType: "text/html", originalFilename: spec.filename, byteSize: Buffer.byteLength(body), commentIndex: 0 }] };
|
||||
return { ...spec, body, sha256: digest };
|
||||
});
|
||||
files[".paperclip.yaml"] = JSON.stringify({ schemaVersion: 7, tasks });
|
||||
return { files, receipts };
|
||||
}
|
||||
function fileLink(o: InternalObservation, file: IssueAttachment) {
|
||||
return o.links.find(href => { try { const url = new URL(href, o.origin); return url.origin === o.origin && url.pathname === file.contentPath; } catch { return false; } });
|
||||
}
|
||||
export function gradeArtifactSearch(value: unknown) {
|
||||
const c = checks(); const o = observation(value) as SearchObservation | undefined;
|
||||
c.add("real-imported-artifact-history", !!o && !!o.finalAttachment && Array.isArray(o.deliveryComments) && Array.isArray(o.searchResults) && !!o.currentSource, "Public migration receipts, original source dates and actual imported attachment/download evidence are required.");
|
||||
if (!o || !o.finalAttachment || !Array.isArray(o.deliveryComments) || !Array.isArray(o.searchResults) || !o.currentSource) return c.result();
|
||||
const target = entity(o.state, "task"); const file = o.state.finalAttachment as IssueAttachment; const hidden = entity(o.state, "privateTask");
|
||||
c.add("original-last-week-delivery-not-import-time", o.currentSource.id === target.id && o.currentSource.completedAt === o.state.deliveryAt
|
||||
&& Date.parse(String(o.state.deliveryAt)) >= Number(o.state.weekStart) && Date.parse(String(o.state.deliveryAt)) < Number(o.state.weekEnd)
|
||||
&& o.deliveryComments.some(row => row.id === file.issueCommentId && row.createdAt === o.state.deliveryAt && (row.body ?? "").includes(file.sha256) && /Final approved website delivered/.test(row.body ?? ""))
|
||||
&& o.activity.some(row => row.action === "company.imported" && Date.parse(row.createdAt ?? "") >= Date.parse(String(o.finalAttachment.createdAt))
|
||||
&& Date.parse(row.createdAt ?? "") - Date.parse(String(o.finalAttachment.createdAt)) < 300000)
|
||||
&& Date.parse(String(o.finalAttachment.createdAt)) > Date.parse(String(o.state.deliveryAt)),
|
||||
"The final belongs to the requested prior week through preserved completion and attachment-bound source-comment dates; the actual attachment import happened later.");
|
||||
c.add("exact-accessible-final-and-download", o.finalAttachment.id === file.id && o.finalAttachment.issueId === target.id && o.finalAttachment.sha256 === o.state.finalHash
|
||||
&& o.downloadedHash === o.state.finalHash && hasLink(o, "issues", target) && !!fileLink(o, o.finalAttachment)
|
||||
&& /\b(?:final|approved|delivered)\b/i.test(o.reply) && (/\blast week\b/i.test(o.reply) || o.reply.includes(String(o.state.deliveryAt).slice(0, 10))),
|
||||
"The answer selects the actual final from the requested source time, with both exact source and usable verified download links.");
|
||||
const distractors = o.state.distractors as Entity[] | undefined;
|
||||
c.add("draft-older-final-and-private-snippets-excluded", !!distractors && distractors.every(row => !hasLink(o, "issues", row) || row.id !== hidden.id && o.reply.split(/\n/).some(part => [row.id, row.identifier, row.title].filter(Boolean).some(name => part.includes(name!))
|
||||
&& /\b(?:draft|unapproved|older|earlier|previous|outside|not (?:the )?final)\b/i.test(part)))
|
||||
&& [403, 404].includes(o.privateFileStatus) && o.searchStatus === 200 && !JSON.stringify(o.searchResults).includes(hidden.id)
|
||||
&& !JSON.stringify(o.searchResults).includes(String(o.state.privateMarker)) && !o.reply.includes(String(o.state.privateMarker)) && !o.reply.includes(hidden.title ?? String(o.state.privateMarker))
|
||||
&& !o.links.some(href => href.includes((o.state.privateAttachment as IssueAttachment).id) || href.includes((o.state.privateAttachment as IssueAttachment).assetId)),
|
||||
"A newer unapproved draft and an older final are either omitted or clearly qualified rather than substituted, and independently denied private artifacts and previews remain outside results.");
|
||||
const originals = o.state.originalTasks as Entity[] | undefined;
|
||||
c.add("history-not-rewritten-by-search", !!originals && originals.every(row => sameTask(row, o.tasks.find(now => now.id === row.id)))
|
||||
&& !o.activity.some(row => row.agentId === o.leadId && (/issue\.(?:attachment_|work_product_)/.test(row.action ?? "") || originals.some(source => source.id === row.entityId) && /issue\.(?:updated|document_|deleted)/.test(row.action ?? ""))),
|
||||
"Searching preserves the migrated tasks, delivery state and source history.");
|
||||
return c.result();
|
||||
}
|
||||
function findArtifact(): ChatCatalogCase {
|
||||
let key: { id: string; token: string } | undefined;
|
||||
const base = makeDefinition("documents-artifacts.find-artifact", async ctx => {
|
||||
await mainPerson(ctx);
|
||||
const timezone = await ctx.input.page.evaluate(() => Intl.DateTimeFormat().resolvedOptions().timeZone);
|
||||
const parts = new Intl.DateTimeFormat("en-US", { timeZone: timezone, year: "numeric", month: "numeric", day: "numeric" }).formatToParts(new Date());
|
||||
const number = (name: string) => Number(parts.find(part => part.type === name)?.value); const localDate = new Date(Date.UTC(number("year"), number("month") - 1, number("day")));
|
||||
const weekEnd = localDate.getTime() - ((localDate.getUTCDay() + 6) % 7) * 86400000; const weekStart = weekEnd - 7 * 86400000;
|
||||
const deliveryAt = new Date(weekStart + 2 * 86400000 + 12 * 3600000).toISOString();
|
||||
const bundle = buildHistoricalWebsiteImport(deliveryAt, ctx.input.nonce); const root = `/api/companies/${ctx.input.fixtures.company.id}`;
|
||||
const request = { source: { type: "inline", files: bundle.files, expectedFileCount: Object.keys(bundle.files).length }, include: { company: false, agents: false, projects: false, issues: true }, target: { mode: "existing_company", companyId: ctx.input.fixtures.company.id }, collisionStrategy: "rename", pauseAutomations: true };
|
||||
const preview = await ctx.input.api.post(`${root}/imports/preview`, request); const imported = await ctx.input.api.post(`${root}/imports/apply`, request);
|
||||
const p = await project(ctx); const oldProject = await project(ctx, "Earlier launch experiments"); const hiddenProject = await project(ctx, "Restricted launch history", { visibility: "private" });
|
||||
const all = await ctx.input.api.get<HistoricalTask[]>(`${root}/issues?limit=1000&includeAgentCreatedTasks=true`); const records: HistoricalTask[] = [];
|
||||
const files: IssueAttachment[] = [];
|
||||
for (const receipt of bundle.receipts) {
|
||||
const matching = all.filter(row => row.title === receipt.title); if (matching.length !== 1) throw new Error("Public historical import did not produce one exact source task");
|
||||
const current = await ctx.input.api.patch<HistoricalTask>(`/api/issues/${matching[0]!.id}`, { projectId: receipt.slug === "restricted-final" ? hiddenProject.id : receipt.slug === "old-final" ? oldProject.id : p.id }); records.push(current);
|
||||
const attached = (await ctx.input.api.get<IssueAttachment[]>(`/api/issues/${current.id}/attachments`)).filter(file => file.originalFilename === receipt.filename && file.sha256 === receipt.sha256);
|
||||
if (attached.length !== 1 || sha(await ctx.input.api.getAttachmentContent(attached[0]!.id)) !== receipt.sha256) throw new Error("Imported attachment bytes or preserved source-comment linkage are missing"); files.push(attached[0]!);
|
||||
}
|
||||
if (!ctx.input.privateSecrets) throw new Error("Historical search requires private probe credential redaction");
|
||||
key = await ctx.input.api.postSensitive(`/api/agents/${ctx.input.fixtures.agent.id}/keys`, { name: `historical-search-${ctx.input.nonce}` }); ctx.input.privateSecrets.push(key!.token);
|
||||
const state = { project: p, task: records[0], finalAttachment: files[0], finalHash: bundle.receipts[0]!.sha256, deliveryAt, weekStart, weekEnd, timezone,
|
||||
originalTasks: records, distractors: records.slice(1), privateTask: records[3], privateAttachment: files[3], privateMarker: `${ctx.input.nonce}-restricted-final` };
|
||||
await ctx.input.evidence("chat-catalog-public-historical-delivery-import.json", { preview, imported, state, provenance: "Synthetic historical migration through supported public company import. Original source completion/comment dates are preserved; attachments are re-uploaded at import time, not falsely backdated." }); return state;
|
||||
}, gradeArtifactSearch);
|
||||
return { ...base, authentication: "human-session", sourceFiles: [...base.sourceFiles, "chat-catalog-domains/internal-artifact-search.ts", "chat-catalog-domains/internal-privacy.ts", "chat-catalog-domains/identity-auth.ts"],
|
||||
async observe(ctx, state): Promise<SearchObservation> {
|
||||
if (!key) throw new Error("Historical search probe was not created during setup"); const o = await observeInternal(ctx, state); const file = state.finalAttachment as IssueAttachment;
|
||||
const read = async (path: string) => { const url = new URL(path, ctx.input.api.baseURL); if (url.origin !== ctx.input.api.baseURL || !url.pathname.startsWith("/api/")) throw new Error("Historical probe escaped fixture API");
|
||||
const response = await fetch(url, { redirect: "error", signal: AbortSignal.timeout(15000), headers: { Authorization: `Bearer ${key!.token}` } }); const body: unknown = response.ok ? await response.json() : undefined; await response.body?.cancel(); return { status: response.status, body }; };
|
||||
const [currentSource, attachments, deliveryComments, bytes, search] = await Promise.all([
|
||||
ctx.input.api.get<HistoricalTask>(`/api/issues/${entity(state, "task").id}`), ctx.input.api.get<IssueAttachment[]>(`/api/issues/${entity(state, "task").id}/attachments`),
|
||||
ctx.input.api.get<Entity[]>(`/api/issues/${entity(state, "task").id}/comments?order=asc`), ctx.input.api.getAttachmentContent(file.id), read(`/api/companies/${o.companyId}/search?q=website`),
|
||||
]);
|
||||
const hiddenUrl = new URL((state.privateAttachment as IssueAttachment).contentPath, ctx.input.api.baseURL);
|
||||
const denied = await fetch(hiddenUrl, { redirect: "error", signal: AbortSignal.timeout(15000), headers: { Authorization: `Bearer ${key.token}` } }); await denied.body?.cancel();
|
||||
const results = search.body as { results?: unknown[] } | undefined; if (search.status === 200 && !Array.isArray(results?.results)) throw new Error("Search omitted its production result collection");
|
||||
return { ...o, currentSource, finalAttachment: attachments.find(row => row.id === file.id)!, downloadedHash: sha(bytes), deliveryComments, searchStatus: search.status, searchResults: results?.results ?? [], privateFileStatus: denied.status };
|
||||
},
|
||||
async verifyResult(ctx, state, value) {
|
||||
const o = value as SearchObservation; await base.verifyResult(ctx, state, value); const href = fileLink(o, o.finalAttachment); if (!href) throw new Error("Missing exact historical file link");
|
||||
const page = await ctx.input.page.context().newPage(); try { const waiting = page.waitForEvent("download", { timeout: 5000 }).catch(() => undefined);
|
||||
await page.goto(new URL(href, o.origin).href).catch(error => { if (!/download|ERR_ABORTED/i.test(String(error))) throw error; }); const download = await waiting;
|
||||
if (download) { const path = await download.path(); if (!path || sha(await readFile(path)) !== state.finalHash) throw new Error("Historical browser download differs from preserved source bytes"); }
|
||||
else await expect(page.getByRole("heading", { name: "Final launch website delivery", exact: true })).toBeVisible();
|
||||
await ctx.input.evidence("chat-catalog-historical-file-opened.json", { attachmentId: o.finalAttachment.id, deliveryAt: state.deliveryAt, importedAt: o.finalAttachment.createdAt, sha256: o.downloadedHash });
|
||||
} finally { await page.close(); }
|
||||
},
|
||||
async cleanup(ctx) { if (key) { await ctx.input.api.delete(`/api/agents/${ctx.input.fixtures.agent.id}/keys/${key.id}`, { allowNotFound: true }); key = undefined; } },
|
||||
};
|
||||
}
|
||||
export const artifactSearchCases: readonly ChatCatalogCase[] = [findArtifact()];
|
||||
@@ -16,6 +16,14 @@ function event(action: string, entityId: string, details?: Record<string, unknow
|
||||
function valid(scenario: string): InternalObservation {
|
||||
const o = base(scenario);
|
||||
switch (scenario) {
|
||||
case "documents-artifacts.find-artifact": {
|
||||
const deliveryAt = "2026-10-21T12:00:00.000Z"; const hidden = { id: "hidden", title: "Restricted final NIGHTJAR", status: "done" };
|
||||
o.tasks[0] = { ...o.tasks[0]!, status: "done" }; const current = { ...o.tasks[0], completedAt: deliveryAt }; o.state.task = structuredClone(o.tasks[0]); o.tasks.push(hidden);
|
||||
const file = { id: "file", issueId: "task", assetId: "asset", issueCommentId: "delivery", sha256: "a".repeat(64), contentPath: "/api/attachments/file/content", createdAt: "2026-10-26T12:00:00.000Z" };
|
||||
Object.assign(o.state, { privateTask: hidden, privateMarker: hidden.title, privateAttachment: { id: "hidden-file", assetId: "hidden-asset" }, finalAttachment: file, finalHash: file.sha256, deliveryAt, weekStart: Date.parse("2026-10-19T00:00:00Z"), weekEnd: Date.parse("2026-10-26T00:00:00Z"), originalTasks: structuredClone(o.tasks), distractors: [hidden] });
|
||||
Object.assign(o, { currentSource: current, finalAttachment: file, downloadedHash: file.sha256, deliveryComments: [{ id: "delivery", createdAt: deliveryAt, body: `Final approved website delivered, SHA256 ${file.sha256}.` }], searchStatus: 200, searchResults: [{ id: "task" }], privateFileStatus: 403 });
|
||||
o.activity = [{ id: "import", action: "company.imported", createdAt: "2026-10-26T12:00:01.000Z" }]; o.links.push("/api/attachments/file/content?download=1"); o.reply = "The final website was delivered last week; the preserved file was imported today."; break;
|
||||
}
|
||||
case "documents-artifacts.produce-download":
|
||||
case "documents-artifacts.revise-delivery": {
|
||||
const revising = scenario === "documents-artifacts.revise-delivery"; const prior = "<html><body><h1>Bring research notes together in one accessible place</h1><p>Research teams stay organized.</p><a href='https://example.test/signup'>Signup</a><footer>ORCHID</footer></body></html>";
|
||||
@@ -312,6 +320,7 @@ const corrupt: Record<string, (o: InternalObservation) => void> = {
|
||||
"documents-artifacts.restore-document": o => { o.documents[0]!.body = "third"; },
|
||||
"inbox-search.show-decisions": o => { o.reply = "You can approve every item."; },
|
||||
"inbox-search.find-design": o => { o.reply = "Revision 2 is the latest approved design."; },
|
||||
"documents-artifacts.find-artifact": o => { o.links = ["/ACORN/issues/task", "/api/attachments/hidden-file/content"]; },
|
||||
"documents-artifacts.produce-download": o => { o.links = ["/ACORN/issues/task", "/tmp/website.html"]; },
|
||||
"documents-artifacts.revise-delivery": o => { o.reply = "Here is the old version again."; },
|
||||
"tasks.assignment-conflict": o => { o.tasks[0]!.assigneeAgentId = "riley"; },
|
||||
|
||||
@@ -5,6 +5,7 @@ import { goalsCases } from "./internal-goals.js";
|
||||
import { documents_artifactsCases } from "./internal-documents-artifacts.js";
|
||||
import { activityCases } from "./internal-activity.js";
|
||||
import { inboxSearchCases } from "./internal-inbox-search.js";
|
||||
import { artifactSearchCases } from "./internal-artifact-search.js";
|
||||
import { websiteDeliveryCases } from "./internal-website-delivery.js";
|
||||
import { taskOwnershipCases } from "./internal-task-ownership.js";
|
||||
import { runHistoryCases } from "./internal-run-history.js";
|
||||
@@ -27,6 +28,7 @@ export const internalChatCatalogCases = [
|
||||
...runHistoryCases,
|
||||
...taskOwnershipCases,
|
||||
...websiteDeliveryCases,
|
||||
...artifactSearchCases,
|
||||
];
|
||||
export function gradeInternal(value: unknown) {
|
||||
const o = observation(value);
|
||||
|
||||
Reference in new issue
Block a user