@tangle-network/agent-app 0.60.214 → 0.60.216
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-CD2LQXY6.js +306 -0
- package/dist/chunk-CD2LQXY6.js.map +1 -0
- package/dist/{chunk-KDPTXXOQ.js → chunk-J3N6RNMH.js} +456 -75
- package/dist/chunk-J3N6RNMH.js.map +1 -0
- package/dist/chunk-LRYY3OX7.js +105 -0
- package/dist/chunk-LRYY3OX7.js.map +1 -0
- package/dist/{chunk-LLO436QO.js → chunk-QM7RTTDY.js} +27 -86
- package/dist/chunk-QM7RTTDY.js.map +1 -0
- package/dist/{chunk-ZRLKB4KB.js → chunk-YWLZOPDS.js} +10 -11
- package/dist/{chunk-ZRLKB4KB.js.map → chunk-YWLZOPDS.js.map} +1 -1
- package/dist/launch-invariants/catalog.d.ts +16 -0
- package/dist/launch-invariants/cli.js +5 -2
- package/dist/launch-invariants/cli.js.map +1 -1
- package/dist/launch-invariants/index.d.ts +1 -1
- package/dist/launch-invariants/index.js +13 -5
- package/dist/launch-invariants/index.js.map +1 -1
- package/dist/launch-invariants/limit-alarms.d.ts +19 -1
- package/dist/launch-invariants/testing.d.ts +0 -3
- package/dist/launch-invariants/testing.js +5 -2
- package/dist/platform/delegated-keys.d.ts +3 -1
- package/dist/platform/index.js +10 -1
- package/dist/platform/index.js.map +1 -1
- package/dist/signoff/app-defaults.d.ts +65 -0
- package/dist/signoff/cli.js +71 -4
- package/dist/signoff/cli.js.map +1 -1
- package/dist/signoff/defaults-cli.d.ts +1 -0
- package/dist/signoff/index.js +2 -1
- package/dist/signoff/types.d.ts +6 -0
- package/dist/signoff/worker-defaults.d.ts +8 -0
- package/dist/turn-health/index.js +12 -291
- package/dist/turn-health/index.js.map +1 -1
- package/dist/turn-trace/index.js +20 -3
- package/dist/turn-trace/index.js.map +1 -1
- package/dist/turn-trace/otel.d.ts +11 -4
- package/package.json +1 -1
- package/dist/chunk-KDPTXXOQ.js.map +0 -1
- package/dist/chunk-LLO436QO.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/launch-invariants/testing.ts","../src/launch-invariants/conformance.ts"],"sourcesContent":["/**\n * `/launch-invariants/testing` — the checks an app runs in its own test suite.\n *\n * Each check drives the app's real code through a small adapter and returns an\n * {@link InvariantVerdict}. {@link recordInvariant} fails the calling test with\n * the verdict's findings and, when `agent-app-invariants` runs the suite,\n * records the verdict so the conformance report can say which invariant\n * passed. The checks need no test runner: vitest, node:test and plain scripts\n * all work.\n *\n * Node-only (`node:v8`, `node:vm`, `node:fs`): import it from tests, never from\n * a Worker.\n */\nimport { appendFileSync } from 'node:fs'\nimport v8 from 'node:v8'\nimport vm from 'node:vm'\nimport type { AlertSink, TurnHealthAlert } from '../turn-health/sink.js'\nimport { describeVerdict, LAUNCH_BUDGETS, type InvariantVerdict } from './catalog.js'\nimport { LIMIT_RESOURCES, type LimitAlarms } from './limit-alarms.js'\nimport { isEdgeBlock, PASTED_CODE_SAMPLES } from './pasted-code.js'\n\nexport { describeVerdict } from './catalog.js'\n\n/** Set by `agent-app-invariants` to the file it reads verdicts from. */\nexport const INVARIANT_RESULTS_ENV = 'AGENT_APP_INVARIANTS_RESULTS'\n\nexport interface RecordInvariantOptions {\n /**\n * The app knows this check fails today: why, who owns the fix and the next\n * check. The failure is recorded (the report counts the invariant as not\n * holding) without failing the suite, so an app adopts the kit before every\n * invariant holds. Once the check passes, this fails the caller until the\n * marker is removed, so a fixed invariant cannot regress silently.\n */\n knownFailing?: string\n}\n\n/**\n * Record a verdict for the conformance report and fail the caller when it did\n * not pass. Returns the verdict on a pass.\n */\nexport function recordInvariant(verdict: InvariantVerdict, options: RecordInvariantOptions = {}): InvariantVerdict {\n const known = options.knownFailing?.trim()\n const recorded = known && !verdict.pass ? { ...verdict, knownFailing: known } : verdict\n const file = process.env[INVARIANT_RESULTS_ENV]\n if (file) appendFileSync(file, `${JSON.stringify({ ...recorded, at: new Date().toISOString() })}\\n`)\n if (known && verdict.pass) {\n throw new Error(`${describeVerdict(verdict)}\\n this invariant now holds; remove its knownFailing marker (\"${known}\") so it cannot regress silently`)\n }\n if (!verdict.pass && !known) throw new Error(describeVerdict(verdict))\n return recorded\n}\n\n/** Record several verdicts; fails after recording all of them. */\nexport function recordInvariants(verdicts: readonly InvariantVerdict[], options: RecordInvariantOptions = {}): InvariantVerdict[] {\n const failures: string[] = []\n for (const verdict of verdicts) {\n try {\n recordInvariant(verdict, options)\n } catch (error) {\n failures.push(error instanceof Error ? error.message : String(error))\n }\n }\n if (failures.length) throw new Error(failures.join('\\n'))\n return [...verdicts]\n}\n\n// ── heap ─────────────────────────────────────────────────────────────────────\n\nlet gc: (() => void) | undefined\n\n/** Run a full garbage collection, so a heap reading counts only live objects. */\nexport function forceGc(): void {\n if (!gc) {\n v8.setFlagsFromString('--expose_gc')\n gc = vm.runInNewContext('gc') as () => void\n }\n gc()\n}\n\n// ── measured D1 ──────────────────────────────────────────────────────────────\n\nconst MB = 1024 * 1024\n\n/** What a measured binding saw while one job ran. */\nexport interface D1Reads {\n queries: number\n maxQueryBytes: number\n /** The query that returned the most bytes. */\n largestSql: string\n peakHeapBytes: number\n /** The query running when the heap peaked: where a job holds what it read. */\n peakSql: string\n}\n\nfunction emptyReads(): D1Reads {\n return { queries: 0, maxQueryBytes: 0, largestSql: '', peakHeapBytes: 0, peakSql: '' }\n}\n\nexport interface MeasureD1Options {\n /** Sample the live heap at all. Default true. */\n sampleHeap?: boolean\n /** Sample after every response at least this large. Default 64 KB. */\n sampleAfterBytes?: number\n /** Also sample every this-many queries, so what a job keeps from small reads still shows. Default 50. */\n sampleEvery?: number\n}\n\n/**\n * The D1 binding with every response measured (serialized bytes) and the live\n * heap sampled after a forced collection. A collection costs about 50 ms on a\n * test heap and a job runs hundreds of small queries, so the heap is sampled\n * after each large read and every 50th query (GTM's sweeps budget took 170 s\n * sampling around every query). Works over Cloudflare D1 and the sqlite-backed\n * D1 shims apps test with. A database engine that keeps its pages off the JS\n * heap (node:sqlite, better-sqlite3) leaves heap growth to the job's own objects.\n */\nexport function measureD1<D extends object>(d1: D, reads: D1Reads = emptyReads(), options: MeasureD1Options = {}): { d1: D; reads: D1Reads } {\n const sampleHeap = options.sampleHeap ?? true\n const sampleAfterBytes = options.sampleAfterBytes ?? 64 * 1024\n const sampleEvery = Math.max(1, options.sampleEvery ?? 50)\n const sample = (sql: string) => {\n if (!sampleHeap) return\n forceGc()\n const heap = process.memoryUsage().heapUsed\n if (heap > reads.peakHeapBytes) {\n reads.peakHeapBytes = heap\n reads.peakSql = sql\n }\n }\n const measure = (sql: string, result: unknown) => {\n reads.queries += 1\n const bytes = Buffer.byteLength(JSON.stringify(result) ?? '')\n if (bytes > reads.maxQueryBytes) {\n reads.maxQueryBytes = bytes\n reads.largestSql = sql\n }\n if (bytes >= sampleAfterBytes || reads.queries % sampleEvery === 0) sample(sql)\n }\n const statement = (target: object, sql: string): object => new Proxy(target, {\n get(stmt, key) {\n const value = Reflect.get(stmt, key) as unknown\n if (typeof value !== 'function') return value\n if (key === 'bind') return (...params: unknown[]) => statement(value.apply(stmt, params) as object, sql)\n if (key === 'all' || key === 'raw' || key === 'first' || key === 'run') {\n return async (...args: unknown[]) => {\n const result: unknown = await value.apply(stmt, args)\n measure(sql, result)\n return result\n }\n }\n return value.bind(stmt)\n },\n })\n const unwrap = new WeakMap<object, object>()\n const proxy = new Proxy(d1, {\n get(binding, key) {\n const value = Reflect.get(binding, key) as unknown\n if (key === 'prepare') {\n return (sql: string) => {\n const stmt = (value as (sql: string) => object).call(binding, sql)\n const wrapped = statement(stmt, sql.replace(/\\s+/g, ' ').trim().slice(0, 200))\n unwrap.set(wrapped, stmt)\n return wrapped\n }\n }\n if (key === 'batch') {\n return async (statements: object[]) => {\n const result: unknown = await (value as (s: object[]) => Promise<unknown>).call(binding, statements.map((stmt) => unwrap.get(stmt) ?? stmt))\n measure('batch', result)\n return result\n }\n }\n return typeof value === 'function' ? value.bind(binding) : value\n },\n })\n return { d1: proxy, reads }\n}\n\n/** One scheduled job (or report route) as the budget check runs it. */\nexport interface BudgetedJob {\n name: string\n run: () => Promise<unknown>\n /** False for a job that reads nothing from D1 (it then passes bounded reads vacuously). Default true. */\n readsD1?: boolean\n}\n\nexport interface JobMeasurement {\n name: string\n reads: D1Reads\n heapGrowthBytes: number\n error?: string\n}\n\nexport interface ScheduledJobBudgetOptions {\n /** The app's D1 over its day-sized fixture. */\n d1: object\n /** Point the app's database accessor at the measured binding (`setD1`, `setDatabase(drizzle(...))`). */\n install: (measured: object) => void\n jobs: readonly BudgetedJob[]\n /** Every job the app schedules, by the names {@link jobs} uses. Each must be measured. */\n scheduled: readonly string[]\n maxQueryBytes?: number\n maxHeapGrowthBytes?: number\n /** Silence the jobs' own logging while they run. Default true. */\n quiet?: boolean\n}\n\n/** Run one job against a measured binding. */\nexport async function measureJob(job: BudgetedJob, d1: object, install: (measured: object) => void, quiet = true): Promise<JobMeasurement> {\n const { d1: measured, reads } = measureD1(d1)\n install(measured)\n forceGc()\n const baseline = process.memoryUsage().heapUsed\n reads.peakHeapBytes = baseline\n const restore = quiet ? silenceConsole() : () => {}\n let error: string | undefined\n try {\n await job.run()\n } catch (caught) {\n error = caught instanceof Error ? caught.message : String(caught)\n } finally {\n restore()\n }\n forceGc()\n const after = process.memoryUsage().heapUsed\n if (after > reads.peakHeapBytes) {\n reads.peakHeapBytes = after\n reads.peakSql = reads.peakSql ? `${reads.peakSql} (still held when the job returned)` : '(after the job returned)'\n }\n return { name: job.name, reads, heapGrowthBytes: reads.peakHeapBytes - baseline, ...(error ? { error } : {}) }\n}\n\nfunction silenceConsole(): () => void {\n const saved = { log: console.log, info: console.info, warn: console.warn, error: console.error }\n console.log = console.info = console.warn = console.error = () => {}\n return () => Object.assign(console, saved)\n}\n\n/**\n * Bounded reads and the memory budget for every scheduled job, on the app's\n * day-sized fixture. Returns a bounded-reads and a memory-budget verdict per\n * job, and a coverage verdict naming any scheduled job that was not run.\n */\nexport async function checkScheduledJobBudgets(options: ScheduledJobBudgetOptions): Promise<InvariantVerdict[]> {\n const maxQueryBytes = options.maxQueryBytes ?? LAUNCH_BUDGETS.queryBytes\n const maxHeap = options.maxHeapGrowthBytes ?? LAUNCH_BUDGETS.heapGrowthBytes\n const verdicts: InvariantVerdict[] = []\n const measured = new Set<string>()\n for (const job of options.jobs) {\n const m = await measureJob(job, options.d1, options.install, options.quiet ?? true)\n measured.add(job.name)\n const failed = m.error ? [`the job threw on the fixture: ${m.error}`] : []\n const readFindings = [...failed]\n if ((job.readsD1 ?? true) && m.reads.queries === 0) readFindings.push('made no D1 query on the fixture; the fixture does not reach it, or it reads through another binding')\n if (m.reads.maxQueryBytes > maxQueryBytes) {\n readFindings.push(`one query returned ${(m.reads.maxQueryBytes / MB).toFixed(1)} MB (the most is ${(maxQueryBytes / MB).toFixed(0)} MB): ${m.reads.largestSql}`)\n }\n verdicts.push({\n invariant: 'bounded-reads',\n subject: job.name,\n pass: readFindings.length === 0,\n details: readFindings.length ? readFindings : [`${m.reads.queries} queries, largest ${(m.reads.maxQueryBytes / MB).toFixed(2)} MB`],\n data: { queries: m.reads.queries, maxQueryBytes: m.reads.maxQueryBytes, largestSql: m.reads.largestSql },\n })\n const heapFindings = [...failed]\n if (m.heapGrowthBytes > maxHeap) {\n heapFindings.push(`the live heap grew ${(m.heapGrowthBytes / MB).toFixed(1)} MB (the most is ${(maxHeap / MB).toFixed(0)} MB), at its peak around: ${m.reads.peakSql}`)\n }\n verdicts.push({\n invariant: 'memory-budget',\n subject: job.name,\n pass: heapFindings.length === 0,\n details: heapFindings.length ? heapFindings : [`heap grew ${(m.heapGrowthBytes / MB).toFixed(1)} MB`],\n data: { heapGrowthBytes: m.heapGrowthBytes, peakSql: m.reads.peakSql },\n })\n }\n const missing = options.scheduled.filter((name) => !measured.has(name))\n verdicts.push({\n invariant: 'memory-budget',\n subject: 'coverage',\n pass: missing.length === 0 && options.scheduled.length > 0,\n details: missing.length\n ? missing.map((name) => `${name} is scheduled but not run on the fixture`)\n : options.scheduled.length === 0\n ? ['no scheduled jobs were named; pass every job the app schedules']\n : [`all ${options.scheduled.length} scheduled jobs ran on the fixture`],\n data: { scheduled: [...options.scheduled], measured: [...measured] },\n })\n return verdicts\n}\n\n// ── a D1 that stalls ─────────────────────────────────────────────────────────\n\nexport type StallMode = 'ok' | 'error' | 'hang'\n\n/** The D1 binding with a switch that makes every query fail or never answer, as an overloaded D1 did. */\nexport function stallingD1<D extends object>(d1: D): { d1: D; stall(mode: StallMode): void; readonly mode: StallMode } {\n let mode: StallMode = 'ok'\n const fail = (): Promise<never> => mode === 'hang'\n ? new Promise<never>(() => {})\n : Promise.reject(new Error('D1_ERROR: Network connection lost.'))\n const unwrapped = new WeakMap<object, object>()\n const statement = (target: object): object => {\n const wrapped = new Proxy(target, {\n get(stmt, key) {\n const value = Reflect.get(stmt, key) as unknown\n if (typeof value !== 'function') return value\n if (key === 'bind') return (...params: unknown[]) => statement(value.apply(stmt, params) as object)\n if (key === 'all' || key === 'raw' || key === 'first' || key === 'run') {\n return (...args: unknown[]) => (mode === 'ok' ? value.apply(stmt, args) : fail())\n }\n return value.bind(stmt)\n },\n })\n unwrapped.set(wrapped, target)\n return wrapped\n }\n const proxy = new Proxy(d1, {\n get(binding, key) {\n const value = Reflect.get(binding, key) as unknown\n if (key === 'prepare') return (sql: string) => statement((value as (sql: string) => object).call(binding, sql))\n if (key === 'batch') {\n return (statements: object[]) => (mode === 'ok'\n ? (value as (s: object[]) => unknown).call(binding, statements.map((stmt) => unwrapped.get(stmt) ?? stmt))\n : fail())\n }\n return typeof value === 'function' ? value.bind(binding) : value\n },\n })\n return {\n d1: proxy,\n stall(next) {\n mode = next\n },\n get mode() {\n return mode\n },\n }\n}\n\n/** Move `Date.now` forward for the duration of a check; returns the restore. */\nfunction shiftableClock(): { advance(ms: number): void; restore(): void } {\n const real = Date.now\n let offset = 0\n Date.now = () => real() + offset\n return {\n advance(ms) {\n offset += ms\n },\n restore() {\n Date.now = real\n },\n }\n}\n\n// ── auth survives a D1 stall ─────────────────────────────────────────────────\n\nexport interface AuthStallOptions {\n /** A label for the lookup: \"session\", \"API key\". */\n subject: string\n /** Three distinct valid credentials the app's store accepts. */\n credentials: { cached: string; revoked: string; aged: string }\n /** The app's real lookup: truthy when the credential is accepted. A throw or a falsy value is a refusal. */\n verify: (credential: string) => Promise<unknown>\n /** The app's real revoke path (revoke the key, sign the session out). */\n revoke: (credential: string) => Promise<void>\n /** Switch the D1 the lookup reads. */\n stall: (mode: StallMode) => void\n /** The keys the app's auth caches hold. */\n cacheKeys: () => string[]\n /** Include a stall that never answers (costs one lookup deadline, 2.5 s). Default true. */\n hang?: boolean\n}\n\n/**\n * A verified credential keeps working while D1 errors or stalls; it is refused\n * once revoked, and once its last verification is more than 60 s old; and the\n * cache holds SHA-256 keys, not secrets. Uses the app's real lookup, revoke\n * path and D1, with `Date.now` moved forward to age the cache.\n */\nexport async function checkAuthSurvivesStall(options: AuthStallOptions): Promise<InvariantVerdict> {\n const findings: string[] = []\n const accepted = async (credential: string): Promise<boolean> => {\n try {\n return Boolean(await options.verify(credential))\n } catch {\n return false\n }\n }\n const { cached, revoked, aged } = options.credentials\n const clock = shiftableClock()\n try {\n options.stall('ok')\n for (const credential of [cached, revoked, aged]) {\n if (!(await accepted(credential))) findings.push(`a valid credential was refused while D1 was healthy (${options.subject})`)\n }\n for (const key of options.cacheKeys()) {\n if (!/^[0-9a-f]{64}$/.test(key) || [cached, revoked, aged].some((credential) => key.includes(credential))) {\n findings.push(`the auth cache holds a key that is not a SHA-256 digest: ${key.slice(0, 12)}…`)\n }\n }\n await options.revoke(revoked)\n if (await accepted(revoked)) findings.push('a revoked credential was accepted while D1 was healthy')\n\n options.stall('error')\n if (!(await accepted(cached))) findings.push('a credential verified moments before was refused while D1 errored')\n if (await accepted(revoked)) findings.push('a revoked credential was accepted from the cache while D1 errored')\n if (options.hang ?? true) {\n options.stall('hang')\n if (!(await accepted(cached))) findings.push('a credential verified moments before was refused while D1 did not answer')\n }\n\n clock.advance(LAUNCH_BUDGETS.authCacheTtlMs + 1_000)\n options.stall('error')\n if (await accepted(aged)) findings.push(`a credential last verified more than ${LAUNCH_BUDGETS.authCacheTtlMs / 1000} s ago was accepted from the cache while D1 errored`)\n } finally {\n options.stall('ok')\n clock.restore()\n }\n return {\n invariant: 'auth-survives-d1-stall',\n subject: options.subject,\n pass: findings.length === 0,\n details: findings.length ? findings : [\n 'served while D1 errored and while it stalled; refused after revoke and after the 60 s cache window; keys are SHA-256',\n ],\n }\n}\n\n// ── authorization before any stream ──────────────────────────────────────────\n\nexport interface AuthorizeBeforeStreamOptions {\n subject: string\n /** The app's real route handler, as its router mounts it. */\n handle: (request: Request) => Promise<Response>\n /** Requests the route must refuse: no session, another workspace's thread, a revoked key. */\n refused: ReadonlyArray<{ name: string; request: () => Request; status?: readonly number[] }>\n /** Product state the route touched (rows written, producers started); empty after every refusal. */\n touched?: () => readonly string[]\n}\n\nconst STREAM_TYPES = /event-stream|ndjson|x-turn-progress/i\n\n/** Each refused request gets a plain 4xx, opens no stream and touches nothing. */\nexport async function checkAuthorizeBeforeStream(options: AuthorizeBeforeStreamOptions): Promise<InvariantVerdict> {\n const findings: string[] = []\n for (const probe of options.refused) {\n const before = options.touched?.().length ?? 0\n const response = await options.handle(probe.request())\n const allowed = probe.status ?? [401, 403, 404]\n if (!allowed.includes(response.status)) findings.push(`${probe.name}: answered ${response.status}, expected ${allowed.join(' or ')}`)\n const type = response.headers.get('content-type') ?? ''\n if (STREAM_TYPES.test(type)) findings.push(`${probe.name}: opened a ${type} stream before refusing`)\n for (const [name] of response.headers) {\n if (/turn-progress|x-turn-id/i.test(name)) findings.push(`${probe.name}: refusal carries turn header ${name}`)\n }\n await response.body?.cancel().catch(() => undefined)\n const after = options.touched?.() ?? []\n if (after.length > before) findings.push(`${probe.name}: touched ${after.slice(before).join(', ')} before refusing`)\n }\n return {\n invariant: 'authorize-before-stream',\n subject: options.subject,\n pass: findings.length === 0 && options.refused.length > 0,\n details: findings.length ? findings : options.refused.length\n ? [`${options.refused.length} refused requests answered plainly with nothing touched`]\n : ['no refused requests were probed'],\n }\n}\n\n// ── pasted code reaches the chat ─────────────────────────────────────────────\n\nexport interface ChatAcceptsCodeOptions {\n subject: string\n /** The app's real chat route handler, as its router mounts it. */\n handle: (request: Request) => Promise<Response>\n /** An authenticated chat request carrying `text` as the message. */\n request: (text: string) => Request\n /** Statuses the route answers an accepted message with. Default any 2xx. */\n accepted?: (status: number) => boolean\n samples?: readonly { name: string; text: string }[]\n}\n\n/** Each pasted-code sample gets the route's normal response, never a refusal or an edge block. Cancels any stream it opens. */\nexport async function checkChatAcceptsCode(options: ChatAcceptsCodeOptions): Promise<InvariantVerdict> {\n const accepted = options.accepted ?? ((status: number) => status >= 200 && status < 300)\n const samples = options.samples ?? PASTED_CODE_SAMPLES\n const findings: string[] = []\n for (const sample of samples) {\n const response = await options.handle(options.request(sample.text))\n if (await isEdgeBlock(response)) findings.push(`${sample.name}: an edge rule blocked it (${response.status})`)\n else if (!accepted(response.status)) {\n const body = (await response.clone().text().catch(() => '')).slice(0, 160)\n findings.push(`${sample.name}: answered ${response.status}${body ? `: ${body}` : ''}`)\n }\n await response.body?.cancel().catch(() => undefined)\n }\n return {\n invariant: 'pasted-code-reaches-chat',\n subject: options.subject,\n pass: findings.length === 0,\n details: findings.length ? findings : [`${samples.length} pasted-code messages reached the chat and got its normal response`],\n }\n}\n\n// ── coalesced turn events ────────────────────────────────────────────────────\n\n/** One event of the recorded long turn and the turn-relative time it arrived. */\nexport interface TimedTurnEvent {\n atMs: number\n event: Record<string, unknown>\n}\n\n/**\n * The long turn GTM recorded on 2026-10-10, reproduced in shape: a reasoning\n * model streaming three events per token (the harness's raw part update, a\n * model-processing heartbeat and the delta), with tool calls between runs of\n * reasoning and text. 59,659 events over about ten minutes, as that turn wrote\n * them before coalescing.\n */\nexport function recordedLongTurn(options: { tokens?: number; toolCalls?: number; tokenMs?: number } = {}): TimedTurnEvent[] {\n const tokens = options.tokens ?? 19_866\n const toolCalls = options.toolCalls ?? 30\n const tokenMs = options.tokenMs ?? 30\n const events: TimedTurnEvent[] = [{ atMs: 0, event: { type: 'turn', turnId: 'recorded-long-turn' } }]\n const toolEvery = Math.max(1, Math.floor(tokens / (toolCalls + 1)))\n let tool = 0\n let part = 0\n for (let index = 0; index < tokens; index += 1) {\n const atMs = (index + 1) * tokenMs\n if (index > 0 && index % toolEvery === 0 && tool < toolCalls) {\n tool += 1\n part += 1\n events.push({ atMs, event: { type: 'tool_call', id: `call_${tool}`, name: 'bash', input: { command: 'ls' } } })\n events.push({ atMs: atMs + 1, event: { type: 'tool_result', id: `call_${tool}`, output: 'ok' } })\n }\n const kind = part % 2 === 0 ? 'reasoning' : 'text'\n const partId = `prt_${part}`\n events.push({ atMs, event: { type: 'raw', data: { type: 'raw', backend: 'opencode', event: { type: kind, timestamp: atMs, part: { id: partId, type: kind, metadata: { at: atMs } } } } } })\n events.push({ atMs, event: { type: 'model-processing', data: { elapsedMs: atMs } } })\n events.push({ atMs, event: { type: kind, text: `t${index} ` } })\n }\n return events\n}\n\nexport interface CoalescedTurnOptions {\n subject: string\n /**\n * Start persisting one turn through the app's real turn persistence (its turn\n * store and buffer, as its chat route builds them). `detach` hands the turn\n * to a durable owner the way the route's completion handoff does; omit it when\n * the app has no detached lane.\n */\n open: (turnId: string) => Promise<{\n onEvent(event: unknown): Promise<void>\n detach?(): Promise<void>\n done(): Promise<void>\n }>\n /** Stored events for the turn. */\n count: (turnId: string) => Promise<number>\n maxEvents?: number\n fixture?: readonly TimedTurnEvent[]\n}\n\n/** The recorded long turn stores at most 5,000 events, attached and after a handoff. */\nexport async function checkCoalescedTurn(options: CoalescedTurnOptions): Promise<InvariantVerdict> {\n const maxEvents = options.maxEvents ?? LAUNCH_BUDGETS.turnEvents\n const fixture = options.fixture ?? recordedLongTurn()\n const findings: string[] = []\n const stored: Record<string, number> = {}\n const real = Date.now\n for (const lane of ['attached', 'detached'] as const) {\n const turnId = `invariant-long-turn-${lane}`\n const start = real()\n let clock = start\n Date.now = () => clock\n try {\n const tap = await options.open(turnId)\n if (lane === 'detached' && !tap.detach) continue\n for (const [index, item] of fixture.entries()) {\n clock = start + item.atMs\n await tap.onEvent(item.event)\n if (lane === 'detached' && index === 0) await tap.detach!()\n }\n clock = start + (fixture.at(-1)?.atMs ?? 0) + 1\n await tap.done()\n stored[lane] = await options.count(turnId)\n } finally {\n Date.now = real\n }\n if (stored[lane] === 0) findings.push(`${lane}: the turn stored no events; the check did not reach the app's turn store`)\n if (stored[lane]! > maxEvents) findings.push(`${lane}: the recorded long turn (${fixture.length.toLocaleString('en-US')} events) stored ${stored[lane]!.toLocaleString('en-US')}; the most is ${maxEvents.toLocaleString('en-US')}`)\n }\n return {\n invariant: 'coalesced-events',\n subject: options.subject,\n pass: findings.length === 0,\n details: findings.length ? findings : Object.entries(stored).map(([lane, count]) => `${lane}: ${fixture.length.toLocaleString('en-US')} events stored as ${count.toLocaleString('en-US')}`),\n data: { fixtureEvents: fixture.length, stored },\n }\n}\n\n// ── limit alarms ─────────────────────────────────────────────────────────────\n\n/** Hard platform limits a declared budget may not exceed. */\nexport const PLATFORM_LIMITS = {\n /** A Worker isolate's memory. */\n 'worker-memory': 128 * MB,\n /** Platform snapshots per sandbox; the cap GTM hit on 2026-10-10. */\n 'snapshot-count': 25,\n /** Requests per minute per operator key. */\n 'key-rate': 60,\n} as const\n\nexport interface LimitAlarmCheckOptions {\n subject: string\n /** Build the app's real limit alarms (its budgets and wiring) over a capturing sink. */\n create: (sink: AlertSink) => LimitAlarms | Promise<LimitAlarms>\n}\n\n/** Every resource alarms at 80% and not below, escalates at the limit, and no budget exceeds the platform's. */\nexport async function checkLimitAlarms(options: LimitAlarmCheckOptions): Promise<InvariantVerdict> {\n const findings: string[] = []\n for (const resource of LIMIT_RESOURCES) {\n const delivered: TurnHealthAlert[] = []\n const alarms = await options.create({ deliver: async (alert) => { delivered.push(alert) } })\n const limit = alarms.budgets[resource]\n const platform = (PLATFORM_LIMITS as Partial<Record<string, number>>)[resource]\n if (platform !== undefined && limit > platform) findings.push(`${resource}: the declared limit ${limit} is above the platform's ${platform}`)\n await alarms.observe(resource, limit * 0.79)\n if (delivered.length) findings.push(`${resource}: alarmed at 79% of its limit`)\n await alarms.observe(resource, limit * LAUNCH_BUDGETS.alarmRatio)\n if (delivered.length !== 1 || delivered[0]?.severity !== 'warning') findings.push(`${resource}: did not warn at 80% of its limit`)\n await alarms.observe(resource, limit)\n if (delivered.length !== 2 || delivered[1]?.severity !== 'critical') findings.push(`${resource}: did not raise a critical alert at its limit`)\n }\n return {\n invariant: 'limit-alarms',\n subject: options.subject,\n pass: findings.length === 0,\n details: findings.length ? findings : [`${LIMIT_RESOURCES.join(', ')} warn at 80% and page at the limit`],\n }\n}\n\nexport {\n buildConformanceReport,\n formatConformanceReport,\n readDeploymentFacts,\n readVerdicts,\n type ConformanceReport,\n type DeploymentFacts,\n type InvariantReport,\n type InvariantStatus,\n type LaunchInvariantsConfig,\n} from './conformance.js'\n","/**\n * The conformance report: which launch invariants an app holds.\n *\n * `agent-app-invariants` runs the app's invariant tests with\n * `AGENT_APP_INVARIANTS_RESULTS` set, reads the verdicts they recorded, and\n * checks what it can see without the app's code: the crons each wrangler\n * environment configures, and whether the app has a D1 binding at all. An\n * invariant passes when at least one verdict was recorded for it and every\n * recorded verdict passed. Not applicable is accepted only where the\n * deployment shows it: no configured crons, or no D1 binding.\n */\nimport { existsSync, readFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { LAUNCH_INVARIANTS, type InvariantVerdict, type LaunchInvariantId } from './catalog.js'\nimport { parseWranglerCrons } from './schedule.js'\n\n/** `launch-invariants.config.mjs` (or `.json`) at the app root. */\nexport interface LaunchInvariantsConfig {\n /** The app's name in the report. */\n product: string\n /** The command that runs the app's invariant tests, e.g. `pnpm exec vitest run tests/launch-invariants.test.ts`. */\n test: string\n /** Wrangler configs, relative to the app root. Default: the first of wrangler.toml, wrangler.jsonc, wrangler.json. */\n wrangler?: readonly string[]\n /** Invariants the deployment shows do not apply, with the reason. */\n notApplicable?: Partial<Record<LaunchInvariantId, string>>\n}\n\n/** What the deployment declares, read from its wrangler configs. */\nexport interface DeploymentFacts {\n wranglerFiles: string[]\n configuredCrons: string[]\n hasD1: boolean\n}\n\nexport type InvariantStatus = 'pass' | 'fail' | 'known-fail' | 'not-applicable'\n\nexport interface InvariantReport {\n id: LaunchInvariantId\n title: string\n status: InvariantStatus\n /** Why it failed or does not apply; on a pass, what was proven. */\n lines: string[]\n verdicts: number\n}\n\nexport interface ConformanceReport {\n product: string\n pass: boolean\n passed: number\n total: number\n testExitCode: number | null\n /** Invariants failing with a known-failing marker: not holding, but not failing the run. */\n knownFailing: number\n invariants: InvariantReport[]\n facts: DeploymentFacts\n}\n\n/** Read the crons and D1 bindings the app's wrangler configs declare. */\nexport function readDeploymentFacts(appDir: string, files?: readonly string[]): DeploymentFacts {\n const candidates = files ?? ['wrangler.toml', 'wrangler.jsonc', 'wrangler.json'].filter((file) => existsSync(join(appDir, file))).slice(0, 1)\n const crons = new Set<string>()\n let hasD1 = false\n for (const file of candidates) {\n const source = readFileSync(join(appDir, file), 'utf8')\n const json = /\\.jsonc?$/.test(file)\n const parsed = parseWranglerCrons(source, json ? 'json' : 'toml')\n for (const cron of [...parsed.top, ...Object.values(parsed.envs).flat()]) crons.add(cron)\n if (json ? /\"d1_databases\"\\s*:\\s*\\[\\s*\\{/.test(source) : /^\\s*\\[\\[(?:env\\.[^.\\]]+\\.)?d1_databases\\]\\]/m.test(source)) hasD1 = true\n }\n return { wranglerFiles: [...candidates], configuredCrons: [...crons], hasD1 }\n}\n\nconst NOT_APPLICABLE_WHEN: Partial<Record<LaunchInvariantId, { holds: (facts: DeploymentFacts) => boolean; condition: string }>> = {\n 'isolated-jobs': { holds: (facts) => facts.configuredCrons.length === 0, condition: 'no configured crons' },\n 'memory-budget': { holds: (facts) => facts.configuredCrons.length === 0, condition: 'no configured crons' },\n 'bounded-reads': { holds: (facts) => !facts.hasD1, condition: 'no D1 binding' },\n 'auth-survives-d1-stall': { holds: (facts) => !facts.hasD1, condition: 'no D1 binding' },\n}\n\n/** Parse the results file the invariant tests appended to. */\nexport function readVerdicts(source: string): InvariantVerdict[] {\n return source.split('\\n').filter((line) => line.trim()).flatMap((line) => {\n try {\n return [JSON.parse(line) as InvariantVerdict]\n } catch {\n return []\n }\n })\n}\n\n/** Judge each invariant from the recorded verdicts and the deployment facts. */\nexport function buildConformanceReport(input: {\n config: LaunchInvariantsConfig\n facts: DeploymentFacts\n verdicts: readonly InvariantVerdict[]\n testExitCode: number | null\n}): ConformanceReport {\n const { config, facts } = input\n const invariants = LAUNCH_INVARIANTS.map((invariant): InvariantReport => {\n const verdicts = input.verdicts.filter((verdict) => verdict.invariant === invariant.id)\n const reason = config.notApplicable?.[invariant.id]\n if (reason !== undefined) {\n const rule = NOT_APPLICABLE_WHEN[invariant.id]\n if (rule?.holds(facts)) return { id: invariant.id, title: invariant.title, status: 'not-applicable', lines: [`${reason} (verified: ${rule.condition})`], verdicts: verdicts.length }\n return {\n id: invariant.id,\n title: invariant.title,\n status: 'fail',\n lines: [rule\n ? `declared not applicable (\"${reason}\"), but the deployment does not show ${rule.condition}`\n : `declared not applicable (\"${reason}\"), but this invariant applies to every agent app`],\n verdicts: verdicts.length,\n }\n }\n if (verdicts.length === 0) {\n return { id: invariant.id, title: invariant.title, status: 'fail', lines: [`no check recorded a verdict; ${invariant.rule}`], verdicts: 0 }\n }\n const failed = verdicts.filter((verdict) => !verdict.pass)\n const unknown = failed.filter((verdict) => !verdict.knownFailing)\n if (failed.length > 0 && unknown.length === 0) {\n const reasons = [...new Set(failed.map((verdict) => verdict.knownFailing!))]\n return {\n id: invariant.id,\n title: invariant.title,\n status: 'known-fail',\n lines: [...reasons.map((reason) => `known failing: ${reason}`), ...failed.flatMap((verdict) => verdict.details.map((detail) => `${verdict.subject}: ${detail}`))],\n verdicts: verdicts.length,\n }\n }\n const lines = failed.length\n ? failed.flatMap((verdict) => verdict.details.map((detail) => `${verdict.subject}: ${detail}`))\n : verdicts.map((verdict) => `${verdict.subject}: ${verdict.details[0] ?? 'passed'}`)\n if (invariant.id === 'isolated-jobs' && failed.length === 0) {\n const checked = new Set(verdicts.flatMap((verdict) => (verdict.data?.configuredCrons as string[] | undefined) ?? []))\n const unchecked = facts.configuredCrons.filter((cron) => !checked.has(cron))\n if (unchecked.length) {\n return {\n id: invariant.id,\n title: invariant.title,\n status: 'fail',\n lines: [`the check did not read every configured cron: ${unchecked.map((cron) => `\"${cron}\"`).join(', ')} (from ${facts.wranglerFiles.join(', ')})`],\n verdicts: verdicts.length,\n }\n }\n }\n return { id: invariant.id, title: invariant.title, status: failed.length ? 'fail' : 'pass', lines, verdicts: verdicts.length }\n })\n const passed = invariants.filter((invariant) => invariant.status === 'pass' || invariant.status === 'not-applicable').length\n const knownFailing = invariants.filter((invariant) => invariant.status === 'known-fail').length\n return {\n product: config.product,\n pass: passed + knownFailing === invariants.length && input.testExitCode === 0,\n passed,\n total: invariants.length,\n testExitCode: input.testExitCode,\n knownFailing,\n invariants,\n facts,\n }\n}\n\n/** The report as the CLI prints it. */\nexport function formatConformanceReport(report: ConformanceReport): string {\n const mark: Record<InvariantStatus, string> = { pass: 'PASS', fail: 'FAIL', 'known-fail': 'KNOWN', 'not-applicable': 'N/A ' }\n const lines = [`launch invariants — ${report.product}: ${report.passed} of ${report.total} hold${report.knownFailing ? `, ${report.knownFailing} known failing` : ''}`]\n for (const invariant of report.invariants) {\n lines.push(` ${mark[invariant.status]} ${invariant.id} — ${invariant.title}`)\n const shown = invariant.status === 'fail' || invariant.status === 'known-fail' ? invariant.lines : invariant.lines.slice(0, 3)\n for (const line of shown) lines.push(` ${line}`)\n if (invariant.status !== 'fail' && invariant.lines.length > shown.length) lines.push(` … ${invariant.lines.length - shown.length} more`)\n }\n if (report.testExitCode !== 0) lines.push(` the invariant test command exited ${report.testExitCode ?? 'by signal'}`)\n lines.push(report.passed === report.total && report.pass\n ? 'RESULT: launch-grade'\n : report.pass ? `RESULT: not launch-grade yet; ${report.knownFailing} known failing, no regressions` : 'RESULT: not launch-grade')\n return lines.join('\\n')\n}\n"],"mappings":"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","names":["reason"]}
|
|
1
|
+
{"version":3,"sources":["../src/launch-invariants/testing.ts","../src/launch-invariants/conformance.ts"],"sourcesContent":["/**\n * `/launch-invariants/testing` — the checks an app runs in its own test suite.\n *\n * Each check drives the app's real code through a small adapter and returns an\n * {@link InvariantVerdict}. {@link recordInvariant} fails the calling test with\n * the verdict's findings and, when `agent-app-invariants` runs the suite,\n * records the verdict so the conformance report can say which invariant\n * passed. The checks need no test runner: vitest, node:test and plain scripts\n * all work.\n *\n * Node-only (`node:v8`, `node:vm`, `node:fs`): import it from tests, never from\n * a Worker.\n */\nimport { appendFileSync } from 'node:fs'\nimport v8 from 'node:v8'\nimport vm from 'node:vm'\nimport type { AlertSink, TurnHealthAlert } from '../turn-health/sink.js'\nimport { describeVerdict, LAUNCH_BUDGETS, type InvariantVerdict } from './catalog.js'\nimport { LIMIT_RESOURCES, type LimitAlarms } from './limit-alarms.js'\nimport { isEdgeBlock, PASTED_CODE_SAMPLES } from './pasted-code.js'\n\nexport { describeVerdict } from './catalog.js'\n\n/** Set by `agent-app-invariants` to the file it reads verdicts from. */\nexport const INVARIANT_RESULTS_ENV = 'AGENT_APP_INVARIANTS_RESULTS'\n\nexport interface RecordInvariantOptions {\n /**\n * The app knows this check fails today: why, who owns the fix and the next\n * check. The failure is recorded (the report counts the invariant as not\n * holding) without failing the suite, so an app adopts the kit before every\n * invariant holds. Once the check passes, this fails the caller until the\n * marker is removed, so a fixed invariant cannot regress silently.\n */\n knownFailing?: string\n}\n\n/**\n * Record a verdict for the conformance report and fail the caller when it did\n * not pass. Returns the verdict on a pass.\n */\nexport function recordInvariant(verdict: InvariantVerdict, options: RecordInvariantOptions = {}): InvariantVerdict {\n const known = options.knownFailing?.trim()\n const recorded = known && !verdict.pass ? { ...verdict, knownFailing: known } : verdict\n const file = process.env[INVARIANT_RESULTS_ENV]\n if (file) appendFileSync(file, `${JSON.stringify({ ...recorded, at: new Date().toISOString() })}\\n`)\n if (known && verdict.pass) {\n throw new Error(`${describeVerdict(verdict)}\\n this invariant now holds; remove its knownFailing marker (\"${known}\") so it cannot regress silently`)\n }\n if (!verdict.pass && !known) throw new Error(describeVerdict(verdict))\n return recorded\n}\n\n/** Record several verdicts; fails after recording all of them. */\nexport function recordInvariants(verdicts: readonly InvariantVerdict[], options: RecordInvariantOptions = {}): InvariantVerdict[] {\n const failures: string[] = []\n for (const verdict of verdicts) {\n try {\n recordInvariant(verdict, options)\n } catch (error) {\n failures.push(error instanceof Error ? error.message : String(error))\n }\n }\n if (failures.length) throw new Error(failures.join('\\n'))\n return [...verdicts]\n}\n\n// ── heap ─────────────────────────────────────────────────────────────────────\n\nlet gc: (() => void) | undefined\n\n/** Run a full garbage collection, so a heap reading counts only live objects. */\nexport function forceGc(): void {\n if (!gc) {\n v8.setFlagsFromString('--expose_gc')\n gc = vm.runInNewContext('gc') as () => void\n }\n gc()\n}\n\n// ── measured D1 ──────────────────────────────────────────────────────────────\n\nconst MB = 1024 * 1024\n\n/** What a measured binding saw while one job ran. */\nexport interface D1Reads {\n queries: number\n maxQueryBytes: number\n /** The query that returned the most bytes. */\n largestSql: string\n peakHeapBytes: number\n /** The query running when the heap peaked: where a job holds what it read. */\n peakSql: string\n}\n\nfunction emptyReads(): D1Reads {\n return { queries: 0, maxQueryBytes: 0, largestSql: '', peakHeapBytes: 0, peakSql: '' }\n}\n\nexport interface MeasureD1Options {\n /** Sample the live heap at all. Default true. */\n sampleHeap?: boolean\n /** Sample after every response at least this large. Default 64 KB. */\n sampleAfterBytes?: number\n /** Also sample every this-many queries, so what a job keeps from small reads still shows. Default 50. */\n sampleEvery?: number\n}\n\n/**\n * The D1 binding with every response measured (serialized bytes) and the live\n * heap sampled after a forced collection. A collection costs about 50 ms on a\n * test heap and a job runs hundreds of small queries, so the heap is sampled\n * after each large read and every 50th query (GTM's sweeps budget took 170 s\n * sampling around every query). Works over Cloudflare D1 and the sqlite-backed\n * D1 shims apps test with. A database engine that keeps its pages off the JS\n * heap (node:sqlite, better-sqlite3) leaves heap growth to the job's own objects.\n */\nexport function measureD1<D extends object>(d1: D, reads: D1Reads = emptyReads(), options: MeasureD1Options = {}): { d1: D; reads: D1Reads } {\n const sampleHeap = options.sampleHeap ?? true\n const sampleAfterBytes = options.sampleAfterBytes ?? 64 * 1024\n const sampleEvery = Math.max(1, options.sampleEvery ?? 50)\n const sample = (sql: string) => {\n if (!sampleHeap) return\n forceGc()\n const heap = process.memoryUsage().heapUsed\n if (heap > reads.peakHeapBytes) {\n reads.peakHeapBytes = heap\n reads.peakSql = sql\n }\n }\n const measure = (sql: string, result: unknown) => {\n reads.queries += 1\n const bytes = Buffer.byteLength(JSON.stringify(result) ?? '')\n if (bytes > reads.maxQueryBytes) {\n reads.maxQueryBytes = bytes\n reads.largestSql = sql\n }\n if (bytes >= sampleAfterBytes || reads.queries % sampleEvery === 0) sample(sql)\n }\n const statement = (target: object, sql: string): object => new Proxy(target, {\n get(stmt, key) {\n const value = Reflect.get(stmt, key) as unknown\n if (typeof value !== 'function') return value\n if (key === 'bind') return (...params: unknown[]) => statement(value.apply(stmt, params) as object, sql)\n if (key === 'all' || key === 'raw' || key === 'first' || key === 'run') {\n return async (...args: unknown[]) => {\n const result: unknown = await value.apply(stmt, args)\n measure(sql, result)\n return result\n }\n }\n return value.bind(stmt)\n },\n })\n const unwrap = new WeakMap<object, object>()\n const proxy = new Proxy(d1, {\n get(binding, key) {\n const value = Reflect.get(binding, key) as unknown\n if (key === 'prepare') {\n return (sql: string) => {\n const stmt = (value as (sql: string) => object).call(binding, sql)\n const wrapped = statement(stmt, sql.replace(/\\s+/g, ' ').trim().slice(0, 200))\n unwrap.set(wrapped, stmt)\n return wrapped\n }\n }\n if (key === 'batch') {\n return async (statements: object[]) => {\n const result: unknown = await (value as (s: object[]) => Promise<unknown>).call(binding, statements.map((stmt) => unwrap.get(stmt) ?? stmt))\n measure('batch', result)\n return result\n }\n }\n return typeof value === 'function' ? value.bind(binding) : value\n },\n })\n return { d1: proxy, reads }\n}\n\n/** One scheduled job (or report route) as the budget check runs it. */\nexport interface BudgetedJob {\n name: string\n run: () => Promise<unknown>\n /** False for a job that reads nothing from D1 (it then passes bounded reads vacuously). Default true. */\n readsD1?: boolean\n}\n\nexport interface JobMeasurement {\n name: string\n reads: D1Reads\n heapGrowthBytes: number\n error?: string\n}\n\nexport interface ScheduledJobBudgetOptions {\n /** The app's D1 over its day-sized fixture. */\n d1: object\n /** Point the app's database accessor at the measured binding (`setD1`, `setDatabase(drizzle(...))`). */\n install: (measured: object) => void\n jobs: readonly BudgetedJob[]\n /** Every job the app schedules, by the names {@link jobs} uses. Each must be measured. */\n scheduled: readonly string[]\n maxQueryBytes?: number\n maxHeapGrowthBytes?: number\n /** Silence the jobs' own logging while they run. Default true. */\n quiet?: boolean\n}\n\n/** Run one job against a measured binding. */\nexport async function measureJob(job: BudgetedJob, d1: object, install: (measured: object) => void, quiet = true): Promise<JobMeasurement> {\n const { d1: measured, reads } = measureD1(d1)\n install(measured)\n forceGc()\n const baseline = process.memoryUsage().heapUsed\n reads.peakHeapBytes = baseline\n const restore = quiet ? silenceConsole() : () => {}\n let error: string | undefined\n try {\n await job.run()\n } catch (caught) {\n error = caught instanceof Error ? caught.message : String(caught)\n } finally {\n restore()\n }\n forceGc()\n const after = process.memoryUsage().heapUsed\n if (after > reads.peakHeapBytes) {\n reads.peakHeapBytes = after\n reads.peakSql = reads.peakSql ? `${reads.peakSql} (still held when the job returned)` : '(after the job returned)'\n }\n return { name: job.name, reads, heapGrowthBytes: reads.peakHeapBytes - baseline, ...(error ? { error } : {}) }\n}\n\nfunction silenceConsole(): () => void {\n const saved = { log: console.log, info: console.info, warn: console.warn, error: console.error }\n console.log = console.info = console.warn = console.error = () => {}\n return () => Object.assign(console, saved)\n}\n\n/**\n * Bounded reads and the memory budget for every scheduled job, on the app's\n * day-sized fixture. Returns a bounded-reads and a memory-budget verdict per\n * job, and a coverage verdict naming any scheduled job that was not run.\n */\nexport async function checkScheduledJobBudgets(options: ScheduledJobBudgetOptions): Promise<InvariantVerdict[]> {\n const maxQueryBytes = options.maxQueryBytes ?? LAUNCH_BUDGETS.queryBytes\n const maxHeap = options.maxHeapGrowthBytes ?? LAUNCH_BUDGETS.heapGrowthBytes\n const verdicts: InvariantVerdict[] = []\n const measured = new Set<string>()\n for (const job of options.jobs) {\n const m = await measureJob(job, options.d1, options.install, options.quiet ?? true)\n measured.add(job.name)\n const failed = m.error ? [`the job threw on the fixture: ${m.error}`] : []\n const readFindings = [...failed]\n if ((job.readsD1 ?? true) && m.reads.queries === 0) readFindings.push('made no D1 query on the fixture; the fixture does not reach it, or it reads through another binding')\n if (m.reads.maxQueryBytes > maxQueryBytes) {\n readFindings.push(`one query returned ${(m.reads.maxQueryBytes / MB).toFixed(1)} MB (the most is ${(maxQueryBytes / MB).toFixed(0)} MB): ${m.reads.largestSql}`)\n }\n verdicts.push({\n invariant: 'bounded-reads',\n subject: job.name,\n pass: readFindings.length === 0,\n details: readFindings.length ? readFindings : [`${m.reads.queries} queries, largest ${(m.reads.maxQueryBytes / MB).toFixed(2)} MB`],\n data: { queries: m.reads.queries, maxQueryBytes: m.reads.maxQueryBytes, largestSql: m.reads.largestSql },\n })\n const heapFindings = [...failed]\n if (m.heapGrowthBytes > maxHeap) {\n heapFindings.push(`the live heap grew ${(m.heapGrowthBytes / MB).toFixed(1)} MB (the most is ${(maxHeap / MB).toFixed(0)} MB), at its peak around: ${m.reads.peakSql}`)\n }\n verdicts.push({\n invariant: 'memory-budget',\n subject: job.name,\n pass: heapFindings.length === 0,\n details: heapFindings.length ? heapFindings : [`heap grew ${(m.heapGrowthBytes / MB).toFixed(1)} MB`],\n data: { heapGrowthBytes: m.heapGrowthBytes, peakSql: m.reads.peakSql },\n })\n }\n const missing = options.scheduled.filter((name) => !measured.has(name))\n verdicts.push({\n invariant: 'memory-budget',\n subject: 'coverage',\n pass: missing.length === 0 && options.scheduled.length > 0,\n details: missing.length\n ? missing.map((name) => `${name} is scheduled but not run on the fixture`)\n : options.scheduled.length === 0\n ? ['no scheduled jobs were named; pass every job the app schedules']\n : [`all ${options.scheduled.length} scheduled jobs ran on the fixture`],\n data: { scheduled: [...options.scheduled], measured: [...measured] },\n })\n return verdicts\n}\n\n// ── a D1 that stalls ─────────────────────────────────────────────────────────\n\nexport type StallMode = 'ok' | 'error' | 'hang'\n\n/** The D1 binding with a switch that makes every query fail or never answer, as an overloaded D1 did. */\nexport function stallingD1<D extends object>(d1: D): { d1: D; stall(mode: StallMode): void; readonly mode: StallMode } {\n let mode: StallMode = 'ok'\n const fail = (): Promise<never> => mode === 'hang'\n ? new Promise<never>(() => {})\n : Promise.reject(new Error('D1_ERROR: Network connection lost.'))\n const unwrapped = new WeakMap<object, object>()\n const statement = (target: object): object => {\n const wrapped = new Proxy(target, {\n get(stmt, key) {\n const value = Reflect.get(stmt, key) as unknown\n if (typeof value !== 'function') return value\n if (key === 'bind') return (...params: unknown[]) => statement(value.apply(stmt, params) as object)\n if (key === 'all' || key === 'raw' || key === 'first' || key === 'run') {\n return (...args: unknown[]) => (mode === 'ok' ? value.apply(stmt, args) : fail())\n }\n return value.bind(stmt)\n },\n })\n unwrapped.set(wrapped, target)\n return wrapped\n }\n const proxy = new Proxy(d1, {\n get(binding, key) {\n const value = Reflect.get(binding, key) as unknown\n if (key === 'prepare') return (sql: string) => statement((value as (sql: string) => object).call(binding, sql))\n if (key === 'batch') {\n return (statements: object[]) => (mode === 'ok'\n ? (value as (s: object[]) => unknown).call(binding, statements.map((stmt) => unwrapped.get(stmt) ?? stmt))\n : fail())\n }\n return typeof value === 'function' ? value.bind(binding) : value\n },\n })\n return {\n d1: proxy,\n stall(next) {\n mode = next\n },\n get mode() {\n return mode\n },\n }\n}\n\n/** Move `Date.now` forward for the duration of a check; returns the restore. */\nfunction shiftableClock(): { advance(ms: number): void; restore(): void } {\n const real = Date.now\n let offset = 0\n Date.now = () => real() + offset\n return {\n advance(ms) {\n offset += ms\n },\n restore() {\n Date.now = real\n },\n }\n}\n\n// ── auth survives a D1 stall ─────────────────────────────────────────────────\n\nexport interface AuthStallOptions {\n /** A label for the lookup: \"session\", \"API key\". */\n subject: string\n /** Three distinct valid credentials the app's store accepts. */\n credentials: { cached: string; revoked: string; aged: string }\n /** The app's real lookup: truthy when the credential is accepted. A throw or a falsy value is a refusal. */\n verify: (credential: string) => Promise<unknown>\n /** The app's real revoke path (revoke the key, sign the session out). */\n revoke: (credential: string) => Promise<void>\n /** Switch the D1 the lookup reads. */\n stall: (mode: StallMode) => void\n /** The keys the app's auth caches hold. */\n cacheKeys: () => string[]\n /** Include a stall that never answers (costs one lookup deadline, 2.5 s). Default true. */\n hang?: boolean\n}\n\n/**\n * A verified credential keeps working while D1 errors or stalls; it is refused\n * once revoked, and once its last verification is more than 60 s old; and the\n * cache holds SHA-256 keys, not secrets. Uses the app's real lookup, revoke\n * path and D1, with `Date.now` moved forward to age the cache.\n */\nexport async function checkAuthSurvivesStall(options: AuthStallOptions): Promise<InvariantVerdict> {\n const findings: string[] = []\n const accepted = async (credential: string): Promise<boolean> => {\n try {\n return Boolean(await options.verify(credential))\n } catch {\n return false\n }\n }\n const { cached, revoked, aged } = options.credentials\n const clock = shiftableClock()\n try {\n options.stall('ok')\n for (const credential of [cached, revoked, aged]) {\n if (!(await accepted(credential))) findings.push(`a valid credential was refused while D1 was healthy (${options.subject})`)\n }\n for (const key of options.cacheKeys()) {\n if (!/^[0-9a-f]{64}$/.test(key) || [cached, revoked, aged].some((credential) => key.includes(credential))) {\n findings.push(`the auth cache holds a key that is not a SHA-256 digest: ${key.slice(0, 12)}…`)\n }\n }\n await options.revoke(revoked)\n if (await accepted(revoked)) findings.push('a revoked credential was accepted while D1 was healthy')\n\n options.stall('error')\n if (!(await accepted(cached))) findings.push('a credential verified moments before was refused while D1 errored')\n if (await accepted(revoked)) findings.push('a revoked credential was accepted from the cache while D1 errored')\n if (options.hang ?? true) {\n options.stall('hang')\n if (!(await accepted(cached))) findings.push('a credential verified moments before was refused while D1 did not answer')\n }\n\n clock.advance(LAUNCH_BUDGETS.authCacheTtlMs + 1_000)\n options.stall('error')\n if (await accepted(aged)) findings.push(`a credential last verified more than ${LAUNCH_BUDGETS.authCacheTtlMs / 1000} s ago was accepted from the cache while D1 errored`)\n } finally {\n options.stall('ok')\n clock.restore()\n }\n return {\n invariant: 'auth-survives-d1-stall',\n subject: options.subject,\n pass: findings.length === 0,\n details: findings.length ? findings : [\n 'served while D1 errored and while it stalled; refused after revoke and after the 60 s cache window; keys are SHA-256',\n ],\n }\n}\n\n// ── authorization before any stream ──────────────────────────────────────────\n\nexport interface AuthorizeBeforeStreamOptions {\n subject: string\n /** The app's real route handler, as its router mounts it. */\n handle: (request: Request) => Promise<Response>\n /** Requests the route must refuse: no session, another workspace's thread, a revoked key. */\n refused: ReadonlyArray<{ name: string; request: () => Request; status?: readonly number[] }>\n /** Product state the route touched (rows written, producers started); empty after every refusal. */\n touched?: () => readonly string[]\n}\n\nconst STREAM_TYPES = /event-stream|ndjson|x-turn-progress/i\n\n/** Each refused request gets a plain 4xx, opens no stream and touches nothing. */\nexport async function checkAuthorizeBeforeStream(options: AuthorizeBeforeStreamOptions): Promise<InvariantVerdict> {\n const findings: string[] = []\n for (const probe of options.refused) {\n const before = options.touched?.().length ?? 0\n const response = await options.handle(probe.request())\n const allowed = probe.status ?? [401, 403, 404]\n if (!allowed.includes(response.status)) findings.push(`${probe.name}: answered ${response.status}, expected ${allowed.join(' or ')}`)\n const type = response.headers.get('content-type') ?? ''\n if (STREAM_TYPES.test(type)) findings.push(`${probe.name}: opened a ${type} stream before refusing`)\n for (const [name] of response.headers) {\n if (/turn-progress|x-turn-id/i.test(name)) findings.push(`${probe.name}: refusal carries turn header ${name}`)\n }\n await response.body?.cancel().catch(() => undefined)\n const after = options.touched?.() ?? []\n if (after.length > before) findings.push(`${probe.name}: touched ${after.slice(before).join(', ')} before refusing`)\n }\n return {\n invariant: 'authorize-before-stream',\n subject: options.subject,\n pass: findings.length === 0 && options.refused.length > 0,\n details: findings.length ? findings : options.refused.length\n ? [`${options.refused.length} refused requests answered plainly with nothing touched`]\n : ['no refused requests were probed'],\n }\n}\n\n// ── pasted code reaches the chat ─────────────────────────────────────────────\n\nexport interface ChatAcceptsCodeOptions {\n subject: string\n /** The app's real chat route handler, as its router mounts it. */\n handle: (request: Request) => Promise<Response>\n /** An authenticated chat request carrying `text` as the message. */\n request: (text: string) => Request\n /** Statuses the route answers an accepted message with. Default any 2xx. */\n accepted?: (status: number) => boolean\n samples?: readonly { name: string; text: string }[]\n}\n\n/** Each pasted-code sample gets the route's normal response, never a refusal or an edge block. Cancels any stream it opens. */\nexport async function checkChatAcceptsCode(options: ChatAcceptsCodeOptions): Promise<InvariantVerdict> {\n const accepted = options.accepted ?? ((status: number) => status >= 200 && status < 300)\n const samples = options.samples ?? PASTED_CODE_SAMPLES\n const findings: string[] = []\n for (const sample of samples) {\n const response = await options.handle(options.request(sample.text))\n if (await isEdgeBlock(response)) findings.push(`${sample.name}: an edge rule blocked it (${response.status})`)\n else if (!accepted(response.status)) {\n const body = (await response.clone().text().catch(() => '')).slice(0, 160)\n findings.push(`${sample.name}: answered ${response.status}${body ? `: ${body}` : ''}`)\n }\n await response.body?.cancel().catch(() => undefined)\n }\n return {\n invariant: 'pasted-code-reaches-chat',\n subject: options.subject,\n pass: findings.length === 0,\n details: findings.length ? findings : [`${samples.length} pasted-code messages reached the chat and got its normal response`],\n }\n}\n\n// ── coalesced turn events ────────────────────────────────────────────────────\n\n/** One event of the recorded long turn and the turn-relative time it arrived. */\nexport interface TimedTurnEvent {\n atMs: number\n event: Record<string, unknown>\n}\n\n/**\n * The long turn GTM recorded on 2026-10-10, reproduced in shape: a reasoning\n * model streaming three events per token (the harness's raw part update, a\n * model-processing heartbeat and the delta), with tool calls between runs of\n * reasoning and text. 59,659 events over about ten minutes, as that turn wrote\n * them before coalescing.\n */\nexport function recordedLongTurn(options: { tokens?: number; toolCalls?: number; tokenMs?: number } = {}): TimedTurnEvent[] {\n const tokens = options.tokens ?? 19_866\n const toolCalls = options.toolCalls ?? 30\n const tokenMs = options.tokenMs ?? 30\n const events: TimedTurnEvent[] = [{ atMs: 0, event: { type: 'turn', turnId: 'recorded-long-turn' } }]\n const toolEvery = Math.max(1, Math.floor(tokens / (toolCalls + 1)))\n let tool = 0\n let part = 0\n for (let index = 0; index < tokens; index += 1) {\n const atMs = (index + 1) * tokenMs\n if (index > 0 && index % toolEvery === 0 && tool < toolCalls) {\n tool += 1\n part += 1\n events.push({ atMs, event: { type: 'tool_call', id: `call_${tool}`, name: 'bash', input: { command: 'ls' } } })\n events.push({ atMs: atMs + 1, event: { type: 'tool_result', id: `call_${tool}`, output: 'ok' } })\n }\n const kind = part % 2 === 0 ? 'reasoning' : 'text'\n const partId = `prt_${part}`\n events.push({ atMs, event: { type: 'raw', data: { type: 'raw', backend: 'opencode', event: { type: kind, timestamp: atMs, part: { id: partId, type: kind, metadata: { at: atMs } } } } } })\n events.push({ atMs, event: { type: 'model-processing', data: { elapsedMs: atMs } } })\n events.push({ atMs, event: { type: kind, text: `t${index} ` } })\n }\n return events\n}\n\nexport interface CoalescedTurnOptions {\n subject: string\n /**\n * Start persisting one turn through the app's real turn persistence (its turn\n * store and buffer, as its chat route builds them). `detach` hands the turn\n * to a durable owner the way the route's completion handoff does; omit it when\n * the app has no detached lane.\n */\n open: (turnId: string) => Promise<{\n onEvent(event: unknown): Promise<void>\n detach?(): Promise<void>\n done(): Promise<void>\n }>\n /** Stored events for the turn. */\n count: (turnId: string) => Promise<number>\n maxEvents?: number\n fixture?: readonly TimedTurnEvent[]\n}\n\n/** The recorded long turn stores at most 5,000 events, attached and after a handoff. */\nexport async function checkCoalescedTurn(options: CoalescedTurnOptions): Promise<InvariantVerdict> {\n const maxEvents = options.maxEvents ?? LAUNCH_BUDGETS.turnEvents\n const fixture = options.fixture ?? recordedLongTurn()\n const findings: string[] = []\n const stored: Record<string, number> = {}\n const real = Date.now\n for (const lane of ['attached', 'detached'] as const) {\n const turnId = `invariant-long-turn-${lane}`\n const start = real()\n let clock = start\n Date.now = () => clock\n try {\n const tap = await options.open(turnId)\n if (lane === 'detached' && !tap.detach) continue\n for (const [index, item] of fixture.entries()) {\n clock = start + item.atMs\n await tap.onEvent(item.event)\n if (lane === 'detached' && index === 0) await tap.detach!()\n }\n clock = start + (fixture.at(-1)?.atMs ?? 0) + 1\n await tap.done()\n stored[lane] = await options.count(turnId)\n } finally {\n Date.now = real\n }\n if (stored[lane] === 0) findings.push(`${lane}: the turn stored no events; the check did not reach the app's turn store`)\n if (stored[lane]! > maxEvents) findings.push(`${lane}: the recorded long turn (${fixture.length.toLocaleString('en-US')} events) stored ${stored[lane]!.toLocaleString('en-US')}; the most is ${maxEvents.toLocaleString('en-US')}`)\n }\n return {\n invariant: 'coalesced-events',\n subject: options.subject,\n pass: findings.length === 0,\n details: findings.length ? findings : Object.entries(stored).map(([lane, count]) => `${lane}: ${fixture.length.toLocaleString('en-US')} events stored as ${count.toLocaleString('en-US')}`),\n data: { fixtureEvents: fixture.length, stored },\n }\n}\n\n// ── limit alarms ─────────────────────────────────────────────────────────────\n\n/** Hard platform limits a declared budget may not exceed. */\nexport const PLATFORM_LIMITS = {\n 'worker-memory': LAUNCH_BUDGETS.workerMemoryBytes,\n 'snapshot-count': LAUNCH_BUDGETS.snapshotCount,\n 'key-rate': LAUNCH_BUDGETS.keyRatePerMinute,\n} as const\n\nexport interface LimitAlarmCheckOptions {\n subject: string\n /** Build the app's real limit alarms (its budgets and wiring) over a capturing sink. */\n create: (sink: AlertSink) => LimitAlarms | Promise<LimitAlarms>\n}\n\n/** Every resource alarms at 80% and not below, escalates at the limit, and no budget exceeds the platform's. */\nexport async function checkLimitAlarms(options: LimitAlarmCheckOptions): Promise<InvariantVerdict> {\n const findings: string[] = []\n for (const resource of LIMIT_RESOURCES) {\n const delivered: TurnHealthAlert[] = []\n const alarms = await options.create({ deliver: async (alert) => { delivered.push(alert) } })\n const limit = alarms.budgets[resource]\n const platform = (PLATFORM_LIMITS as Partial<Record<string, number>>)[resource]\n if (platform !== undefined && limit > platform) findings.push(`${resource}: the declared limit ${limit} is above the platform's ${platform}`)\n await alarms.observe(resource, limit * 0.79)\n if (delivered.length) findings.push(`${resource}: alarmed at 79% of its limit`)\n await alarms.observe(resource, limit * LAUNCH_BUDGETS.alarmRatio)\n if (delivered.length !== 1 || delivered[0]?.severity !== 'warning') findings.push(`${resource}: did not warn at 80% of its limit`)\n await alarms.observe(resource, limit)\n if (delivered.length !== 2 || delivered[1]?.severity !== 'critical') findings.push(`${resource}: did not raise a critical alert at its limit`)\n }\n return {\n invariant: 'limit-alarms',\n subject: options.subject,\n pass: findings.length === 0,\n details: findings.length ? findings : [`${LIMIT_RESOURCES.join(', ')} warn at 80% and page at the limit`],\n }\n}\n\nexport {\n buildConformanceReport,\n formatConformanceReport,\n readDeploymentFacts,\n readVerdicts,\n type ConformanceReport,\n type DeploymentFacts,\n type InvariantReport,\n type InvariantStatus,\n type LaunchInvariantsConfig,\n} from './conformance.js'\n","/**\n * The conformance report: which launch invariants an app holds.\n *\n * `agent-app-invariants` runs the app's invariant tests with\n * `AGENT_APP_INVARIANTS_RESULTS` set, reads the verdicts they recorded, and\n * checks what it can see without the app's code: the crons each wrangler\n * environment configures, and whether the app has a D1 binding at all. An\n * invariant passes when at least one verdict was recorded for it and every\n * recorded verdict passed. Not applicable is accepted only where the\n * deployment shows it: no configured crons, or no D1 binding.\n */\nimport { existsSync, readFileSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { LAUNCH_INVARIANTS, type InvariantVerdict, type LaunchInvariantId } from './catalog.js'\nimport { parseWranglerCrons } from './schedule.js'\n\n/** `launch-invariants.config.mjs` (or `.json`) at the app root. */\nexport interface LaunchInvariantsConfig {\n /** The app's name in the report. */\n product: string\n /** The command that runs the app's invariant tests, e.g. `pnpm exec vitest run tests/launch-invariants.test.ts`. */\n test: string\n /** Wrangler configs, relative to the app root. Default: the first of wrangler.toml, wrangler.jsonc, wrangler.json. */\n wrangler?: readonly string[]\n /** Invariants the deployment shows do not apply, with the reason. */\n notApplicable?: Partial<Record<LaunchInvariantId, string>>\n}\n\n/** What the deployment declares, read from its wrangler configs. */\nexport interface DeploymentFacts {\n wranglerFiles: string[]\n configuredCrons: string[]\n hasD1: boolean\n}\n\nexport type InvariantStatus = 'pass' | 'fail' | 'known-fail' | 'not-applicable'\n\nexport interface InvariantReport {\n id: LaunchInvariantId\n title: string\n status: InvariantStatus\n /** Why it failed or does not apply; on a pass, what was proven. */\n lines: string[]\n verdicts: number\n}\n\nexport interface ConformanceReport {\n product: string\n pass: boolean\n passed: number\n total: number\n testExitCode: number | null\n /** Invariants failing with a known-failing marker: not holding, but not failing the run. */\n knownFailing: number\n invariants: InvariantReport[]\n facts: DeploymentFacts\n}\n\n/** Read the crons and D1 bindings the app's wrangler configs declare. */\nexport function readDeploymentFacts(appDir: string, files?: readonly string[]): DeploymentFacts {\n const candidates = files ?? ['wrangler.toml', 'wrangler.jsonc', 'wrangler.json'].filter((file) => existsSync(join(appDir, file))).slice(0, 1)\n const crons = new Set<string>()\n let hasD1 = false\n for (const file of candidates) {\n const source = readFileSync(join(appDir, file), 'utf8')\n const json = /\\.jsonc?$/.test(file)\n const parsed = parseWranglerCrons(source, json ? 'json' : 'toml')\n for (const cron of [...parsed.top, ...Object.values(parsed.envs).flat()]) crons.add(cron)\n if (json ? /\"d1_databases\"\\s*:\\s*\\[\\s*\\{/.test(source) : /^\\s*\\[\\[(?:env\\.[^.\\]]+\\.)?d1_databases\\]\\]/m.test(source)) hasD1 = true\n }\n return { wranglerFiles: [...candidates], configuredCrons: [...crons], hasD1 }\n}\n\nconst NOT_APPLICABLE_WHEN: Partial<Record<LaunchInvariantId, { holds: (facts: DeploymentFacts) => boolean; condition: string }>> = {\n 'isolated-jobs': { holds: (facts) => facts.configuredCrons.length === 0, condition: 'no configured crons' },\n 'memory-budget': { holds: (facts) => facts.configuredCrons.length === 0, condition: 'no configured crons' },\n 'bounded-reads': { holds: (facts) => !facts.hasD1, condition: 'no D1 binding' },\n 'auth-survives-d1-stall': { holds: (facts) => !facts.hasD1, condition: 'no D1 binding' },\n}\n\n/** Parse the results file the invariant tests appended to. */\nexport function readVerdicts(source: string): InvariantVerdict[] {\n return source.split('\\n').filter((line) => line.trim()).flatMap((line) => {\n try {\n return [JSON.parse(line) as InvariantVerdict]\n } catch {\n return []\n }\n })\n}\n\n/** Judge each invariant from the recorded verdicts and the deployment facts. */\nexport function buildConformanceReport(input: {\n config: LaunchInvariantsConfig\n facts: DeploymentFacts\n verdicts: readonly InvariantVerdict[]\n testExitCode: number | null\n}): ConformanceReport {\n const { config, facts } = input\n const invariants = LAUNCH_INVARIANTS.map((invariant): InvariantReport => {\n const verdicts = input.verdicts.filter((verdict) => verdict.invariant === invariant.id)\n const reason = config.notApplicable?.[invariant.id]\n if (reason !== undefined) {\n const rule = NOT_APPLICABLE_WHEN[invariant.id]\n if (rule?.holds(facts)) return { id: invariant.id, title: invariant.title, status: 'not-applicable', lines: [`${reason} (verified: ${rule.condition})`], verdicts: verdicts.length }\n return {\n id: invariant.id,\n title: invariant.title,\n status: 'fail',\n lines: [rule\n ? `declared not applicable (\"${reason}\"), but the deployment does not show ${rule.condition}`\n : `declared not applicable (\"${reason}\"), but this invariant applies to every agent app`],\n verdicts: verdicts.length,\n }\n }\n if (verdicts.length === 0) {\n return { id: invariant.id, title: invariant.title, status: 'fail', lines: [`no check recorded a verdict; ${invariant.rule}`], verdicts: 0 }\n }\n const failed = verdicts.filter((verdict) => !verdict.pass)\n const unknown = failed.filter((verdict) => !verdict.knownFailing)\n if (failed.length > 0 && unknown.length === 0) {\n const reasons = [...new Set(failed.map((verdict) => verdict.knownFailing!))]\n return {\n id: invariant.id,\n title: invariant.title,\n status: 'known-fail',\n lines: [...reasons.map((reason) => `known failing: ${reason}`), ...failed.flatMap((verdict) => verdict.details.map((detail) => `${verdict.subject}: ${detail}`))],\n verdicts: verdicts.length,\n }\n }\n const lines = failed.length\n ? failed.flatMap((verdict) => verdict.details.map((detail) => `${verdict.subject}: ${detail}`))\n : verdicts.map((verdict) => `${verdict.subject}: ${verdict.details[0] ?? 'passed'}`)\n if (invariant.id === 'isolated-jobs' && failed.length === 0) {\n const checked = new Set(verdicts.flatMap((verdict) => (verdict.data?.configuredCrons as string[] | undefined) ?? []))\n const unchecked = facts.configuredCrons.filter((cron) => !checked.has(cron))\n if (unchecked.length) {\n return {\n id: invariant.id,\n title: invariant.title,\n status: 'fail',\n lines: [`the check did not read every configured cron: ${unchecked.map((cron) => `\"${cron}\"`).join(', ')} (from ${facts.wranglerFiles.join(', ')})`],\n verdicts: verdicts.length,\n }\n }\n }\n return { id: invariant.id, title: invariant.title, status: failed.length ? 'fail' : 'pass', lines, verdicts: verdicts.length }\n })\n const passed = invariants.filter((invariant) => invariant.status === 'pass' || invariant.status === 'not-applicable').length\n const knownFailing = invariants.filter((invariant) => invariant.status === 'known-fail').length\n return {\n product: config.product,\n pass: passed + knownFailing === invariants.length && input.testExitCode === 0,\n passed,\n total: invariants.length,\n testExitCode: input.testExitCode,\n knownFailing,\n invariants,\n facts,\n }\n}\n\n/** The report as the CLI prints it. */\nexport function formatConformanceReport(report: ConformanceReport): string {\n const mark: Record<InvariantStatus, string> = { pass: 'PASS', fail: 'FAIL', 'known-fail': 'KNOWN', 'not-applicable': 'N/A ' }\n const lines = [`launch invariants — ${report.product}: ${report.passed} of ${report.total} hold${report.knownFailing ? `, ${report.knownFailing} known failing` : ''}`]\n for (const invariant of report.invariants) {\n lines.push(` ${mark[invariant.status]} ${invariant.id} — ${invariant.title}`)\n const shown = invariant.status === 'fail' || invariant.status === 'known-fail' ? invariant.lines : invariant.lines.slice(0, 3)\n for (const line of shown) lines.push(` ${line}`)\n if (invariant.status !== 'fail' && invariant.lines.length > shown.length) lines.push(` … ${invariant.lines.length - shown.length} more`)\n }\n if (report.testExitCode !== 0) lines.push(` the invariant test command exited ${report.testExitCode ?? 'by signal'}`)\n lines.push(report.passed === report.total && report.pass\n ? 'RESULT: launch-grade'\n : report.pass ? `RESULT: not launch-grade yet; ${report.knownFailing} known failing, no regressions` : 'RESULT: not launch-grade')\n return lines.join('\\n')\n}\n"],"mappings":"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","names":["reason"]}
|
|
@@ -23,6 +23,22 @@ export declare const LAUNCH_BUDGETS: {
|
|
|
23
23
|
readonly authCacheTtlMs: 60000;
|
|
24
24
|
/** A platform-limit alarm fires at this share of the limit. */
|
|
25
25
|
readonly alarmRatio: 0.8;
|
|
26
|
+
/** A Worker isolate's memory. */
|
|
27
|
+
readonly workerMemoryBytes: number;
|
|
28
|
+
/** Rows one D1 query may read before an alarm; a sized read stays far below it (GTM's 1.63 M-event scan overloaded D1). */
|
|
29
|
+
readonly d1RowsPerQuery: 250000;
|
|
30
|
+
/** A workspace box's disk: the Sandbox SDK's default tier (10 GB). */
|
|
31
|
+
readonly sandboxDiskBytes: number;
|
|
32
|
+
/** Platform snapshots per box; the cap GTM hit on 2026-10-10. */
|
|
33
|
+
readonly snapshotCount: 25;
|
|
34
|
+
/** Operator key requests per minute. */
|
|
35
|
+
readonly keyRatePerMinute: 60;
|
|
36
|
+
/** Hours one resource's limit alarm waits before it repeats. */
|
|
37
|
+
readonly alarmRepeatHours: 6;
|
|
38
|
+
/** Cloudflare's gzip limit on a Worker's Total Upload on the paid plan, in KiB; Legal deployed 9,659 KiB (94%) on 2026-10-19. */
|
|
39
|
+
readonly workerUploadGzipKiB: number;
|
|
40
|
+
/** Cloudflare's raw limit on a Worker's Total Upload, in KiB. */
|
|
41
|
+
readonly workerUploadRawKiB: number;
|
|
26
42
|
};
|
|
27
43
|
export type LaunchInvariantId = 'bounded-reads' | 'memory-budget' | 'isolated-jobs' | 'honest-settlement' | 'coalesced-events' | 'auth-survives-d1-stall' | 'authorize-before-stream' | 'limit-alarms' | 'pasted-code-reaches-chat';
|
|
28
44
|
export interface LaunchInvariant {
|
|
@@ -5,8 +5,11 @@ import {
|
|
|
5
5
|
formatConformanceReport,
|
|
6
6
|
readDeploymentFacts,
|
|
7
7
|
readVerdicts
|
|
8
|
-
} from "../chunk-
|
|
9
|
-
import "../chunk-
|
|
8
|
+
} from "../chunk-YWLZOPDS.js";
|
|
9
|
+
import "../chunk-QM7RTTDY.js";
|
|
10
|
+
import "../chunk-LRYY3OX7.js";
|
|
11
|
+
import "../chunk-CD2LQXY6.js";
|
|
12
|
+
import "../chunk-F2VX6TTJ.js";
|
|
10
13
|
import {
|
|
11
14
|
invokedAsScript
|
|
12
15
|
} from "../chunk-C3CAYGGQ.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/launch-invariants/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * `agent-app-invariants` — report which launch invariants an app holds, and\n * fail its CI when one does not.\n *\n * Runs in the app's repo. Add a `launch-invariants.config.mjs` at the root:\n *\n * export default {\n * product: 'gtm',\n * test: 'pnpm exec vitest run tests/launch-invariants.test.ts',\n * }\n *\n * and a script next to typecheck:\n *\n * \"scripts\": { \"invariants\": \"agent-app-invariants\" }\n *\n * Usage: agent-app-invariants [appDir] [--config <file>] [--json <out>]\n * Exits 0 when every invariant holds, 1 when one does not, 2 on a usage error.\n */\nimport { spawnSync } from 'node:child_process'\nimport { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'\nimport { tmpdir } from 'node:os'\nimport { join, resolve } from 'node:path'\nimport { pathToFileURL } from 'node:url'\nimport { invokedAsScript } from '../signoff/invoked-as-script.js'\nimport {\n buildConformanceReport,\n formatConformanceReport,\n readDeploymentFacts,\n readVerdicts,\n type ConformanceReport,\n type LaunchInvariantsConfig,\n} from './conformance.js'\nimport { INVARIANT_RESULTS_ENV } from './testing.js'\n\nconst CONFIG_NAMES = ['launch-invariants.config.mjs', 'launch-invariants.config.js', 'launch-invariants.config.json']\n\ninterface CliArgs {\n appDir: string\n config?: string\n json?: string\n}\n\nfunction parseArgs(argv: readonly string[]): CliArgs {\n const args: CliArgs = { appDir: process.cwd() }\n for (let index = 0; index < argv.length; index += 1) {\n const arg = argv[index]!\n if (arg === '--config') args.config = argv[++index]\n else if (arg === '--json') args.json = argv[++index]\n else if (arg.startsWith('--')) throw new Error(`unknown option ${arg}`)\n else args.appDir = resolve(arg)\n }\n return args\n}\n\nasync function loadConfig(appDir: string, explicit?: string): Promise<LaunchInvariantsConfig> {\n const file = explicit ? resolve(appDir, explicit) : CONFIG_NAMES.map((name) => join(appDir, name)).find((path) => existsSync(path))\n if (!file || !existsSync(file)) throw new Error(`no ${CONFIG_NAMES[0]} in ${appDir}`)\n const config = file.endsWith('.json')\n ? JSON.parse(readFileSync(file, 'utf8')) as LaunchInvariantsConfig\n : ((await import(pathToFileURL(file).href)) as { default: LaunchInvariantsConfig }).default\n if (!config?.product || !config.test) throw new Error(`${file} must export { product, test }`)\n return config\n}\n\n/** Run the app's invariant tests and judge the result. */\nexport async function runConformance(args: CliArgs): Promise<ConformanceReport> {\n const config = await loadConfig(args.appDir, args.config)\n const facts = readDeploymentFacts(args.appDir, config.wrangler)\n const dir = mkdtempSync(join(tmpdir(), 'agent-app-invariants-'))\n const results = join(dir, 'verdicts.jsonl')\n writeFileSync(results, '')\n try {\n const run = spawnSync(config.test, {\n cwd: args.appDir,\n shell: true,\n stdio: 'inherit',\n env: { ...process.env, [INVARIANT_RESULTS_ENV]: results },\n })\n return buildConformanceReport({\n config,\n facts,\n verdicts: readVerdicts(readFileSync(results, 'utf8')),\n testExitCode: run.status,\n })\n } finally {\n rmSync(dir, { recursive: true, force: true })\n }\n}\n\nasync function main(): Promise<number> {\n let args: CliArgs\n try {\n args = parseArgs(process.argv.slice(2))\n } catch (error) {\n console.error(error instanceof Error ? error.message : String(error))\n return 2\n }\n let report: ConformanceReport\n try {\n report = await runConformance(args)\n } catch (error) {\n console.error(`agent-app-invariants: ${error instanceof Error ? error.message : String(error)}`)\n return 2\n }\n console.log(formatConformanceReport(report))\n if (args.json) writeFileSync(resolve(args.appDir, args.json), `${JSON.stringify(report, null, 2)}\\n`)\n return report.pass ? 0 : 1\n}\n\nif (invokedAsScript(import.meta.url, process.argv[1])) {\n void main().then((code) => process.exit(code))\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../src/launch-invariants/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * `agent-app-invariants` — report which launch invariants an app holds, and\n * fail its CI when one does not.\n *\n * Runs in the app's repo. Add a `launch-invariants.config.mjs` at the root:\n *\n * export default {\n * product: 'gtm',\n * test: 'pnpm exec vitest run tests/launch-invariants.test.ts',\n * }\n *\n * and a script next to typecheck:\n *\n * \"scripts\": { \"invariants\": \"agent-app-invariants\" }\n *\n * Usage: agent-app-invariants [appDir] [--config <file>] [--json <out>]\n * Exits 0 when every invariant holds, 1 when one does not, 2 on a usage error.\n */\nimport { spawnSync } from 'node:child_process'\nimport { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'\nimport { tmpdir } from 'node:os'\nimport { join, resolve } from 'node:path'\nimport { pathToFileURL } from 'node:url'\nimport { invokedAsScript } from '../signoff/invoked-as-script.js'\nimport {\n buildConformanceReport,\n formatConformanceReport,\n readDeploymentFacts,\n readVerdicts,\n type ConformanceReport,\n type LaunchInvariantsConfig,\n} from './conformance.js'\nimport { INVARIANT_RESULTS_ENV } from './testing.js'\n\nconst CONFIG_NAMES = ['launch-invariants.config.mjs', 'launch-invariants.config.js', 'launch-invariants.config.json']\n\ninterface CliArgs {\n appDir: string\n config?: string\n json?: string\n}\n\nfunction parseArgs(argv: readonly string[]): CliArgs {\n const args: CliArgs = { appDir: process.cwd() }\n for (let index = 0; index < argv.length; index += 1) {\n const arg = argv[index]!\n if (arg === '--config') args.config = argv[++index]\n else if (arg === '--json') args.json = argv[++index]\n else if (arg.startsWith('--')) throw new Error(`unknown option ${arg}`)\n else args.appDir = resolve(arg)\n }\n return args\n}\n\nasync function loadConfig(appDir: string, explicit?: string): Promise<LaunchInvariantsConfig> {\n const file = explicit ? resolve(appDir, explicit) : CONFIG_NAMES.map((name) => join(appDir, name)).find((path) => existsSync(path))\n if (!file || !existsSync(file)) throw new Error(`no ${CONFIG_NAMES[0]} in ${appDir}`)\n const config = file.endsWith('.json')\n ? JSON.parse(readFileSync(file, 'utf8')) as LaunchInvariantsConfig\n : ((await import(pathToFileURL(file).href)) as { default: LaunchInvariantsConfig }).default\n if (!config?.product || !config.test) throw new Error(`${file} must export { product, test }`)\n return config\n}\n\n/** Run the app's invariant tests and judge the result. */\nexport async function runConformance(args: CliArgs): Promise<ConformanceReport> {\n const config = await loadConfig(args.appDir, args.config)\n const facts = readDeploymentFacts(args.appDir, config.wrangler)\n const dir = mkdtempSync(join(tmpdir(), 'agent-app-invariants-'))\n const results = join(dir, 'verdicts.jsonl')\n writeFileSync(results, '')\n try {\n const run = spawnSync(config.test, {\n cwd: args.appDir,\n shell: true,\n stdio: 'inherit',\n env: { ...process.env, [INVARIANT_RESULTS_ENV]: results },\n })\n return buildConformanceReport({\n config,\n facts,\n verdicts: readVerdicts(readFileSync(results, 'utf8')),\n testExitCode: run.status,\n })\n } finally {\n rmSync(dir, { recursive: true, force: true })\n }\n}\n\nasync function main(): Promise<number> {\n let args: CliArgs\n try {\n args = parseArgs(process.argv.slice(2))\n } catch (error) {\n console.error(error instanceof Error ? error.message : String(error))\n return 2\n }\n let report: ConformanceReport\n try {\n report = await runConformance(args)\n } catch (error) {\n console.error(`agent-app-invariants: ${error instanceof Error ? error.message : String(error)}`)\n return 2\n }\n console.log(formatConformanceReport(report))\n if (args.json) writeFileSync(resolve(args.appDir, args.json), `${JSON.stringify(report, null, 2)}\\n`)\n return report.pass ? 0 : 1\n}\n\nif (invokedAsScript(import.meta.url, process.argv[1])) {\n void main().then((code) => process.exit(code))\n}\n"],"mappings":"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","names":[]}
|
|
@@ -15,6 +15,6 @@ export { describeVerdict, LAUNCH_BUDGETS, LAUNCH_INVARIANT_IDS, LAUNCH_INVARIANT
|
|
|
15
15
|
export { D1_MAX_BOUND_PARAMETERS, readInSizedBatches, SIZED_READ_BYTES, SIZED_READ_COMPOSITE_ROWS, SIZED_READ_ROWS, sizedBatches, type SizedBatchOptions, type SizedRow, } from './sized-reads.js';
|
|
16
16
|
export { AUTH_LOOKUP_DEADLINE_MS, AUTH_LOOKUP_TTL_MS, AuthLookupRefused, createAuthLookupCache, credentialCacheKey, type AuthLookupCache, type AuthLookupCacheOptions, type AuthLookupOptions, } from './auth-lookup-cache.js';
|
|
17
17
|
export { checkIsolatedJobs, createScheduledDispatch, cronDailyFirings, longestGapMinutes, parseWranglerCrons, slotsOf, type IsolatedJobsInput, type ObservedSlot, type ScheduledDispatch, type ScheduledDispatchOptions, type ScheduledDispatchResult, type ScheduledJob, type ScheduledJobInput, type ScheduledJobOutcome, type ScheduleEntry, type ScheduleSlot, type WranglerCrons, } from './schedule.js';
|
|
18
|
-
export { createLimitAlarms, estimatedBytes, LIMIT_RESOURCES, withD1LimitAlarms, type LimitAlarmOptions, type LimitAlarms, type LimitBudgets, type LimitLevel, type LimitObservation, type LimitResource, } from './limit-alarms.js';
|
|
18
|
+
export { createLimitAlarms, createProductLimitAlarms, DEFAULT_LIMIT_BUDGETS, estimatedBytes, LIMIT_RESOURCES, withD1LimitAlarms, type LimitAlarmOptions, type LimitAlarms, type LimitBudgets, type LimitLevel, type LimitObservation, type LimitResource, type ProductLimitAlarmOptions, } from './limit-alarms.js';
|
|
19
19
|
export { checkSettlementScenarios, checkTurnSettlement, SETTLEMENT_SCENARIOS, type SettledTurnRecord, type SettlementScenario, type TurnSettlementCounts, type TurnTerminalState, } from './settlement.js';
|
|
20
20
|
export { isEdgeBlock, PASTED_CODE_SAMPLES } from './pasted-code.js';
|
|
@@ -7,23 +7,29 @@ import {
|
|
|
7
7
|
sizedBatches
|
|
8
8
|
} from "../chunk-5UQJ7F3P.js";
|
|
9
9
|
import {
|
|
10
|
-
|
|
11
|
-
LAUNCH_INVARIANTS,
|
|
12
|
-
LAUNCH_INVARIANT_IDS,
|
|
10
|
+
DEFAULT_LIMIT_BUDGETS,
|
|
13
11
|
LIMIT_RESOURCES,
|
|
14
12
|
PASTED_CODE_SAMPLES,
|
|
15
13
|
checkIsolatedJobs,
|
|
16
14
|
createLimitAlarms,
|
|
15
|
+
createProductLimitAlarms,
|
|
17
16
|
createScheduledDispatch,
|
|
18
17
|
cronDailyFirings,
|
|
19
|
-
describeVerdict,
|
|
20
18
|
estimatedBytes,
|
|
21
19
|
isEdgeBlock,
|
|
22
20
|
longestGapMinutes,
|
|
23
21
|
parseWranglerCrons,
|
|
24
22
|
slotsOf,
|
|
25
23
|
withD1LimitAlarms
|
|
26
|
-
} from "../chunk-
|
|
24
|
+
} from "../chunk-QM7RTTDY.js";
|
|
25
|
+
import {
|
|
26
|
+
LAUNCH_BUDGETS,
|
|
27
|
+
LAUNCH_INVARIANTS,
|
|
28
|
+
LAUNCH_INVARIANT_IDS,
|
|
29
|
+
describeVerdict
|
|
30
|
+
} from "../chunk-LRYY3OX7.js";
|
|
31
|
+
import "../chunk-CD2LQXY6.js";
|
|
32
|
+
import "../chunk-F2VX6TTJ.js";
|
|
27
33
|
|
|
28
34
|
// src/launch-invariants/auth-lookup-cache.ts
|
|
29
35
|
var AUTH_LOOKUP_TTL_MS = 6e4;
|
|
@@ -227,6 +233,7 @@ export {
|
|
|
227
233
|
AUTH_LOOKUP_TTL_MS,
|
|
228
234
|
AuthLookupRefused,
|
|
229
235
|
D1_MAX_BOUND_PARAMETERS,
|
|
236
|
+
DEFAULT_LIMIT_BUDGETS,
|
|
230
237
|
LAUNCH_BUDGETS,
|
|
231
238
|
LAUNCH_INVARIANTS,
|
|
232
239
|
LAUNCH_INVARIANT_IDS,
|
|
@@ -241,6 +248,7 @@ export {
|
|
|
241
248
|
checkTurnSettlement,
|
|
242
249
|
createAuthLookupCache,
|
|
243
250
|
createLimitAlarms,
|
|
251
|
+
createProductLimitAlarms,
|
|
244
252
|
createScheduledDispatch,
|
|
245
253
|
credentialCacheKey,
|
|
246
254
|
cronDailyFirings,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/launch-invariants/auth-lookup-cache.ts","../../src/launch-invariants/settlement.ts"],"sourcesContent":["/**\n * The last valid result of each auth lookup (API key, key owner, browser\n * session), served only while the store cannot answer.\n *\n * Every lookup reads the store first, so a revoked or expired credential is\n * refused as soon as the store says so, and that answer drops its entry. When\n * the store errors or stalls past the deadline, a valid result verified less\n * than the TTL ago is served instead: on 2026-10-10 an overloaded D1 failed\n * API-key checks and session reads for requests whose credentials were valid\n * moments before (GTM #1488). The TTL counts from the last successful store\n * read and a served entry never renews it, so a credential revoked during an\n * outage lives at most the TTL past its last verification. Entries are per\n * isolate; revoking or rotating a key must also drop its entry here.\n *\n * The cache serves the verified value itself, never a copy: GTM keyed a\n * WeakMap on the verified key object, a copy missed it, and operator requests\n * answered 401 (GTM #1492).\n */\n\n/** Longest a verified result may be served while the store cannot answer. A larger configured TTL is capped here. */\nexport const AUTH_LOOKUP_TTL_MS = 60_000\n/** A store read slower than this is treated as unavailable; a healthy D1 lookup answers in tens of milliseconds. */\nexport const AUTH_LOOKUP_DEADLINE_MS = 2_500\nconst MAX_ENTRIES = 2_000\n\n/** The store answered that the credential is not valid; never answered from the cache. */\nexport class AuthLookupRefused extends Error {}\n\ninterface Entry<V> {\n value: V\n until: number\n}\n\nexport interface AuthLookupOptions<V> {\n /** When the credential itself expires (Unix ms); the entry never outlives it. */\n expiresAt?: (value: V) => number | null | undefined\n}\n\nexport interface AuthLookupCache<V> {\n /**\n * Read through the store. `read` resolves the valid value or null, or throws\n * {@link AuthLookupRefused} (or a `Response`) when the store refuses; any\n * other failure, or no answer by the deadline, falls back to a fresh entry.\n * `key` must not be the secret: use {@link credentialCacheKey}.\n */\n lookup(key: string, read: () => Promise<V | null>, options?: AuthLookupOptions<V>): Promise<V | null>\n /** Drop every entry whose value matches, as when a key is revoked or rotated. */\n invalidate(matches: (value: V) => boolean): void\n /** Drop one entry by its key. */\n forget(key: string): void\n clear(): void\n /** The keys held, for a check that no secret is stored. */\n keys(): string[]\n}\n\nexport interface AuthLookupCacheOptions {\n /** Names the lookup in the warning logged when a cached result is served. */\n label: string\n /** Capped at {@link AUTH_LOOKUP_TTL_MS}. */\n ttlMs?: number\n deadlineMs?: number\n now?: () => number\n warn?: (message: string, detail: Record<string, unknown>) => void\n}\n\nexport function createAuthLookupCache<V>(options: AuthLookupCacheOptions): AuthLookupCache<V> {\n const ttlMs = Math.min(options.ttlMs ?? AUTH_LOOKUP_TTL_MS, AUTH_LOOKUP_TTL_MS)\n const deadlineMs = options.deadlineMs ?? AUTH_LOOKUP_DEADLINE_MS\n const now = options.now ?? (() => Date.now())\n const warn = options.warn ?? ((message, detail) => console.warn(message, detail))\n const entries = new Map<string, Entry<V>>()\n\n return {\n async lookup(key, read, lookupOptions) {\n let value: V | null\n try {\n value = await withDeadline(read(), deadlineMs)\n } catch (error) {\n if (error instanceof AuthLookupRefused || error instanceof Response) {\n entries.delete(key)\n throw error\n }\n const entry = entries.get(key)\n if (entry && now() < entry.until) {\n warn(`[auth-cache] ${options.label}: served the last valid result while the store was unavailable`, {\n error: error instanceof Error ? error.message : String(error),\n remainingMs: entry.until - now(),\n })\n return entry.value\n }\n entries.delete(key)\n throw error\n }\n if (value === null) {\n entries.delete(key)\n return null\n }\n const expiresAt = lookupOptions?.expiresAt?.(value)\n const until = Math.min(now() + ttlMs, typeof expiresAt === 'number' && expiresAt > 0 ? expiresAt : Number.POSITIVE_INFINITY)\n entries.delete(key)\n entries.set(key, { value, until })\n if (entries.size > MAX_ENTRIES) entries.delete(entries.keys().next().value!)\n return value\n },\n invalidate(matches) {\n for (const [key, entry] of entries) if (matches(entry.value)) entries.delete(key)\n },\n forget(key) {\n entries.delete(key)\n },\n clear() {\n entries.clear()\n },\n keys() {\n return [...entries.keys()]\n },\n }\n}\n\n/** A credential's cache key: its SHA-256 in hex, so the cache never holds the secret. */\nexport async function credentialCacheKey(secret: string): Promise<string> {\n const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(secret))\n return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, '0')).join('')\n}\n\nfunction withDeadline<T>(promise: Promise<T>, ms: number): Promise<T> {\n let timer: ReturnType<typeof setTimeout> | undefined\n const deadline = new Promise<never>((_, reject) => {\n timer = setTimeout(() => reject(new Error(`auth lookup did not answer within ${ms} ms`)), ms)\n })\n return Promise.race([promise, deadline]).finally(() => clearTimeout(timer))\n}\n","/**\n * Honest turn settlement, checked over a product's turn records.\n *\n * A turn ends in exactly one terminal state: an answer, a typed failure the\n * person can Retry, or a Stop the person chose, counted on its own. It gets\n * there within 15 minutes of its owner stopping. A failure belongs to the time\n * the turn was requested, not the time it was noticed, and how late it was\n * noticed is measured. A run that completed with a tool call the Sandbox\n * aborted stopped mid-work, so it is a failure.\n *\n * On 2026-10-10 GTM held turns in five places (turn status, admissions,\n * Workflows, Sandbox sessions and the sidecar tracker) and they disagreed:\n * 51 stream rows stayed `running` after their producers died, failure notices\n * arrived 8–11 hours late, and completions with aborted tools counted as\n * answers. {@link checkTurnSettlement} reads one normalized record per turn,\n * however the product derives it, and names every turn that breaks the rule.\n */\nimport { LAUNCH_BUDGETS, type InvariantVerdict } from './catalog.js'\n\nexport type TurnTerminalState = 'answered' | 'failed' | 'stopped'\n\n/** One turn as its product records it, normalized. Times are Unix ms. */\nexport interface SettledTurnRecord {\n turnId: string\n /** When the request was accepted (the user message was saved). */\n requestedAt: number\n /** Every terminal state recorded for the turn, in order; exactly one is honest. */\n terminals: ReadonlyArray<{\n state: TurnTerminalState\n /** When the terminal state was recorded. */\n at: number\n /** A typed failure code, for `failed`. */\n code?: string\n /** Whether the person is offered Retry, for `failed`. */\n retryable?: boolean\n /** The time the failure is reported under; it must be the request time. */\n attributedTo?: number\n }>\n /** When the turn's owner stopped: for a leased stream, when its lease expired. Null while an owner holds it. */\n ownerEndedAt: number | null\n /** A completed run in which the Sandbox aborted a tool call. */\n abortedToolCall?: boolean\n}\n\nexport interface TurnSettlementCounts {\n turns: number\n answered: number\n failed: number\n stopped: number\n open: number\n /** Most minutes between an owner stopping and its turn's terminal state. */\n maxDetectionLagMinutes: number\n}\n\n/**\n * Check turn records against honest settlement at `now`.\n * A turn still open whose owner has not stopped is fine; one whose owner stopped\n * more than {@link LAUNCH_BUDGETS.settleWithinMs} ago is not.\n */\nexport function checkTurnSettlement(\n turns: readonly SettledTurnRecord[],\n options: { now: number; settleWithinMs?: number; subject?: string },\n): InvariantVerdict {\n const within = options.settleWithinMs ?? LAUNCH_BUDGETS.settleWithinMs\n const findings: string[] = []\n const counts: TurnSettlementCounts = { turns: turns.length, answered: 0, failed: 0, stopped: 0, open: 0, maxDetectionLagMinutes: 0 }\n for (const turn of turns) {\n const label = `turn ${turn.turnId}`\n if (turn.terminals.length > 1) {\n findings.push(`${label} has ${turn.terminals.length} terminal states (${turn.terminals.map((terminal) => terminal.state).join(' then ')}); a turn ends once`)\n }\n const terminal = turn.terminals[0]\n if (!terminal) {\n counts.open += 1\n if (turn.ownerEndedAt !== null && options.now - turn.ownerEndedAt > within) {\n findings.push(`${label} is still open ${minutes(options.now - turn.ownerEndedAt)} min after its owner stopped; the most is ${minutes(within)}`)\n }\n continue\n }\n counts[terminal.state] += 1\n if (turn.ownerEndedAt !== null) {\n const lag = terminal.at - turn.ownerEndedAt\n counts.maxDetectionLagMinutes = Math.max(counts.maxDetectionLagMinutes, minutes(lag))\n if (lag > within) findings.push(`${label} was settled ${minutes(lag)} min after its owner stopped; the most is ${minutes(within)}`)\n }\n if (terminal.state === 'answered' && turn.abortedToolCall) {\n findings.push(`${label} is counted as an answer, but the Sandbox aborted a tool call in it; it stopped mid-work and is a failure`)\n }\n if (terminal.state === 'failed') {\n if (!terminal.code) findings.push(`${label} failed without a typed failure code`)\n if (terminal.retryable !== true) findings.push(`${label} failed without offering Retry`)\n if (terminal.attributedTo !== undefined && terminal.attributedTo !== turn.requestedAt) {\n findings.push(`${label}'s failure is reported at ${new Date(terminal.attributedTo).toISOString()}, not its request time ${new Date(turn.requestedAt).toISOString()}`)\n }\n }\n if (terminal.state === 'stopped' && terminal.code && /fail|error/i.test(terminal.code)) {\n findings.push(`${label} was stopped by the person but carries failure code ${terminal.code}; a Stop is counted on its own`)\n }\n }\n return {\n invariant: 'honest-settlement',\n subject: options.subject ?? 'turns',\n pass: findings.length === 0,\n details: findings.length ? findings : [\n `${counts.turns} turns: ${counts.answered} answered, ${counts.failed} failed with Retry, ${counts.stopped} stopped, ${counts.open} held by a live owner`,\n `longest detection lag ${counts.maxDetectionLagMinutes} min`,\n ],\n data: { ...counts },\n }\n}\n\nfunction minutes(ms: number): number {\n return Math.round((ms / 60_000) * 10) / 10\n}\n\n/**\n * The failure shapes GTM shipped on 2026-10-10, as scenarios a product seeds\n * into its own storage, settles with its own scheduled jobs, and reads back as\n * {@link SettledTurnRecord}s. `expect` is the one honest terminal state.\n */\nexport interface SettlementScenario {\n id: string\n /** What happened to the turn, for the product's seed step. */\n story: string\n /** Minutes before the check's `now` that the request was accepted. */\n requestedMinutesAgo: number\n /**\n * Minutes before `now` that the owner stopped (for a stream, its lease\n * expired), or null for a live owner. Every scenario that stopped did so 12\n * minutes ago, so an app that settles within 15 minutes has settled it by `now`.\n */\n ownerEndedMinutesAgo: number | null\n expect: TurnTerminalState | 'open'\n}\n\nexport const SETTLEMENT_SCENARIOS: readonly SettlementScenario[] = [\n {\n id: 'orphaned-stream',\n story: 'The Worker streaming the turn died; its stream row is still running, no admission or Workflow holds it, and its 5-minute lease expired 12 minutes ago.',\n requestedMinutesAgo: 30,\n ownerEndedMinutesAgo: 12,\n expect: 'failed',\n },\n {\n id: 'abandoned-before-admission',\n story: 'The user message was saved 12 minutes ago, then the request died before any completion owner admitted the turn: no reply, no stream, no admission.',\n requestedMinutesAgo: 12,\n ownerEndedMinutesAgo: 12,\n expect: 'failed',\n },\n {\n id: 'runtime-restarted',\n story: 'The admitted run\\'s runtime restarted mid-turn 12 minutes ago: the session still reports running while its execution ledger failed the run (terminal event interrupted-done).',\n requestedMinutesAgo: 40,\n ownerEndedMinutesAgo: 12,\n expect: 'failed',\n },\n {\n id: 'completed-with-aborted-tool',\n story: 'The run reported completed 12 minutes ago, but a tool part ended with \"Tool execution aborted\".',\n requestedMinutesAgo: 20,\n ownerEndedMinutesAgo: 12,\n expect: 'failed',\n },\n {\n id: 'user-stop',\n story: 'The person pressed Stop 12 minutes ago while the turn ran.',\n requestedMinutesAgo: 20,\n ownerEndedMinutesAgo: 12,\n expect: 'stopped',\n },\n {\n id: 'answered',\n story: 'The turn answered normally 12 minutes ago.',\n requestedMinutesAgo: 20,\n ownerEndedMinutesAgo: 12,\n expect: 'answered',\n },\n {\n id: 'live-long-turn',\n story: 'A long turn is still running under an owner that renews its lease.',\n requestedMinutesAgo: 90,\n ownerEndedMinutesAgo: null,\n expect: 'open',\n },\n]\n\n/**\n * Check a product's settlement of {@link SETTLEMENT_SCENARIOS}: each scenario's\n * record ends in its expected state, and all of them settle honestly. The\n * product seeds each scenario at `now`, runs its own recovery and settlement\n * jobs at `now`, and reads the records back keyed by scenario id.\n */\nexport function checkSettlementScenarios(\n records: Readonly<Record<string, SettledTurnRecord | undefined>>,\n options: { now: number; scenarios?: readonly SettlementScenario[] },\n): InvariantVerdict {\n const scenarios = options.scenarios ?? SETTLEMENT_SCENARIOS\n const findings: string[] = []\n const present: SettledTurnRecord[] = []\n for (const scenario of scenarios) {\n const record = records[scenario.id]\n if (!record) {\n findings.push(`scenario ${scenario.id}: no turn record was read back`)\n continue\n }\n present.push(record)\n const state = record.terminals[0]?.state ?? 'open'\n if (state !== scenario.expect) findings.push(`scenario ${scenario.id}: ended ${state}, expected ${scenario.expect} (${scenario.story})`)\n }\n const honest = checkTurnSettlement(present, { now: options.now, subject: 'settlement scenarios' })\n if (!honest.pass) findings.push(...honest.details)\n return {\n invariant: 'honest-settlement',\n subject: 'settlement scenarios',\n pass: findings.length === 0,\n details: findings.length ? findings : [`${scenarios.length} failure scenarios settle to their honest state`, ...honest.details],\n data: honest.data,\n }\n}\n"],"mappings":"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","names":["terminal"]}
|
|
1
|
+
{"version":3,"sources":["../../src/launch-invariants/auth-lookup-cache.ts","../../src/launch-invariants/settlement.ts"],"sourcesContent":["/**\n * The last valid result of each auth lookup (API key, key owner, browser\n * session), served only while the store cannot answer.\n *\n * Every lookup reads the store first, so a revoked or expired credential is\n * refused as soon as the store says so, and that answer drops its entry. When\n * the store errors or stalls past the deadline, a valid result verified less\n * than the TTL ago is served instead: on 2026-10-10 an overloaded D1 failed\n * API-key checks and session reads for requests whose credentials were valid\n * moments before (GTM #1488). The TTL counts from the last successful store\n * read and a served entry never renews it, so a credential revoked during an\n * outage lives at most the TTL past its last verification. Entries are per\n * isolate; revoking or rotating a key must also drop its entry here.\n *\n * The cache serves the verified value itself, never a copy: GTM keyed a\n * WeakMap on the verified key object, a copy missed it, and operator requests\n * answered 401 (GTM #1492).\n */\n\n/** Longest a verified result may be served while the store cannot answer. A larger configured TTL is capped here. */\nexport const AUTH_LOOKUP_TTL_MS = 60_000\n/** A store read slower than this is treated as unavailable; a healthy D1 lookup answers in tens of milliseconds. */\nexport const AUTH_LOOKUP_DEADLINE_MS = 2_500\nconst MAX_ENTRIES = 2_000\n\n/** The store answered that the credential is not valid; never answered from the cache. */\nexport class AuthLookupRefused extends Error {}\n\ninterface Entry<V> {\n value: V\n until: number\n}\n\nexport interface AuthLookupOptions<V> {\n /** When the credential itself expires (Unix ms); the entry never outlives it. */\n expiresAt?: (value: V) => number | null | undefined\n}\n\nexport interface AuthLookupCache<V> {\n /**\n * Read through the store. `read` resolves the valid value or null, or throws\n * {@link AuthLookupRefused} (or a `Response`) when the store refuses; any\n * other failure, or no answer by the deadline, falls back to a fresh entry.\n * `key` must not be the secret: use {@link credentialCacheKey}.\n */\n lookup(key: string, read: () => Promise<V | null>, options?: AuthLookupOptions<V>): Promise<V | null>\n /** Drop every entry whose value matches, as when a key is revoked or rotated. */\n invalidate(matches: (value: V) => boolean): void\n /** Drop one entry by its key. */\n forget(key: string): void\n clear(): void\n /** The keys held, for a check that no secret is stored. */\n keys(): string[]\n}\n\nexport interface AuthLookupCacheOptions {\n /** Names the lookup in the warning logged when a cached result is served. */\n label: string\n /** Capped at {@link AUTH_LOOKUP_TTL_MS}. */\n ttlMs?: number\n deadlineMs?: number\n now?: () => number\n warn?: (message: string, detail: Record<string, unknown>) => void\n}\n\nexport function createAuthLookupCache<V>(options: AuthLookupCacheOptions): AuthLookupCache<V> {\n const ttlMs = Math.min(options.ttlMs ?? AUTH_LOOKUP_TTL_MS, AUTH_LOOKUP_TTL_MS)\n const deadlineMs = options.deadlineMs ?? AUTH_LOOKUP_DEADLINE_MS\n const now = options.now ?? (() => Date.now())\n const warn = options.warn ?? ((message, detail) => console.warn(message, detail))\n const entries = new Map<string, Entry<V>>()\n\n return {\n async lookup(key, read, lookupOptions) {\n let value: V | null\n try {\n value = await withDeadline(read(), deadlineMs)\n } catch (error) {\n if (error instanceof AuthLookupRefused || error instanceof Response) {\n entries.delete(key)\n throw error\n }\n const entry = entries.get(key)\n if (entry && now() < entry.until) {\n warn(`[auth-cache] ${options.label}: served the last valid result while the store was unavailable`, {\n error: error instanceof Error ? error.message : String(error),\n remainingMs: entry.until - now(),\n })\n return entry.value\n }\n entries.delete(key)\n throw error\n }\n if (value === null) {\n entries.delete(key)\n return null\n }\n const expiresAt = lookupOptions?.expiresAt?.(value)\n const until = Math.min(now() + ttlMs, typeof expiresAt === 'number' && expiresAt > 0 ? expiresAt : Number.POSITIVE_INFINITY)\n entries.delete(key)\n entries.set(key, { value, until })\n if (entries.size > MAX_ENTRIES) entries.delete(entries.keys().next().value!)\n return value\n },\n invalidate(matches) {\n for (const [key, entry] of entries) if (matches(entry.value)) entries.delete(key)\n },\n forget(key) {\n entries.delete(key)\n },\n clear() {\n entries.clear()\n },\n keys() {\n return [...entries.keys()]\n },\n }\n}\n\n/** A credential's cache key: its SHA-256 in hex, so the cache never holds the secret. */\nexport async function credentialCacheKey(secret: string): Promise<string> {\n const digest = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(secret))\n return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, '0')).join('')\n}\n\nfunction withDeadline<T>(promise: Promise<T>, ms: number): Promise<T> {\n let timer: ReturnType<typeof setTimeout> | undefined\n const deadline = new Promise<never>((_, reject) => {\n timer = setTimeout(() => reject(new Error(`auth lookup did not answer within ${ms} ms`)), ms)\n })\n return Promise.race([promise, deadline]).finally(() => clearTimeout(timer))\n}\n","/**\n * Honest turn settlement, checked over a product's turn records.\n *\n * A turn ends in exactly one terminal state: an answer, a typed failure the\n * person can Retry, or a Stop the person chose, counted on its own. It gets\n * there within 15 minutes of its owner stopping. A failure belongs to the time\n * the turn was requested, not the time it was noticed, and how late it was\n * noticed is measured. A run that completed with a tool call the Sandbox\n * aborted stopped mid-work, so it is a failure.\n *\n * On 2026-10-10 GTM held turns in five places (turn status, admissions,\n * Workflows, Sandbox sessions and the sidecar tracker) and they disagreed:\n * 51 stream rows stayed `running` after their producers died, failure notices\n * arrived 8–11 hours late, and completions with aborted tools counted as\n * answers. {@link checkTurnSettlement} reads one normalized record per turn,\n * however the product derives it, and names every turn that breaks the rule.\n */\nimport { LAUNCH_BUDGETS, type InvariantVerdict } from './catalog.js'\n\nexport type TurnTerminalState = 'answered' | 'failed' | 'stopped'\n\n/** One turn as its product records it, normalized. Times are Unix ms. */\nexport interface SettledTurnRecord {\n turnId: string\n /** When the request was accepted (the user message was saved). */\n requestedAt: number\n /** Every terminal state recorded for the turn, in order; exactly one is honest. */\n terminals: ReadonlyArray<{\n state: TurnTerminalState\n /** When the terminal state was recorded. */\n at: number\n /** A typed failure code, for `failed`. */\n code?: string\n /** Whether the person is offered Retry, for `failed`. */\n retryable?: boolean\n /** The time the failure is reported under; it must be the request time. */\n attributedTo?: number\n }>\n /** When the turn's owner stopped: for a leased stream, when its lease expired. Null while an owner holds it. */\n ownerEndedAt: number | null\n /** A completed run in which the Sandbox aborted a tool call. */\n abortedToolCall?: boolean\n}\n\nexport interface TurnSettlementCounts {\n turns: number\n answered: number\n failed: number\n stopped: number\n open: number\n /** Most minutes between an owner stopping and its turn's terminal state. */\n maxDetectionLagMinutes: number\n}\n\n/**\n * Check turn records against honest settlement at `now`.\n * A turn still open whose owner has not stopped is fine; one whose owner stopped\n * more than {@link LAUNCH_BUDGETS.settleWithinMs} ago is not.\n */\nexport function checkTurnSettlement(\n turns: readonly SettledTurnRecord[],\n options: { now: number; settleWithinMs?: number; subject?: string },\n): InvariantVerdict {\n const within = options.settleWithinMs ?? LAUNCH_BUDGETS.settleWithinMs\n const findings: string[] = []\n const counts: TurnSettlementCounts = { turns: turns.length, answered: 0, failed: 0, stopped: 0, open: 0, maxDetectionLagMinutes: 0 }\n for (const turn of turns) {\n const label = `turn ${turn.turnId}`\n if (turn.terminals.length > 1) {\n findings.push(`${label} has ${turn.terminals.length} terminal states (${turn.terminals.map((terminal) => terminal.state).join(' then ')}); a turn ends once`)\n }\n const terminal = turn.terminals[0]\n if (!terminal) {\n counts.open += 1\n if (turn.ownerEndedAt !== null && options.now - turn.ownerEndedAt > within) {\n findings.push(`${label} is still open ${minutes(options.now - turn.ownerEndedAt)} min after its owner stopped; the most is ${minutes(within)}`)\n }\n continue\n }\n counts[terminal.state] += 1\n if (turn.ownerEndedAt !== null) {\n const lag = terminal.at - turn.ownerEndedAt\n counts.maxDetectionLagMinutes = Math.max(counts.maxDetectionLagMinutes, minutes(lag))\n if (lag > within) findings.push(`${label} was settled ${minutes(lag)} min after its owner stopped; the most is ${minutes(within)}`)\n }\n if (terminal.state === 'answered' && turn.abortedToolCall) {\n findings.push(`${label} is counted as an answer, but the Sandbox aborted a tool call in it; it stopped mid-work and is a failure`)\n }\n if (terminal.state === 'failed') {\n if (!terminal.code) findings.push(`${label} failed without a typed failure code`)\n if (terminal.retryable !== true) findings.push(`${label} failed without offering Retry`)\n if (terminal.attributedTo !== undefined && terminal.attributedTo !== turn.requestedAt) {\n findings.push(`${label}'s failure is reported at ${new Date(terminal.attributedTo).toISOString()}, not its request time ${new Date(turn.requestedAt).toISOString()}`)\n }\n }\n if (terminal.state === 'stopped' && terminal.code && /fail|error/i.test(terminal.code)) {\n findings.push(`${label} was stopped by the person but carries failure code ${terminal.code}; a Stop is counted on its own`)\n }\n }\n return {\n invariant: 'honest-settlement',\n subject: options.subject ?? 'turns',\n pass: findings.length === 0,\n details: findings.length ? findings : [\n `${counts.turns} turns: ${counts.answered} answered, ${counts.failed} failed with Retry, ${counts.stopped} stopped, ${counts.open} held by a live owner`,\n `longest detection lag ${counts.maxDetectionLagMinutes} min`,\n ],\n data: { ...counts },\n }\n}\n\nfunction minutes(ms: number): number {\n return Math.round((ms / 60_000) * 10) / 10\n}\n\n/**\n * The failure shapes GTM shipped on 2026-10-10, as scenarios a product seeds\n * into its own storage, settles with its own scheduled jobs, and reads back as\n * {@link SettledTurnRecord}s. `expect` is the one honest terminal state.\n */\nexport interface SettlementScenario {\n id: string\n /** What happened to the turn, for the product's seed step. */\n story: string\n /** Minutes before the check's `now` that the request was accepted. */\n requestedMinutesAgo: number\n /**\n * Minutes before `now` that the owner stopped (for a stream, its lease\n * expired), or null for a live owner. Every scenario that stopped did so 12\n * minutes ago, so an app that settles within 15 minutes has settled it by `now`.\n */\n ownerEndedMinutesAgo: number | null\n expect: TurnTerminalState | 'open'\n}\n\nexport const SETTLEMENT_SCENARIOS: readonly SettlementScenario[] = [\n {\n id: 'orphaned-stream',\n story: 'The Worker streaming the turn died; its stream row is still running, no admission or Workflow holds it, and its 5-minute lease expired 12 minutes ago.',\n requestedMinutesAgo: 30,\n ownerEndedMinutesAgo: 12,\n expect: 'failed',\n },\n {\n id: 'abandoned-before-admission',\n story: 'The user message was saved 12 minutes ago, then the request died before any completion owner admitted the turn: no reply, no stream, no admission.',\n requestedMinutesAgo: 12,\n ownerEndedMinutesAgo: 12,\n expect: 'failed',\n },\n {\n id: 'runtime-restarted',\n story: 'The admitted run\\'s runtime restarted mid-turn 12 minutes ago: the session still reports running while its execution ledger failed the run (terminal event interrupted-done).',\n requestedMinutesAgo: 40,\n ownerEndedMinutesAgo: 12,\n expect: 'failed',\n },\n {\n id: 'completed-with-aborted-tool',\n story: 'The run reported completed 12 minutes ago, but a tool part ended with \"Tool execution aborted\".',\n requestedMinutesAgo: 20,\n ownerEndedMinutesAgo: 12,\n expect: 'failed',\n },\n {\n id: 'user-stop',\n story: 'The person pressed Stop 12 minutes ago while the turn ran.',\n requestedMinutesAgo: 20,\n ownerEndedMinutesAgo: 12,\n expect: 'stopped',\n },\n {\n id: 'answered',\n story: 'The turn answered normally 12 minutes ago.',\n requestedMinutesAgo: 20,\n ownerEndedMinutesAgo: 12,\n expect: 'answered',\n },\n {\n id: 'live-long-turn',\n story: 'A long turn is still running under an owner that renews its lease.',\n requestedMinutesAgo: 90,\n ownerEndedMinutesAgo: null,\n expect: 'open',\n },\n]\n\n/**\n * Check a product's settlement of {@link SETTLEMENT_SCENARIOS}: each scenario's\n * record ends in its expected state, and all of them settle honestly. The\n * product seeds each scenario at `now`, runs its own recovery and settlement\n * jobs at `now`, and reads the records back keyed by scenario id.\n */\nexport function checkSettlementScenarios(\n records: Readonly<Record<string, SettledTurnRecord | undefined>>,\n options: { now: number; scenarios?: readonly SettlementScenario[] },\n): InvariantVerdict {\n const scenarios = options.scenarios ?? SETTLEMENT_SCENARIOS\n const findings: string[] = []\n const present: SettledTurnRecord[] = []\n for (const scenario of scenarios) {\n const record = records[scenario.id]\n if (!record) {\n findings.push(`scenario ${scenario.id}: no turn record was read back`)\n continue\n }\n present.push(record)\n const state = record.terminals[0]?.state ?? 'open'\n if (state !== scenario.expect) findings.push(`scenario ${scenario.id}: ended ${state}, expected ${scenario.expect} (${scenario.story})`)\n }\n const honest = checkTurnSettlement(present, { now: options.now, subject: 'settlement scenarios' })\n if (!honest.pass) findings.push(...honest.details)\n return {\n invariant: 'honest-settlement',\n subject: 'settlement scenarios',\n pass: findings.length === 0,\n details: findings.length ? findings : [`${scenarios.length} failure scenarios settle to their honest state`, ...honest.details],\n data: honest.data,\n }\n}\n"],"mappings":"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","names":["terminal"]}
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* - `sandbox-disk` and `snapshot-count`: the Sandbox SDK's box usage and snapshot list, read on a schedule.
|
|
17
17
|
* - `key-rate`: the per-key request counter the operator API keeps.
|
|
18
18
|
*/
|
|
19
|
-
import type
|
|
19
|
+
import { type AlertSink } from '../turn-health/sink.js';
|
|
20
20
|
export type LimitResource = 'worker-memory' | 'd1-rows-per-query' | 'sandbox-disk' | 'snapshot-count' | 'key-rate';
|
|
21
21
|
export declare const LIMIT_RESOURCES: readonly LimitResource[];
|
|
22
22
|
/** The limit of each resource, in its own unit: bytes, rows, bytes, snapshots, requests per minute. */
|
|
@@ -47,6 +47,24 @@ export interface LimitAlarmOptions {
|
|
|
47
47
|
now?: () => number;
|
|
48
48
|
}
|
|
49
49
|
export declare function createLimitAlarms(options: LimitAlarmOptions): LimitAlarms;
|
|
50
|
+
/** The budgets every product starts from: {@link LAUNCH_BUDGETS}' platform limits. */
|
|
51
|
+
export declare const DEFAULT_LIMIT_BUDGETS: LimitBudgets;
|
|
52
|
+
export interface ProductLimitAlarmOptions {
|
|
53
|
+
/** The product's name in alerts, e.g. `legal`. */
|
|
54
|
+
product: string;
|
|
55
|
+
/** The ops webhook; without one alarms go to the console only. */
|
|
56
|
+
webhookUrl?: string;
|
|
57
|
+
/** The few budgets this product measured differently, e.g. a larger sandbox disk. */
|
|
58
|
+
overrides?: Partial<LimitBudgets>;
|
|
59
|
+
/** Replace the default console-and-webhook sink (tests, or an app with its own alert transport). */
|
|
60
|
+
sink?: AlertSink;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* A product's limit alarms with every default in place: {@link DEFAULT_LIMIT_BUDGETS}, the console and optional webhook
|
|
64
|
+
* sink, and one page per resource every {@link LAUNCH_BUDGETS.alarmRepeatHours} hours. A product passes only what it
|
|
65
|
+
* measured differently; Legal, Creative, Tax and GTM each kept a 40-line copy of this before.
|
|
66
|
+
*/
|
|
67
|
+
export declare function createProductLimitAlarms(options: ProductLimitAlarmOptions): LimitAlarms;
|
|
50
68
|
/** The D1 surface the wrapper measures; Cloudflare `D1Database` satisfies it. */
|
|
51
69
|
interface D1Measurable {
|
|
52
70
|
prepare(sql: string): unknown;
|
|
@@ -191,11 +191,8 @@ export interface CoalescedTurnOptions {
|
|
|
191
191
|
export declare function checkCoalescedTurn(options: CoalescedTurnOptions): Promise<InvariantVerdict>;
|
|
192
192
|
/** Hard platform limits a declared budget may not exceed. */
|
|
193
193
|
export declare const PLATFORM_LIMITS: {
|
|
194
|
-
/** A Worker isolate's memory. */
|
|
195
194
|
readonly 'worker-memory': number;
|
|
196
|
-
/** Platform snapshots per sandbox; the cap GTM hit on 2026-10-10. */
|
|
197
195
|
readonly 'snapshot-count': 25;
|
|
198
|
-
/** Requests per minute per operator key. */
|
|
199
196
|
readonly 'key-rate': 60;
|
|
200
197
|
};
|
|
201
198
|
export interface LimitAlarmCheckOptions {
|
|
@@ -18,10 +18,13 @@ import {
|
|
|
18
18
|
recordInvariants,
|
|
19
19
|
recordedLongTurn,
|
|
20
20
|
stallingD1
|
|
21
|
-
} from "../chunk-
|
|
21
|
+
} from "../chunk-YWLZOPDS.js";
|
|
22
|
+
import "../chunk-QM7RTTDY.js";
|
|
22
23
|
import {
|
|
23
24
|
describeVerdict
|
|
24
|
-
} from "../chunk-
|
|
25
|
+
} from "../chunk-LRYY3OX7.js";
|
|
26
|
+
import "../chunk-CD2LQXY6.js";
|
|
27
|
+
import "../chunk-F2VX6TTJ.js";
|
|
25
28
|
export {
|
|
26
29
|
INVARIANT_RESULTS_ENV,
|
|
27
30
|
PLATFORM_LIMITS,
|
|
@@ -31,13 +31,15 @@
|
|
|
31
31
|
* Errors carry a code and status, never a response body, a source key or a
|
|
32
32
|
* child key.
|
|
33
33
|
*/
|
|
34
|
-
export type DelegatedKeyCode = 'delegated_key.payment_required' | 'delegated_key.budget_exhausted' | 'delegated_key.owner_unverified' | 'delegated_key.unavailable' | 'delegated_key.refused' | 'delegated_key.response_invalid' | 'delegated_key.invalid_request';
|
|
34
|
+
export type DelegatedKeyCode = 'delegated_key.payment_required' | 'delegated_key.budget_exhausted' | 'delegated_key.owner_unverified' | 'delegated_key.unavailable' | 'delegated_key.refused' | 'delegated_key.failed' | 'delegated_key.response_invalid' | 'delegated_key.invalid_request';
|
|
35
35
|
export interface DelegatedKeyRefusal {
|
|
36
36
|
status: number;
|
|
37
37
|
code: DelegatedKeyCode;
|
|
38
38
|
message: string;
|
|
39
39
|
/** Set when an untrusted child could not be revoked: the id (never the key) the app must retire. */
|
|
40
40
|
unrevokedKeyId?: string;
|
|
41
|
+
/** Platform's own error code (never its message) for a decision this client does not type, for logs. */
|
|
42
|
+
platformCode?: string;
|
|
41
43
|
}
|
|
42
44
|
export type DelegatedKeyOutcome<T> = {
|
|
43
45
|
succeeded: true;
|