UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

351 lines (350 loc) • 25.5 kB
import { randomUUID } from "node:crypto"; import { thrownText } from "../../core/thrown-value.js"; import { readArtifactHostCapabilities, } from "../../core/artifact-host.js"; class Violation extends Error { } const fail = (detail) => { throw new Violation(detail); }; const check = (cond, detail) => { if (!cond) fail(detail); }; const enc = new TextEncoder(); const FOREIGN_URL = "https://conformance.invalid/artifact/none"; const HTML_MEDIA = "text/html"; const ctxOf = (h, attemptId) => ({ taskId: `conformance-${h.runId}`, runId: h.runId, attemptId: attemptId ?? `${h.runId}-${++h.seq}` }); const fixtureBudget = (h) => Math.max(1, Math.min(h.caps.maxFileBytes, Math.floor(h.caps.maxTotalBytes / 2))); const fit = (h, text) => enc.encode(text).slice(0, fixtureBudget(h)); const el = (tag, inner) => `<${tag}>${inner}</${tag}>`; const page = (h, n, extraFiles = []) => [{ path: "index.html", content: fit(h, `${n}${el("title", `C${n}`)}${el("p", String(n))}`), mediaType: HTML_MEDIA }, ...extraFiles]; const script = (h, n) => ({ path: "assets/app.js", content: fit(h, `${n};console.log(${n})`).slice(0, Math.floor(h.caps.maxTotalBytes / 2)), mediaType: "text/javascript" }); const req = (files, over = {}) => ({ files, publishContext: "plain", ...over }); const same = (a, b) => a.byteLength === b.byteLength && a.every((x, i) => x === b[i]); async function publish(h, r, attemptId) { try { return await h.host.publish(r, ctxOf(h, attemptId)); } catch (err) { return fail(`publish threw: ${thrownText(err)}`); } } async function publishOk(h, r, what, attemptId) { const out = await publish(h, r, attemptId); if (!out.ok) return fail(`${what}: expected ok, got ${out.code}${"message" in out ? ` (${out.message})` : ""}`); check(typeof out.ref.url === "string" && out.ref.url.length > 0 && typeof out.ref.slug === "string" && typeof out.ref.version === "string", `${what}: the ref must carry url, slug and version strings`); return out.ref; } async function listMine(h) { const out = await h.host.list({ scope: "mine", limit: 1000 }, ctxOf(h)); if (!out.ok) return fail(`list mine refused: ${out.message}`); return out.rows; } const vectorTable = []; const define = (v, run) => void vectorTable.push({ ...v, run }); define({ id: "publish.fresh_mints_distinct_pages", describe: "two publishes without `url` mint two pages with distinct urls (fresh never touches an existing page)", requiresActions: ["publish"] }, async (h) => { const a = await publishOk(h, req(page(h, 1)), "first fresh"); const b = await publishOk(h, req(page(h, 2)), "second fresh"); check(a.url !== b.url, `both fresh publishes answered the same url ${a.url}`); return `two pages: ${a.url}, ${b.url}`; }); define({ id: "publish.update_by_url_advances_same_page", describe: "a publish with `url` and the current `baseVersion` advances that page's version on the same url and slug (update never mints a page); where read / read_file are declared, the NEW bytes are what reads back", requiresActions: ["publish"] }, async (h) => { const a = await publishOk(h, req(page(h, 1)), "fresh"); const next = page(h, 2); const b = await publishOk(h, req(next, { url: a.url, baseVersion: a.version }), "update"); check(b.url === a.url && b.slug === a.slug, `the update answered url ${b.url} / slug ${b.slug}, expected ${a.url} / ${a.slug}`); check(b.version !== a.version, `the update left the version at ${a.version}`); const persisted = []; if (h.caps.actions.includes("read")) { const r = await h.host.read({ url: a.url }, ctxOf(h)); check(r.ok && r.content === new TextDecoder().decode(next[0].content) && r.ref.version === b.version, "read after the update returns the PREVIOUS content or version: the update was acknowledged but not persisted"); persisted.push("read"); } if (h.caps.actions.includes("read_file")) { const f = await h.host.readFile({ url: a.url, path: next[0].path }, ctxOf(h)); check(f.ok && same(f.file.content, next[0].content), "read_file after the update returns the PREVIOUS bytes: the update was acknowledged but not persisted"); persisted.push("read_file"); } return `version ${a.version} → ${b.version} on ${a.url}${persisted.length > 0 ? `; new bytes read back via ${persisted.join(", ")}` : ""}`; }); define({ id: "publish.no_base_version_is_last_write_wins", describe: "`url` present and `baseVersion` absent ⇒ the write applies (last-write-wins is a defined behaviour, never a conflict)", requiresActions: ["publish"] }, async (h) => { const a = await publishOk(h, req(page(h, 1)), "fresh"); const b = await publishOk(h, req(page(h, 2), { url: a.url }), "first update without baseVersion"); const c = await publishOk(h, req(page(h, 3), { url: a.url }), "second update without baseVersion"); check(b.version !== a.version && c.version !== b.version, `versions did not advance: ${a.version}, ${b.version}, ${c.version}`); return `three versions on ${a.url}`; }); define({ id: "publish.stale_base_conflicts", describe: "a stale `baseVersion` ⇒ `conflict` carrying the CURRENT version and either a full snapshot or the explicit `\"reread\"` word; nothing is written", requiresActions: ["publish"] }, async (h) => { const a = await publishOk(h, req(page(h, 1)), "fresh"); const b = await publishOk(h, req(page(h, 2), { url: a.url, baseVersion: a.version }), "update to v2"); const out = await publish(h, req(page(h, 3), { url: a.url, baseVersion: a.version })); check(!out.ok && out.code === "conflict", `a stale base answered ${out.ok ? "ok" : out.code}, expected conflict`); if (out.ok || out.code !== "conflict") return ""; check(out.currentVersion === b.version, `conflict names currentVersion ${out.currentVersion}, expected ${b.version}`); check(out.current === "reread" || (typeof out.current === "object" && Array.isArray(out.current.files)), "conflict.current must be a snapshot object or the string \"reread\""); const after = await publishOk(h, req(page(h, 4), { url: a.url, baseVersion: b.version }), "update on the true current version after the conflict"); check(after.version !== b.version, "the conflict wrote a version: the base the host itself named was stale one call later"); return `conflict at ${b.version}, current: ${out.current === "reread" ? "reread" : "snapshot"}`; }); for (const action of ["read", "read_file"]) { define({ id: `publish.conflict_preserves_${action}`, describe: `a stale-base rejection leaves readable bytes unchanged through ${action}, even if the host preserves the version number and returns a correct pre-write snapshot`, requiresActions: ["publish", action] }, async (h) => { const a = await publishOk(h, req(page(h, 1)), "fresh"); const b = await publishOk(h, req(page(h, 2), { url: a.url, baseVersion: a.version }), "update"); const observe = async () => { if (action === "read") { const out = await h.host.read({ url: a.url }, ctxOf(h)); if (!out.ok) return fail(`read refused: ${out.code}`); return JSON.stringify([out.ref.url, out.ref.slug, out.ref.version, out.ref.title, out.content, out.isolatedSummary]); } const out = await h.host.readFile({ url: a.url, path: "index.html" }, ctxOf(h)); if (!out.ok) return fail(`read_file refused: ${out.code}`); return JSON.stringify([out.file.path, out.file.mediaType, Array.from(out.file.content)]); }; const before = await observe(); const rejected = await publish(h, req(page(h, 3), { url: a.url, baseVersion: a.version })); check(!rejected.ok && rejected.code === "conflict" && rejected.currentVersion === b.version, "a stale base must conflict at the current version"); check(await observe() === before, `${action} changed after a rejected publish`); return `${action} unchanged after conflict`; }); } define({ id: "publish.conflict_snapshot_is_complete", describe: "when the conflict hands back a snapshot it is the WHOLE current version: every published path with its bytes and the media type sent, plus the metadata", requiresActions: ["publish"] }, async (h) => { const extra = h.caps.multiFile ? [script(h, 1)] : []; const a = await publishOk(h, req(page(h, 1, extra), { title: "Conformance title" }), "fresh"); const current = page(h, 2, extra); const b = await publishOk(h, req(current, { url: a.url, baseVersion: a.version, title: "Conformance title" }), "update to v2"); const out = await publish(h, req(page(h, 3, extra), { url: a.url, baseVersion: a.version })); check(!out.ok && out.code === "conflict", "a stale base must conflict"); if (out.ok || out.code !== "conflict") return ""; if (out.current === "reread") return `host answers "reread" (explicit; snapshot completeness not claimed) at ${b.version}`; for (const f of current) { const got = out.current.files.find((x) => x.path === f.path); check(got !== undefined, `snapshot lacks published path ${f.path}`); check(got !== undefined && same(got.content, f.content), `snapshot bytes differ for ${f.path}`); check(got !== undefined && got.mediaType === f.mediaType, `snapshot media type for ${f.path} is ${got?.mediaType}, expected ${f.mediaType}`); } check(out.current.files.length === current.length, `snapshot carries ${out.current.files.length} files, the current version has ${current.length}`); check(out.current.title === "Conformance title", `snapshot title is ${out.current.title}, expected the current title`); return `snapshot complete: ${current.length} file(s) + metadata`; }); define({ id: "publish.force_overrides_stale_base", describe: "`force: true` over a stale `baseVersion` applies (a plain published version is overwritable)", requiresActions: ["publish"] }, async (h) => { const a = await publishOk(h, req(page(h, 1)), "fresh"); const b = await publishOk(h, req(page(h, 2), { url: a.url, baseVersion: a.version }), "update to v2"); const forced = await publishOk(h, req(page(h, 3), { url: a.url, baseVersion: a.version, force: true }), "forced publish over a stale base"); check(forced.version !== b.version, "force answered ok but the version did not advance"); return `forced ${b.version} → ${forced.version}`; }); define({ id: "publish.force_refused_over_page_saved_version", describe: "`force: true` over a version saved from inside the page ⇒ `force_refused` (the one version force must not discard)", requiresActions: ["publish"], requiresHook: "pageSavedVersion" }, async (h) => { const a = await publishOk(h, req(page(h, 1)), "fresh"); await h.hooks.pageSavedVersion(a); const out = await publish(h, req(page(h, 2), { url: a.url, baseVersion: a.version, force: true })); check(!out.ok && out.code === "force_refused", `force over a page-saved version answered ${out.ok ? "ok" : out.code}, expected force_refused`); return "force_refused"; }); define({ id: "publish.attempt_replay_is_idempotent", describe: "the same `attemptId` replays the first outcome: a fresh publish never mints a second page, an update never advances twice", requiresActions: ["publish"] }, async (h) => { const before = h.caps.actions.includes("list") ? (await listMine(h)).length : undefined; const key = `${h.runId}-replay-fresh`; const a = await publishOk(h, req(page(h, 1), { title: "first" }), "fresh under key", key); const again = await publishOk(h, req(page(h, 9), { title: "changed" }), "replay under the same key", key); check(again.url === a.url && again.version === a.version, `the replay answered ${again.url} @ ${again.version}, expected ${a.url} @ ${a.version}`); if (before !== undefined) { const after = (await listMine(h)).length; check(after === before + 1, `two fresh calls under one key left ${after - before} new page(s) in the listing, expected 1`); } const key2 = `${h.runId}-replay-update`; const u1 = await publishOk(h, req(page(h, 2), { url: a.url, baseVersion: a.version }), "update under key2", key2); const u2 = await publishOk(h, req(page(h, 3), { url: a.url, baseVersion: a.version }), "replayed update under key2", key2); check(u2.version === u1.version, `the replayed update answered version ${u2.version}, expected ${u1.version}`); await publishOk(h, req(page(h, 4), { url: a.url, baseVersion: u1.version }), "an update on the replayed version (proves the version did not advance twice)"); return `fresh and update each replayed once on ${a.url}`; }); define({ id: "publish.attempt_replay_of_refusal", describe: "a typed refusal is replayed under the same `attemptId` too: a later call under that key with a request that would otherwise SUCCEED still gets the first refusal (the host does not re-decide)", requiresActions: ["publish"] }, async (h) => { const key = `${h.runId}-replay-refusal`; const a = await publish(h, req(page(h, 1), { url: FOREIGN_URL }), key); check(!a.ok && a.code === "url_foreign", `a foreign url answered ${a.ok ? "ok" : a.code}, expected url_foreign`); const b = await publish(h, req(page(h, 2)), key); check(!b.ok && b.code === "url_foreign", `the replay re-decided: a fresh request under the refused key answered ${b.ok ? "ok" : b.code}, expected the replayed url_foreign`); return "the refusal replayed over a request that would otherwise succeed"; }); define({ id: "publish.url_foreign_refused", describe: "a `url` this host never minted ⇒ `url_foreign` (a typed refusal, never a throw, never a page)", requiresActions: ["publish"] }, async (h) => { const out = await publish(h, req(page(h, 1), { url: FOREIGN_URL })); check(!out.ok && out.code === "url_foreign", `a foreign url answered ${out.ok ? "ok" : out.code}, expected url_foreign`); return "url_foreign"; }); define({ id: "publish.atomic_under_precommit_fault", describe: "a failure injected BEFORE the commit point leaves no observable change: no phantom version (the same base still applies) and, when list is declared, no new page", requiresActions: ["publish"], requiresHook: "precommitFault" }, async (h) => { const a = await publishOk(h, req(page(h, 1)), "fresh"); const before = h.caps.actions.includes("list") ? (await listMine(h)).length : undefined; const contentBefore = h.caps.actions.includes("read") ? await h.host.read({ url: a.url }, ctxOf(h)) : undefined; await h.hooks.precommitFault.arm(); let faulted; try { const out = await h.host.publish(req(page(h, 2), { url: a.url, baseVersion: a.version }), ctxOf(h)); faulted = out.ok ? "ok" : "typed"; } catch { faulted = "threw"; } finally { await h.hooks.precommitFault.disarm(); } check(faulted !== "ok", "the armed fault did not fail the publish — the hook is not reaching the host's write path"); if (contentBefore !== undefined) { const contentAfter = await h.host.read({ url: a.url }, ctxOf(h)); check(contentBefore.ok && contentAfter.ok && contentAfter.content === contentBefore.content && contentAfter.ref.version === contentBefore.ref.version, "the faulted publish changed the readable content or version before any commit"); } const after = await publishOk(h, req(page(h, 3), { url: a.url, baseVersion: a.version }), "update on the pre-fault base"); check(after.version !== a.version, "the update after the fault did not advance"); if (before !== undefined) check((await listMine(h)).length === before, "the faulted publish left a new page in the listing"); return `fault ${faulted}; base ${a.version} still applied → ${after.version}`; }); define({ id: "publish.byte_limits_enforced", describe: "one file over `maxFileBytes` ⇒ `payload_too_large`; with multiFile, files each within the per-file limit but summing past `maxTotalBytes` ⇒ `payload_too_large`", requiresActions: ["publish"] }, async (h) => { const big = await publish(h, req([{ path: "index.html", content: new Uint8Array(h.caps.maxFileBytes + 1), mediaType: HTML_MEDIA }])); check(!big.ok && big.code === "payload_too_large", `a file of maxFileBytes+1 answered ${big.ok ? "ok" : big.code}`); if (!h.caps.multiFile) return "per-file limit enforced (single-file host)"; const files = []; let left = h.caps.maxTotalBytes + 1; for (let i = 0; left > 0; i++) { const size = Math.min(left, h.caps.maxFileBytes); files.push({ path: i === 0 ? "index.html" : `part${i}.bin`, content: new Uint8Array(size), mediaType: i === 0 ? HTML_MEDIA : "application/octet-stream" }); left -= size; } const total = await publish(h, req(files)); check(!total.ok && total.code === "payload_too_large", `${files.length} files summing to maxTotalBytes+1 answered ${total.ok ? "ok" : total.code}`); return `per-file and total limits enforced (${files.length} files)`; }); define({ id: "publish.at_limit_accepted", describe: "a file of exactly `maxFileBytes` (within the total) is accepted; with multiFile, files summing to exactly `maxTotalBytes` are accepted", requiresActions: ["publish"], requiresLargePayloads: true }, async (h) => { const fileSize = Math.min(h.caps.maxFileBytes, h.caps.maxTotalBytes); await publishOk(h, req([{ path: "index.html", content: new Uint8Array(fileSize), mediaType: HTML_MEDIA }]), `a file of exactly ${fileSize} bytes`); if (!h.caps.multiFile) return `${fileSize} bytes accepted`; const files = []; let left = h.caps.maxTotalBytes; for (let i = 0; left > 0; i++) { const size = Math.min(left, h.caps.maxFileBytes); files.push({ path: i === 0 ? "index.html" : `part${i}.bin`, content: new Uint8Array(size), mediaType: i === 0 ? HTML_MEDIA : "application/octet-stream" }); left -= size; } await publishOk(h, req(files), `${files.length} files summing to exactly maxTotalBytes`); return `at-limit file and at-limit total accepted`; }); define({ id: "list.scope_words", describe: "`mine` lists only the caller's pages (including the ones just published), `shared` only others', `all` both; `truncated` is a boolean on every answer", requiresActions: ["publish", "list"] }, async (h) => { const a = await publishOk(h, req(page(h, 1)), "fresh"); const b = await publishOk(h, req(page(h, 2)), "fresh"); const mine = await h.host.list({ scope: "mine", limit: 1000 }, ctxOf(h)); const shared = await h.host.list({ scope: "shared", limit: 1000 }, ctxOf(h)); const all = await h.host.list({ scope: "all", limit: 1000 }, ctxOf(h)); for (const [scope, out] of [["mine", mine], ["shared", shared], ["all", all]]) { check(out.ok, `list ${scope} refused`); check(out.ok && typeof out.truncated === "boolean", `list ${scope}: truncated is not a boolean`); } if (!mine.ok || !shared.ok || !all.ok) return ""; check(mine.rows.every((r) => r.owner === "mine"), "a `mine` row is not owned by the caller"); check(shared.rows.every((r) => r.owner === "shared"), "a `shared` row is owned by the caller"); check([a.url, b.url].every((u) => mine.rows.some((r) => r.url === u)), "the pages just published are missing from `mine`"); check([a.url, b.url].every((u) => all.rows.some((r) => r.url === u)), "the pages just published are missing from `all`"); check(all.rows.length >= mine.rows.length, "`all` lists fewer rows than `mine`"); return `mine ${mine.rows.length}, shared ${shared.rows.length}, all ${all.rows.length}`; }); define({ id: "list.limit_and_truncated", describe: "`limit` bounds the rows and `truncated` says so when more exist", requiresActions: ["publish", "list"] }, async (h) => { await publishOk(h, req(page(h, 1)), "fresh"); await publishOk(h, req(page(h, 2)), "fresh"); const out = await h.host.list({ scope: "mine", limit: 1 }, ctxOf(h)); check(out.ok, "list refused"); if (!out.ok) return ""; check(out.rows.length <= 1, `limit 1 answered ${out.rows.length} rows`); check(out.truncated === true, "two pages exist but `truncated` is not true at limit 1"); return "limit honoured, truncated true"; }); define({ id: "read.own_page_full_content", describe: "`read` of the caller's own single-file page returns its full text with `isolatedSummary: false` and the page's ref", requiresActions: ["publish", "read"] }, async (h) => { const files = page(h, 1); const a = await publishOk(h, req(files), "fresh"); const out = await h.host.read({ url: a.url }, ctxOf(h)); check(out.ok, `read refused: ${out.ok ? "" : out.code}`); if (!out.ok) return ""; check(out.isolatedSummary === false, "the caller's own page came back as an isolated summary"); check(out.content === new TextDecoder().decode(files[0].content), "the content differs from the published text"); check(out.ref.url === a.url, `read.ref.url is ${out.ref.url}, expected ${a.url}`); return "full text, own page"; }); define({ id: "read.unknown_url_typed", describe: "`read` of a url this host never minted ⇒ a typed `not_found` / `forbidden`, never a throw", requiresActions: ["read"] }, async (h) => { let out; try { out = await h.host.read({ url: FOREIGN_URL }, ctxOf(h)); } catch (err) { return fail(`read threw: ${thrownText(err)}`); } check(!out.ok && (out.code === "not_found" || out.code === "forbidden"), `an unknown url answered ${out.ok ? "ok" : out.code}`); return out.ok ? "" : out.code; }); define({ id: "files.list_and_read_file", describe: "`list_files` names every published path with its byte count; `read_file` returns each file's bytes and media type; an unknown path ⇒ `not_found`", requiresActions: ["publish", "list_files", "read_file"] }, async (h) => { const files = page(h, 1, h.caps.multiFile ? [script(h, 2)] : []); const a = await publishOk(h, req(files), "fresh"); const listed = await h.host.listFiles({ url: a.url }, ctxOf(h)); check(listed.ok, `list_files refused: ${listed.ok ? "" : listed.code}`); if (!listed.ok) return ""; for (const f of files) { const e = listed.entries.find((x) => x.path === f.path); check(e !== undefined && e.bytes === f.content.byteLength, `list_files lacks ${f.path} at ${f.content.byteLength} bytes`); const got = await h.host.readFile({ url: a.url, path: f.path }, ctxOf(h)); check(got.ok, `read_file ${f.path} refused: ${got.ok ? "" : got.code}`); if (!got.ok) return ""; check(same(got.file.content, f.content) && got.file.mediaType === f.mediaType, `read_file ${f.path}: bytes or media type differ`); } const missing = await h.host.readFile({ url: a.url, path: "no/such/file.txt" }, ctxOf(h)); check(!missing.ok && missing.code === "not_found", `an unknown path answered ${missing.ok ? "ok" : missing.code}`); return `${files.length} file(s) listed and read`; }); export const ARTIFACT_CONFORMANCE_VECTORS = vectorTable.map(({ run: _run, ...v }) => Object.freeze(v)); const applies = (v, caps) => v.requiresActions.every((a) => caps.actions.includes(a)) && (v.requiresMultiFile !== true || caps.multiFile); export function artifactConformanceVectors(host) { const verdict = readArtifactHostCapabilities(host); if (!verdict.ok) throw new Error(`artifactConformanceVectors: the host's declaration is invalid — ${verdict.problem}`); return ARTIFACT_CONFORMANCE_VECTORS.filter((v) => applies(v, verdict.capabilities)); } export async function runArtifactConformance(host, opts = {}) { const verdict = readArtifactHostCapabilities(host); if (!verdict.ok) throw new Error(`runArtifactConformance: the host's declaration is invalid — ${verdict.problem}`); const h = { host, caps: verdict.capabilities, runId: opts.runId ?? randomUUID(), hooks: opts.hooks ?? {}, largePayloads: opts.largePayloads === true, seq: 0 }; host.onMount?.(h.runId); const results = []; for (const v of vectorTable) { if (!applies(v, h.caps)) { results.push({ id: v.id, status: "not_applicable", detail: `requires ${[...v.requiresActions, ...(v.requiresMultiFile ? ["multiFile"] : [])].join(" + ")}; not declared` }); continue; } if (v.requiresHook !== undefined && h.hooks[v.requiresHook] === undefined) { results.push({ id: v.id, status: "skipped", detail: `hook ${v.requiresHook} not supplied` }); continue; } if (v.requiresLargePayloads === true && !h.largePayloads) { results.push({ id: v.id, status: "skipped", detail: "largePayloads not enabled" }); continue; } let row; try { row = { id: v.id, status: "pass", detail: await v.run(h) }; } catch (err) { row = { id: v.id, status: "fail", detail: err instanceof Violation ? err.message : `threw: ${thrownText(err)}` }; } results.push(row); } const count = (s) => results.filter((r) => r.status === s).length; return { capabilities: h.caps, runId: h.runId, results, passed: count("pass"), failed: count("fail"), notApplicable: count("not_applicable"), skipped: count("skipped") }; }