diff --git a/.changeset/calm-agents-carry.md b/.changeset/calm-agents-carry.md new file mode 100644 index 000000000..4b6168655 --- /dev/null +++ b/.changeset/calm-agents-carry.md @@ -0,0 +1,15 @@ +--- +"@sapiom/tools": minor +--- + +Add a private v1 runtime-provenance carrier for agent invocations. Instrumented +calls send opaque callsite evidence out of band, terminal results retain an +SDK-only server receipt, and only exact direct result-to-input handoffs forward +that receipt through a trusted, one-shot build callsite. Private receipt state is +not package-exported; reflected values are redacted from errors. Request/result +JSON and calls without metadata remain unchanged. CJS and ESM imports share one +bundled lexical store, including mixed-format direct handoffs, without exposing +private extraction or rebinding helpers through the module cache. Build tooling +uses the unsupported implementation subpath +`@sapiom/tools/_internal/agent-runtime-provenance`; that subpath may change in +any release. diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 940bc7a12..9a42d5350 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,7 +2,6 @@ name: Test on: pull_request: - branches: [main] push: branches: [main] @@ -42,6 +41,9 @@ jobs: - name: Build run: pnpm build + - name: Verify tools runtime provenance package + run: pnpm --filter @sapiom/tools test:runtime-provenance-package + - name: Type check run: pnpm typecheck diff --git a/packages/tools/package.json b/packages/tools/package.json index 3a9a0228b..07bab145f 100644 --- a/packages/tools/package.json +++ b/packages/tools/package.json @@ -87,6 +87,11 @@ "types": "./dist/cjs/stub/index.d.ts", "import": "./dist/esm/stub/index.js", "require": "./dist/cjs/stub/index.js" + }, + "./_internal/agent-runtime-provenance": { + "types": "./dist/cjs/_internal/agent-runtime-provenance.d.ts", + "import": "./dist/esm/_internal/agent-runtime-provenance.js", + "require": "./dist/cjs/_internal/agent-runtime-provenance.js" } }, "files": [ @@ -100,8 +105,9 @@ "@sapiom/analytics-core": "workspace:^" }, "scripts": { - "build": "npm run gen:version && npm run build:cjs && npm run build:esm && npm run build:esm-pkg", + "build": "npm run gen:version && npm run build:cjs && npm run build:esm && npm run build:canonical-exports && npm run build:esm-pkg", "build:cjs": "tsc --project tsconfig.cjs.json", + "build:canonical-exports": "node scripts/canonicalize-runtime-provenance-exports.mjs", "build:esm": "tsc --project tsconfig.esm.json", "build:esm-pkg": "node -e \"require('fs').writeFileSync('dist/esm/package.json',JSON.stringify({type:'module'}))\"", "clean": "rm -rf dist *.tsbuildinfo", @@ -109,6 +115,7 @@ "gen:version": "node scripts/generate-version.mjs", "test": "npm run gen:version && jest", "test:coverage": "npm run gen:version && jest --coverage", + "test:runtime-provenance-package": "node scripts/test-runtime-provenance-package.mjs", "test:watch": "npm run gen:version && jest --watch", "typecheck": "npm run gen:version && tsc --noEmit", "lint": "eslint src --ext .ts", @@ -120,6 +127,7 @@ "@types/node": "^20.11.30", "@typescript-eslint/eslint-plugin": "^7.3.1", "@typescript-eslint/parser": "^7.3.1", + "esbuild": "^0.28.1", "eslint": "^8.57.0", "jest": "^29.7.0", "prettier": "^3.2.5", diff --git a/packages/tools/scripts/agent-runtime-provenance-entry.ts b/packages/tools/scripts/agent-runtime-provenance-entry.ts new file mode 100644 index 000000000..9e7607377 --- /dev/null +++ b/packages/tools/scripts/agent-runtime-provenance-entry.ts @@ -0,0 +1,5 @@ +export * as agents from "../src/agents/index.js"; +export { + AGENT_RUNTIME_PROVENANCE_VERSION, + carryAgentRuntimeProvenance, +} from "../src/_internal/agent-runtime-provenance.js"; diff --git a/packages/tools/scripts/canonicalize-runtime-provenance-exports.mjs b/packages/tools/scripts/canonicalize-runtime-provenance-exports.mjs new file mode 100644 index 000000000..a6b35809e --- /dev/null +++ b/packages/tools/scripts/canonicalize-runtime-provenance-exports.mjs @@ -0,0 +1,193 @@ +import assert from "node:assert/strict"; +import { rm, writeFile } from "node:fs/promises"; +import { createRequire } from "node:module"; +import { dirname, relative, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +import { build } from "esbuild"; + +const require = createRequire(import.meta.url); +const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."); +const cjsCarrierPath = resolve( + packageRoot, + "dist/cjs/_internal/agent-runtime-provenance.js", +); +const cjsAgentsPath = resolve(packageRoot, "dist/cjs/agents/index.js"); +const bundlePath = resolve( + packageRoot, + "dist/cjs/agents/runtime-provenance.cjs", +); + +function runtimeExportNames(modulePath) { + const names = Object.keys(require(modulePath)).sort(); + assert(names.length > 0, `no runtime exports found in ${modulePath}`); + for (const name of names) { + assert.match(name, /^[A-Z_$][0-9A-Z_$]*$/i, `unsupported export: ${name}`); + } + return names; +} + +const carrierExportNames = runtimeExportNames(cjsCarrierPath); +const agentExportNames = runtimeExportNames(cjsAgentsPath); + +const canonicalBuild = await build({ + entryPoints: [ + resolve(packageRoot, "scripts/agent-runtime-provenance-entry.ts"), + ], + outfile: bundlePath, + bundle: true, + format: "cjs", + platform: "node", + target: "node18", + packages: "external", + plugins: [ + { + name: "native-agent-transport-boundary", + setup(builder) { + builder.onResolve({ filter: /^\.\.\/_client\/index\.js$/ }, () => ({ + path: "agent-runtime-provenance-transport-shim", + namespace: "agent-runtime-provenance", + })); + builder.onLoad( + { + filter: /^agent-runtime-provenance-transport-shim$/, + namespace: "agent-runtime-provenance", + }, + () => ({ + contents: + 'export function defaultTransport() { throw new Error("agent transport must be supplied by the format-native facade"); }', + loader: "js", + }), + ); + }, + }, + ], + metafile: true, + sourcemap: false, + logLevel: "warning", +}); + +assert.equal( + Object.keys(canonicalBuild.metafile.inputs).some((input) => + input.includes("src/_client/"), + ), + false, + "canonical provenance artifact must not bundle a real client graph", +); + +assert.deepEqual(runtimeExportNames(bundlePath), [ + "AGENT_RUNTIME_PROVENANCE_VERSION", + "agents", + "carryAgentRuntimeProvenance", +]); +assert.deepEqual( + Object.keys(require(bundlePath).agents).sort(), + agentExportNames, +); +assert( + agentExportNames.length > 0, + "canonical agents namespace has no exports", +); + +function moduleSpecifier(fromPath, toPath) { + const path = relative(dirname(fromPath), toPath).replaceAll("\\", "/"); + return path.startsWith(".") ? path : `./${path}`; +} + +async function writeCjsFacade(outputPath, exportNames, namespace) { + const bundleSpecifier = moduleSpecifier(outputPath, bundlePath); + const canonical = namespace ? `canonical.${namespace}` : "canonical"; + const source = [ + '"use strict";', + "// Generated by scripts/canonicalize-runtime-provenance-exports.mjs.", + "// Private provenance state is lexical inside the canonical bundle.", + `const canonical = require(${JSON.stringify(bundleSpecifier)});`, + 'Object.defineProperty(exports, "__esModule", { value: true });', + ...exportNames.map((name) => `exports.${name} = ${canonical}.${name};`), + "", + ].join("\n"); + await writeFile(outputPath, source); + await rm(`${outputPath}.map`, { force: true }); +} + +async function writeEsmFacade(outputPath, cjsPath, exportNames) { + const cjsSpecifier = moduleSpecifier(outputPath, cjsPath); + const source = [ + "// Generated by scripts/canonicalize-runtime-provenance-exports.mjs.", + "// Import and require share the canonical closure-backed implementation.", + `import canonical from ${JSON.stringify(cjsSpecifier)};`, + ...exportNames.map((name) => `export const ${name} = canonical.${name};`), + "", + ].join("\n"); + await writeFile(outputPath, source); + await rm(`${outputPath}.map`, { force: true }); +} + +async function writeCjsAgentsFacade(outputPath, exportNames) { + const bundleSpecifier = moduleSpecifier(outputPath, bundlePath); + const source = [ + '"use strict";', + "// Generated by scripts/canonicalize-runtime-provenance-exports.mjs.", + "// The facade supplies this format's native default transport.", + `const canonical = require(${JSON.stringify(bundleSpecifier)}).agents;`, + 'const { defaultTransport } = require("../_client/index.js");', + 'Object.defineProperty(exports, "__esModule", { value: true });', + ...exportNames + .filter((name) => name !== "launch" && name !== "run") + .map((name) => `exports.${name} = canonical.${name};`), + "exports.launch = async function launch(spec, transport = defaultTransport(), baseUrl) {", + " return canonical.launch(spec, transport, baseUrl);", + "};", + "exports.run = async function run(spec, transport = defaultTransport(), baseUrl) {", + " return canonical.run(spec, transport, baseUrl);", + "};", + "", + ].join("\n"); + await writeFile(outputPath, source); + await rm(`${outputPath}.map`, { force: true }); +} + +async function writeEsmAgentsFacade(outputPath, exportNames) { + const bundleSpecifier = moduleSpecifier(outputPath, bundlePath); + const source = [ + "// Generated by scripts/canonicalize-runtime-provenance-exports.mjs.", + "// The facade supplies this format's native default transport.", + `import canonicalModule from ${JSON.stringify(bundleSpecifier)};`, + 'import { defaultTransport } from "../_client/index.js";', + "const canonical = canonicalModule.agents;", + ...exportNames + .filter((name) => name !== "launch" && name !== "run") + .map((name) => `export const ${name} = canonical.${name};`), + "export async function launch(spec, transport = defaultTransport(), baseUrl) {", + " return canonical.launch(spec, transport, baseUrl);", + "}", + "export async function run(spec, transport = defaultTransport(), baseUrl) {", + " return canonical.run(spec, transport, baseUrl);", + "}", + "", + ].join("\n"); + await writeFile(outputPath, source); + await rm(`${outputPath}.map`, { force: true }); +} + +await writeCjsFacade(cjsCarrierPath, carrierExportNames); +await writeCjsAgentsFacade(cjsAgentsPath, agentExportNames); +await writeEsmFacade( + resolve(packageRoot, "dist/esm/_internal/agent-runtime-provenance.js"), + cjsCarrierPath, + carrierExportNames, +); +await writeEsmAgentsFacade( + resolve(packageRoot, "dist/esm/agents/index.js"), + agentExportNames, +); + +for (const format of ["cjs", "esm"]) { + const storeBase = resolve( + packageRoot, + `dist/${format}/agents/runtime-callsite-store`, + ); + for (const extension of [".js", ".js.map", ".d.ts", ".d.ts.map"]) { + await rm(`${storeBase}${extension}`, { force: true }); + } +} diff --git a/packages/tools/scripts/test-runtime-provenance-package.mjs b/packages/tools/scripts/test-runtime-provenance-package.mjs new file mode 100644 index 000000000..826594507 --- /dev/null +++ b/packages/tools/scripts/test-runtime-provenance-package.mjs @@ -0,0 +1,804 @@ +import assert from "node:assert/strict"; +import { existsSync, readFileSync } from "node:fs"; +import { createRequire } from "node:module"; +import { dirname, resolve } from "node:path"; +import { spawnSync } from "node:child_process"; +import { fileURLToPath } from "node:url"; + +const require = createRequire(import.meta.url); +const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."); +const CARRIER_EXPORT = "@sapiom/tools/_internal/agent-runtime-provenance"; +const VERSION_HEADER = "x-sapiom-runtime-provenance-version"; +const CALLSITE_HEADER = "x-sapiom-runtime-callsite-evidence"; +const LINEAGE_HEADER = "x-sapiom-runtime-lineage-receipt"; + +function verifyIsolatedFormat(label, source, inputType) { + const result = spawnSync( + process.execPath, + [...(inputType ? ["--input-type", inputType] : []), "--eval", source], + { + cwd: packageRoot, + encoding: "utf8", + env: { ...process.env, SAPIOM_API_KEY: "isolated-format-key" }, + }, + ); + assert.equal( + result.status, + 0, + `${label} isolated probe failed\n${result.stdout}\n${result.stderr}`, + ); +} + +const isolatedServerSource = ` +let execution = 0; +globalThis.fetch = async (_input, init = {}) => { + new Headers(init.headers); + if (init.method === "POST") { + execution += 1; + return new Response(JSON.stringify({ status: "enqueued", executionId: "isolated-" + execution }), { status: 201, headers: { "content-type": "application/json" } }); + } + return new Response(JSON.stringify({ status: "completed", output: { ok: true }, error: null }), { status: 200, headers: { "content-type": "application/json" } }); +};`; + +verifyIsolatedFormat( + "ESM native transport", + ` +import { createRequire } from "node:module"; +${isolatedServerSource} +const require = createRequire(import.meta.url); +const tools = await import("@sapiom/tools"); +const handle = await tools.agents.launch({ definition: "esm-native-launch" }); +if ((await handle.wait({ pollMs: 1 })).status !== "completed") process.exit(2); +if ((await tools.agents.run({ definition: "esm-native-run" })).status !== "completed") process.exit(3); +if (Object.keys(require.cache).some((path) => path.endsWith("/dist/cjs/_client/index.js"))) process.exit(4); +`, + "module", +); + +verifyIsolatedFormat( + "CJS native transport", + ` +${isolatedServerSource} +const tools = require("@sapiom/tools"); +(async () => { + const handle = await tools.agents.launch({ definition: "cjs-native-launch" }); + if ((await handle.wait({ pollMs: 1 })).status !== "completed") process.exit(2); + if ((await tools.agents.run({ definition: "cjs-native-run" })).status !== "completed") process.exit(3); + if (Object.keys(require.cache).some((path) => path.includes("/dist/esm/"))) process.exit(4); +})().catch((error) => { console.error(error); process.exit(5); }); +`, +); + +function fakeAgentServer() { + const calls = []; + let execution = 0; + const fetch = async (input, init = {}) => { + calls.push({ url: String(input), init }); + if (init.method === "POST") { + execution += 1; + return new Response( + JSON.stringify({ + status: "enqueued", + executionId: `exec-${execution}`, + }), + { status: 201, headers: { "content-type": "application/json" } }, + ); + } + return new Response( + JSON.stringify({ + status: "completed", + output: { ok: true }, + error: null, + }), + { + status: 200, + headers: { + "content-type": "application/json", + [VERSION_HEADER]: "1", + [LINEAGE_HEADER]: "signed.package-surface", + }, + }, + ); + }; + return { fetch, calls }; +} + +function header(call, name) { + return new Headers(call.init.headers).get(name); +} + +async function verifySameFormat(label, tools, carrier) { + assert.deepEqual(Object.keys(carrier).sort(), [ + "AGENT_RUNTIME_PROVENANCE_VERSION", + "carryAgentRuntimeProvenance", + ]); + const server = fakeAgentServer(); + const client = tools.createClient({ apiKey: "k", fetch: server.fetch }); + const result = await client.agents.run({ definition: `${label}-producer` }); + await client.agents.run( + carrier.carryAgentRuntimeProvenance( + { definition: `${label}-consumer`, input: result.output }, + { version: 1, callsite: `callsite.${label}` }, + ), + ); + const posts = server.calls.filter((call) => call.init.method === "POST"); + assert.equal(header(posts[1], CALLSITE_HEADER), `callsite.${label}`); + assert.equal(header(posts[1], LINEAGE_HEADER), "signed.package-surface"); + assert.equal( + JSON.stringify(result).includes("signed.package-surface"), + false, + ); + await client.shutdown(); +} + +async function captureLaunchError(client, spec) { + try { + await client.agents.launch(spec); + } catch (error) { + return error; + } + assert.fail("expected agent launch to throw"); +} + +class NativeStackTransportError extends TypeError { + constructor(message, cause, diagnostics) { + super(message, { cause }); + this.name = "NativeStackTransportError"; + this.code = "EAGENT"; + this.diagnostics = diagnostics; + } +} + +function nativeStackFixture(callsite, diagnostics) { + const cause = new Error(`native cause reflected ${callsite}`); + cause.code = "ECONNREFUSED"; + return new NativeStackTransportError( + `native transport reflected ${callsite}`, + cause, + diagnostics, + ); +} + +async function verifyNativeErrorStackRedaction(tools, carrier) { + const callsite = "callsite.package-native-stack"; + let nestedGetterReads = 0; + const diagnostics = { + request: { + headers: { + [CALLSITE_HEADER]: callsite, + "x-request-id": "request-public", + }, + }, + response: { status: 502, retryable: true }, + }; + Object.defineProperty(diagnostics.request, "lazy", { + configurable: true, + enumerable: false, + get() { + nestedGetterReads += 1; + return callsite; + }, + }); + const failure = nativeStackFixture(callsite, diagnostics); + const failureStackDescriptor = Object.getOwnPropertyDescriptor( + failure, + "stack", + ); + assert.ok(failureStackDescriptor); + const failureStackIsDataDescriptor = "value" in failureStackDescriptor; + assert.equal(typeof failure.stack, "string"); + assert.match(failure.stack, /nativeStackFixture/); + assert.match(failure.stack, new RegExp(callsite)); + const client = tools.createClient({ + apiKey: "k", + fetch: async () => { + throw failure; + }, + }); + const caught = await captureLaunchError( + client, + carrier.carryAgentRuntimeProvenance( + { definition: "package-native-stack" }, + { version: 1, callsite }, + ), + ); + + assert.notEqual(caught, failure); + assert.ok(caught instanceof NativeStackTransportError); + assert.equal( + Object.getPrototypeOf(caught), + NativeStackTransportError.prototype, + ); + assert.equal(caught.code, "EAGENT"); + assert.equal(caught.diagnostics.response.status, 502); + assert.equal(caught.diagnostics.response.retryable, true); + assert.equal( + caught.diagnostics.request.headers["x-request-id"], + "request-public", + ); + assert.equal( + caught.diagnostics.request.headers[CALLSITE_HEADER], + "[REDACTED runtime provenance]", + ); + assert.equal(typeof caught.stack, "string"); + assert.match(caught.stack, /nativeStackFixture/); + assert.equal(caught.stack.includes(callsite), false); + const caughtStackDescriptor = Object.getOwnPropertyDescriptor( + caught, + "stack", + ); + assert.ok(caughtStackDescriptor); + assert.equal("value" in caughtStackDescriptor, failureStackIsDataDescriptor); + assert.equal( + caughtStackDescriptor.configurable, + failureStackDescriptor.configurable, + ); + assert.equal( + caughtStackDescriptor.enumerable, + failureStackDescriptor.enumerable, + ); + if (failureStackIsDataDescriptor) { + assert.equal( + caughtStackDescriptor.writable, + failureStackDescriptor.writable, + ); + assert.equal(caughtStackDescriptor.value, caught.stack); + } else { + assert.equal(caughtStackDescriptor.get, failureStackDescriptor.get); + assert.equal(caughtStackDescriptor.set, failureStackDescriptor.set); + } + assert.equal(caught.message.includes(callsite), false); + assert.equal(caught.cause.message.includes(callsite), false); + assert.equal(typeof caught.cause.stack, "string"); + assert.match(caught.cause.stack, /nativeStackFixture/); + assert.equal(caught.cause.stack.includes(callsite), false); + assert.equal(caught.cause.code, "ECONNREFUSED"); + assert.equal(nestedGetterReads, 0); + assert.equal(failure.message.includes(callsite), true); + assert.equal(failure.stack.includes(callsite), true); + assert.equal(failure.cause.message.includes(callsite), true); + assert.equal(failure.cause.stack.includes(callsite), true); + assert.equal(failure.diagnostics.request.headers[CALLSITE_HEADER], callsite); + await client.shutdown(); + + let customStackReads = 0; + let customDiagnosticReads = 0; + const customDiagnostics = { reflected: callsite }; + Object.defineProperty(customDiagnostics, "lazy", { + configurable: true, + enumerable: false, + get() { + customDiagnosticReads += 1; + return callsite; + }, + }); + const customFailure = new NativeStackTransportError( + "custom stack transport", + new Error("public cause"), + customDiagnostics, + ); + const customStackGetter = () => { + customStackReads += 1; + return `custom stack ${callsite}`; + }; + Object.defineProperty(customFailure, "stack", { + configurable: true, + enumerable: false, + get: customStackGetter, + }); + const customClient = tools.createClient({ + apiKey: "k", + fetch: async () => { + throw customFailure; + }, + }); + const customCaught = await captureLaunchError( + customClient, + carrier.carryAgentRuntimeProvenance( + { definition: "package-custom-stack" }, + { version: 1, callsite }, + ), + ); + assert.notEqual(customCaught, customFailure); + assert.equal(customStackReads, 0); + assert.equal(customDiagnosticReads, 0); + const customStackDescriptor = Object.getOwnPropertyDescriptor( + customCaught, + "stack", + ); + assert.equal(customStackDescriptor.value, "[REDACTED runtime provenance]"); + assert.equal("get" in customStackDescriptor, false); + assert.equal("set" in customStackDescriptor, false); + const customLazyDescriptor = Object.getOwnPropertyDescriptor( + customCaught.diagnostics, + "lazy", + ); + assert.equal(customLazyDescriptor.value, "[REDACTED runtime provenance]"); + assert.equal("get" in customLazyDescriptor, false); + assert.equal("set" in customLazyDescriptor, false); + assert.equal( + customCaught.diagnostics.reflected, + "[REDACTED runtime provenance]", + ); + await customClient.shutdown(); + + const noMatchFailure = nativeStackFixture("public-value", { + request: { headers: { "x-request-id": "request-public" } }, + }); + const noMatchClient = tools.createClient({ + apiKey: "k", + fetch: async () => { + throw noMatchFailure; + }, + }); + const noMatchCaught = await captureLaunchError( + noMatchClient, + carrier.carryAgentRuntimeProvenance( + { definition: "package-no-match" }, + { version: 1, callsite: "callsite.not-reflected" }, + ), + ); + assert.equal(noMatchCaught, noMatchFailure); + await noMatchClient.shutdown(); + + const noPrivateFailure = new TypeError("uninstrumented transport"); + const noPrivateClient = tools.createClient({ + apiKey: "k", + fetch: async () => { + throw noPrivateFailure; + }, + }); + const noPrivateCaught = await captureLaunchError(noPrivateClient, { + definition: "package-no-private", + }); + assert.equal(noPrivateCaught, noPrivateFailure); + await noPrivateClient.shutdown(); +} + +async function verifyContainerDiagnosticRedaction(tools, carrier) { + const callsite = "callsite.package-container-private"; + const secretSymbol = Symbol("secret diagnostic"); + const lazySymbol = Symbol("lazy diagnostic"); + let symbolAccessorReads = 0; + const symbolHeaders = new Headers({ "x-request-id": "request-public" }); + symbolHeaders[secretSymbol] = callsite; + Object.defineProperty(symbolHeaders, lazySymbol, { + configurable: true, + get() { + symbolAccessorReads += 1; + return callsite; + }, + }); + const symbolFailure = Object.assign( + new TypeError("symbol transport failed"), + { + request: { headers: symbolHeaders }, + }, + ); + const symbolClient = tools.createClient({ + apiKey: "k", + fetch: async () => { + throw symbolFailure; + }, + }); + const symbolCaught = await captureLaunchError( + symbolClient, + carrier.carryAgentRuntimeProvenance( + { definition: "package-symbol-diagnostics" }, + { version: 1, callsite }, + ), + ); + assert.notEqual(symbolCaught, symbolFailure); + assert.equal(symbolCaught.request.headers[secretSymbol], undefined); + assert.equal( + Object.getOwnPropertyDescriptor(symbolCaught.request.headers, lazySymbol), + undefined, + ); + assert.equal(symbolAccessorReads, 0); + assert.equal(symbolFailure.request.headers[secretSymbol], callsite); + assert.equal( + typeof Object.getOwnPropertyDescriptor( + symbolFailure.request.headers, + lazySymbol, + ).get, + "function", + ); + await symbolClient.shutdown(); + + let poisonedMethodReads = 0; + let customGetterReads = 0; + const poison = () => { + poisonedMethodReads += 1; + throw new Error("instance container method must not run"); + }; + const headers = new Headers({ + [CALLSITE_HEADER]: callsite, + "x-request-id": "request-public", + }); + Object.defineProperty(headers, "forEach", { + configurable: true, + value: poison, + }); + const shared = { privateValue: callsite, publicValue: "shared-public" }; + const map = new Map([["shared", shared]]); + const set = new Set([shared]); + map.set("self", map); + set.add(set); + for (const [container, methods] of [ + [map, ["entries", "forEach", "set", Symbol.iterator]], + [set, ["entries", "forEach", "add", Symbol.iterator]], + ]) { + for (const method of methods) { + Object.defineProperty(container, method, { + configurable: true, + value: poison, + }); + } + } + class OpaqueDiagnostic { + constructor() { + this.privateValue = callsite; + this.publicValue = "opaque-public"; + } + } + const opaque = new OpaqueDiagnostic(); + const diagnostics = { + request: { headers, requestId: "request-public" }, + map, + set, + opaque, + publicValue: "diagnostic-public", + }; + Object.defineProperty(diagnostics, "lazy", { + configurable: true, + get() { + customGetterReads += 1; + return callsite; + }, + }); + const failure = Object.assign(new TypeError("container transport failed"), { + code: "EAGENT", + diagnostics, + }); + const client = tools.createClient({ + apiKey: "k", + fetch: async () => { + throw failure; + }, + }); + const caught = await captureLaunchError( + client, + carrier.carryAgentRuntimeProvenance( + { definition: "package-container-diagnostics" }, + { version: 1, callsite }, + ), + ); + + assert.notEqual(caught, failure); + assert.ok(caught instanceof TypeError); + assert.equal(caught.code, "EAGENT"); + assert.equal(caught.diagnostics.request.requestId, "request-public"); + assert.equal( + Headers.prototype.get.call( + caught.diagnostics.request.headers, + CALLSITE_HEADER, + ), + "[REDACTED runtime provenance]", + ); + const caughtShared = Map.prototype.get.call(caught.diagnostics.map, "shared"); + assert.equal( + Map.prototype.get.call(caught.diagnostics.map, "self"), + caught.diagnostics.map, + ); + assert.equal( + Set.prototype.has.call(caught.diagnostics.set, caught.diagnostics.set), + true, + ); + assert.equal( + Set.prototype.has.call(caught.diagnostics.set, caughtShared), + true, + ); + assert.equal(caughtShared.privateValue, "[REDACTED runtime provenance]"); + assert.equal(caughtShared.publicValue, "shared-public"); + assert.equal(caught.diagnostics.opaque, "[REDACTED runtime provenance]"); + assert.equal(caught.diagnostics.publicValue, "diagnostic-public"); + const lazyDescriptor = Object.getOwnPropertyDescriptor( + caught.diagnostics, + "lazy", + ); + assert.equal(lazyDescriptor.value, "[REDACTED runtime provenance]"); + assert.equal("get" in lazyDescriptor, false); + assert.equal("set" in lazyDescriptor, false); + assert.equal(poisonedMethodReads, 0); + assert.equal(customGetterReads, 0); + assert.equal(Headers.prototype.get.call(headers, CALLSITE_HEADER), callsite); + assert.equal(Map.prototype.get.call(map, "shared"), shared); + assert.equal(Map.prototype.get.call(map, "self"), map); + assert.equal(Set.prototype.has.call(set, set), true); + assert.equal(shared.privateValue, callsite); + assert.equal(opaque.privateValue, callsite); + assert.equal(failure.diagnostics, diagnostics); + await client.shutdown(); +} + +const cjsTools = require("@sapiom/tools"); +const cjsCarrier = require(CARRIER_EXPORT); +const cjsSandboxes = require("@sapiom/tools/sandboxes"); +const cjsRepositories = require("@sapiom/tools/repositories"); +const cjsMemory = require("@sapiom/tools/memory"); +const cjsFileStorage = require("@sapiom/tools/file-storage"); +const cjsContentGeneration = require("@sapiom/tools/content-generation"); +const cjsSearch = require("@sapiom/tools/search"); +const cjsDatabase = require("@sapiom/tools/database"); +const cjsRootSource = readFileSync( + resolve(packageRoot, "dist/cjs/index.js"), + "utf8", +); +assert.equal("default" in cjsTools, false); +assert.equal("default" in cjsCarrier, false); +assert.match(cjsRootSource, /require\("\.\/client\.js"\)/); +assert.doesNotMatch(cjsRootSource, /runtime-provenance\.cjs/); +assert.equal(cjsTools.Sandbox, cjsSandboxes.Sandbox); +assert.equal(cjsTools.Repository, cjsRepositories.Repository); +assert.equal(cjsTools.MemoryHttpError, cjsMemory.MemoryHttpError); +assert.equal( + cjsTools.FileStorageHttpError, + cjsFileStorage.FileStorageHttpError, +); +assert.equal( + cjsTools.ContentGenerationHttpError, + cjsContentGeneration.ContentGenerationHttpError, +); +assert.equal(cjsTools.SearchHttpError, cjsSearch.SearchHttpError); +assert.equal(cjsTools.DatabaseHttpError, cjsDatabase.DatabaseHttpError); +assert.equal(cjsTools.sandboxes.create, cjsSandboxes.create); +assert.equal(cjsTools.repositories.create, cjsRepositories.create); +assert.equal(cjsTools.memory.recall, cjsMemory.recall); +assert.equal(cjsTools.agents.launch.name, "launch"); +assert.equal(cjsTools.agents.launch.length, 1); +assert.equal(cjsTools.agents.launch.constructor.name, "AsyncFunction"); +const cjsClientModules = Object.values(require.cache).filter((loaded) => + loaded?.filename.endsWith("/dist/cjs/_client/index.js"), +); +assert.equal(cjsClientModules.length, 1); +assert.equal( + cjsClientModules[0].exports.defaultTransport(), + cjsClientModules[0].exports.defaultTransport(), +); +assert.throws( + () => require("@sapiom/tools/dist/cjs/agents/runtime-callsite-store.js"), + (error) => error?.code === "ERR_PACKAGE_PATH_NOT_EXPORTED", +); +await verifySameFormat("cjs", cjsTools, cjsCarrier); +await verifyNativeErrorStackRedaction(cjsTools, cjsCarrier); +await verifyContainerDiagnosticRedaction(cjsTools, cjsCarrier); + +const esmTools = await import("@sapiom/tools"); +const esmCarrier = await import(CARRIER_EXPORT); +const esmSandboxes = await import("@sapiom/tools/sandboxes"); +const esmRepositories = await import("@sapiom/tools/repositories"); +const esmMemory = await import("@sapiom/tools/memory"); +const esmFileStorage = await import("@sapiom/tools/file-storage"); +const esmContentGeneration = await import("@sapiom/tools/content-generation"); +const esmSearch = await import("@sapiom/tools/search"); +const esmDatabase = await import("@sapiom/tools/database"); +await assert.rejects( + import("@sapiom/tools/dist/esm/agents/runtime-callsite-store.js"), + (error) => error?.code === "ERR_PACKAGE_PATH_NOT_EXPORTED", +); +assert.equal("default" in esmTools, false); +assert.equal("default" in esmCarrier, false); +assert.deepEqual(Object.keys(esmTools).sort(), Object.keys(cjsTools).sort()); +assert.equal(esmTools.Sandbox, esmSandboxes.Sandbox); +assert.equal(esmTools.Repository, esmRepositories.Repository); +assert.equal(esmTools.MemoryHttpError, esmMemory.MemoryHttpError); +assert.equal( + esmTools.FileStorageHttpError, + esmFileStorage.FileStorageHttpError, +); +assert.equal( + esmTools.ContentGenerationHttpError, + esmContentGeneration.ContentGenerationHttpError, +); +assert.equal(esmTools.SearchHttpError, esmSearch.SearchHttpError); +assert.equal(esmTools.DatabaseHttpError, esmDatabase.DatabaseHttpError); +assert.equal(esmTools.sandboxes.create, esmSandboxes.create); +assert.equal(esmTools.repositories.create, esmRepositories.create); +assert.equal(esmTools.memory.recall, esmMemory.recall); +assert.equal(esmTools.agents.launch.name, "launch"); +assert.equal(esmTools.agents.launch.length, 1); +assert.equal(esmTools.agents.launch.constructor.name, "AsyncFunction"); +assert.equal( + esmCarrier.carryAgentRuntimeProvenance, + cjsCarrier.carryAgentRuntimeProvenance, +); +await verifySameFormat("esm", esmTools, esmCarrier); + +const esmRootSource = readFileSync( + resolve(packageRoot, "dist/esm/index.js"), + "utf8", +); +assert.match(esmRootSource, /from "\.\/client\.js"/); +assert.match(esmRootSource, /export \* as agents from "\.\/agents\/index\.js"/); +assert.doesNotMatch(esmRootSource, /\.\.\/cjs\/index\.js/); +assert.doesNotMatch(esmRootSource, /runtime-provenance\.cjs/); + +async function verifyCrossFormatCallsite(label, tools, carrier) { + const server = fakeAgentServer(); + const client = tools.createClient({ apiKey: "k", fetch: server.fetch }); + await client.agents.launch( + carrier.carryAgentRuntimeProvenance( + { definition: `${label}-consumer` }, + { version: 1, callsite: `callsite.${label}` }, + ), + ); + const post = server.calls.find((call) => call.init.method === "POST"); + assert.equal(header(post, CALLSITE_HEADER), `callsite.${label}`); + await client.shutdown(); +} + +async function verifyCrossFormatResult( + label, + producerTools, + consumerTools, + consumerCarrier, +) { + const server = fakeAgentServer(); + const producer = producerTools.createClient({ + apiKey: "k", + fetch: server.fetch, + }); + const consumer = consumerTools.createClient({ + apiKey: "k", + fetch: server.fetch, + }); + for (const target of ["full-result", "output"]) { + const result = await producer.agents.run({ + definition: `${label}-${target}-producer`, + }); + await consumer.agents.run( + consumerCarrier.carryAgentRuntimeProvenance( + { + definition: `${label}-${target}-consumer`, + input: target === "full-result" ? result : result.output, + }, + { version: 1, callsite: `callsite.${label}.${target}` }, + ), + ); + const post = server.calls + .filter((call) => call.init.method === "POST") + .at(-1); + assert.equal(header(post, CALLSITE_HEADER), `callsite.${label}.${target}`); + assert.equal(header(post, LINEAGE_HEADER), "signed.package-surface"); + } + await Promise.all([producer.shutdown(), consumer.shutdown()]); +} + +await verifyCrossFormatCallsite("cjs-carrier-esm-client", esmTools, cjsCarrier); +await verifyCrossFormatCallsite("esm-carrier-cjs-client", cjsTools, esmCarrier); +await verifyCrossFormatResult( + "cjs-result-esm-client", + cjsTools, + esmTools, + esmCarrier, +); +await verifyCrossFormatResult( + "esm-result-cjs-client", + esmTools, + cjsTools, + cjsCarrier, +); + +for (const format of ["cjs", "esm"]) { + for (const extension of [".js", ".js.map", ".d.ts", ".d.ts.map"]) { + assert.equal( + existsSync( + resolve( + packageRoot, + `dist/${format}/agents/runtime-callsite-store${extension}`, + ), + ), + false, + ); + } +} + +const loadedToolsModules = Object.values(require.cache).filter((loaded) => + loaded?.filename.includes("/packages/tools/dist/"), +); +const loadedExportNames = loadedToolsModules.flatMap((loaded) => + Object.keys(loaded?.exports ?? {}), +); +const forbiddenHelperNames = [ + "registerAgentRuntimeCallsite", + "takeAgentRuntimeCallsite", +]; +for (const name of forbiddenHelperNames) { + assert.equal(loadedExportNames.includes(name), false); +} +const supportedProvenanceCacheExports = new Set([ + ...Object.keys(cjsTools), + ...Object.keys(cjsTools.agents), + ...Object.keys(cjsCarrier), +]); +const loadedProvenanceModules = loadedToolsModules.filter( + (loaded) => + loaded.filename.endsWith("/agents/index.js") || + loaded.filename.includes("agent-runtime-provenance") || + loaded.filename.includes("runtime-provenance.cjs"), +); +for (const loaded of loadedProvenanceModules) { + assert.deepEqual( + Object.keys(loaded.exports).filter( + (name) => !supportedProvenanceCacheExports.has(name), + ), + [], + `unsupported cache exports from ${loaded.filename}`, + ); +} + +const attackSource = cjsCarrier.carryAgentRuntimeProvenance( + { definition: "cache-attack-source" }, + { version: 1, callsite: "callsite.cache-attack" }, +); +const cachedTake = loadedToolsModules + .map((loaded) => loaded.exports?.takeAgentRuntimeCallsite) + .find(Boolean); +const cachedRegister = loadedToolsModules + .map((loaded) => loaded.exports?.registerAgentRuntimeCallsite) + .find(Boolean); +assert.equal(cachedTake, undefined); +assert.equal(cachedRegister, undefined); +const reboundSpec = { definition: "cache-attack-rebound" }; +if (cachedTake && cachedRegister) { + cachedRegister(reboundSpec, 1, cachedTake(attackSource)); +} +const attackServer = fakeAgentServer(); +const attackClient = cjsTools.createClient({ + apiKey: "k", + fetch: attackServer.fetch, +}); +await attackClient.agents.launch(reboundSpec); +const attackPost = attackServer.calls.find( + (call) => call.init.method === "POST", +); +assert.equal(header(attackPost, CALLSITE_HEADER), null); +await attackClient.shutdown(); + +async function verifyStubSurface(label, stubModule) { + assert.equal("default" in stubModule, false); + assert.equal(typeof stubModule.createStubClient, "function"); + const stub = stubModule.createStubClient(); + const runResult = await stub.agents.run({ + definition: `${label}-stub-run`, + }); + assert.equal(runResult.status, "completed"); + const handle = await stub.agents.launch({ + definition: `${label}-stub-launch`, + }); + assert.equal(handle.dispatch.resultSignal, cjsTools.AGENTS_RESULT_SIGNAL); + assert.equal((await handle.wait()).status, "completed"); + await stub.shutdown(); +} + +const cjsStub = require("@sapiom/tools/stub"); +const esmStub = await import("@sapiom/tools/stub"); +await verifyStubSurface("cjs", cjsStub); +await verifyStubSurface("esm", esmStub); + +const loadedAfterStub = Object.values(require.cache).filter((loaded) => + loaded?.filename.includes("/packages/tools/dist/"), +); +for (const loaded of loadedAfterStub) { + for (const name of forbiddenHelperNames) { + assert.equal( + Object.prototype.hasOwnProperty.call(loaded.exports ?? {}, name), + false, + `private helper ${name} exposed by ${loaded.filename}`, + ); + } +} + +console.log( + "runtime provenance package surfaces: fail-closed diagnostics, native Error stacks, native roots, constructor identity, cache-private CJS + ESM, four cross-format paths, and both stub formats passed", +); diff --git a/packages/tools/src/_internal/agent-runtime-provenance.ts b/packages/tools/src/_internal/agent-runtime-provenance.ts new file mode 100644 index 000000000..970565137 --- /dev/null +++ b/packages/tools/src/_internal/agent-runtime-provenance.ts @@ -0,0 +1,27 @@ +/** + * Build-facing runtime-provenance carrier for instrumented agent bundles. + * + * This published internal subpath intentionally exposes only the opaque v1 + * callsite carrier. Receipt retention, extraction, header assembly, and + * redaction remain package-private in the agents implementation. + * + * @internal Versioned build integration contract; not an author-facing API. + */ +import { registerAgentRuntimeCallsite } from "../agents/runtime-callsite-store.js"; + +export const AGENT_RUNTIME_PROVENANCE_VERSION = 1 as const; + +export interface AgentRuntimeProvenanceV1 { + readonly version: typeof AGENT_RUNTIME_PROVENANCE_VERSION; + /** Opaque build-owned reference. It contains no graph identity. */ + readonly callsite: string; +} + +/** Associate validated build evidence without mutating or wrapping the spec. */ +export function carryAgentRuntimeProvenance( + spec: T, + provenance: AgentRuntimeProvenanceV1, +): T { + registerAgentRuntimeCallsite(spec, provenance.version, provenance.callsite); + return spec; +} diff --git a/packages/tools/src/agents/index.ts b/packages/tools/src/agents/index.ts index 09225b78c..62d777905 100644 --- a/packages/tools/src/agents/index.ts +++ b/packages/tools/src/agents/index.ts @@ -14,7 +14,9 @@ * use it for inline standalone calls, NOT to pause a step (it returns a result, not * a pausable handle). An orchestration is addressed by its **slug** (its stable handle). */ -import { Transport, defaultTransport } from "../_client/index.js"; +import { defaultTransport } from "../_client/index.js"; +import type { Transport } from "../_client/index.js"; +import { takeAgentRuntimeCallsite } from "./runtime-callsite-store.js"; import type { DispatchHandle } from "../dispatch.js"; const DEFAULT_BASE_URL = @@ -39,6 +41,431 @@ export type ExecutionStatus = | "cancelled"; const TERMINAL = new Set(["completed", "failed", "cancelled"]); +const AGENT_RUNTIME_PROVENANCE_VERSION = 1 as const; +const AGENT_RUNTIME_PROVENANCE_VERSION_HEADER = + "x-sapiom-runtime-provenance-version"; +const AGENT_RUNTIME_CALLSITE_HEADER = "x-sapiom-runtime-callsite-evidence"; +const AGENT_RUNTIME_LINEAGE_HEADER = "x-sapiom-runtime-lineage-receipt"; +const MAX_OPAQUE_TOKEN_LENGTH = 8_192; +const RUNTIME_PROVENANCE_REDACTION = "[REDACTED runtime provenance]"; +const NATIVE_ARRAY_IS_ARRAY = Array.isArray; +const NATIVE_ARRAY_PROTOTYPE = Array.prototype; +const NATIVE_DATE = Date; +const NATIVE_DATE_PROTOTYPE = Date.prototype; +const NATIVE_DATE_GET_TIME = Date.prototype.getTime; +const NATIVE_ERROR = Error; +const NATIVE_HEADERS = Headers; +const NATIVE_HEADERS_PROTOTYPE = Headers.prototype; +const NATIVE_HEADERS_APPEND = Headers.prototype.append; +const NATIVE_HEADERS_FOR_EACH = Headers.prototype.forEach; +const NATIVE_HEADERS_HAS = Headers.prototype.has; +const NATIVE_MAP = Map; +const NATIVE_MAP_PROTOTYPE = Map.prototype; +const NATIVE_MAP_FOR_EACH = Map.prototype.forEach; +const NATIVE_MAP_HAS = Map.prototype.has; +const NATIVE_MAP_SET = Map.prototype.set; +const NATIVE_SET = Set; +const NATIVE_SET_PROTOTYPE = Set.prototype; +const NATIVE_SET_ADD = Set.prototype.add; +const NATIVE_SET_FOR_EACH = Set.prototype.forEach; +const NATIVE_SET_HAS = Set.prototype.has; +const NATIVE_OBJECT_CREATE = Object.create; +const NATIVE_OBJECT_DEFINE_PROPERTY = Object.defineProperty; +const NATIVE_OBJECT_GET_OWN_PROPERTY_DESCRIPTOR = + Object.getOwnPropertyDescriptor; +const NATIVE_OBJECT_GET_PROTOTYPE_OF = Object.getPrototypeOf; +const NATIVE_OBJECT_PROTOTYPE = Object.prototype; +const NATIVE_OBJECT_SET_PROTOTYPE_OF = Object.setPrototypeOf; +const NATIVE_REFLECT_APPLY = Reflect.apply; +const NATIVE_REFLECT_DELETE_PROPERTY = Reflect.deleteProperty; +const NATIVE_REFLECT_OWN_KEYS = Reflect.ownKeys; +const DIAGNOSTIC_BRAND_SENTINEL = {}; +const DIAGNOSTIC_HEADERS_BRAND_SENTINEL = "x-sapiom-diagnostic-brand"; + +interface LineageRecord { + readonly receipt: string; + active: boolean; + consumed: boolean; +} + +const resultLineage = new WeakMap(); + +function supportedOpaqueToken(value: unknown): value is string { + return ( + typeof value === "string" && + value.length > 0 && + value.length <= MAX_OPAQUE_TOKEN_LENGTH && + !/[\r\n]/.test(value) + ); +} + +function takeAgentRuntimeLineage(directInput: unknown): string | undefined { + if (directInput === null || typeof directInput !== "object") return undefined; + const record = resultLineage.get(directInput); + resultLineage.delete(directInput); + const receipt = + record?.active && !record.consumed ? record.receipt : undefined; + if (record) record.consumed = true; + return receipt; +} + +function retainAgentRuntimeLineage( + targets: readonly object[], + version: string | null, + receipt: string | null, +): void { + if ( + version !== String(AGENT_RUNTIME_PROVENANCE_VERSION) || + !supportedOpaqueToken(receipt) + ) { + return; + } + const record: LineageRecord = { + receipt: `${receipt}`, + active: true, + consumed: false, + }; + for (const target of targets) resultLineage.set(target, record); + const timer = setTimeout(() => { + record.active = false; + }, 0); + timer.unref?.(); +} + +function redactAgentRuntimeProvenance( + text: string, + privateValues: readonly (string | null | undefined)[], +): string { + let redacted = text; + const values = [...new Set(privateValues.filter(supportedOpaqueToken))].sort( + (left, right) => right.length - left.length, + ); + for (const value of values) { + redacted = redacted.split(value).join(RUNTIME_PROVENANCE_REDACTION); + } + return redacted; +} + +function redactedAgentRuntimeError( + error: unknown, + privateValues: readonly (string | null | undefined)[], +): unknown { + const values = [...new Set(privateValues.filter(supportedOpaqueToken))].sort( + (left, right) => right.length - left.length, + ); + if (values.length === 0) return error; + + const redactString = (value: string): string => + redactAgentRuntimeProvenance(value, values); + const nativeErrorStackDescriptor = NATIVE_OBJECT_GET_OWN_PROPERTY_DESCRIPTOR( + new NATIVE_ERROR(), + "stack", + ); + const isNativeErrorStackAccessor = ( + source: object, + key: PropertyKey, + descriptor: PropertyDescriptor, + ): descriptor is PropertyDescriptor & { + get: () => unknown; + set: (value: unknown) => void; + } => + source instanceof NATIVE_ERROR && + key === "stack" && + !("value" in descriptor) && + nativeErrorStackDescriptor !== undefined && + !("value" in nativeErrorStackDescriptor) && + typeof descriptor.get === "function" && + typeof descriptor.set === "function" && + descriptor.get === nativeErrorStackDescriptor.get && + descriptor.set === nativeErrorStackDescriptor.set; + const nativeErrorStacks = new WeakMap(); + const nativeErrorStack = ( + source: object, + descriptor: PropertyDescriptor & { get: () => unknown }, + ): string | undefined => { + const cached = nativeErrorStacks.get(source); + if (cached !== undefined) return cached; + let stack: unknown; + try { + stack = NATIVE_REFLECT_APPLY(descriptor.get, source, []); + } catch { + return undefined; + } + if (typeof stack !== "string") return undefined; + nativeErrorStacks.set(source, stack); + return stack; + }; + + type DiagnosticKind = + | "error" + | "array" + | "plain" + | "map" + | "set" + | "headers" + | "date" + | "opaque"; + const hasNativeBrand = ( + intrinsic: (...args: never[]) => unknown, + value: object, + args: readonly unknown[], + ): boolean => { + try { + NATIVE_REFLECT_APPLY(intrinsic, value, args); + return true; + } catch { + return false; + } + }; + const diagnosticKind = (value: object): DiagnosticKind => { + if (value instanceof NATIVE_ERROR) return "error"; + const prototype = NATIVE_OBJECT_GET_PROTOTYPE_OF(value); + if (NATIVE_ARRAY_IS_ARRAY(value)) { + return prototype === NATIVE_ARRAY_PROTOTYPE ? "array" : "opaque"; + } + if (prototype === NATIVE_OBJECT_PROTOTYPE || prototype === null) { + return "plain"; + } + if ( + prototype === NATIVE_MAP_PROTOTYPE && + hasNativeBrand(NATIVE_MAP_HAS, value, [DIAGNOSTIC_BRAND_SENTINEL]) + ) { + return "map"; + } + if ( + prototype === NATIVE_SET_PROTOTYPE && + hasNativeBrand(NATIVE_SET_HAS, value, [DIAGNOSTIC_BRAND_SENTINEL]) + ) { + return "set"; + } + if ( + prototype === NATIVE_HEADERS_PROTOTYPE && + hasNativeBrand(NATIVE_HEADERS_HAS, value, [ + DIAGNOSTIC_HEADERS_BRAND_SENTINEL, + ]) + ) { + return "headers"; + } + if ( + prototype === NATIVE_DATE_PROTOTYPE && + hasNativeBrand(NATIVE_DATE_GET_TIME, value, []) + ) { + return "date"; + } + return "opaque"; + }; + + type DescriptorSnapshot = readonly [PropertyKey, PropertyDescriptor]; + const inspected = new WeakSet(); + const kinds = new WeakMap(); + const descriptors = new WeakMap(); + const mapEntries = new WeakMap(); + const setEntries = new WeakMap(); + const headerEntries = new WeakMap(); + const dateValues = new WeakMap(); + + const descriptorSnapshots = (value: object): DescriptorSnapshot[] => { + const snapshots: DescriptorSnapshot[] = []; + for (const key of NATIVE_REFLECT_OWN_KEYS(value)) { + const descriptor = NATIVE_OBJECT_GET_OWN_PROPERTY_DESCRIPTOR(value, key); + if (descriptor) snapshots.push([key, descriptor]); + } + descriptors.set(value, snapshots); + return snapshots; + }; + + const requiresSanitization = (value: unknown): boolean => { + if (typeof value === "string") return redactString(value) !== value; + if (typeof value === "function" || typeof value === "symbol") return true; + if (value === null || typeof value !== "object") return false; + const kind = diagnosticKind(value); + kinds.set(value, kind); + if (kind === "opaque") return true; + if (inspected.has(value)) return false; + inspected.add(value); + + let required = false; + if (kind === "map") { + const entries: [unknown, unknown][] = []; + NATIVE_REFLECT_APPLY(NATIVE_MAP_FOR_EACH, value, [ + (entryValue: unknown, entryKey: unknown) => { + entries.push([entryKey, entryValue]); + }, + ]); + mapEntries.set(value, entries); + for (const [entryKey, entryValue] of entries) { + if (requiresSanitization(entryKey)) required = true; + if (requiresSanitization(entryValue)) required = true; + } + } else if (kind === "set") { + const entries: unknown[] = []; + NATIVE_REFLECT_APPLY(NATIVE_SET_FOR_EACH, value, [ + (entryValue: unknown) => { + entries.push(entryValue); + }, + ]); + setEntries.set(value, entries); + for (const entryValue of entries) { + if (requiresSanitization(entryValue)) required = true; + } + } else if (kind === "headers") { + const entries: [string, string][] = []; + NATIVE_REFLECT_APPLY(NATIVE_HEADERS_FOR_EACH, value, [ + (entryValue: string, entryKey: string) => { + entries.push([entryKey, entryValue]); + }, + ]); + headerEntries.set(value, entries); + for (const [entryKey, entryValue] of entries) { + if (redactString(entryKey) !== entryKey) required = true; + if (redactString(entryValue) !== entryValue) required = true; + } + } else if (kind === "date") { + dateValues.set( + value, + NATIVE_REFLECT_APPLY(NATIVE_DATE_GET_TIME, value, []), + ); + } + + for (const [key, descriptor] of descriptorSnapshots(value)) { + if (typeof key !== "string" || redactString(key) !== key) { + required = true; + continue; + } + if (descriptor && isNativeErrorStackAccessor(value, key, descriptor)) { + const stack = nativeErrorStack(value, descriptor); + if (stack === undefined || redactString(stack) !== stack) { + required = true; + } + continue; + } + if (!("value" in descriptor)) { + required = true; + continue; + } + if (requiresSanitization(descriptor.value)) required = true; + } + return required; + }; + + if (!requiresSanitization(error)) return error; + + const sanitized = new WeakMap(); + const sanitizeValue = (value: unknown): unknown => { + if (typeof value === "string") return redactString(value); + if (typeof value === "function" || typeof value === "symbol") { + return RUNTIME_PROVENANCE_REDACTION; + } + if (value === null || typeof value !== "object") return value; + const kind = kinds.get(value) ?? diagnosticKind(value); + if (kind === "opaque") return RUNTIME_PROVENANCE_REDACTION; + const cached = sanitized.get(value); + if (cached) return cached; + + let initializedStackDescriptor: PropertyDescriptor | undefined; + let target: object; + if (kind === "error") { + target = new NATIVE_ERROR(); + initializedStackDescriptor = NATIVE_OBJECT_GET_OWN_PROPERTY_DESCRIPTOR( + target, + "stack", + ); + NATIVE_OBJECT_SET_PROTOTYPE_OF( + target, + NATIVE_OBJECT_GET_PROTOTYPE_OF(value), + ); + const sourceDescriptors = descriptors.get(value) ?? []; + for (const targetKey of NATIVE_REFLECT_OWN_KEYS(target)) { + let sourceHasKey = false; + for (const [sourceKey] of sourceDescriptors) { + if (sourceKey === targetKey) sourceHasKey = true; + } + if (!sourceHasKey) { + NATIVE_REFLECT_DELETE_PROPERTY(target, targetKey); + } + } + } else if (kind === "array") { + target = NATIVE_OBJECT_SET_PROTOTYPE_OF( + [], + NATIVE_OBJECT_GET_PROTOTYPE_OF(value), + ); + } else if (kind === "map") { + target = new NATIVE_MAP(); + } else if (kind === "set") { + target = new NATIVE_SET(); + } else if (kind === "headers") { + target = new NATIVE_HEADERS(); + } else if (kind === "date") { + target = new NATIVE_DATE(dateValues.get(value)!); + } else { + target = NATIVE_OBJECT_CREATE(NATIVE_OBJECT_GET_PROTOTYPE_OF(value)); + } + sanitized.set(value, target); + + if (kind === "map") { + for (const [entryKey, entryValue] of mapEntries.get(value) ?? []) { + NATIVE_REFLECT_APPLY(NATIVE_MAP_SET, target, [ + sanitizeValue(entryKey), + sanitizeValue(entryValue), + ]); + } + } else if (kind === "set") { + for (const entryValue of setEntries.get(value) ?? []) { + NATIVE_REFLECT_APPLY(NATIVE_SET_ADD, target, [ + sanitizeValue(entryValue), + ]); + } + } else if (kind === "headers") { + for (const [entryKey, entryValue] of headerEntries.get(value) ?? []) { + if (redactString(entryKey) !== entryKey) continue; + NATIVE_REFLECT_APPLY(NATIVE_HEADERS_APPEND, target, [ + entryKey, + redactString(entryValue), + ]); + } + } + + for (const [key, descriptor] of descriptors.get(value) ?? []) { + if (typeof key !== "string" || redactString(key) !== key) continue; + if ( + initializedStackDescriptor && + !("value" in initializedStackDescriptor) && + typeof initializedStackDescriptor.get === "function" && + typeof initializedStackDescriptor.set === "function" && + isNativeErrorStackAccessor(value, key, descriptor) + ) { + const stack = nativeErrorStack(value, descriptor); + if (stack !== undefined) { + NATIVE_REFLECT_APPLY(initializedStackDescriptor.set, target, [ + redactString(stack), + ]); + NATIVE_OBJECT_DEFINE_PROPERTY(target, key, { + configurable: descriptor.configurable, + enumerable: descriptor.enumerable, + get: initializedStackDescriptor.get, + set: initializedStackDescriptor.set, + }); + continue; + } + } + if (!("value" in descriptor)) { + NATIVE_OBJECT_DEFINE_PROPERTY(target, key, { + configurable: descriptor.configurable, + enumerable: descriptor.enumerable, + value: RUNTIME_PROVENANCE_REDACTION, + writable: false, + }); + continue; + } + descriptor.value = sanitizeValue(descriptor.value); + NATIVE_OBJECT_DEFINE_PROPERTY(target, key, descriptor); + } + return target; + }; + + return sanitizeValue(error); +} + export interface AgentRunSpec { /** Slug of the deployed orchestration to run (its stable handle). */ definition: string; @@ -107,9 +534,7 @@ export class AgentResultSchemaError extends Error {} * `output` itself is the child orchestration's contract, not validated here. */ export const agentResultSchema = { - parse( - value: unknown, - ): AgentRunResultPayload { + parse(value: unknown): AgentRunResultPayload { const fail = (msg: string): never => { throw new AgentResultSchemaError( `invalid orchestration result payload: ${msg}`, @@ -144,10 +569,7 @@ export interface RunHandle extends DispatchHandle { /** Fetch the current status without blocking. */ status(): Promise; /** Poll to a terminal state and resolve the run result. */ - wait(opts?: { - timeoutMs?: number; - pollMs?: number; - }): Promise; + wait(opts?: { timeoutMs?: number; pollMs?: number }): Promise; } /** @@ -181,12 +603,21 @@ interface ExecutionDoc { * engine stamps on the eventually-fired child, so the resume lands. Pause-only: there is no child * to poll until the scheduled time, so `status`/`wait` throw. */ -async function launchScheduled(spec: AgentRunSpec, transport: Transport, baseUrl: string): Promise { +async function launchScheduled( + spec: AgentRunSpec, + input: Record, + transport: Transport, + baseUrl: string, +): Promise { const res = await transport.request<{ id: string }>( `${baseUrl}/agents/v1/definitions/${encodeURIComponent(spec.definition)}/triggers`, { method: "POST", - body: JSON.stringify({ kind: "schedule_once", at: spec.at, input: spec.input ?? {} }), + body: JSON.stringify({ + kind: "schedule_once", + at: spec.at, + input, + }), headers: workflowResumeHeaders(transport.resumeToken), }, ); @@ -197,7 +628,10 @@ async function launchScheduled(spec: AgentRunSpec, transport: Transport, baseUrl }; return { executionId: "", // no child execution exists until the schedule fires - dispatch: { correlationId: `trigger-${res.id}`, resultSignal: AGENTS_RESULT_SIGNAL }, + dispatch: { + correlationId: `trigger-${res.id}`, + resultSignal: AGENTS_RESULT_SIGNAL, + }, status: notAvailable, wait: notAvailable, }; @@ -208,26 +642,98 @@ export async function launch( transport: Transport = defaultTransport(), baseUrl = DEFAULT_BASE_URL, ): Promise { + const input = spec.input ?? {}; + const callsite = takeAgentRuntimeCallsite(spec); + // Always consume an exact input receipt at an observed agent boundary. It is + // forwarded only when this same invocation has trusted v1 build evidence. + const inputLineageReceipt = takeAgentRuntimeLineage(input); if (spec.at) { - return launchScheduled(spec, transport, baseUrl); + return launchScheduled(spec, input, transport, baseUrl); + } + const provenanceHeaders: Record = {}; + const privateProvenanceValues: string[] = []; + if (callsite) { + provenanceHeaders[AGENT_RUNTIME_PROVENANCE_VERSION_HEADER] = String( + AGENT_RUNTIME_PROVENANCE_VERSION, + ); + provenanceHeaders[AGENT_RUNTIME_CALLSITE_HEADER] = callsite; + privateProvenanceValues.push(callsite); + if (inputLineageReceipt) { + provenanceHeaders[AGENT_RUNTIME_LINEAGE_HEADER] = inputLineageReceipt; + privateProvenanceValues.push(inputLineageReceipt); + } + } + let res: StartResponse; + try { + res = await transport.request( + `${baseUrl}/agents/v1/definitions/${encodeURIComponent(spec.definition)}/executions`, + { + method: "POST", + body: JSON.stringify({ + input, + idempotencyKey: spec.idempotencyKey, + }), + headers: { + ...workflowResumeHeaders(transport.resumeToken), + ...provenanceHeaders, + }, + }, + ); + } catch (error) { + throw redactedAgentRuntimeError(error, privateProvenanceValues); } - const res = await transport.request( - `${baseUrl}/agents/v1/definitions/${encodeURIComponent(spec.definition)}/executions`, - { - method: "POST", - body: JSON.stringify({ - input: spec.input ?? {}, - idempotencyKey: spec.idempotencyKey, - }), - headers: workflowResumeHeaders(transport.resumeToken), - }, - ); const executionId = res.executionId; - const fetchDoc = () => - transport.request( - `${baseUrl}/agents/v1/executions/${encodeURIComponent(executionId)}`, - ); + const fetchDoc = async (): Promise<{ + doc: ExecutionDoc; + provenanceVersion: string | null; + lineageReceipt: string | null; + }> => { + const url = `${baseUrl}/agents/v1/executions/${encodeURIComponent(executionId)}`; + let response: Response; + try { + response = await transport.fetch(url, { + headers: { "content-type": "application/json" }, + }); + } catch (error) { + throw redactedAgentRuntimeError(error, privateProvenanceValues); + } + const provenanceVersion = + response.headers?.get?.(AGENT_RUNTIME_PROVENANCE_VERSION_HEADER) ?? null; + const lineageReceipt = + response.headers?.get?.(AGENT_RUNTIME_LINEAGE_HEADER) ?? null; + if (!response.ok) { + let body: string; + try { + body = await response.text(); + } catch (error) { + throw redactedAgentRuntimeError(error, [ + ...privateProvenanceValues, + lineageReceipt, + ]); + } + throw new Error( + redactAgentRuntimeProvenance( + `GET ${url} → ${response.status} ${body}`, + [...privateProvenanceValues, lineageReceipt], + ), + ); + } + let doc: ExecutionDoc; + try { + doc = (await response.json()) as ExecutionDoc; + } catch (error) { + throw redactedAgentRuntimeError(error, [ + ...privateProvenanceValues, + lineageReceipt, + ]); + } + return { + doc, + provenanceVersion, + lineageReceipt, + }; + }; return { executionId, @@ -238,24 +744,40 @@ export async function launch( resultSignal: AGENTS_RESULT_SIGNAL, }, async status() { - return (await fetchDoc()).status; + return (await fetchDoc()).doc.status; }, async wait({ timeoutMs = 60 * 60_000, pollMs = 3_000 } = {}) { const deadline = Date.now() + timeoutMs; // eslint-disable-next-line no-constant-condition while (true) { - const d = await fetchDoc(); + const { doc: d, provenanceVersion, lineageReceipt } = await fetchDoc(); if (TERMINAL.has(d.status)) { - return { + const result: AgentRunResult = { executionId, status: d.status, output: d.output ?? null, error: d.error ?? null, }; + const lineageTargets: object[] = [result]; + // This exact output object is the author-facing value commonly handed + // to the next agent. Copies, nested values, and primitives remain + // deliberately unassociated. + if (d.output !== null && typeof d.output === "object") { + lineageTargets.push(d.output); + } + retainAgentRuntimeLineage( + lineageTargets, + provenanceVersion, + lineageReceipt, + ); + return result; } if (Date.now() > deadline) { throw new Error( - `orchestration ${executionId} timed out after ${timeoutMs}ms (last status: ${d.status})`, + redactAgentRuntimeProvenance( + `orchestration ${executionId} timed out after ${timeoutMs}ms (last status: ${d.status})`, + [...privateProvenanceValues, lineageReceipt], + ), ); } await new Promise((r) => setTimeout(r, pollMs)); diff --git a/packages/tools/src/agents/runtime-callsite-store.ts b/packages/tools/src/agents/runtime-callsite-store.ts new file mode 100644 index 000000000..767f40180 --- /dev/null +++ b/packages/tools/src/agents/runtime-callsite-store.ts @@ -0,0 +1,57 @@ +/** Package-private bridge between the published build carrier and agent calls. */ + +const AGENT_RUNTIME_PROVENANCE_VERSION = 1 as const; +const MAX_OPAQUE_TOKEN_LENGTH = 8_192; + +interface CallsiteRecord { + readonly callsite: string; + active: boolean; +} + +const invocationCallsites = new WeakMap(); + +function supportedCallsite(value: unknown): value is string { + return ( + typeof value === "string" && + value.length > 0 && + value.length <= MAX_OPAQUE_TOKEN_LENGTH && + value.trim() === value && + !/[\r\n]/.test(value) && + /^[\x20-\x7e]+$/.test(value) + ); +} + +/** Called only by the published build carrier. Snapshots scalars after validation. */ +export function registerAgentRuntimeCallsite( + spec: object, + version: unknown, + callsite: unknown, +): void { + if ( + version !== AGENT_RUNTIME_PROVENANCE_VERSION || + !supportedCallsite(callsite) + ) { + return; + } + const record: CallsiteRecord = { + callsite: `${callsite}`, + active: true, + }; + invocationCallsites.set(spec, record); + const timer = setTimeout(() => { + record.active = false; + }, 0); + timer.unref?.(); +} + +/** Consume one validated build callsite. Receipt state never enters this module. */ +export function takeAgentRuntimeCallsite(spec: object): string | undefined { + const record = invocationCallsites.get(spec); + invocationCallsites.delete(spec); + const callsite = + record?.active && supportedCallsite(record.callsite) + ? record.callsite + : undefined; + if (record) record.active = false; + return callsite; +} diff --git a/packages/tools/src/agents/runtime-provenance.spec.ts b/packages/tools/src/agents/runtime-provenance.spec.ts new file mode 100644 index 000000000..74fa637ad --- /dev/null +++ b/packages/tools/src/agents/runtime-provenance.spec.ts @@ -0,0 +1,1346 @@ +import { createClient } from "../index.js"; +import { carryAgentRuntimeProvenance } from "../_internal/agent-runtime-provenance.js"; +import * as publicCarrier from "../_internal/agent-runtime-provenance.js"; + +const AGENT_RUNTIME_PROVENANCE_VERSION_HEADER = + "x-sapiom-runtime-provenance-version"; +const AGENT_RUNTIME_CALLSITE_HEADER = "x-sapiom-runtime-callsite-evidence"; +const AGENT_RUNTIME_LINEAGE_HEADER = "x-sapiom-runtime-lineage-receipt"; + +interface CapturedCall { + url: string; + init: RequestInit; +} + +function response( + value: unknown, + status = 200, + headers: Record = {}, +): Response { + return { + ok: status >= 200 && status < 300, + status, + headers: new Headers(headers), + json: async () => value, + text: async () => JSON.stringify(value), + } as Response; +} + +function agentServer( + opts: { + receiptVersion?: string; + receipt?: string; + terminalStatus?: "completed" | "failed" | "cancelled"; + } = {}, +): { fetch: typeof globalThis.fetch; calls: CapturedCall[] } { + const calls: CapturedCall[] = []; + let nextExecution = 0; + const fetch = (async ( + input: string | URL | Request, + init: RequestInit = {}, + ) => { + const url = String(input); + // Mirror Fetch's header-value conversion so optional provenance can never + // make an otherwise valid invocation fail before the request is observed. + new Headers(init.headers); + calls.push({ url, init }); + if (init.method === "POST") { + nextExecution += 1; + return response( + { status: "enqueued", executionId: `exec-${nextExecution}` }, + 201, + ); + } + const terminalStatus = opts.terminalStatus ?? "completed"; + return response( + terminalStatus === "completed" + ? { status: terminalStatus, output: { ok: true }, error: null } + : { + status: terminalStatus, + error: { message: `${terminalStatus} privately` }, + }, + 200, + { + ...(opts.receiptVersion + ? { + [AGENT_RUNTIME_PROVENANCE_VERSION_HEADER]: opts.receiptVersion, + } + : {}), + ...(opts.receipt + ? { [AGENT_RUNTIME_LINEAGE_HEADER]: opts.receipt } + : {}), + }, + ); + }) as typeof globalThis.fetch; + return { fetch, calls }; +} + +function header(call: CapturedCall, name: string): string | undefined { + const headers = call.init.headers as Record; + return headers?.[name]; +} + +function posts(calls: CapturedCall[]): CapturedCall[] { + return calls.filter((call) => call.init.method === "POST"); +} + +describe("agents runtime provenance v1", () => { + it("publishes only the minimal build-facing carrier surface", () => { + expect(Object.keys(publicCarrier).sort()).toEqual([ + "AGENT_RUNTIME_PROVENANCE_VERSION", + "carryAgentRuntimeProvenance", + ]); + }); + + it("launch carries opaque callsite evidence out of band and wait retains a private receipt", async () => { + const server = agentServer({ + receiptVersion: "1", + receipt: "signed.receipt", + }); + const client = createClient({ apiKey: "k", fetch: server.fetch }); + const spec = carryAgentRuntimeProvenance( + { definition: "child", input: { public: true } }, + { version: 1, callsite: "callsite.opaque" }, + ); + + const handle = await client.agents.launch(spec); + const result = await handle.wait({ pollMs: 1 }); + const launch = posts(server.calls)[0]!; + + expect(header(launch, AGENT_RUNTIME_PROVENANCE_VERSION_HEADER)).toBe("1"); + expect(header(launch, AGENT_RUNTIME_CALLSITE_HEADER)).toBe( + "callsite.opaque", + ); + expect(JSON.parse(String(launch.init.body))).toEqual({ + input: { public: true }, + }); + expect(result).toEqual({ + executionId: "exec-1", + status: "completed", + output: { ok: true }, + error: null, + }); + expect(Object.keys(result)).toEqual([ + "executionId", + "status", + "output", + "error", + ]); + expect(Reflect.ownKeys(result)).toEqual([ + "executionId", + "status", + "output", + "error", + ]); + expect(Reflect.ownKeys(result.output as object)).toEqual(["ok"]); + for (const descriptor of [ + ...Object.values(Object.getOwnPropertyDescriptors(result)), + ...Object.values( + Object.getOwnPropertyDescriptors(result.output as object), + ), + ]) { + expect(descriptor.value).not.toBe("signed.receipt"); + } + expect(JSON.stringify(result)).not.toContain("signed.receipt"); + }); + + it("run forwards a receipt when the exact SDK output is the next input", async () => { + const server = agentServer({ + receiptVersion: "1", + receipt: "signed.direct", + }); + const client = createClient({ apiKey: "k", fetch: server.fetch }); + const result = await client.agents.run({ definition: "producer" }); + + await client.agents.run( + carryAgentRuntimeProvenance( + { + definition: "consumer", + input: result.output as Record, + }, + { version: 1, callsite: "callsite.consumer" }, + ), + ); + + const secondLaunch = posts(server.calls)[1]!; + expect(header(secondLaunch, AGENT_RUNTIME_PROVENANCE_VERSION_HEADER)).toBe( + "1", + ); + expect(header(secondLaunch, AGENT_RUNTIME_LINEAGE_HEADER)).toBe( + "signed.direct", + ); + expect(JSON.parse(String(secondLaunch.init.body)).input).toEqual( + result.output, + ); + }); + + it("forwards the exact full SDK result and consumes the shared output alias", async () => { + const server = agentServer({ + receiptVersion: "1", + receipt: "signed.full-result", + }); + const client = createClient({ apiKey: "k", fetch: server.fetch }); + const result = await client.agents.run({ definition: "producer" }); + + await client.agents.run( + carryAgentRuntimeProvenance( + { + definition: "full-result-consumer", + input: result as unknown as Record, + }, + { version: 1, callsite: "callsite.full-result" }, + ), + ); + await client.agents.run( + carryAgentRuntimeProvenance( + { + definition: "output-alias-replay", + input: result.output as Record, + }, + { version: 1, callsite: "callsite.output-replay" }, + ), + ); + + const [, direct, replay] = posts(server.calls); + expect(header(direct!, AGENT_RUNTIME_LINEAGE_HEADER)).toBe( + "signed.full-result", + ); + expect(header(replay!, AGENT_RUNTIME_LINEAGE_HEADER)).toBeUndefined(); + }); + + it.each(["failed", "cancelled"] as const)( + "retains a private receipt on a %s full result", + async (terminalStatus) => { + const server = agentServer({ + receiptVersion: "1", + receipt: `signed.${terminalStatus}`, + terminalStatus, + }); + const client = createClient({ apiKey: "k", fetch: server.fetch }); + const result = await client.agents.run({ definition: "producer" }); + + await client.agents.run( + carryAgentRuntimeProvenance( + { + definition: "consumer", + input: result as unknown as Record, + }, + { version: 1, callsite: `callsite.${terminalStatus}` }, + ), + ); + + expect(result.status).toBe(terminalStatus); + expect( + header(posts(server.calls)[1]!, AGENT_RUNTIME_LINEAGE_HEADER), + ).toBe(`signed.${terminalStatus}`); + expect(Reflect.ownKeys(result)).toEqual([ + "executionId", + "status", + "output", + "error", + ]); + }, + ); + + it("captures input once for both serialization and lineage lookup", async () => { + const server = agentServer({ + receiptVersion: "1", + receipt: "signed.single-read", + }); + const client = createClient({ apiKey: "k", fetch: server.fetch }); + const result = await client.agents.run({ definition: "producer" }); + let reads = 0; + const spec = carryAgentRuntimeProvenance( + { + definition: "consumer", + get input(): Record { + reads += 1; + return reads === 1 + ? (result.output as Record) + : { swapped: true }; + }, + }, + { version: 1, callsite: "callsite.single-read" }, + ); + + await client.agents.run(spec); + + const secondLaunch = posts(server.calls)[1]!; + expect(reads).toBe(1); + expect(JSON.parse(String(secondLaunch.init.body)).input).toEqual( + result.output, + ); + expect(header(secondLaunch, AGENT_RUNTIME_LINEAGE_HEADER)).toBe( + "signed.single-read", + ); + }); + + it("snapshots validated callsite scalars", async () => { + const server = agentServer(); + const client = createClient({ apiKey: "k", fetch: server.fetch }); + const provenance = { version: 1 as const, callsite: "callsite.safe" }; + const spec = carryAgentRuntimeProvenance( + { definition: "consumer" }, + provenance, + ); + provenance.callsite = "callsite.mutated\r\nprivate"; + + await client.agents.launch(spec); + + expect(header(posts(server.calls)[0]!, AGENT_RUNTIME_CALLSITE_HEADER)).toBe( + "callsite.safe", + ); + }); + + it.each([ + ["NUL/control", `opaque${String.fromCharCode(0)}token`], + [ + "non-ByteString Unicode/emoji", + `opaque${String.fromCodePoint(0x1f680)}token`, + ], + ["leading whitespace", " opaque-token"], + ["trailing whitespace", "opaque-token "], + ])( + "omits unsupported %s callsite evidence without changing launch behavior", + async (_label, callsite) => { + const server = agentServer(); + const client = createClient({ apiKey: "k", fetch: server.fetch }); + const spec = carryAgentRuntimeProvenance( + { definition: "ordinary", input: { public: true } }, + { version: 1, callsite }, + ); + + const handle = await client.agents.launch(spec); + expect(handle.executionId).toBe("exec-1"); + const launch = posts(server.calls)[0]!; + expect( + header(launch, AGENT_RUNTIME_PROVENANCE_VERSION_HEADER), + ).toBeUndefined(); + expect(header(launch, AGENT_RUNTIME_CALLSITE_HEADER)).toBeUndefined(); + expect(JSON.parse(String(launch.init.body))).toEqual({ + input: { public: true }, + }); + }, + ); + + it("requires a build-carried callsite and consumes lineage at an uninstrumented boundary", async () => { + const server = agentServer({ + receiptVersion: "1", + receipt: "signed.consume", + }); + const client = createClient({ apiKey: "k", fetch: server.fetch }); + const result = await client.agents.run({ definition: "producer" }); + + await client.agents.run({ + definition: "uninstrumented", + input: result.output as Record, + }); + await client.agents.run( + carryAgentRuntimeProvenance( + { + definition: "replay", + input: result.output as Record, + }, + { version: 1, callsite: "callsite.replay" }, + ), + ); + + const [, uninstrumented, replay] = posts(server.calls); + expect( + header(uninstrumented!, AGENT_RUNTIME_LINEAGE_HEADER), + ).toBeUndefined(); + expect(header(replay!, AGENT_RUNTIME_LINEAGE_HEADER)).toBeUndefined(); + }); + + it("consumes one carried callsite and lineage receipt once", async () => { + const server = agentServer({ + receiptVersion: "1", + receipt: "signed.once", + }); + const client = createClient({ apiKey: "k", fetch: server.fetch }); + const result = await client.agents.run({ definition: "producer" }); + const spec = carryAgentRuntimeProvenance( + { + definition: "consumer", + input: result.output as Record, + }, + { version: 1, callsite: "callsite.once" }, + ); + + await client.agents.run(spec); + await client.agents.run(spec); + + const [, first, replay] = posts(server.calls); + expect(header(first!, AGENT_RUNTIME_LINEAGE_HEADER)).toBe("signed.once"); + expect(header(replay!, AGENT_RUNTIME_CALLSITE_HEADER)).toBeUndefined(); + expect(header(replay!, AGENT_RUNTIME_LINEAGE_HEADER)).toBeUndefined(); + }); + + it("does not forward an exact reference after a timer boundary", async () => { + const server = agentServer({ + receiptVersion: "1", + receipt: "signed.timer", + }); + const client = createClient({ apiKey: "k", fetch: server.fetch }); + const result = await client.agents.run({ definition: "producer" }); + await new Promise((resolve) => setTimeout(resolve, 0)); + + await client.agents.run( + carryAgentRuntimeProvenance( + { + definition: "consumer", + input: result.output as Record, + }, + { version: 1, callsite: "callsite.after-timer" }, + ), + ); + + expect( + header(posts(server.calls)[1]!, AGENT_RUNTIME_LINEAGE_HEADER), + ).toBeUndefined(); + }); + + it.each(["array", "map"] as const)( + "does not replay an exact reference from %s after an uninstrumented boundary", + async (container) => { + const server = agentServer({ + receiptVersion: "1", + receipt: `signed.${container}`, + }); + const client = createClient({ apiKey: "k", fetch: server.fetch }); + const result = await client.agents.run({ definition: "producer" }); + let exact: unknown; + if (container === "array") { + const stored = [result.output]; + exact = stored[0]; + } else { + const stored = new Map([["result", result.output]]); + exact = stored.get("result"); + } + + await client.agents.run({ + definition: "queue-worker-uninstrumented", + input: exact as Record, + }); + await client.agents.run( + carryAgentRuntimeProvenance( + { + definition: "replay", + input: exact as Record, + }, + { version: 1, callsite: `callsite.${container}` }, + ), + ); + + const [, uninstrumented, replay] = posts(server.calls); + expect( + header(uninstrumented!, AGENT_RUNTIME_LINEAGE_HEADER), + ).toBeUndefined(); + expect(header(replay!, AGENT_RUNTIME_LINEAGE_HEADER)).toBeUndefined(); + }, + ); + + it.each([ + ["a copied output", (result: object) => ({ ...result })], + ["a nested output", (result: object) => ({ result })], + ["a transformed primitive", () => ({ value: "ok" })], + ])("does not infer lineage through %s", async (_label, toInput) => { + const server = agentServer({ + receiptVersion: "1", + receipt: "signed.private", + }); + const client = createClient({ apiKey: "k", fetch: server.fetch }); + const result = await client.agents.run({ definition: "producer" }); + + await client.agents.run( + carryAgentRuntimeProvenance( + { + definition: "consumer", + input: toInput(result.output as object), + }, + { version: 1, callsite: `callsite.${_label.replace(/\s/g, "-")}` }, + ), + ); + + const secondLaunch = posts(server.calls)[1]!; + expect(header(secondLaunch, AGENT_RUNTIME_LINEAGE_HEADER)).toBeUndefined(); + expect(header(secondLaunch, AGENT_RUNTIME_PROVENANCE_VERSION_HEADER)).toBe( + "1", + ); + }); + + it("ignores an unsupported receipt version", async () => { + const server = agentServer({ + receiptVersion: "2", + receipt: "signed.future", + }); + const client = createClient({ apiKey: "k", fetch: server.fetch }); + const result = await client.agents.run({ definition: "producer" }); + await client.agents.run( + carryAgentRuntimeProvenance( + { + definition: "consumer", + input: result.output as Record, + }, + { version: 1, callsite: "callsite.unsupported" }, + ), + ); + + expect( + header(posts(server.calls)[1]!, AGENT_RUNTIME_LINEAGE_HEADER), + ).toBeUndefined(); + }); + + it("does not carry provenance through delayed dispatch", async () => { + const server = agentServer({ + receiptVersion: "1", + receipt: "signed.queue", + }); + const client = createClient({ apiKey: "k", fetch: server.fetch }); + const result = await client.agents.run({ definition: "producer" }); + const scheduled = carryAgentRuntimeProvenance( + { + definition: "consumer", + input: result.output as Record, + at: "2026-09-01T00:00:00.000Z", + }, + { version: 1, callsite: "callsite.delayed" }, + ); + + await client.agents.launch(scheduled); + await client.agents.launch( + carryAgentRuntimeProvenance( + { + definition: "consumer-replay", + input: result.output as Record, + }, + { version: 1, callsite: "callsite.after-delayed" }, + ), + ); + + const delayedLaunch = posts(server.calls)[1]!; + const replay = posts(server.calls)[2]!; + expect( + header(delayedLaunch, AGENT_RUNTIME_CALLSITE_HEADER), + ).toBeUndefined(); + expect(header(delayedLaunch, AGENT_RUNTIME_LINEAGE_HEADER)).toBeUndefined(); + expect( + header(delayedLaunch, AGENT_RUNTIME_PROVENANCE_VERSION_HEADER), + ).toBeUndefined(); + expect(header(replay, AGENT_RUNTIME_LINEAGE_HEADER)).toBeUndefined(); + }); + + it("redacts reflected request provenance from invocation errors", async () => { + const calls: CapturedCall[] = []; + const receipt = "signed.invocation-private"; + let postCount = 0; + const fetch = (async ( + input: string | URL | Request, + init: RequestInit = {}, + ) => { + calls.push({ url: String(input), init }); + if (init.method !== "POST") { + return response( + { status: "completed", output: { ok: true }, error: null }, + 200, + { + [AGENT_RUNTIME_PROVENANCE_VERSION_HEADER]: "1", + [AGENT_RUNTIME_LINEAGE_HEADER]: receipt, + }, + ); + } + postCount += 1; + if (postCount === 1) { + return response( + { status: "enqueued", executionId: "exec-producer" }, + 201, + ); + } + const headers = init.headers as Record; + return response( + { + message: `rejected ${headers[AGENT_RUNTIME_CALLSITE_HEADER] ?? ""} ${ + headers[AGENT_RUNTIME_LINEAGE_HEADER] ?? "" + }`, + }, + 400, + ); + }) as typeof globalThis.fetch; + const client = createClient({ apiKey: "k", fetch }); + const result = await client.agents.run({ definition: "producer" }); + const spec = carryAgentRuntimeProvenance( + { + definition: "child", + input: result.output as Record, + }, + { version: 1, callsite: "callsite.must-stay-private" }, + ); + + let error: unknown; + try { + await client.agents.launch(spec); + } catch (value) { + error = value; + } + + expect(error).toBeInstanceOf(Error); + expect((error as Error).message).toContain("rejected"); + expect((error as Error).message).not.toContain( + "callsite.must-stay-private", + ); + expect((error as Error).message).not.toContain(receipt); + expect(String(posts(calls)[1]!.init.body)).not.toContain( + "callsite.must-stay-private", + ); + expect(String(posts(calls)[1]!.init.body)).not.toContain(receipt); + }); + + it("rethrows an uninstrumented typed transport error untouched", async () => { + const cause = Object.assign(new Error("connect refused"), { + code: "ECONNREFUSED", + }); + const failure = new TypeError("fetch failed") as TypeError & { + cause: Error; + }; + Object.defineProperty(failure, "cause", { + configurable: true, + value: cause, + writable: true, + }); + const originalStack = failure.stack; + const fetch = (async () => { + throw failure; + }) as typeof globalThis.fetch; + const client = createClient({ apiKey: "k", fetch }); + + let error: unknown; + try { + await client.agents.launch({ definition: "uninstrumented" }); + } catch (value) { + error = value; + } + + expect(error).toBe(failure); + expect(error).toBeInstanceOf(TypeError); + expect((error as Error & { cause: Error }).cause).toBe(cause); + expect((error as Error).stack).toBe(originalStack); + expect( + ((error as Error & { cause: Error }).cause as Error & { code: string }) + .code, + ).toBe("ECONNREFUSED"); + }); + + it("redacts launch headers captured by a custom transport without invoking instance methods", async () => { + const callsite = "callsite.headers-private"; + let instanceForEachReads = 0; + let failure: + | (TypeError & { + code: string; + request: { headers: Headers; requestId: string }; + }) + | undefined; + const fetch = (async ( + _input: string | URL | Request, + init: RequestInit = {}, + ) => { + const headers = new Headers(init.headers); + Object.defineProperty(headers, "forEach", { + configurable: true, + value() { + instanceForEachReads += 1; + throw new Error("instance forEach must not run"); + }, + }); + failure = Object.assign(new TypeError("fetch failed"), { + code: "EAGENT", + request: { headers, requestId: "request-public" }, + }); + throw failure; + }) as typeof globalThis.fetch; + const client = createClient({ apiKey: "k", fetch }); + + let error: unknown; + try { + await client.agents.launch( + carryAgentRuntimeProvenance( + { definition: "instrumented-headers" }, + { version: 1, callsite }, + ), + ); + } catch (value) { + error = value; + } + + const surfaced = error as typeof failure & { + request: { headers: Headers; requestId: string }; + }; + expect(failure).toBeDefined(); + expect(error).not.toBe(failure); + expect(error).toBeInstanceOf(TypeError); + expect(surfaced.code).toBe("EAGENT"); + expect(surfaced.request.requestId).toBe("request-public"); + expect(surfaced.request.headers).toBeInstanceOf(Headers); + expect( + Headers.prototype.get.call( + surfaced.request.headers, + AGENT_RUNTIME_CALLSITE_HEADER, + ), + ).toBe("[REDACTED runtime provenance]"); + expect(instanceForEachReads).toBe(0); + expect( + Headers.prototype.get.call( + failure!.request.headers, + AGENT_RUNTIME_CALLSITE_HEADER, + ), + ).toBe(callsite); + expect(failure!.request.requestId).toBe("request-public"); + }); + + it("fails closed for custom symbol surfaces on captured Headers without invoking accessors", async () => { + const callsite = "callsite.headers-symbol-private"; + const secretSymbol = Symbol("secret diagnostic"); + const lazySymbol = Symbol("lazy diagnostic"); + let symbolAccessorReads = 0; + let failure: + | (TypeError & { + request: { headers: Headers & Record }; + }) + | undefined; + const fetch = (async ( + _input: string | URL | Request, + init: RequestInit = {}, + ) => { + const headers = new Headers(init.headers) as Headers & + Record; + Headers.prototype.delete.call(headers, AGENT_RUNTIME_CALLSITE_HEADER); + headers[secretSymbol] = callsite; + Object.defineProperty(headers, lazySymbol, { + configurable: true, + get() { + symbolAccessorReads += 1; + return callsite; + }, + }); + failure = Object.assign(new TypeError("fetch failed"), { + request: { headers }, + }); + throw failure; + }) as typeof globalThis.fetch; + const client = createClient({ apiKey: "k", fetch }); + + let error: unknown; + try { + await client.agents.launch( + carryAgentRuntimeProvenance( + { definition: "instrumented-headers-symbol" }, + { version: 1, callsite }, + ), + ); + } catch (value) { + error = value; + } + + const surfaced = error as typeof failure & { + request: { headers: Headers & Record }; + }; + expect(failure).toBeDefined(); + expect(error).not.toBe(failure); + expect(error).toBeInstanceOf(TypeError); + expect(surfaced.request.headers[secretSymbol]).toBeUndefined(); + expect( + Object.getOwnPropertyDescriptor(surfaced.request.headers, lazySymbol), + ).toBeUndefined(); + expect(symbolAccessorReads).toBe(0); + expect(failure!.request.headers[secretSymbol]).toBe(callsite); + expect( + Object.getOwnPropertyDescriptor(failure!.request.headers, lazySymbol) + ?.get, + ).toBeDefined(); + }); + + it("redacts Map and Set entries while preserving cycles and shared references", async () => { + const callsite = "callsite.containers-private"; + const shared = { privateValue: callsite, publicValue: "shared-public" }; + const map = new Map(); + const set = new Set(); + map.set("shared", shared); + map.set("self", map); + set.add(shared); + set.add(set); + let instanceMethodReads = 0; + const poisonedMethod = () => { + instanceMethodReads += 1; + throw new Error("instance container method must not run"); + }; + for (const [container, methods] of [ + [map, ["entries", "forEach", "set", Symbol.iterator]], + [set, ["entries", "forEach", "add", Symbol.iterator]], + ] as const) { + for (const method of methods) { + Object.defineProperty(container, method, { + configurable: true, + value: poisonedMethod, + }); + } + } + const diagnostics = { map, set }; + const failure = Object.assign(new TypeError("fetch failed"), { + code: "EAGENT", + diagnostics, + }); + const fetch = (async () => { + throw failure; + }) as typeof globalThis.fetch; + const client = createClient({ apiKey: "k", fetch }); + + let error: unknown; + try { + await client.agents.launch( + carryAgentRuntimeProvenance( + { definition: "instrumented-containers" }, + { version: 1, callsite }, + ), + ); + } catch (value) { + error = value; + } + + const surfaced = error as typeof failure; + const surfacedMap = surfaced.diagnostics.map; + const surfacedSet = surfaced.diagnostics.set; + const surfacedShared = surfacedMap.get("shared") as typeof shared; + expect(error).not.toBe(failure); + expect(surfacedMap).toBeInstanceOf(Map); + expect(surfacedSet).toBeInstanceOf(Set); + expect(surfacedMap.get("self")).toBe(surfacedMap); + expect(surfacedSet.has(surfacedSet)).toBe(true); + expect([...surfacedSet][0]).toBe(surfacedShared); + expect(surfacedShared.privateValue).toBe("[REDACTED runtime provenance]"); + expect(surfacedShared.publicValue).toBe("shared-public"); + expect(instanceMethodReads).toBe(0); + expect(map.get("shared")).toBe(shared); + expect(map.get("self")).toBe(map); + expect(set.has(set)).toBe(true); + expect(shared.privateValue).toBe(callsite); + }); + + it("fails closed for custom diagnostic instances without creating invalid shells", async () => { + const callsite = "callsite.custom-instance-private"; + class CustomDiagnostic { + readonly privateValue = callsite; + readonly publicValue = "custom-public"; + } + const custom = new CustomDiagnostic(); + const diagnostics = { custom, publicSibling: "sibling-public" }; + const failure = Object.assign(new TypeError("fetch failed"), { + code: "EAGENT", + diagnostics, + }); + const fetch = (async () => { + throw failure; + }) as typeof globalThis.fetch; + const client = createClient({ apiKey: "k", fetch }); + + let error: unknown; + try { + await client.agents.launch( + carryAgentRuntimeProvenance( + { definition: "instrumented-custom-instance" }, + { version: 1, callsite }, + ), + ); + } catch (value) { + error = value; + } + + const surfaced = error as typeof failure; + expect(error).not.toBe(failure); + expect(error).toBeInstanceOf(TypeError); + expect(surfaced.code).toBe("EAGENT"); + expect(surfaced.diagnostics.custom).toBe("[REDACTED runtime provenance]"); + expect(surfaced.diagnostics.custom).not.toBeInstanceOf(CustomDiagnostic); + expect(surfaced.diagnostics.publicSibling).toBe("sibling-public"); + expect(failure.diagnostics).toBe(diagnostics); + expect(custom.privateValue).toBe(callsite); + expect(custom.publicValue).toBe("custom-public"); + }); + + it("redacts nested ordinary diagnostics without mutating the original error graph", async () => { + const callsite = "callsite.nested-private"; + interface NestedDiagnostics { + request: { + headers: Record; + lazyDiagnostic?: string; + }; + response: { status: number; retryable: boolean }; + observedAt: Date; + } + let accessorReads = 0; + const observedAt = new Date("2026-09-01T00:00:00.000Z"); + const diagnostics: NestedDiagnostics = { + request: { + headers: { + [AGENT_RUNTIME_CALLSITE_HEADER]: callsite, + "x-request-id": "request-public", + }, + }, + response: { status: 502, retryable: true }, + observedAt, + }; + Object.defineProperty(diagnostics.request, "lazyDiagnostic", { + configurable: true, + enumerable: false, + get() { + accessorReads += 1; + return "lazy-public"; + }, + }); + class DiagnosticTransportError extends TypeError { + readonly code = "EAGENT"; + constructor(readonly diagnostics: NestedDiagnostics) { + super("fetch failed with diagnostics"); + } + } + const failure = new DiagnosticTransportError(diagnostics); + const originalStack = failure.stack; + const originalStackDescriptor = Object.getOwnPropertyDescriptor( + failure, + "stack", + ); + expect(originalStackDescriptor).toBeDefined(); + const originalStackIsDataDescriptor = + originalStackDescriptor !== undefined && + "value" in originalStackDescriptor; + expect(typeof originalStack).toBe("string"); + expect(originalStack).toContain("runtime-provenance.spec.ts"); + const originalDescriptor = Object.getOwnPropertyDescriptor( + failure, + "diagnostics", + ); + const fetch = (async () => { + throw failure; + }) as typeof globalThis.fetch; + const client = createClient({ apiKey: "k", fetch }); + + let error: unknown; + try { + await client.agents.launch( + carryAgentRuntimeProvenance( + { definition: "instrumented-nested" }, + { version: 1, callsite }, + ), + ); + } catch (value) { + error = value; + } + + expect(error).not.toBe(failure); + expect(error).toBeInstanceOf(DiagnosticTransportError); + expect((error as DiagnosticTransportError).code).toBe("EAGENT"); + expect((error as DiagnosticTransportError).message).toBe(failure.message); + expect((error as DiagnosticTransportError).stack).toBe(originalStack); + const caughtStackDescriptor = Object.getOwnPropertyDescriptor( + error, + "stack", + ); + expect(caughtStackDescriptor).toBeDefined(); + expect( + caughtStackDescriptor !== undefined && "value" in caughtStackDescriptor, + ).toBe(originalStackIsDataDescriptor); + expect(caughtStackDescriptor?.configurable).toBe( + originalStackDescriptor?.configurable, + ); + expect(caughtStackDescriptor?.enumerable).toBe( + originalStackDescriptor?.enumerable, + ); + if (originalStackIsDataDescriptor) { + expect(caughtStackDescriptor?.writable).toBe( + originalStackDescriptor?.writable, + ); + expect(caughtStackDescriptor?.value).toBe(originalStack); + } else { + expect(caughtStackDescriptor?.get).toBe(originalStackDescriptor?.get); + expect(caughtStackDescriptor?.set).toBe(originalStackDescriptor?.set); + } + expect( + (error as DiagnosticTransportError).diagnostics.request.headers[ + AGENT_RUNTIME_CALLSITE_HEADER + ], + ).toBe("[REDACTED runtime provenance]"); + expect( + (error as DiagnosticTransportError).diagnostics.request.headers[ + "x-request-id" + ], + ).toBe("request-public"); + expect((error as DiagnosticTransportError).diagnostics.response).toEqual({ + status: 502, + retryable: true, + }); + expect((error as DiagnosticTransportError).diagnostics.observedAt).not.toBe( + observedAt, + ); + expect( + (error as DiagnosticTransportError).diagnostics.observedAt, + ).toBeInstanceOf(Date); + expect(observedAt.toISOString()).toBe("2026-09-01T00:00:00.000Z"); + expect( + (error as DiagnosticTransportError).diagnostics.observedAt.toISOString(), + ).toBe("2026-09-01T00:00:00.000Z"); + expect(accessorReads).toBe(0); + const lazyDiagnosticDescriptor = Object.getOwnPropertyDescriptor( + (error as DiagnosticTransportError).diagnostics.request, + "lazyDiagnostic", + ); + expect(lazyDiagnosticDescriptor?.value).toBe( + "[REDACTED runtime provenance]", + ); + expect("get" in lazyDiagnosticDescriptor!).toBe(false); + expect("set" in lazyDiagnosticDescriptor!).toBe(false); + expect(Object.getOwnPropertyDescriptor(error, "diagnostics")).toEqual( + expect.objectContaining({ + configurable: originalDescriptor?.configurable, + enumerable: originalDescriptor?.enumerable, + writable: originalDescriptor?.writable, + }), + ); + + expect(failure.diagnostics).toBe(diagnostics); + expect( + failure.diagnostics.request.headers[AGENT_RUNTIME_CALLSITE_HEADER], + ).toBe(callsite); + expect(failure.stack).toBe(originalStack); + expect(failure.code).toBe("EAGENT"); + }); + + it("does not invoke custom stack or nested diagnostic accessors", async () => { + const callsite = "callsite.custom-accessor-private"; + let stackReads = 0; + let diagnosticReads = 0; + const diagnostics: Record = { + reflected: callsite, + publicValue: "diagnostic-public", + }; + Object.defineProperty(diagnostics, "lazy", { + configurable: true, + enumerable: false, + get() { + diagnosticReads += 1; + return callsite; + }, + }); + const failure = Object.assign(new TypeError("fetch failed"), { + code: "EAGENT", + diagnostics, + }); + const customStackGetter = () => { + stackReads += 1; + return `custom stack ${callsite}`; + }; + Object.defineProperty(failure, "stack", { + configurable: true, + enumerable: false, + get: customStackGetter, + }); + const fetch = (async () => { + throw failure; + }) as typeof globalThis.fetch; + const client = createClient({ apiKey: "k", fetch }); + + let error: unknown; + try { + await client.agents.launch( + carryAgentRuntimeProvenance( + { definition: "instrumented-custom-accessors" }, + { version: 1, callsite }, + ), + ); + } catch (value) { + error = value; + } + + expect(error).not.toBe(failure); + expect(error).toBeInstanceOf(TypeError); + expect(stackReads).toBe(0); + expect(diagnosticReads).toBe(0); + const surfacedStackDescriptor = Object.getOwnPropertyDescriptor( + error, + "stack", + ); + expect(surfacedStackDescriptor?.value).toBe( + "[REDACTED runtime provenance]", + ); + expect("get" in surfacedStackDescriptor!).toBe(false); + expect("set" in surfacedStackDescriptor!).toBe(false); + const surfacedLazyDescriptor = Object.getOwnPropertyDescriptor( + (error as typeof failure).diagnostics, + "lazy", + ); + expect(surfacedLazyDescriptor?.value).toBe("[REDACTED runtime provenance]"); + expect("get" in surfacedLazyDescriptor!).toBe(false); + expect("set" in surfacedLazyDescriptor!).toBe(false); + expect((error as typeof failure).diagnostics.reflected).toBe( + "[REDACTED runtime provenance]", + ); + expect((error as typeof failure).diagnostics.publicValue).toBe( + "diagnostic-public", + ); + expect(failure.diagnostics).toBe(diagnostics); + expect(failure.diagnostics.reflected).toBe(callsite); + }); + + it("preserves exact identity when supplied provenance does not occur in nested diagnostics", async () => { + const diagnostics = { + request: { headers: { "x-request-id": "request-public" } }, + }; + const failure = Object.assign(new TypeError("fetch failed"), { + code: "EAGENT", + diagnostics, + }); + const fetch = (async () => { + throw failure; + }) as typeof globalThis.fetch; + const client = createClient({ apiKey: "k", fetch }); + + let error: unknown; + try { + await client.agents.launch( + carryAgentRuntimeProvenance( + { definition: "instrumented-no-match" }, + { version: 1, callsite: "callsite.not-reflected" }, + ), + ); + } catch (value) { + error = value; + } + + expect(error).toBe(failure); + expect((error as typeof failure).diagnostics).toBe(diagnostics); + }); + + it("redacts arrays and preserves cycles and shared ordinary diagnostics", async () => { + const callsite = "callsite.cyclic-private"; + const shared: Record = { + privateValue: callsite, + publicValue: "shared-public", + }; + const diagnostics: Record = { + entries: [shared, shared], + shared, + }; + diagnostics.self = diagnostics; + const failure = Object.assign(new TypeError("fetch failed"), { + code: "EAGENT", + diagnostics, + }); + const fetch = (async () => { + throw failure; + }) as typeof globalThis.fetch; + const client = createClient({ apiKey: "k", fetch }); + + let error: unknown; + try { + await client.agents.launch( + carryAgentRuntimeProvenance( + { definition: "instrumented-cyclic" }, + { version: 1, callsite }, + ), + ); + } catch (value) { + error = value; + } + + const sanitized = (error as typeof failure).diagnostics; + const sanitizedEntries = sanitized.entries as Record[]; + expect(error).not.toBe(failure); + expect(error).toBeInstanceOf(TypeError); + expect((error as typeof failure).code).toBe("EAGENT"); + expect(sanitized).not.toBe(diagnostics); + expect(sanitized.self).toBe(sanitized); + expect(sanitizedEntries[0]).toBe(sanitizedEntries[1]); + expect(sanitizedEntries[0]).toBe(sanitized.shared); + expect(sanitizedEntries[0]!.privateValue).toBe( + "[REDACTED runtime provenance]", + ); + expect(sanitizedEntries[0]!.publicValue).toBe("shared-public"); + + expect(diagnostics.self).toBe(diagnostics); + expect((diagnostics.entries as object[])[0]).toBe(shared); + expect(shared.privateValue).toBe(callsite); + }); + + it("preserves typed errors, causes, diagnostics, and stack frames while redacting", async () => { + const callsite = "callsite.typed-private"; + class AgentTransportError extends TypeError { + readonly diagnostic = "request transport failed"; + readonly code = "EAGENT"; + } + const cause = Object.assign(new Error(`connect failed for ${callsite}`), { + code: "ECONNREFUSED", + }); + const failure = new AgentTransportError( + `fetch failed for ${callsite}`, + ) as AgentTransportError & { cause: Error }; + Object.defineProperty(failure, "stack", { + configurable: true, + value: + `AgentTransportError: fetch failed for ${callsite}\n` + + " at runtime-provenance.spec.ts:1:1", + writable: true, + }); + Object.defineProperty(cause, "stack", { + configurable: true, + value: + `Error: connect failed for ${callsite}\n` + + " at runtime-provenance.spec.ts:2:1", + writable: true, + }); + Object.defineProperty(failure, "cause", { + configurable: true, + value: cause, + writable: true, + }); + const fetch = (async () => { + throw failure; + }) as typeof globalThis.fetch; + const client = createClient({ apiKey: "k", fetch }); + + let error: unknown; + try { + await client.agents.launch( + carryAgentRuntimeProvenance( + { definition: "instrumented" }, + { version: 1, callsite }, + ), + ); + } catch (value) { + error = value; + } + + expect(error).not.toBe(failure); + expect(error).toBeInstanceOf(AgentTransportError); + expect(Object.getPrototypeOf(error)).toBe(AgentTransportError.prototype); + expect((error as AgentTransportError).name).toBe(failure.name); + expect((error as AgentTransportError).code).toBe("EAGENT"); + expect((error as AgentTransportError).diagnostic).toBe( + "request transport failed", + ); + expect((error as AgentTransportError).message).toContain("fetch failed"); + expect((error as AgentTransportError).message).not.toContain(callsite); + expect((error as AgentTransportError).stack).toContain( + "runtime-provenance.spec.ts", + ); + expect((error as AgentTransportError).stack).not.toContain(callsite); + const redactedCause = (error as AgentTransportError & { cause: Error }) + .cause as Error & { code: string }; + expect(redactedCause).not.toBe(cause); + expect(redactedCause).toBeInstanceOf(Error); + expect(redactedCause.code).toBe("ECONNREFUSED"); + expect(redactedCause.message).toContain("connect failed"); + expect(redactedCause.message).not.toContain(callsite); + expect(redactedCause.stack).not.toContain(callsite); + }); + + it("redacts reflected callsite and response receipt from status errors", async () => { + const receipt = "signed.status-private"; + const callsite = "callsite.status-private"; + const fetch = (async ( + _input: string | URL | Request, + init: RequestInit = {}, + ) => { + if (init.method === "POST") { + return response( + { status: "enqueued", executionId: "exec-status" }, + 201, + ); + } + return response({ message: `reflected ${callsite} ${receipt}` }, 500, { + [AGENT_RUNTIME_PROVENANCE_VERSION_HEADER]: "1", + [AGENT_RUNTIME_LINEAGE_HEADER]: receipt, + }); + }) as typeof globalThis.fetch; + const client = createClient({ apiKey: "k", fetch }); + const handle = await client.agents.launch( + carryAgentRuntimeProvenance( + { definition: "child" }, + { version: 1, callsite }, + ), + ); + + let error: unknown; + try { + await handle.status(); + } catch (value) { + error = value; + } + + expect(error).toBeInstanceOf(Error); + expect((error as Error).message).toContain("reflected"); + expect((error as Error).message).not.toContain(callsite); + expect((error as Error).message).not.toContain(receipt); + }); + + it("redacts known provenance from status parsing errors", async () => { + const receipt = "signed.parse-private"; + const callsite = "callsite.parse-private"; + const fetch = (async ( + _input: string | URL | Request, + init: RequestInit = {}, + ) => { + if (init.method === "POST") { + return response({ status: "enqueued", executionId: "exec-parse" }, 201); + } + return { + ok: true, + status: 200, + headers: new Headers({ + [AGENT_RUNTIME_PROVENANCE_VERSION_HEADER]: "1", + [AGENT_RUNTIME_LINEAGE_HEADER]: receipt, + }), + json: async () => { + throw new Error(`invalid response ${callsite} ${receipt}`); + }, + } as unknown as Response; + }) as typeof globalThis.fetch; + const client = createClient({ apiKey: "k", fetch }); + const handle = await client.agents.launch( + carryAgentRuntimeProvenance( + { definition: "child" }, + { version: 1, callsite }, + ), + ); + + let error: unknown; + try { + await handle.status(); + } catch (value) { + error = value; + } + + expect(error).toBeInstanceOf(Error); + expect((error as Error).message).toContain("invalid response"); + expect((error as Error).message).not.toContain(callsite); + expect((error as Error).message).not.toContain(receipt); + }); + + it("preserves legacy request and result behavior when provenance is absent", async () => { + const server = agentServer(); + const client = createClient({ apiKey: "k", fetch: server.fetch }); + const result = await client.agents.run({ + definition: "legacy", + input: { value: 1 }, + idempotencyKey: "same", + }); + const launch = posts(server.calls)[0]!; + + expect(header(launch, AGENT_RUNTIME_CALLSITE_HEADER)).toBeUndefined(); + expect(header(launch, AGENT_RUNTIME_LINEAGE_HEADER)).toBeUndefined(); + expect( + header(launch, AGENT_RUNTIME_PROVENANCE_VERSION_HEADER), + ).toBeUndefined(); + expect(JSON.parse(String(launch.init.body))).toEqual({ + input: { value: 1 }, + idempotencyKey: "same", + }); + expect(result.output).toEqual({ ok: true }); + }); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3c0a05e41..978a61571 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -767,6 +767,9 @@ importers: '@typescript-eslint/parser': specifier: ^7.3.1 version: 7.18.0(eslint@8.57.1)(typescript@5.9.3) + esbuild: + specifier: ^0.28.1 + version: 0.28.1 eslint: specifier: ^8.57.0 version: 8.57.1