@tangle-network/agent-app 0.45.26 → 0.45.28

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.
Files changed (70) hide show
  1. package/dist/assistant/index.d.ts +1 -0
  2. package/dist/assistant/index.js +5 -5
  3. package/dist/chat-routes/index.js +21 -21
  4. package/dist/chat-store/index.js +2 -2
  5. package/dist/{chunk-B2UEW62O.js → chunk-5ZTFZBS6.js} +4 -4
  6. package/dist/{chunk-AGCRCOQH.js → chunk-AWPK7ZDS.js} +1 -1
  7. package/dist/chunk-C3CAYGGQ.js +18 -0
  8. package/dist/chunk-C3CAYGGQ.js.map +1 -0
  9. package/dist/chunk-EMLGWEHV.js +33 -0
  10. package/dist/chunk-EMLGWEHV.js.map +1 -0
  11. package/dist/{chunk-XEA5EG6P.js → chunk-EPZYYWCT.js} +8 -30
  12. package/dist/chunk-EPZYYWCT.js.map +1 -0
  13. package/dist/{chunk-WD2B6HGY.js → chunk-K4RA5T3A.js} +11 -32
  14. package/dist/chunk-K4RA5T3A.js.map +1 -0
  15. package/dist/chunk-NEJ7OMQS.js +351 -0
  16. package/dist/chunk-NEJ7OMQS.js.map +1 -0
  17. package/dist/{chunk-7V2I3JGZ.js → chunk-OTLEYHOG.js} +50 -12
  18. package/dist/chunk-OTLEYHOG.js.map +1 -0
  19. package/dist/{chunk-RT5RKAFX.js → chunk-QGSVF6DZ.js} +8 -18
  20. package/dist/chunk-QGSVF6DZ.js.map +1 -0
  21. package/dist/{chunk-KOMP6NDW.js → chunk-WC43OQMR.js} +45 -34
  22. package/dist/chunk-WC43OQMR.js.map +1 -0
  23. package/dist/{chunk-N3G3FSM4.js → chunk-YXA3U4JZ.js} +9 -47
  24. package/dist/chunk-YXA3U4JZ.js.map +1 -0
  25. package/dist/design-canvas-react/index.js +4 -4
  26. package/dist/design-canvas-react/lazy.js +1 -1
  27. package/dist/legibility/cli.js +14 -8
  28. package/dist/legibility/cli.js.map +1 -1
  29. package/dist/legibility/index.js +2 -1
  30. package/dist/peer-floors/cli.js +14 -8
  31. package/dist/peer-floors/cli.js.map +1 -1
  32. package/dist/preflight/cli.js +4 -2
  33. package/dist/preflight/cli.js.map +1 -1
  34. package/dist/{run-tEsZUhAf.d.ts → run-D80hoLKh.d.ts} +1 -1
  35. package/dist/sandbox/index.d.ts +53 -1
  36. package/dist/sandbox/index.js +1 -1
  37. package/dist/signoff/cli.d.ts +2 -2
  38. package/dist/signoff/cli.js +6 -18
  39. package/dist/signoff/cli.js.map +1 -1
  40. package/dist/signoff/index.d.ts +4 -420
  41. package/dist/signoff/index.js +3 -55
  42. package/dist/signoff/proof-cli.js +11 -6
  43. package/dist/signoff/proof-cli.js.map +1 -1
  44. package/dist/signoff/proof.d.ts +70 -271
  45. package/dist/signoff/proof.js +3 -73
  46. package/dist/signoff/proof.js.map +1 -1
  47. package/dist/spend/cli.d.ts +1 -0
  48. package/dist/spend/cli.js +119 -0
  49. package/dist/spend/cli.js.map +1 -0
  50. package/dist/spend/index.d.ts +640 -0
  51. package/dist/spend/index.js +196 -0
  52. package/dist/spend/index.js.map +1 -0
  53. package/dist/stream/index.js +14 -14
  54. package/dist/teams/index.js +9 -9
  55. package/dist/teams/invitations-api.js +7 -7
  56. package/dist/theme-contract/cli.js +6 -2
  57. package/dist/theme-contract/cli.js.map +1 -1
  58. package/dist/theme-contract/index.js +2 -1
  59. package/dist/{types-U7Nz-txa.d.ts → types-Bd6uW9vQ.d.ts} +1 -1
  60. package/dist/web-react/index.d.ts +28 -40
  61. package/dist/web-react/index.js +11 -11
  62. package/package.json +7 -1
  63. package/dist/chunk-7V2I3JGZ.js.map +0 -1
  64. package/dist/chunk-KOMP6NDW.js.map +0 -1
  65. package/dist/chunk-N3G3FSM4.js.map +0 -1
  66. package/dist/chunk-RT5RKAFX.js.map +0 -1
  67. package/dist/chunk-WD2B6HGY.js.map +0 -1
  68. package/dist/chunk-XEA5EG6P.js.map +0 -1
  69. /package/dist/{chunk-B2UEW62O.js.map → chunk-5ZTFZBS6.js.map} +0 -0
  70. /package/dist/{chunk-AGCRCOQH.js.map → chunk-AWPK7ZDS.js.map} +0 -0
@@ -1,3 +1,11 @@
1
+ // src/signoff/proof-record.ts
2
+ import { spawnSync as spawnSync2 } from "child_process";
3
+ import { createHash, createHmac, timingSafeEqual } from "crypto";
4
+ import { readFileSync } from "fs";
5
+ import { hostname, userInfo } from "os";
6
+ import { join as join2 } from "path";
7
+ import { z } from "zod";
8
+
1
9
  // src/signoff/proof-git.ts
2
10
  import { spawnSync } from "child_process";
3
11
  import { mkdtempSync, rmSync } from "fs";
@@ -69,12 +77,6 @@ function computeWorktreeTree(repoDir) {
69
77
  }
70
78
 
71
79
  // src/signoff/proof-record.ts
72
- import { spawnSync as spawnSync2 } from "child_process";
73
- import { createHash, createHmac, timingSafeEqual } from "crypto";
74
- import { readFileSync } from "fs";
75
- import { hostname, userInfo } from "os";
76
- import { join as join2 } from "path";
77
- import { z } from "zod";
78
80
  var SIGNOFF_PROOF_VERSION = 1;
79
81
  var isoString = z.string().regex(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$/, "must be a UTC ISO-8601 timestamp");
80
82
  var sha1Hex = z.string().regex(/^[0-9a-f]{40}$/, "must be a 40-hex git object id");
@@ -259,10 +261,6 @@ function buildSignoffProof(input) {
259
261
  function parseSignoffProof(json) {
260
262
  return signoffProofSchema.parse(JSON.parse(json));
261
263
  }
262
- function serializeSignoffProof(proof) {
263
- return `${JSON.stringify(proof, null, 2)}
264
- `;
265
- }
266
264
  function formatDuration(ms) {
267
265
  const seconds = Math.round(ms / 1e3);
268
266
  if (seconds < 60) return `${seconds}s`;
@@ -344,13 +342,6 @@ var SIGNOFF_REQUIRED_STEPS = {
344
342
  "tax-agent": ["install", "peer-check", "typecheck", "test", "toolkit-deps", "toolkit-test", "build", "worker-startup"],
345
343
  "legal-agent": ["install", "peer-check", "typegen", "typecheck", "test", "build:check"]
346
344
  };
347
- function requiredStepsFor(repo) {
348
- const steps = SIGNOFF_REQUIRED_STEPS[repo];
349
- if (steps === void 0) {
350
- throw new Error(`no required-step table for repo ${JSON.stringify(repo)}; known: ${Object.keys(SIGNOFF_REQUIRED_STEPS).sort().join(", ")}`);
351
- }
352
- return steps;
353
- }
354
345
  function verifySignoffProof(proof, options) {
355
346
  const failures = [];
356
347
  const { body, seal } = proof;
@@ -465,45 +456,16 @@ function formatSignoffVerification(result) {
465
456
  }
466
457
 
467
458
  export {
468
- SignoffGitError,
469
- runGit,
470
- gitText,
471
- gitIsAncestor,
472
- resolveCommit,
473
459
  readCommitFacts,
474
- computeWorktreeTree,
475
- SIGNOFF_PROOF_VERSION,
476
- SIGNOFF_STEP_STATUSES,
477
- signoffProofStepSchema,
478
- signoffProofBodySchema,
479
- signoffProofSealSchema,
480
- signoffProofSchema,
481
- canonicalJson,
482
- canonicalizeProofBody,
483
- hashProofBody,
484
460
  hashStepOutput,
485
- signoffKeyId,
486
461
  readSignoffKey,
487
- sealProof,
488
- macMatches,
489
- collectToolingFacts,
490
- readInstalledVersion,
491
- tangleDependencyNames,
492
462
  buildSignoffProof,
493
463
  parseSignoffProof,
494
- serializeSignoffProof,
495
464
  formatSignoffSummary,
496
- SIGNOFF_NOTES_REF,
497
- SIGNOFF_NOTES_GIT_CONFIG,
498
465
  attachSignoffProof,
499
- readSignoffProofNote,
500
- listSignoffProofs,
501
- resolveSignoffProof,
502
- SIGNOFF_REQUIRED_STEPS,
503
- requiredStepsFor,
504
466
  verifySignoffProof,
505
467
  verifySignoffAtRev,
506
468
  verifySignoffProofFile,
507
469
  formatSignoffVerification
508
470
  };
509
- //# sourceMappingURL=chunk-N3G3FSM4.js.map
471
+ //# sourceMappingURL=chunk-YXA3U4JZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/signoff/proof-record.ts","../src/signoff/proof-git.ts","../src/signoff/proof-attach.ts","../src/signoff/proof-verify.ts"],"sourcesContent":["/**\n * The sign-off proof record: what a local verification run produces so that a\n * merged commit can be interrogated later.\n *\n * ## Threat model — read this before trusting a proof\n *\n * A proof defends against **accident and drift**:\n * - a check that was never run, or ran and failed, being reported as green\n * - a proof produced over a different tree than the commit carries\n * - a proof copied from one commit onto another\n * - a field edited by hand after the fact\n * - a run whose peer versions or tool versions differ from what a reader assumes\n *\n * It does **not** defend against a malicious operator. The HMAC key is a local\n * file on the same machine that runs the checks, so anyone who can run a\n * sign-off can also mint a proof for checks that never ran. There is no secret\n * server, by requirement. What the seal buys is that a proof cannot be produced\n * or altered by someone WITHOUT that key, and that a proof cannot be silently\n * retargeted, which is the whole failure class an absent CI leaves open.\n *\n * Trust in the numbers themselves comes from the steps being real commands with\n * real exit codes and an output digest — not from cryptography.\n */\nimport { spawnSync } from 'node:child_process'\nimport { createHash, createHmac, timingSafeEqual } from 'node:crypto'\nimport { readFileSync } from 'node:fs'\nimport { hostname, userInfo } from 'node:os'\nimport { join } from 'node:path'\nimport { z } from 'zod'\nimport { computeWorktreeTree, readCommitFacts } from './proof-git'\nimport type { SignoffStepStatus } from './types'\n\n/** Bumped when the canonical body shape changes; a verifier refuses versions it does not know. */\nexport const SIGNOFF_PROOF_VERSION = 1\n\nconst isoString = z.string().regex(/^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(\\.\\d+)?Z$/, 'must be a UTC ISO-8601 timestamp')\nconst sha1Hex = z.string().regex(/^[0-9a-f]{40}$/, 'must be a 40-hex git object id')\nconst sha256Hex = z.string().regex(/^[0-9a-f]{64}$/, 'must be a 64-hex sha256 digest')\n\n/**\n * The runner's step outcomes, restated as proof vocabulary.\n *\n * A status is carried alongside the exit code because they answer different\n * questions and only one of them is safe on its own: a step that was `skipped`,\n * `cancelled` or `blocked` never produced an exit code at all, and defaulting\n * that to `0` is precisely how a run that did not happen reads as a pass.\n *\n * The `satisfies` and the `Exclude` below pin this list to the runner's own\n * union in BOTH directions, so a status added there fails this file's typecheck\n * instead of quietly arriving as an unmodelled string.\n */\nconst SIGNOFF_STEP_STATUSES = ['passed', 'failed', 'skipped', 'cancelled', 'blocked'] as const satisfies readonly SignoffStepStatus[]\ntype UnmodelledStatus = Exclude<SignoffStepStatus, (typeof SIGNOFF_STEP_STATUSES)[number]>\nconst _everyRunnerStatusIsModelled: UnmodelledStatus[] = []\nvoid _everyRunnerStatusIsModelled\n\nconst signoffProofStepSchema = z.object({\n /** Stable id a repo's required-step table refers to (`typecheck`, `test`, `knip`, …). */\n id: z.string().min(1),\n command: z.string().min(1),\n cwd: z.string().min(1),\n /** How the runner judged the step. Only `passed` can satisfy a requirement. */\n status: z.enum(SIGNOFF_STEP_STATUSES),\n exitCode: z.number().int(),\n durationMs: z.number().int().nonnegative(),\n startedAt: isoString,\n /** sha256 of the step's combined stdout+stderr. Logs are not carried; the digest is. */\n outputSha256: sha256Hex,\n})\n\nconst signoffProofPeerSchema = z.object({\n name: z.string().min(1),\n /** `null` when the package is not resolvable on disk — recorded, never guessed. */\n version: z.string().min(1).nullable(),\n})\n\nconst signoffProofSubjectSchema = z.object({\n repo: z.string().min(1),\n commit: sha1Hex,\n /** Tree the checks actually ran against, including uncommitted work. */\n tree: sha1Hex,\n /** Tree the commit itself carries. Equal to `tree` on a clean sign-off. */\n commitTree: sha1Hex,\n parents: z.array(sha1Hex),\n committedAt: isoString,\n})\n\nconst signoffProofBodySchema = z.object({\n proofVersion: z.number().int().positive(),\n subject: signoffProofSubjectSchema,\n signedAt: isoString,\n host: z.object({\n hostname: z.string().min(1),\n platform: z.string().min(1),\n arch: z.string().min(1),\n user: z.string().min(1),\n }),\n tooling: z.object({\n node: z.string().min(1),\n pnpm: z.string().min(1).nullable(),\n peers: z.array(signoffProofPeerSchema),\n }),\n /**\n * Real elapsed time for the whole run. Recorded separately from the steps\n * because the runner schedules them as wide as their dependencies allow, so\n * the sum of step durations is the SERIAL cost and would overstate this.\n */\n wallClockMs: z.number().int().nonnegative(),\n /** Seeds fed to anything non-deterministic, so a reader can reproduce the same run. */\n seeds: z.record(z.string(), z.union([z.string(), z.number()])),\n /** The step ids this run claims were required. The verifier holds the authoritative table. */\n declaredRequired: z.array(z.string().min(1)),\n steps: z.array(signoffProofStepSchema),\n verdict: z.enum(['pass', 'fail']),\n})\n\nconst signoffProofSealSchema = z.object({\n algorithm: z.enum(['sha256', 'hmac-sha256']),\n /** sha256 over the canonical body. Chains the seal to every field, including the commit and tree. */\n bodySha256: sha256Hex,\n /** First 12 hex of sha256(key), so a reader can tell WHICH key sealed this. */\n keyId: z.string().regex(/^[0-9a-f]{12}$/).nullable(),\n mac: sha256Hex.nullable(),\n})\n\nconst signoffProofSchema = z.object({\n body: signoffProofBodySchema,\n seal: signoffProofSealSchema,\n})\n\nexport type SignoffProofStep = z.infer<typeof signoffProofStepSchema>\nexport type SignoffProofPeer = z.infer<typeof signoffProofPeerSchema>\nexport type SignoffProofSubject = z.infer<typeof signoffProofSubjectSchema>\nexport type SignoffProofBody = z.infer<typeof signoffProofBodySchema>\nexport type SignoffProofSeal = z.infer<typeof signoffProofSealSchema>\nexport type SignoffProof = z.infer<typeof signoffProofSchema>\n\ntype CanonicalValue = string | number | boolean | null | readonly CanonicalValue[] | { readonly [key: string]: CanonicalValue }\n\n/**\n * Deterministic JSON: object keys sorted, array order preserved, no whitespace.\n *\n * The seal is a hash over this string, so two readers must produce byte-identical\n * bytes from the same record. `undefined` throws rather than vanishing — a field\n * that silently disappears is a field the hash stops covering.\n */\nexport function canonicalJson(value: CanonicalValue): string {\n if (value === null) return 'null'\n if (typeof value === 'string') return JSON.stringify(value)\n if (typeof value === 'boolean') return value ? 'true' : 'false'\n if (typeof value === 'number') {\n if (!Number.isFinite(value)) throw new Error(`canonicalJson: ${String(value)} is not representable`)\n return JSON.stringify(value)\n }\n if (Array.isArray(value)) return `[${value.map((entry) => canonicalJson(entry)).join(',')}]`\n const record = value as { readonly [key: string]: CanonicalValue }\n const keys = Object.keys(record).sort()\n const fields = keys.map((key) => {\n const entry = record[key]\n if (entry === undefined) throw new Error(`canonicalJson: field ${JSON.stringify(key)} is undefined`)\n return `${JSON.stringify(key)}:${canonicalJson(entry)}`\n })\n return `{${fields.join(',')}}`\n}\n\nexport function canonicalizeProofBody(body: SignoffProofBody): string {\n return canonicalJson(body as unknown as CanonicalValue)\n}\n\nexport function hashProofBody(body: SignoffProofBody): string {\n return createHash('sha256').update(canonicalizeProofBody(body), 'utf8').digest('hex')\n}\n\n/** The digest a runner records for a step's combined output. Shared so both halves agree. */\nexport function hashStepOutput(output: string): string {\n return createHash('sha256').update(output, 'utf8').digest('hex')\n}\n\nexport function signoffKeyId(key: Uint8Array): string {\n return createHash('sha256').update(key).digest('hex').slice(0, 12)\n}\n\n/** Read the local sign-off key. Throws when absent — an unreadable key is never a silent downgrade to unsealed. */\nexport function readSignoffKey(path: string): Uint8Array {\n const raw = readFileSync(path)\n if (raw.byteLength < 16) throw new Error(`sign-off key at ${path} is ${raw.byteLength} bytes; at least 16 are required`)\n return new Uint8Array(raw)\n}\n\nexport function sealProof(body: SignoffProofBody, key?: Uint8Array): SignoffProof {\n const canonical = canonicalizeProofBody(body)\n const bodySha256 = createHash('sha256').update(canonical, 'utf8').digest('hex')\n if (key === undefined) {\n return { body, seal: { algorithm: 'sha256', bodySha256, keyId: null, mac: null } }\n }\n return {\n body,\n seal: {\n algorithm: 'hmac-sha256',\n bodySha256,\n keyId: signoffKeyId(key),\n mac: createHmac('sha256', key).update(canonical, 'utf8').digest('hex'),\n },\n }\n}\n\n/** Constant-time comparison of two hex digests of equal length. */\nexport function macMatches(expected: string, actual: string): boolean {\n if (expected.length !== actual.length) return false\n return timingSafeEqual(Buffer.from(expected, 'hex'), Buffer.from(actual, 'hex'))\n}\n\ninterface ToolingFactsInput {\n readonly repoDir: string\n /** Package names whose resolved on-disk version belongs in the proof. */\n readonly peerNames: readonly string[]\n}\n\n/**\n * The versions that actually resolved on disk — the field CI's clean install\n * makes trustworthy and a warm local `node_modules` does not. A below-floor peer\n * is invisible to typecheck and to a green suite (it fails at the wire call), so\n * the proof records what was really there rather than what the manifest asks for.\n */\nfunction collectToolingFacts(input: ToolingFactsInput): SignoffProofBody['tooling'] {\n return {\n node: process.version,\n pnpm: readPnpmVersion(input.repoDir),\n peers: input.peerNames.map((name) => ({ name, version: readInstalledVersion(input.repoDir, name) })),\n }\n}\n\n/** `null` when pnpm is not on PATH — a missing version is recorded as missing, never as a guess. */\nfunction readPnpmVersion(repoDir: string): string | null {\n const result = spawnSync('pnpm', ['--version'], { cwd: repoDir, encoding: 'utf8' })\n if (result.error || result.status !== 0) return null\n return result.stdout.trim()\n}\n\n/** Resolved version from the consumer's own `node_modules`, or `null` when the package is not there. */\nfunction readInstalledVersion(repoDir: string, packageName: string): string | null {\n try {\n const manifest = JSON.parse(readFileSync(join(repoDir, 'node_modules', packageName, 'package.json'), 'utf8')) as { version?: unknown }\n return typeof manifest.version === 'string' ? manifest.version : null\n } catch {\n return null\n }\n}\n\n/** Every `@tangle-network/*` name the repo declares as a peer or a dependency. */\nfunction tangleDependencyNames(repoDir: string): readonly string[] {\n const manifest = JSON.parse(readFileSync(join(repoDir, 'package.json'), 'utf8')) as {\n dependencies?: Record<string, string>\n peerDependencies?: Record<string, string>\n devDependencies?: Record<string, string>\n }\n const names = new Set<string>()\n for (const block of [manifest.dependencies, manifest.peerDependencies, manifest.devDependencies]) {\n for (const name of Object.keys(block ?? {})) {\n if (name.startsWith('@tangle-network/')) names.add(name)\n }\n }\n return [...names].sort()\n}\n\nexport interface BuildSignoffProofInput {\n readonly repoDir: string\n /** Repo identity the verifier's required-step table is keyed on. */\n readonly repo: string\n /** Revision the sign-off is for; defaults to `HEAD`. */\n readonly rev?: string\n readonly steps: readonly SignoffProofStep[]\n /** Measured elapsed time for the run. Required — it is not derivable from the steps. */\n readonly wallClockMs: number\n /** Step ids this run treated as required. Checked against the verifier's table. */\n readonly declaredRequired: readonly string[]\n readonly seeds: Readonly<Record<string, string | number>>\n readonly peerNames?: readonly string[]\n readonly key?: Uint8Array\n readonly now?: Date\n}\n\nexport function buildSignoffProof(input: BuildSignoffProofInput): SignoffProof {\n const facts = readCommitFacts(input.repoDir, input.rev ?? 'HEAD')\n const body: SignoffProofBody = {\n proofVersion: SIGNOFF_PROOF_VERSION,\n subject: {\n repo: input.repo,\n commit: facts.commit,\n tree: computeWorktreeTree(input.repoDir),\n commitTree: facts.commitTree,\n parents: [...facts.parents],\n committedAt: facts.committedAt,\n },\n signedAt: (input.now ?? new Date()).toISOString(),\n wallClockMs: Math.max(0, Math.round(input.wallClockMs)),\n host: { hostname: hostname(), platform: process.platform, arch: process.arch, user: userInfo().username },\n tooling: collectToolingFacts({ repoDir: input.repoDir, peerNames: input.peerNames ?? tangleDependencyNames(input.repoDir) }),\n seeds: { ...input.seeds },\n declaredRequired: [...input.declaredRequired],\n steps: input.steps.map((step) => ({ ...step })),\n verdict: input.steps.every((step) => step.status === 'passed' && step.exitCode === 0) ? 'pass' : 'fail',\n }\n return sealProof(signoffProofBodySchema.parse(body), input.key)\n}\n\n/** Parse an untrusted proof document, failing loud on any shape the verifier cannot reason about. */\nexport function parseSignoffProof(json: string): SignoffProof {\n return signoffProofSchema.parse(JSON.parse(json))\n}\n\nexport function serializeSignoffProof(proof: SignoffProof): string {\n return `${JSON.stringify(proof, null, 2)}\\n`\n}\n\nfunction formatDuration(ms: number): string {\n const seconds = Math.round(ms / 1000)\n if (seconds < 60) return `${seconds}s`\n return `${Math.floor(seconds / 60)}m${String(seconds % 60).padStart(2, '0')}s`\n}\n\n/**\n * The single line an operator pastes into a PR or a merge commit: how many steps\n * ran, how long it took, the seeds, and the verdict.\n */\nexport function formatSignoffSummary(proof: SignoffProof): string {\n const { body, seal } = proof\n const passed = body.steps.filter((step) => step.status === 'passed' && step.exitCode === 0).length\n const serialMs = body.steps.reduce((total, step) => total + step.durationMs, 0)\n // Wall clock is the number an operator is deciding on. The serial total rides\n // alongside it only when the schedule actually overlapped, so a parallel run\n // shows its saving instead of claiming one.\n const wall = serialMs > body.wallClockMs ? `${formatDuration(body.wallClockMs)} (serial ${formatDuration(serialMs)})` : formatDuration(body.wallClockMs)\n const seeds = Object.entries(body.seeds)\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([name, value]) => `${name}=${value}`)\n .join(' ')\n const seal_ = seal.algorithm === 'hmac-sha256' ? `sealed ${seal.algorithm} key ${seal.keyId ?? 'unknown'}` : `unsealed sha256 ${seal.bodySha256.slice(0, 12)}`\n const dirty = body.subject.tree === body.subject.commitTree ? '' : ' DIRTY-TREE'\n return [\n `signoff ${body.verdict}${dirty}`,\n `${passed}/${body.steps.length} steps`,\n wall,\n `${body.subject.repo}@${body.subject.commit.slice(0, 9)} tree ${body.subject.tree.slice(0, 9)}`,\n seeds.length === 0 ? 'seeds none' : `seeds ${seeds}`,\n `node ${body.tooling.node} pnpm ${body.tooling.pnpm ?? 'unresolved'}`,\n seal_,\n body.signedAt,\n ].join(' · ')\n}\n","/**\n * The git facts a sign-off proof is bound to, read straight from the object\n * database rather than reported by the process that ran the checks.\n *\n * Everything here is re-derivable by anyone holding the repository, which is\n * what makes the proof checkable: a verifier never trusts a field in the JSON,\n * it recomputes the same value with the same commands and compares.\n */\nimport { spawnSync } from 'node:child_process'\nimport { mkdtempSync, rmSync } from 'node:fs'\nimport { tmpdir } from 'node:os'\nimport { join } from 'node:path'\n\n/** A git invocation that exited non-zero, carrying stderr so the caller can act. */\nexport class SignoffGitError extends Error {\n readonly args: readonly string[]\n readonly status: number | null\n readonly stderr: string\n\n constructor(args: readonly string[], status: number | null, stderr: string) {\n super(`git ${args.join(' ')} exited ${status ?? 'null'}: ${stderr.trim()}`)\n this.name = 'SignoffGitError'\n this.args = args\n this.status = status\n this.stderr = stderr\n }\n}\n\nexport interface GitResult {\n readonly status: number | null\n readonly stdout: string\n readonly stderr: string\n}\n\n/**\n * Run git and hand back the raw result. `GIT_OPTIONAL_LOCKS=0` keeps a read\n * from touching the index while another agent works the same worktree — this\n * repo is shared by concurrent sessions.\n */\nexport function runGit(repoDir: string, args: readonly string[], options: { readonly input?: string; readonly env?: Readonly<Record<string, string>> } = {}): GitResult {\n const result = spawnSync('git', [...args], {\n cwd: repoDir,\n encoding: 'utf8',\n input: options.input,\n env: { ...process.env, GIT_OPTIONAL_LOCKS: '0', ...options.env },\n maxBuffer: 128 * 1024 * 1024,\n })\n if (result.error) throw new Error(`git ${args.join(' ')} could not run: ${result.error.message}`)\n return { status: result.status, stdout: result.stdout ?? '', stderr: result.stderr ?? '' }\n}\n\n/** Run git, or throw. Trailing newline is stripped — every caller wants the value, not the line. */\nexport function gitText(repoDir: string, args: readonly string[], options: { readonly input?: string; readonly env?: Readonly<Record<string, string>> } = {}): string {\n const result = runGit(repoDir, args, options)\n if (result.status !== 0) throw new SignoffGitError(args, result.status, result.stderr)\n return result.stdout.replace(/\\n$/, '')\n}\n\n/**\n * Is `ancestor` reachable from `descendant`? A port rather than a direct call so\n * the verifier stays a pure function over facts a caller supplies.\n */\nexport type IsAncestorFn = (ancestor: string, descendant: string) => boolean\n\nexport function gitIsAncestor(repoDir: string): IsAncestorFn {\n return (ancestor, descendant) => {\n const result = runGit(repoDir, ['merge-base', '--is-ancestor', ancestor, descendant])\n if (result.status === 0) return true\n if (result.status === 1) return false\n throw new SignoffGitError(['merge-base', '--is-ancestor', ancestor, descendant], result.status, result.stderr)\n }\n}\n\nexport interface CommitFacts {\n /** 40-hex commit id. */\n readonly commit: string\n /** 40-hex id of the tree the COMMIT points at. */\n readonly commitTree: string\n readonly parents: readonly string[]\n /** Committer date, UTC ISO-8601 with a `Z` suffix. */\n readonly committedAt: string\n}\n\n/** Resolve a revision to the commit it names, failing loud on an unknown rev. */\nexport function resolveCommit(repoDir: string, rev: string): string {\n return gitText(repoDir, ['rev-parse', '--verify', `${rev}^{commit}`])\n}\n\nexport function readCommitFacts(repoDir: string, rev: string): CommitFacts {\n const commit = resolveCommit(repoDir, rev)\n const record = gitText(repoDir, ['show', '--no-patch', '--format=%T%n%P%n%cI', commit])\n const [tree, parents, committedAt] = record.split('\\n')\n if (tree === undefined || parents === undefined || committedAt === undefined) {\n throw new Error(`git show returned an unreadable record for ${commit}: ${JSON.stringify(record)}`)\n }\n return {\n commit,\n commitTree: tree,\n parents: parents.length === 0 ? [] : parents.split(' '),\n committedAt: new Date(committedAt).toISOString(),\n }\n}\n\n/**\n * Hash the tree the checks actually ran against, including uncommitted and\n * untracked (non-ignored) files.\n *\n * This is the field that makes drift detectable. A sign-off that ran over an\n * edited worktree produces a tree id no commit carries, so it cannot verify\n * against the commit it claims — which is the intended outcome, not a bug.\n *\n * Written through a throwaway index (`GIT_INDEX_FILE`) so a concurrent agent's\n * staged work in the real index is neither read nor disturbed. `git add -A`\n * honours `.gitignore`, so `node_modules` / `dist` stay out.\n */\nexport function computeWorktreeTree(repoDir: string): string {\n const scratch = mkdtempSync(join(tmpdir(), 'agent-app-signoff-index-'))\n const indexFile = join(scratch, 'index')\n try {\n const env = { GIT_INDEX_FILE: indexFile }\n gitText(repoDir, ['add', '-A', '--'], { env })\n return gitText(repoDir, ['write-tree'], { env })\n } finally {\n rmSync(scratch, { recursive: true, force: true })\n }\n}\n","/**\n * Attaching a proof to the commit it verified.\n *\n * ## Why `git notes`, and what the two rejected carriers cost\n *\n * **A commit-message trailer** cannot work for the field that matters. The\n * trailer is part of the commit object, so writing it changes the SHA the proof\n * claims to cover — the proof would have to be produced before the commit it\n * names exists. Amending re-writes the SHA again, and GitHub's squash-merge\n * rebuilds the message from the PR body, so the trailer does not survive the one\n * transition it would need to.\n *\n * **A committed artifact directory** (`.signoff/<sha>.json`) changes the tree,\n * so the tree hash inside the proof can never cover the file carrying it. It\n * also lands in every diff, and it is exactly the \"file someone can forget\"\n * shape the requirement rules out — deleting it is a normal-looking change.\n *\n * **`git notes` wins because it is the only carrier that is addressed BY the\n * SHA without being part of it.** The note is written after the commit exists,\n * it changes neither the SHA nor the tree, and `git notes show <sha>` is a\n * definite yes/no for any commit anyone can name.\n *\n * ## The tradeoff `git notes` really carries, and how this module answers it\n *\n * Notes are attached to a commit id, and a rebase or a squash-merge produces a\n * NEW commit id, so the note does not follow by itself. Two mechanisms close\n * that gap, and both are needed:\n *\n * 1. `notes.rewriteRef` (see `SIGNOFF_NOTES_GIT_CONFIG`) makes local rebase and\n * amend copy the note forward automatically.\n * 2. `resolveSignoffProof` falls back to a **tree scan**: it reads every proof\n * under the notes ref and matches on the TREE the commit carries. A rebase\n * or a squash of one branch onto an unchanged base preserves the tree, so\n * the content that was verified is still provably the content that merged,\n * even though the SHA changed. The verifier reports which binding it used —\n * `exact` or `tree-equivalent` — and never conflates them.\n *\n * Notes are neither pushed nor fetched by default; `SIGNOFF_NOTES_GIT_CONFIG`\n * carries the refspecs that fix that, and a missing note is reported as\n * `not-found`, never as a pass.\n */\nimport { parseSignoffProof, type SignoffProof } from './proof-record'\nimport { gitText, readCommitFacts, resolveCommit, runGit, SignoffGitError } from './proof-git'\n\nexport const SIGNOFF_NOTES_REF = 'refs/notes/signoff'\n\n/**\n * The git configuration a repo needs for notes to travel. Emitted by the CLI\n * when a proof cannot be found, because the usual cause is an unconfigured repo\n * rather than an unsigned commit.\n */\nexport const SIGNOFF_NOTES_GIT_CONFIG: readonly string[] = [\n `git config --add remote.origin.fetch '+${SIGNOFF_NOTES_REF}:${SIGNOFF_NOTES_REF}'`,\n `git config --add remote.origin.push '${SIGNOFF_NOTES_REF}'`,\n `git config notes.rewriteRef '${SIGNOFF_NOTES_REF}'`,\n 'git config notes.rewrite.amend true',\n 'git config notes.rewrite.rebase true',\n]\n\nexport interface AttachSignoffProofInput {\n readonly repoDir: string\n readonly proof: SignoffProof\n /** Revision to annotate; defaults to the commit the proof names. */\n readonly rev?: string\n /** Replace an existing note. Off by default so a second sign-off cannot quietly overwrite the first. */\n readonly overwrite?: boolean\n}\n\nexport interface AttachedSignoffProof {\n readonly commit: string\n readonly ref: string\n}\n\nexport function attachSignoffProof(input: AttachSignoffProofInput): AttachedSignoffProof {\n const commit = resolveCommit(input.repoDir, input.rev ?? input.proof.body.subject.commit)\n const args = ['notes', `--ref=${SIGNOFF_NOTES_REF}`, 'add']\n if (input.overwrite === true) args.push('-f')\n args.push('-F', '-', commit)\n const result = runGit(input.repoDir, args, { input: `${JSON.stringify(input.proof, null, 2)}\\n` })\n if (result.status !== 0) throw new SignoffGitError(args, result.status, result.stderr)\n return { commit, ref: SIGNOFF_NOTES_REF }\n}\n\nexport type SignoffProofLookup =\n | { readonly found: true; readonly proof: SignoffProof; readonly commit: string; readonly binding: 'exact' | 'tree-equivalent' }\n | { readonly found: false; readonly commit: string }\n\n/** The note attached to exactly this commit, or `found: false`. Never searches. */\nexport function readSignoffProofNote(repoDir: string, rev: string): SignoffProofLookup {\n const commit = resolveCommit(repoDir, rev)\n const result = runGit(repoDir, ['notes', `--ref=${SIGNOFF_NOTES_REF}`, 'show', commit])\n if (result.status !== 0) return { found: false, commit }\n return { found: true, proof: parseSignoffProof(result.stdout), commit, binding: 'exact' }\n}\n\n/** Every proof stored under the notes ref, paired with the commit it annotates. */\nexport function listSignoffProofs(repoDir: string): readonly { readonly commit: string; readonly proof: SignoffProof }[] {\n const listed = runGit(repoDir, ['notes', `--ref=${SIGNOFF_NOTES_REF}`, 'list'])\n if (listed.status !== 0) return []\n const out: { commit: string; proof: SignoffProof }[] = []\n for (const line of listed.stdout.split('\\n')) {\n const [noteBlob, commit] = line.trim().split(/\\s+/)\n if (noteBlob === undefined || commit === undefined) continue\n out.push({ commit, proof: parseSignoffProof(gitText(repoDir, ['cat-file', 'blob', noteBlob])) })\n }\n return out\n}\n\n/**\n * Answer \"was this SHA signed off\" for any commit, including one produced by a\n * rebase or a squash-merge of the branch that was actually verified.\n *\n * Exact note first. Then the tree scan — a proof whose `commitTree` equals this\n * commit's tree verified byte-identical content, which is the strongest claim\n * available once the SHA has been rewritten. The binding is returned so a caller\n * can tell the two apart; nothing here treats them as the same thing.\n */\nexport function resolveSignoffProof(repoDir: string, rev: string): SignoffProofLookup {\n const direct = readSignoffProofNote(repoDir, rev)\n if (direct.found) return direct\n const facts = readCommitFacts(repoDir, rev)\n for (const entry of listSignoffProofs(repoDir)) {\n if (entry.proof.body.subject.commitTree === facts.commitTree) {\n return { found: true, proof: entry.proof, commit: facts.commit, binding: 'tree-equivalent' }\n }\n }\n return { found: false, commit: facts.commit }\n}\n","/**\n * `verify-proof` — the half a reader runs, on a machine that did not produce\n * the proof, to decide whether a commit was really signed off.\n *\n * The verifier never believes the document. Every claim it can re-derive, it\n * re-derives from git and compares; every claim it cannot re-derive is reported\n * as recorded, not as checked. The one field the proof is deliberately not\n * allowed to own is **which steps were required**: a run that declares its own\n * bar can declare an empty one, so the authoritative table lives here and a\n * proof declaring fewer requirements than its repo's table fails as\n * `lowered-bar`.\n */\nimport type { CommitFacts, IsAncestorFn } from './proof-git'\nimport { gitIsAncestor, readCommitFacts } from './proof-git'\nimport { readFileSync } from 'node:fs'\nimport { resolveSignoffProof, SIGNOFF_NOTES_GIT_CONFIG } from './proof-attach'\nimport { canonicalizeProofBody, hashProofBody, macMatches, parseSignoffProof, SIGNOFF_PROOF_VERSION, type SignoffProof } from './proof-record'\nimport { createHmac } from 'node:crypto'\n\n/**\n * The steps a repo's sign-off MUST cover, transcribed from each repo's CI job.\n * A runner is free to run more; it may never run fewer.\n *\n * agent-app .github/workflows/ci.yml\n * tax-agent .github/workflows/deploy.yml (the `ci` job)\n * legal-agent .github/workflows/deploy.yml (the `ci` job)\n */\nexport const SIGNOFF_REQUIRED_STEPS: Readonly<Record<string, readonly string[]>> = {\n 'agent-app': ['install', 'typecheck', 'test:gates', 'test', 'build', 'test:generated', 'knip'],\n 'tax-agent': ['install', 'peer-check', 'typecheck', 'test', 'toolkit-deps', 'toolkit-test', 'build', 'worker-startup'],\n 'legal-agent': ['install', 'peer-check', 'typegen', 'typecheck', 'test', 'build:check'],\n}\n\n/** Throws for a repo with no table — an unrecognised repo has no bar, and no bar is not a pass. */\nexport function requiredStepsFor(repo: string): readonly string[] {\n const steps = SIGNOFF_REQUIRED_STEPS[repo]\n if (steps === undefined) {\n throw new Error(`no required-step table for repo ${JSON.stringify(repo)}; known: ${Object.keys(SIGNOFF_REQUIRED_STEPS).sort().join(', ')}`)\n }\n return steps\n}\n\nexport type SignoffFailureCode =\n | 'unsupported-version'\n | 'body-tampered'\n | 'mac-missing'\n | 'mac-invalid'\n | 'unknown-repo'\n | 'repo-mismatch'\n | 'tree-mismatch'\n | 'dirty-worktree'\n | 'commit-unbound'\n | 'missing-required-step'\n | 'lowered-bar'\n | 'duplicate-step'\n | 'step-failed'\n | 'verdict-fail'\n | 'stale-proof'\n\nexport interface SignoffFailure {\n readonly code: SignoffFailureCode\n readonly detail: string\n}\n\n/** How the proof attaches to the commit that was asked about. */\nexport type SignoffCommitBinding =\n /** The proof names this exact commit. */\n | 'exact'\n /** A different commit id, but byte-identical content — a rebase or a squash of what was verified. */\n | 'tree-equivalent'\n /** Neither. The proof does not describe this commit. */\n | 'none'\n\nexport interface SignoffVerification {\n readonly ok: boolean\n readonly commitBinding: SignoffCommitBinding\n /** True only when a key was supplied AND the mac over the canonical body matched. */\n readonly macChecked: boolean\n readonly failures: readonly SignoffFailure[]\n readonly proof: SignoffProof\n readonly target: CommitFacts\n readonly requiredSteps: readonly string[]\n}\n\nexport interface VerifySignoffProofOptions {\n /** The commit the reader is asking about, read from git — never from the proof. */\n readonly target: CommitFacts\n /**\n * Reachability in the target's history. Required, because the clock cannot\n * answer staleness on its own: git records committer time to the SECOND, so a\n * proof and the commit it is being replayed onto routinely share a timestamp.\n * Ancestry is exact and clock-free.\n */\n readonly isAncestor: IsAncestorFn\n /** Local HMAC key. Omitted, the mac is reported unchecked rather than assumed good. */\n readonly key?: Uint8Array\n /** Overrides the built-in table. Supplying `[]` is a deliberate no-bar check, and says so. */\n readonly requiredSteps?: readonly string[]\n /** Repo identity the caller expects; a mismatch against the proof is a failure, not a rename. */\n readonly expectRepo?: string\n}\n\nexport function verifySignoffProof(proof: SignoffProof, options: VerifySignoffProofOptions): SignoffVerification {\n const failures: SignoffFailure[] = []\n const { body, seal } = proof\n const target = options.target\n\n if (body.proofVersion !== SIGNOFF_PROOF_VERSION) {\n failures.push({ code: 'unsupported-version', detail: `proof declares version ${body.proofVersion}; this verifier reads ${SIGNOFF_PROOF_VERSION}` })\n }\n\n const recomputed = hashProofBody(body)\n if (recomputed !== seal.bodySha256) {\n failures.push({ code: 'body-tampered', detail: `seal claims body sha256 ${seal.bodySha256}; the body hashes to ${recomputed}` })\n }\n\n let macChecked = false\n if (options.key !== undefined) {\n if (seal.mac === null) {\n failures.push({ code: 'mac-missing', detail: 'a key was supplied but the proof carries no mac; it was produced unsealed' })\n } else {\n const expected = createHmac('sha256', options.key).update(canonicalizeProofBody(body), 'utf8').digest('hex')\n if (macMatches(expected, seal.mac)) macChecked = true\n else failures.push({ code: 'mac-invalid', detail: `mac does not verify under the supplied key (proof keyId ${seal.keyId ?? 'none'})` })\n }\n }\n\n if (options.expectRepo !== undefined && options.expectRepo !== body.subject.repo) {\n failures.push({ code: 'repo-mismatch', detail: `proof is for repo ${body.subject.repo}; ${options.expectRepo} was requested` })\n }\n\n let requiredSteps: readonly string[] = options.requiredSteps ?? []\n if (options.requiredSteps === undefined) {\n const known = SIGNOFF_REQUIRED_STEPS[body.subject.repo]\n if (known === undefined) {\n failures.push({ code: 'unknown-repo', detail: `no required-step table for ${body.subject.repo}; a repo with no declared bar cannot be signed off` })\n } else {\n requiredSteps = known\n }\n }\n\n const seen = new Map<string, number>()\n for (const step of body.steps) seen.set(step.id, (seen.get(step.id) ?? 0) + 1)\n for (const [id, count] of seen) {\n if (count > 1) failures.push({ code: 'duplicate-step', detail: `step ${id} appears ${count} times; a repeated id makes coverage ambiguous` })\n }\n\n const missing = requiredSteps.filter((id) => !seen.has(id))\n if (missing.length > 0) {\n failures.push({ code: 'missing-required-step', detail: `required step(s) never ran: ${missing.join(', ')}` })\n }\n\n const declared = new Set(body.declaredRequired)\n const understated = requiredSteps.filter((id) => !declared.has(id))\n if (understated.length > 0) {\n failures.push({ code: 'lowered-bar', detail: `proof declares a smaller required set than ${body.subject.repo}'s table; missing: ${understated.join(', ')}` })\n }\n\n for (const step of body.steps) {\n if (step.status !== 'passed') {\n failures.push({ code: 'step-failed', detail: `step ${step.id} is ${step.status}, not passed (exit ${step.exitCode}, ${step.command})` })\n } else if (step.exitCode !== 0) {\n // An internally inconsistent record: the runner called it passed and the\n // process disagreed. Reported rather than resolved in either direction.\n failures.push({ code: 'step-failed', detail: `step ${step.id} claims status passed but exited ${step.exitCode} (${step.command})` })\n }\n }\n\n if (body.verdict !== 'pass') {\n failures.push({ code: 'verdict-fail', detail: `the run recorded verdict ${body.verdict}` })\n }\n\n if (body.subject.tree !== body.subject.commitTree) {\n failures.push({\n code: 'dirty-worktree',\n detail: `checks ran against tree ${body.subject.tree} while the commit carries ${body.subject.commitTree}; uncommitted work was in the tree`,\n })\n }\n\n if (body.subject.commitTree !== target.commitTree) {\n failures.push({ code: 'tree-mismatch', detail: `proof covers tree ${body.subject.commitTree}; ${target.commit} carries ${target.commitTree}` })\n }\n\n const commitBinding: SignoffCommitBinding =\n body.subject.commit === target.commit ? 'exact' : body.subject.commitTree === target.commitTree ? 'tree-equivalent' : 'none'\n if (commitBinding === 'none') {\n failures.push({ code: 'commit-unbound', detail: `proof names commit ${body.subject.commit}, which is neither ${target.commit} nor its content` })\n }\n\n // Staleness, checked two ways because neither is sufficient alone.\n //\n // The clock catches a proof back-dated relative to the commit it names. Its\n // resolution is git's, one second, so it is deliberately not the only check.\n const signedAt = Date.parse(body.signedAt)\n if (signedAt < Date.parse(body.subject.committedAt)) {\n failures.push({ code: 'stale-proof', detail: `signed at ${body.signedAt}, before the commit it names was written at ${body.subject.committedAt}` })\n }\n // Ancestry catches the case the clock cannot: a proof accepted on CONTENT\n // grounds for a LATER commit in the same history. A rebase or a squash\n // replaces the verified commit, so it is not reachable from the result — but a\n // revert restores an old tree on top of work nobody verified, and there the\n // proof's commit IS an ancestor. Same tree, different history, unverified\n // commits in between.\n if (commitBinding === 'tree-equivalent' && options.isAncestor(body.subject.commit, target.commit)) {\n failures.push({\n code: 'stale-proof',\n detail: `${body.subject.commit} is an ancestor of ${target.commit}; the tree matches only because later work was undone, and that work was never signed off`,\n })\n }\n\n return { ok: failures.length === 0, commitBinding, macChecked, failures, proof, target, requiredSteps }\n}\n\nexport interface VerifySignoffAtRevInput {\n readonly repoDir: string\n readonly rev: string\n readonly key?: Uint8Array\n readonly requiredSteps?: readonly string[]\n readonly expectRepo?: string\n}\n\ntype SignoffLookupFailure = { readonly found: false; readonly commit: string; readonly hint: readonly string[] }\nexport type SignoffVerifyOutcome = ({ readonly found: true } & SignoffVerification) | SignoffLookupFailure\n\n/** Verify by revision: find the proof attached to that SHA (or to its content), then check it. */\nexport function verifySignoffAtRev(input: VerifySignoffAtRevInput): SignoffVerifyOutcome {\n const lookup = resolveSignoffProof(input.repoDir, input.rev)\n const target = readCommitFacts(input.repoDir, input.rev)\n if (!lookup.found) return { found: false, commit: lookup.commit, hint: SIGNOFF_NOTES_GIT_CONFIG }\n return {\n found: true,\n ...verifySignoffProof(lookup.proof, {\n target,\n isAncestor: gitIsAncestor(input.repoDir),\n key: input.key,\n requiredSteps: input.requiredSteps,\n expectRepo: input.expectRepo,\n }),\n }\n}\n\nexport interface VerifySignoffFileInput {\n readonly repoDir: string\n readonly file: string\n /**\n * The commit to check the file against. REQUIRED, and deliberately not\n * defaulted to the commit the proof names: a proof checked against its own\n * subject can never fail the commit binding, which is a check that reads as\n * strong and cannot catch anything. The reader always states what they are\n * asking about.\n */\n readonly rev: string\n readonly key?: Uint8Array\n readonly requiredSteps?: readonly string[]\n readonly expectRepo?: string\n}\n\n/** Verify a proof document on disk. The repo is still required — \"matches the tree it claims\" is not answerable without it. */\nexport function verifySignoffProofFile(input: VerifySignoffFileInput): SignoffVerification {\n const proof = parseSignoffProof(readFileSync(input.file, 'utf8'))\n const target = readCommitFacts(input.repoDir, input.rev)\n return verifySignoffProof(proof, {\n target,\n isAncestor: gitIsAncestor(input.repoDir),\n key: input.key,\n requiredSteps: input.requiredSteps,\n expectRepo: input.expectRepo,\n })\n}\n\n/** One line per failure, prefixed by its code, in the order the checks ran. */\nexport function formatSignoffVerification(result: SignoffVerification): string {\n const head = result.ok\n ? `VERIFIED ${result.proof.body.subject.repo}@${result.target.commit.slice(0, 9)} binding=${result.commitBinding} mac=${result.macChecked ? 'checked' : 'unchecked'}`\n : `REJECTED ${result.proof.body.subject.repo}@${result.target.commit.slice(0, 9)} binding=${result.commitBinding} (${result.failures.length} failure${result.failures.length === 1 ? '' : 's'})`\n return [head, ...result.failures.map((failure) => ` ${failure.code}: ${failure.detail}`)].join('\\n')\n}\n"],"mappings":"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","names":["spawnSync","join","spawnSync","join","readFileSync","createHmac","createHmac","readFileSync"]}
@@ -83,15 +83,15 @@ import {
83
83
  setPagePropsCommand,
84
84
  ungroupElementCommand
85
85
  } from "../chunk-X5DQ5QOP.js";
86
- import {
87
- DesignCanvasChromeLazy,
88
- DesignCanvasLazy
89
- } from "../chunk-AGCRCOQH.js";
90
86
  import {
91
87
  assertSceneMediaSrc,
92
88
  bleedAwareExportBounds,
93
89
  scaleForPreset
94
90
  } from "../chunk-DRB77FYL.js";
91
+ import {
92
+ DesignCanvasChromeLazy,
93
+ DesignCanvasLazy
94
+ } from "../chunk-AWPK7ZDS.js";
95
95
  import "../chunk-F7VH4G5C.js";
96
96
 
97
97
  // src/design-canvas-react/components/CanvasInsertPanel.tsx
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  DesignCanvasChromeLazy,
3
3
  DesignCanvasLazy
4
- } from "../chunk-AGCRCOQH.js";
4
+ } from "../chunk-AWPK7ZDS.js";
5
5
  export {
6
6
  DesignCanvasChromeLazy,
7
7
  DesignCanvasLazy
@@ -4,7 +4,11 @@ import {
4
4
  checkLegibility,
5
5
  formatLegibilityReport,
6
6
  legibilityReportToJson
7
- } from "../chunk-XEA5EG6P.js";
7
+ } from "../chunk-EPZYYWCT.js";
8
+ import "../chunk-EMLGWEHV.js";
9
+ import {
10
+ invokedAsScript
11
+ } from "../chunk-C3CAYGGQ.js";
8
12
 
9
13
  // src/legibility/cli.ts
10
14
  import { existsSync } from "fs";
@@ -130,14 +134,16 @@ async function main() {
130
134
  `);
131
135
  return report.ok ? 0 : 1;
132
136
  }
133
- try {
134
- process.exit(await main());
135
- } catch (error) {
136
- const message = error instanceof Error ? error.message : String(error);
137
- process.stderr.write(`agent-app-legibility-check: ${message}
137
+ if (invokedAsScript(import.meta.url, process.argv[1])) {
138
+ try {
139
+ process.exit(await main());
140
+ } catch (error) {
141
+ const message = error instanceof Error ? error.message : String(error);
142
+ process.stderr.write(`agent-app-legibility-check: ${message}
138
143
  `);
139
- if (error instanceof LegibilityUsageError) process.stderr.write(`${USAGE}
144
+ if (error instanceof LegibilityUsageError) process.stderr.write(`${USAGE}
140
145
  `);
141
- process.exit(2);
146
+ process.exit(2);
147
+ }
142
148
  }
143
149
  //# sourceMappingURL=cli.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/legibility/cli.ts","../../src/legibility/cli-args.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * `agent-app-legibility-check` — fail a product's CI when a screen leaves its\n * reader guessing.\n *\n * Runs in the PRODUCT's repo, over the product's own source, next to typecheck:\n *\n * \"scripts\": {\n * \"legibility\": \"agent-app-legibility-check --src src --routes src/routes.ts --nav src/components/workspace-sidebar.tsx\"\n * }\n *\n * Flags (repeatable where a list makes sense):\n * --src <dir> source directory to scan (required unless the config supplies it)\n * --routes <file> react-router route config, for the unreachable-capability check\n * --nav <file> a file whose path literals are navigation entries\n * --ignore <substr> skip paths containing this substring\n * --skip <check> turn one check off wholesale (prefer a per-line suppression)\n * --config <file> a legibility.config.mjs default-exporting the config\n * (default: ./legibility.config.mjs when present)\n * --json print the report as JSON instead of text\n * --list-suppressions print every honoured suppression with its reason\n *\n * Exit 0 with no findings, 1 with findings, 2 on a usage or config error — the\n * same contract as `agent-app-preflight` and `agent-app-peer-check`.\n */\n\nimport { existsSync } from 'node:fs'\nimport { resolve } from 'node:path'\nimport { pathToFileURL } from 'node:url'\n\nimport { DEFAULT_CONFIG_FILE, LegibilityUsageError, mergeConfig, parseArgs, USAGE } from './cli-args'\nimport { checkLegibility } from './index'\nimport { formatLegibilityReport, legibilityReportToJson } from './report'\nimport type { LegibilityConfig } from './types'\n\nasync function loadConfigFile(path: string | undefined): Promise<Partial<LegibilityConfig>> {\n const explicit = path !== undefined\n const resolved = resolve(process.cwd(), path ?? DEFAULT_CONFIG_FILE)\n if (!existsSync(resolved)) {\n if (explicit) throw new LegibilityUsageError(`no config at ${resolved}`)\n return {}\n }\n const module: unknown = await import(pathToFileURL(resolved).href)\n const exported = (module as { default?: unknown; config?: unknown }).default ?? (module as { config?: unknown }).config\n const value = typeof exported === 'function' ? await (exported as () => Promise<unknown>)() : exported\n if (value === null || typeof value !== 'object') {\n throw new LegibilityUsageError(`${resolved} must default-export a legibility config object`)\n }\n return value as Partial<LegibilityConfig>\n}\n\nasync function main(): Promise<number> {\n const args = parseArgs(process.argv.slice(2))\n if (args.help) {\n process.stdout.write(`${USAGE}\\n`)\n return 0\n }\n\n const config = mergeConfig(await loadConfigFile(args.config), args)\n if (config.srcDirs.length === 0) {\n throw new LegibilityUsageError('at least one --src <dir> is required (or srcDirs in the config file)')\n }\n\n const report = checkLegibility(config)\n const text = args.json\n ? legibilityReportToJson(report)\n : formatLegibilityReport(report, { listSuppressions: args.listSuppressions })\n\n if (report.ok) process.stdout.write(`${text}\\n`)\n else process.stderr.write(`${text}\\n`)\n return report.ok ? 0 : 1\n}\n\ntry {\n process.exit(await main())\n} catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n process.stderr.write(`agent-app-legibility-check: ${message}\\n`)\n if (error instanceof LegibilityUsageError) process.stderr.write(`${USAGE}\\n`)\n process.exit(2)\n}\n","/**\n * The bin's argument and config layer, split from `cli.ts` so it can be tested\n * without executing a `main()` that calls `process.exit`.\n *\n * Errors are thrown as {@link LegibilityUsageError} rather than exiting here:\n * exiting is the bin's job, and a parser that exits cannot be exercised.\n */\n\nimport { LEGIBILITY_CHECKS, type LegibilityCheckId, type LegibilityConfig } from './types'\n\nexport const DEFAULT_CONFIG_FILE = 'legibility.config.mjs'\n\n/** A usage or config problem — exit code 2, never a finding. */\nexport class LegibilityUsageError extends Error {}\n\nexport interface ParsedArgs {\n srcDirs: string[]\n navFiles: string[]\n ignorePaths: string[]\n skip: LegibilityCheckId[]\n routes?: string\n config?: string\n json: boolean\n listSuppressions: boolean\n help: boolean\n}\n\nexport const USAGE = [\n 'Usage: agent-app-legibility-check --src <dir> [--src <dir>…] [--routes <file>] [--nav <file>…]',\n ' [--ignore <substr>…] [--skip <check>…] [--config <file>]',\n ' [--json] [--list-suppressions]',\n `Checks: ${LEGIBILITY_CHECKS.join(', ')}`,\n].join('\\n')\n\nexport function parseArgs(argv: readonly string[]): ParsedArgs {\n const out: ParsedArgs = {\n srcDirs: [],\n navFiles: [],\n ignorePaths: [],\n skip: [],\n json: false,\n listSuppressions: false,\n help: false,\n }\n for (let i = 0; i < argv.length; i++) {\n const flag = argv[i]\n const take = (): string => {\n const value = argv[++i]\n if (value === undefined) throw new LegibilityUsageError(`${flag} needs a value`)\n return value\n }\n switch (flag) {\n case '--src':\n out.srcDirs.push(take())\n break\n case '--routes':\n out.routes = take()\n break\n case '--nav':\n out.navFiles.push(take())\n break\n case '--ignore':\n out.ignorePaths.push(take())\n break\n case '--skip': {\n const check = take()\n if (!LEGIBILITY_CHECKS.includes(check as LegibilityCheckId)) {\n throw new LegibilityUsageError(`--skip ${check} is not a check. Known: ${LEGIBILITY_CHECKS.join(', ')}`)\n }\n out.skip.push(check as LegibilityCheckId)\n break\n }\n case '--config':\n out.config = take()\n break\n case '--json':\n out.json = true\n break\n case '--list-suppressions':\n out.listSuppressions = true\n break\n case '-h':\n case '--help':\n out.help = true\n break\n default:\n throw new LegibilityUsageError(`unknown argument: ${flag}`)\n }\n }\n return out\n}\n\n/**\n * Merge a config file with the flags. Flags WIN, so a one-off CI invocation can\n * narrow a run without editing the committed config.\n */\nexport function mergeConfig(fromFile: Partial<LegibilityConfig>, args: ParsedArgs): LegibilityConfig {\n const checks: Partial<Record<LegibilityCheckId, boolean>> = { ...fromFile.checks }\n for (const check of args.skip) checks[check] = false\n\n const reachability = {\n ...fromFile.reachability,\n ...(args.routes ? { routeConfigFile: args.routes } : {}),\n ...(args.navFiles.length > 0 ? { navFiles: args.navFiles } : {}),\n }\n\n return {\n ...fromFile,\n srcDirs: args.srcDirs.length > 0 ? args.srcDirs : (fromFile.srcDirs ?? []),\n ignorePaths: [...(fromFile.ignorePaths ?? []), ...args.ignorePaths],\n ...(Object.keys(checks).length > 0 ? { checks } : {}),\n ...(reachability.routeConfigFile || reachability.routePaths ? { reachability } : {}),\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/legibility/cli.ts","../../src/legibility/cli-args.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * `agent-app-legibility-check` — fail a product's CI when a screen leaves its\n * reader guessing.\n *\n * Runs in the PRODUCT's repo, over the product's own source, next to typecheck:\n *\n * \"scripts\": {\n * \"legibility\": \"agent-app-legibility-check --src src --routes src/routes.ts --nav src/components/workspace-sidebar.tsx\"\n * }\n *\n * Flags (repeatable where a list makes sense):\n * --src <dir> source directory to scan (required unless the config supplies it)\n * --routes <file> react-router route config, for the unreachable-capability check\n * --nav <file> a file whose path literals are navigation entries\n * --ignore <substr> skip paths containing this substring\n * --skip <check> turn one check off wholesale (prefer a per-line suppression)\n * --config <file> a legibility.config.mjs default-exporting the config\n * (default: ./legibility.config.mjs when present)\n * --json print the report as JSON instead of text\n * --list-suppressions print every honoured suppression with its reason\n *\n * Exit 0 with no findings, 1 with findings, 2 on a usage or config error — the\n * same contract as `agent-app-preflight` and `agent-app-peer-check`.\n */\n\nimport { existsSync } from 'node:fs'\nimport { resolve } from 'node:path'\nimport { pathToFileURL } from 'node:url'\n\nimport { invokedAsScript } from '../signoff/invoked-as-script'\nimport { DEFAULT_CONFIG_FILE, LegibilityUsageError, mergeConfig, parseArgs, USAGE } from './cli-args'\nimport { checkLegibility } from './index'\nimport { formatLegibilityReport, legibilityReportToJson } from './report'\nimport type { LegibilityConfig } from './types'\n\nasync function loadConfigFile(path: string | undefined): Promise<Partial<LegibilityConfig>> {\n const explicit = path !== undefined\n const resolved = resolve(process.cwd(), path ?? DEFAULT_CONFIG_FILE)\n if (!existsSync(resolved)) {\n if (explicit) throw new LegibilityUsageError(`no config at ${resolved}`)\n return {}\n }\n const module: unknown = await import(pathToFileURL(resolved).href)\n const exported = (module as { default?: unknown; config?: unknown }).default ?? (module as { config?: unknown }).config\n const value = typeof exported === 'function' ? await (exported as () => Promise<unknown>)() : exported\n if (value === null || typeof value !== 'object') {\n throw new LegibilityUsageError(`${resolved} must default-export a legibility config object`)\n }\n return value as Partial<LegibilityConfig>\n}\n\nasync function main(): Promise<number> {\n const args = parseArgs(process.argv.slice(2))\n if (args.help) {\n process.stdout.write(`${USAGE}\\n`)\n return 0\n }\n\n const config = mergeConfig(await loadConfigFile(args.config), args)\n if (config.srcDirs.length === 0) {\n throw new LegibilityUsageError('at least one --src <dir> is required (or srcDirs in the config file)')\n }\n\n const report = checkLegibility(config)\n const text = args.json\n ? legibilityReportToJson(report)\n : formatLegibilityReport(report, { listSuppressions: args.listSuppressions })\n\n if (report.ok) process.stdout.write(`${text}\\n`)\n else process.stderr.write(`${text}\\n`)\n return report.ok ? 0 : 1\n}\n\n/* c8 ignore start — process wiring, exercised by the bin itself */\nif (invokedAsScript(import.meta.url, process.argv[1])) {\n try {\n process.exit(await main())\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n process.stderr.write(`agent-app-legibility-check: ${message}\\n`)\n if (error instanceof LegibilityUsageError) process.stderr.write(`${USAGE}\\n`)\n process.exit(2)\n }\n}\n/* c8 ignore stop */\n","/**\n * The bin's argument and config layer, split from `cli.ts` so it can be tested\n * without executing a `main()` that calls `process.exit`.\n *\n * Errors are thrown as {@link LegibilityUsageError} rather than exiting here:\n * exiting is the bin's job, and a parser that exits cannot be exercised.\n */\n\nimport { LEGIBILITY_CHECKS, type LegibilityCheckId, type LegibilityConfig } from './types'\n\nexport const DEFAULT_CONFIG_FILE = 'legibility.config.mjs'\n\n/** A usage or config problem — exit code 2, never a finding. */\nexport class LegibilityUsageError extends Error {}\n\nexport interface ParsedArgs {\n srcDirs: string[]\n navFiles: string[]\n ignorePaths: string[]\n skip: LegibilityCheckId[]\n routes?: string\n config?: string\n json: boolean\n listSuppressions: boolean\n help: boolean\n}\n\nexport const USAGE = [\n 'Usage: agent-app-legibility-check --src <dir> [--src <dir>…] [--routes <file>] [--nav <file>…]',\n ' [--ignore <substr>…] [--skip <check>…] [--config <file>]',\n ' [--json] [--list-suppressions]',\n `Checks: ${LEGIBILITY_CHECKS.join(', ')}`,\n].join('\\n')\n\nexport function parseArgs(argv: readonly string[]): ParsedArgs {\n const out: ParsedArgs = {\n srcDirs: [],\n navFiles: [],\n ignorePaths: [],\n skip: [],\n json: false,\n listSuppressions: false,\n help: false,\n }\n for (let i = 0; i < argv.length; i++) {\n const flag = argv[i]\n const take = (): string => {\n const value = argv[++i]\n if (value === undefined) throw new LegibilityUsageError(`${flag} needs a value`)\n return value\n }\n switch (flag) {\n case '--src':\n out.srcDirs.push(take())\n break\n case '--routes':\n out.routes = take()\n break\n case '--nav':\n out.navFiles.push(take())\n break\n case '--ignore':\n out.ignorePaths.push(take())\n break\n case '--skip': {\n const check = take()\n if (!LEGIBILITY_CHECKS.includes(check as LegibilityCheckId)) {\n throw new LegibilityUsageError(`--skip ${check} is not a check. Known: ${LEGIBILITY_CHECKS.join(', ')}`)\n }\n out.skip.push(check as LegibilityCheckId)\n break\n }\n case '--config':\n out.config = take()\n break\n case '--json':\n out.json = true\n break\n case '--list-suppressions':\n out.listSuppressions = true\n break\n case '-h':\n case '--help':\n out.help = true\n break\n default:\n throw new LegibilityUsageError(`unknown argument: ${flag}`)\n }\n }\n return out\n}\n\n/**\n * Merge a config file with the flags. Flags WIN, so a one-off CI invocation can\n * narrow a run without editing the committed config.\n */\nexport function mergeConfig(fromFile: Partial<LegibilityConfig>, args: ParsedArgs): LegibilityConfig {\n const checks: Partial<Record<LegibilityCheckId, boolean>> = { ...fromFile.checks }\n for (const check of args.skip) checks[check] = false\n\n const reachability = {\n ...fromFile.reachability,\n ...(args.routes ? { routeConfigFile: args.routes } : {}),\n ...(args.navFiles.length > 0 ? { navFiles: args.navFiles } : {}),\n }\n\n return {\n ...fromFile,\n srcDirs: args.srcDirs.length > 0 ? args.srcDirs : (fromFile.srcDirs ?? []),\n ignorePaths: [...(fromFile.ignorePaths ?? []), ...args.ignorePaths],\n ...(Object.keys(checks).length > 0 ? { checks } : {}),\n ...(reachability.routeConfigFile || reachability.routePaths ? { reachability } : {}),\n }\n}\n"],"mappings":"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","names":[]}
@@ -13,7 +13,8 @@ import {
13
13
  scanSource,
14
14
  scanSources,
15
15
  staticSegments
16
- } from "../chunk-XEA5EG6P.js";
16
+ } from "../chunk-EPZYYWCT.js";
17
+ import "../chunk-EMLGWEHV.js";
17
18
  export {
18
19
  COPY_CALLS,
19
20
  COPY_KEYS,
@@ -1,19 +1,25 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ invokedAsScript
4
+ } from "../chunk-C3CAYGGQ.js";
2
5
  import {
3
6
  checkPeerFloors,
4
7
  formatPeerFloorReport
5
8
  } from "../chunk-S24VVLKV.js";
6
9
 
7
10
  // src/peer-floors/cli.ts
8
- var appDir = process.argv[2] ?? process.cwd();
9
- try {
10
- const report = checkPeerFloors({ appDir });
11
- process.stdout.write(`${formatPeerFloorReport(report)}
11
+ function main() {
12
+ const appDir = process.argv[2] ?? process.cwd();
13
+ try {
14
+ const report = checkPeerFloors({ appDir });
15
+ process.stdout.write(`${formatPeerFloorReport(report)}
12
16
  `);
13
- process.exit(report.ok ? 0 : 1);
14
- } catch (err) {
15
- process.stderr.write(`agent-app-peer-check failed: ${err instanceof Error ? err.message : String(err)}
17
+ process.exit(report.ok ? 0 : 1);
18
+ } catch (err) {
19
+ process.stderr.write(`agent-app-peer-check failed: ${err instanceof Error ? err.message : String(err)}
16
20
  `);
17
- process.exit(1);
21
+ process.exit(1);
22
+ }
18
23
  }
24
+ if (invokedAsScript(import.meta.url, process.argv[1])) main();
19
25
  //# sourceMappingURL=cli.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/peer-floors/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * `agent-app-peer-check` — fail a consumer's CI when its installed tree sits\n * below a peer floor this package declares.\n *\n * Runs in the CONSUMER's repo, over the consumer's own `node_modules`, because\n * the floors only mean anything against a real install. Add it next to\n * typecheck:\n *\n * \"scripts\": { \"peer-check\": \"agent-app-peer-check\" }\n *\n * Exits 1 on any violation, 0 otherwise. Takes no configuration — the floors\n * come from the installed shell manifest, so the check cannot go stale when the\n * shell adds an engine.\n */\nimport { checkPeerFloors, formatPeerFloorReport } from './check'\n\nconst appDir = process.argv[2] ?? process.cwd()\n\ntry {\n const report = checkPeerFloors({ appDir })\n process.stdout.write(`${formatPeerFloorReport(report)}\\n`)\n process.exit(report.ok ? 0 : 1)\n} catch (err) {\n process.stderr.write(`agent-app-peer-check failed: ${err instanceof Error ? err.message : String(err)}\\n`)\n process.exit(1)\n}\n"],"mappings":";;;;;;;AAiBA,IAAM,SAAS,QAAQ,KAAK,CAAC,KAAK,QAAQ,IAAI;AAE9C,IAAI;AACF,QAAM,SAAS,gBAAgB,EAAE,OAAO,CAAC;AACzC,UAAQ,OAAO,MAAM,GAAG,sBAAsB,MAAM,CAAC;AAAA,CAAI;AACzD,UAAQ,KAAK,OAAO,KAAK,IAAI,CAAC;AAChC,SAAS,KAAK;AACZ,UAAQ,OAAO,MAAM,gCAAgC,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,CAAI;AACzG,UAAQ,KAAK,CAAC;AAChB;","names":[]}
1
+ {"version":3,"sources":["../../src/peer-floors/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * `agent-app-peer-check` — fail a consumer's CI when its installed tree sits\n * below a peer floor this package declares.\n *\n * Runs in the CONSUMER's repo, over the consumer's own `node_modules`, because\n * the floors only mean anything against a real install. Add it next to\n * typecheck:\n *\n * \"scripts\": { \"peer-check\": \"agent-app-peer-check\" }\n *\n * Exits 1 on any violation, 0 otherwise. Takes no configuration — the floors\n * come from the installed shell manifest, so the check cannot go stale when the\n * shell adds an engine.\n */\nimport { checkPeerFloors, formatPeerFloorReport } from './check'\nimport { invokedAsScript } from '../signoff/invoked-as-script'\n\nfunction main(): void {\n const appDir = process.argv[2] ?? process.cwd()\n try {\n const report = checkPeerFloors({ appDir })\n process.stdout.write(`${formatPeerFloorReport(report)}\\n`)\n process.exit(report.ok ? 0 : 1)\n } catch (err) {\n process.stderr.write(`agent-app-peer-check failed: ${err instanceof Error ? err.message : String(err)}\\n`)\n process.exit(1)\n }\n}\n\n/* c8 ignore start — process wiring, exercised by the bin itself */\nif (invokedAsScript(import.meta.url, process.argv[1])) main()\n/* c8 ignore stop */\n"],"mappings":";;;;;;;;;;AAkBA,SAAS,OAAa;AACpB,QAAM,SAAS,QAAQ,KAAK,CAAC,KAAK,QAAQ,IAAI;AAC9C,MAAI;AACF,UAAM,SAAS,gBAAgB,EAAE,OAAO,CAAC;AACzC,YAAQ,OAAO,MAAM,GAAG,sBAAsB,MAAM,CAAC;AAAA,CAAI;AACzD,YAAQ,KAAK,OAAO,KAAK,IAAI,CAAC;AAAA,EAChC,SAAS,KAAK;AACZ,YAAQ,OAAO,MAAM,gCAAgC,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,CAAI;AACzG,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;AAGA,IAAI,gBAAgB,YAAY,KAAK,QAAQ,KAAK,CAAC,CAAC,EAAG,MAAK;","names":[]}
@@ -3,6 +3,9 @@ import {
3
3
  formatPreflightReport,
4
4
  runPreflight
5
5
  } from "../chunk-5FAKBTCK.js";
6
+ import {
7
+ invokedAsScript
8
+ } from "../chunk-C3CAYGGQ.js";
6
9
 
7
10
  // src/preflight/cli.ts
8
11
  import { pathToFileURL } from "url";
@@ -34,8 +37,7 @@ async function runPreflightCli(argv) {
34
37
  console.log(formatPreflightReport(report));
35
38
  return report.ok ? 0 : 1;
36
39
  }
37
- var invokedDirectly = process.argv[1] !== void 0 && import.meta.url === pathToFileURL(process.argv[1]).href;
38
- if (invokedDirectly) {
40
+ if (invokedAsScript(import.meta.url, process.argv[1])) {
39
41
  runPreflightCli(process.argv.slice(2)).then((code) => {
40
42
  process.exit(code);
41
43
  }).catch((err) => {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/preflight/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * `agent-app-preflight` — the deploy-time probe runner.\n *\n * Loads a product's `preflight.config.mjs` (default export: `PreflightProbe[]`),\n * runs every probe, prints the operator table, and exits non-zero when a\n * critical probe fails so a deploy step can gate on it.\n *\n * Run it where the REAL secrets are — after `wrangler secret put …`, not in CI.\n * CI has no secrets, so a probe there proves nothing.\n *\n * agent-app-preflight # ./preflight.config.mjs\n * agent-app-preflight ops/probes.mjs # explicit path\n *\n * This bin existed only as a promise in the module docs until 0.44.0; at least\n * one product had hand-rolled a byte-identical `scripts/preflight.mjs` around\n * `runPreflight` + `formatPreflightReport` to fill the gap.\n */\nimport { pathToFileURL } from 'node:url'\nimport { resolve } from 'node:path'\nimport { existsSync } from 'node:fs'\nimport { formatPreflightReport, runPreflight, type PreflightProbe } from './index'\n\nconst DEFAULT_CONFIG = 'preflight.config.mjs'\n\n/**\n * Load the probe list from a config module. Fail loud on every miss — a\n * preflight that silently probes nothing and exits 0 is worse than no\n * preflight, because it reports the deploy as verified.\n */\nexport async function loadProbes(configPath: string): Promise<PreflightProbe[]> {\n const abs = resolve(process.cwd(), configPath)\n if (!existsSync(abs)) {\n throw new Error(\n `agent-app-preflight: no config at \"${abs}\". Create one that default-exports a PreflightProbe[], or pass a path.`,\n )\n }\n const mod: unknown = await import(pathToFileURL(abs).href)\n const probes = (mod as { default?: unknown }).default\n if (!Array.isArray(probes)) {\n throw new Error(\n `agent-app-preflight: \"${abs}\" must default-export an array of probes; got ${typeof probes}.`,\n )\n }\n if (probes.length === 0) {\n throw new Error(`agent-app-preflight: \"${abs}\" exported an empty probe list — nothing would be verified.`)\n }\n return probes as PreflightProbe[]\n}\n\n/** Run the probes named by `argv` and resolve with the process exit code. */\nexport async function runPreflightCli(argv: readonly string[]): Promise<number> {\n const probes = await loadProbes(argv[0] ?? DEFAULT_CONFIG)\n const report = await runPreflight(probes)\n console.log(formatPreflightReport(report))\n return report.ok ? 0 : 1\n}\n\n/* c8 ignore start — process wiring, exercised by the bin itself */\nconst invokedDirectly =\n process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href\n\nif (invokedDirectly) {\n runPreflightCli(process.argv.slice(2))\n .then((code) => {\n process.exit(code)\n })\n .catch((err: unknown) => {\n console.error(err instanceof Error ? err.message : String(err))\n process.exit(1)\n })\n}\n/* c8 ignore stop */\n"],"mappings":";;;;;;;AAkBA,SAAS,qBAAqB;AAC9B,SAAS,eAAe;AACxB,SAAS,kBAAkB;AAG3B,IAAM,iBAAiB;AAOvB,eAAsB,WAAW,YAA+C;AAC9E,QAAM,MAAM,QAAQ,QAAQ,IAAI,GAAG,UAAU;AAC7C,MAAI,CAAC,WAAW,GAAG,GAAG;AACpB,UAAM,IAAI;AAAA,MACR,sCAAsC,GAAG;AAAA,IAC3C;AAAA,EACF;AACA,QAAM,MAAe,MAAM,OAAO,cAAc,GAAG,EAAE;AACrD,QAAM,SAAU,IAA8B;AAC9C,MAAI,CAAC,MAAM,QAAQ,MAAM,GAAG;AAC1B,UAAM,IAAI;AAAA,MACR,yBAAyB,GAAG,iDAAiD,OAAO,MAAM;AAAA,IAC5F;AAAA,EACF;AACA,MAAI,OAAO,WAAW,GAAG;AACvB,UAAM,IAAI,MAAM,yBAAyB,GAAG,kEAA6D;AAAA,EAC3G;AACA,SAAO;AACT;AAGA,eAAsB,gBAAgB,MAA0C;AAC9E,QAAM,SAAS,MAAM,WAAW,KAAK,CAAC,KAAK,cAAc;AACzD,QAAM,SAAS,MAAM,aAAa,MAAM;AACxC,UAAQ,IAAI,sBAAsB,MAAM,CAAC;AACzC,SAAO,OAAO,KAAK,IAAI;AACzB;AAGA,IAAM,kBACJ,QAAQ,KAAK,CAAC,MAAM,UAAa,YAAY,QAAQ,cAAc,QAAQ,KAAK,CAAC,CAAC,EAAE;AAEtF,IAAI,iBAAiB;AACnB,kBAAgB,QAAQ,KAAK,MAAM,CAAC,CAAC,EAClC,KAAK,CAAC,SAAS;AACd,YAAQ,KAAK,IAAI;AAAA,EACnB,CAAC,EACA,MAAM,CAAC,QAAiB;AACvB,YAAQ,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAC9D,YAAQ,KAAK,CAAC;AAAA,EAChB,CAAC;AACL;","names":[]}
1
+ {"version":3,"sources":["../../src/preflight/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * `agent-app-preflight` — the deploy-time probe runner.\n *\n * Loads a product's `preflight.config.mjs` (default export: `PreflightProbe[]`),\n * runs every probe, prints the operator table, and exits non-zero when a\n * critical probe fails so a deploy step can gate on it.\n *\n * Run it where the REAL secrets are — after `wrangler secret put …`, not in CI.\n * CI has no secrets, so a probe there proves nothing.\n *\n * agent-app-preflight # ./preflight.config.mjs\n * agent-app-preflight ops/probes.mjs # explicit path\n *\n * This bin existed only as a promise in the module docs until 0.44.0; at least\n * one product had hand-rolled a byte-identical `scripts/preflight.mjs` around\n * `runPreflight` + `formatPreflightReport` to fill the gap.\n */\nimport { pathToFileURL } from 'node:url'\nimport { resolve } from 'node:path'\nimport { existsSync } from 'node:fs'\nimport { formatPreflightReport, runPreflight, type PreflightProbe } from './index'\nimport { invokedAsScript } from '../signoff/invoked-as-script'\n\nconst DEFAULT_CONFIG = 'preflight.config.mjs'\n\n/**\n * Load the probe list from a config module. Fail loud on every miss — a\n * preflight that silently probes nothing and exits 0 is worse than no\n * preflight, because it reports the deploy as verified.\n */\nexport async function loadProbes(configPath: string): Promise<PreflightProbe[]> {\n const abs = resolve(process.cwd(), configPath)\n if (!existsSync(abs)) {\n throw new Error(\n `agent-app-preflight: no config at \"${abs}\". Create one that default-exports a PreflightProbe[], or pass a path.`,\n )\n }\n const mod: unknown = await import(pathToFileURL(abs).href)\n const probes = (mod as { default?: unknown }).default\n if (!Array.isArray(probes)) {\n throw new Error(\n `agent-app-preflight: \"${abs}\" must default-export an array of probes; got ${typeof probes}.`,\n )\n }\n if (probes.length === 0) {\n throw new Error(`agent-app-preflight: \"${abs}\" exported an empty probe list — nothing would be verified.`)\n }\n return probes as PreflightProbe[]\n}\n\n/** Run the probes named by `argv` and resolve with the process exit code. */\nexport async function runPreflightCli(argv: readonly string[]): Promise<number> {\n const probes = await loadProbes(argv[0] ?? DEFAULT_CONFIG)\n const report = await runPreflight(probes)\n console.log(formatPreflightReport(report))\n return report.ok ? 0 : 1\n}\n\n/* c8 ignore start — process wiring, exercised by the bin itself */\nif (invokedAsScript(import.meta.url, process.argv[1])) {\n runPreflightCli(process.argv.slice(2))\n .then((code) => {\n process.exit(code)\n })\n .catch((err: unknown) => {\n console.error(err instanceof Error ? err.message : String(err))\n process.exit(1)\n })\n}\n/* c8 ignore stop */\n"],"mappings":";;;;;;;;;;AAkBA,SAAS,qBAAqB;AAC9B,SAAS,eAAe;AACxB,SAAS,kBAAkB;AAI3B,IAAM,iBAAiB;AAOvB,eAAsB,WAAW,YAA+C;AAC9E,QAAM,MAAM,QAAQ,QAAQ,IAAI,GAAG,UAAU;AAC7C,MAAI,CAAC,WAAW,GAAG,GAAG;AACpB,UAAM,IAAI;AAAA,MACR,sCAAsC,GAAG;AAAA,IAC3C;AAAA,EACF;AACA,QAAM,MAAe,MAAM,OAAO,cAAc,GAAG,EAAE;AACrD,QAAM,SAAU,IAA8B;AAC9C,MAAI,CAAC,MAAM,QAAQ,MAAM,GAAG;AAC1B,UAAM,IAAI;AAAA,MACR,yBAAyB,GAAG,iDAAiD,OAAO,MAAM;AAAA,IAC5F;AAAA,EACF;AACA,MAAI,OAAO,WAAW,GAAG;AACvB,UAAM,IAAI,MAAM,yBAAyB,GAAG,kEAA6D;AAAA,EAC3G;AACA,SAAO;AACT;AAGA,eAAsB,gBAAgB,MAA0C;AAC9E,QAAM,SAAS,MAAM,WAAW,KAAK,CAAC,KAAK,cAAc;AACzD,QAAM,SAAS,MAAM,aAAa,MAAM;AACxC,UAAQ,IAAI,sBAAsB,MAAM,CAAC;AACzC,SAAO,OAAO,KAAK,IAAI;AACzB;AAGA,IAAI,gBAAgB,YAAY,KAAK,QAAQ,KAAK,CAAC,CAAC,GAAG;AACrD,kBAAgB,QAAQ,KAAK,MAAM,CAAC,CAAC,EAClC,KAAK,CAAC,SAAS;AACd,YAAQ,KAAK,IAAI;AAAA,EACnB,CAAC,EACA,MAAM,CAAC,QAAiB;AACvB,YAAQ,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAC9D,YAAQ,KAAK,CAAC;AAAA,EAChB,CAAC;AACL;","names":[]}
@@ -1,4 +1,4 @@
1
- import { S as SignoffSource, a as SignoffEvent, b as SignoffReport } from './types-U7Nz-txa.js';
1
+ import { S as SignoffSource, a as SignoffEvent, b as SignoffReport } from './types-Bd6uW9vQ.js';
2
2
 
3
3
  /**
4
4
  * The sign-off run: reproduce a clean CI environment, then go further.
@@ -1237,6 +1237,57 @@ interface EnsureWorkspaceSandboxOptions {
1237
1237
  forceNew?: boolean;
1238
1238
  onProgress?: (event: ProvisionEvent) => void;
1239
1239
  billingOwnerId?: string;
1240
+ spend?: SandboxSpendHooks;
1241
+ }
1242
+ /** What `/sandbox` reports once a box is provisioned, reused, or resumed. */
1243
+ interface SandboxProvisionedObservation {
1244
+ readonly workspaceId: string;
1245
+ readonly userId?: string;
1246
+ /** The platform's sandbox id — the join key to every settlement row. */
1247
+ readonly sandboxId: string;
1248
+ /** The deterministic box key this workspace resolves to. */
1249
+ readonly boxKey?: string | undefined;
1250
+ /** The idle timeout this box was asked to run with, seconds. */
1251
+ readonly idleTimeoutSeconds: number;
1252
+ /** The max lifetime it was asked to run with, seconds, when one was asked for. */
1253
+ readonly maxLifetimeSeconds?: number | undefined;
1254
+ readonly at: number;
1255
+ }
1256
+ /**
1257
+ * Optional spend-verification seam on `ensureWorkspaceSandbox`.
1258
+ *
1259
+ * The two halves have deliberately opposite failure contracts.
1260
+ * `beforeProvision` is a GATE: it runs before anything is created and its throw
1261
+ * propagates, because refusing to provision is the whole point of a budget cap.
1262
+ * `onProvisioned` is an OBSERVER: its failures are swallowed, because
1263
+ * bookkeeping must never take down the provisioning it is bookkeeping.
1264
+ */
1265
+ interface SandboxSpendHooks {
1266
+ /** Runs before any create, resume, or reuse. Throw to refuse provisioning. */
1267
+ beforeProvision?(input: {
1268
+ workspaceId: string;
1269
+ userId?: string;
1270
+ }): Promise<void> | void;
1271
+ /** Runs after a box is available. Throwing here cannot fail the provision. */
1272
+ onProvisioned?(observation: SandboxProvisionedObservation): Promise<void> | void;
1273
+ /**
1274
+ * The box is doing work. Fired by the turn primitives at the start of a turn
1275
+ * and again when it settles.
1276
+ *
1277
+ * Deliberately SYNCHRONOUS and unawaited — this sits on the turn path, and a
1278
+ * store write must not add latency to it or hold a stream open. An
1279
+ * implementation that persists should enqueue and return; errors are
1280
+ * swallowed here as they are for `onProvisioned`.
1281
+ *
1282
+ * Wiring it is what keeps the expectation ceiling honest for long turns:
1283
+ * without it, `lastActivityAt` only advances when a box is provisioned, so a
1284
+ * three-hour turn leaves the ceiling three hours too tight and manufactures a
1285
+ * discrepancy out of the product's own silence.
1286
+ */
1287
+ onActivity?(input: {
1288
+ sandboxId: string;
1289
+ at: number;
1290
+ }): void;
1240
1291
  }
1241
1292
  /** Define the specification for a sandbox tool including its name, content, and optional executability */
1242
1293
  interface SandboxToolSpec {
@@ -1473,6 +1524,7 @@ interface StreamSandboxPromptOptions {
1473
1524
  };
1474
1525
  detach?: boolean;
1475
1526
  onProfileResolved?: (fingerprint: ProfileFingerprint) => void;
1527
+ spend?: SandboxSpendHooks;
1476
1528
  }
1477
1529
  /**
1478
1530
  * The small event shape consumed by agent-gateway's streaming adapters.
@@ -1605,4 +1657,4 @@ declare function isTerminalPromptEvent(event: unknown): boolean;
1605
1657
  /** Resolve the interactive question text from a structured event or return null if none found */
1606
1658
  declare function detectInteractiveQuestion(event: unknown): string | null;
1607
1659
 
1608
- export { type AppToolDescriptor, type BuildAppToolMcpServersOptions, type BuildSandboxToolFileMountsOptions, type D1PrewarmClaimStoreOptions, DEFAULT_PREWARM_CLAIM_TABLE, DEFAULT_PROVISION_TIMEOUT_MS, DEFAULT_SANDBOX_RESOURCES, DEFAULT_SIDECAR_PROCESS_PATTERN, type DriveSandboxTurnOptions, EGRESS_PROXY_RECOVERY_PHASE, EGRESS_PROXY_RECOVERY_REQUIRED, ENV_TOTAL_MAX_BYTES, ENV_VALUE_MAX_BYTES, type EnsureWorkspaceSandboxOptions, type LivenessProbeConfig, type MemberSyncSeam, type ModelSelection, type ModelSelectionError, type ModelSelectionFailure, type ModelSelectionSource, type Outcome, PREWARM_CLAIM_TABLE_DDL, PROVISION_PAYLOAD_MAX_BYTES, PYPI_EGRESS_DOMAINS, type PeekWorkspaceSandboxOutcome, type PrewarmClaimD1Like, type PrewarmClaimStore, type PrewarmDecision, type PrewarmEvent, type PrewarmOutcome, type PrewarmResult, type ProfileComposeOptions, type PromptInputPart, type ProviderResolutionConfig, type ProvisionPayloadSections, type ProvisionProfileSection, type ResolveSandboxClientCredentialsOptions, type ResolvedModel, type SafeSandboxErrorCause, type SafeSandboxErrorDiagnostics, type SandboxBuildContext, type SandboxClientCredentials, type SandboxCredentialEnvironment, SandboxEgressPolicyMismatchError, type SandboxEgressPolicySource, type SandboxExecChannel, type SandboxExecOptions, type SandboxExistingBoxStage, type SandboxFileBytesOutcome, type SandboxFileSizeOutcome, SandboxModelResolutionError, type SandboxPermissionLevel, type SandboxPrewarmScope, type SandboxPrewarmer, type SandboxPrewarmerOptions, type SandboxReadiness, SandboxRecoveryFailedError, type SandboxRecoveryPhase, type SandboxResourceConfig, type SandboxRestoreSpec, SandboxRuntimeAuthRefreshError, type SandboxRuntimeConfig, type SandboxScope, type SandboxStepTransition, type SandboxStreamEvent, type SandboxTerminalConnectionRouteOptions, type SandboxToolPathOptions, type SandboxToolSpec, type ScopedTokenResult, type SecretStore, type StoppedSandboxResumeFailure, type StoppedSandboxResumeRecovery, type StreamSandboxPromptOptions, type TerminalConnectionBoxLike, WORKSPACE_SANDBOX_HOST_EXHAUSTED, WORKSPACE_SANDBOX_MISSING, WORKSPACE_SANDBOX_RECOVERY_ACTIONS, WORKSPACE_SANDBOX_RECOVERY_CODES, WORKSPACE_SANDBOX_SNAPSHOT_MAX_AGE_MS, WORKSPACE_SANDBOX_UNRECOVERABLE, type WorkspaceSandboxEnsureContext, type WorkspaceSandboxInstanceLike, type WorkspaceSandboxManager, type WorkspaceSandboxManagerOptions, type WorkspaceSandboxRecoveryAction, type WorkspaceSandboxRecoveryCode, type WorkspaceSandboxRecoveryDecision, type WorkspaceSandboxRecoveryManager, WorkspaceSandboxRecoveryRequiredError, type WorkspaceSandboxRecoveryState, type WorkspaceSandboxRecoveryStore, type WorkspaceSandboxSnapshot, type WorkspaceSandboxSnapshotAssessment, type WorkspaceSandboxSnapshotAvailability, type WorkspaceSandboxSnapshotFreshness, type WriteProfileFilesOptions, adaptSandboxStream, assertEnvWithinLimits, assertProvisionPayloadWithinCap, assessWorkspaceSandboxSnapshot, attachReasoningEffort, buildAppToolMcpServers, buildProductEgressPolicy, buildSandboxToolFileMounts, buildSandboxToolPathSetupScript, classifySeveredStream, collectSandboxPromptText, createD1PrewarmClaimStore, createSandboxPrewarmer, createSandboxTerminalConnectionRoute, createWorkspaceSandboxManager, createWorkspaceSandboxRecoveryManager, deferredCorpusHash, deleteSecret, detectInteractiveQuestion, driveSandboxTurn, ensureWorkspaceSandbox, flattenHistory, formatSandboxProvisioningSupportDetails, formatSandboxProvisioningUserMessage, getClient, isEgressProxyRecoveryRequiredError, isSandboxApiBearerAuthFailure, isSandboxApiSandboxMissingFailure, isSandboxAuthFailure, isSandboxHostCapacityFailure, isTerminalPromptEvent, isWorkspaceSandboxRecoveryAction, isWorkspaceSandboxRecoveryCode, isWorkspaceSandboxRecoveryState, isWorkspaceSandboxSnapshotRestoreError, mergeExtraMcp, mergeHistoryIntoParts, mintSandboxScopedToken, peekWorkspaceSandbox, preferredWorkspaceSandboxRecoveryBoxKey, readSandboxBinaryBytes, readSecret, requireTransportableModel, resetClientCache, resolveModel, resolveModelSelection, resolveSandboxClientCredentials, runSandboxPrompt, runSandboxToolPathSetup, sandboxToolBinDir, sandboxToolPath, sandboxToolRootDir, secretStoreFromClient, serializeSandboxProvisioningError, shellQuote, shouldRestoreWorkspaceSandboxRecovery, splitDeferredProfileFiles, statSandboxFileSize, storeSecret, streamSandboxPrompt, syncSandboxMemberAdd, syncSandboxMemberRemove, syncSandboxMemberRole, workspaceSandboxRecoveryDiagnostic, workspaceSandboxRecoveryFromError, workspaceSandboxRecoveryMessage, workspaceSandboxRecoveryRecommendedActions, writeProfileFilesToBox };
1660
+ export { type AppToolDescriptor, type BuildAppToolMcpServersOptions, type BuildSandboxToolFileMountsOptions, type D1PrewarmClaimStoreOptions, DEFAULT_PREWARM_CLAIM_TABLE, DEFAULT_PROVISION_TIMEOUT_MS, DEFAULT_SANDBOX_RESOURCES, DEFAULT_SIDECAR_PROCESS_PATTERN, type DriveSandboxTurnOptions, EGRESS_PROXY_RECOVERY_PHASE, EGRESS_PROXY_RECOVERY_REQUIRED, ENV_TOTAL_MAX_BYTES, ENV_VALUE_MAX_BYTES, type EnsureWorkspaceSandboxOptions, type LivenessProbeConfig, type MemberSyncSeam, type ModelSelection, type ModelSelectionError, type ModelSelectionFailure, type ModelSelectionSource, type Outcome, PREWARM_CLAIM_TABLE_DDL, PROVISION_PAYLOAD_MAX_BYTES, PYPI_EGRESS_DOMAINS, type PeekWorkspaceSandboxOutcome, type PrewarmClaimD1Like, type PrewarmClaimStore, type PrewarmDecision, type PrewarmEvent, type PrewarmOutcome, type PrewarmResult, type ProfileComposeOptions, type PromptInputPart, type ProviderResolutionConfig, type ProvisionPayloadSections, type ProvisionProfileSection, type ResolveSandboxClientCredentialsOptions, type ResolvedModel, type SafeSandboxErrorCause, type SafeSandboxErrorDiagnostics, type SandboxBuildContext, type SandboxClientCredentials, type SandboxCredentialEnvironment, SandboxEgressPolicyMismatchError, type SandboxEgressPolicySource, type SandboxExecChannel, type SandboxExecOptions, type SandboxExistingBoxStage, type SandboxFileBytesOutcome, type SandboxFileSizeOutcome, SandboxModelResolutionError, type SandboxPermissionLevel, type SandboxPrewarmScope, type SandboxPrewarmer, type SandboxPrewarmerOptions, type SandboxProvisionedObservation, type SandboxReadiness, SandboxRecoveryFailedError, type SandboxRecoveryPhase, type SandboxResourceConfig, type SandboxRestoreSpec, SandboxRuntimeAuthRefreshError, type SandboxRuntimeConfig, type SandboxScope, type SandboxSpendHooks, type SandboxStepTransition, type SandboxStreamEvent, type SandboxTerminalConnectionRouteOptions, type SandboxToolPathOptions, type SandboxToolSpec, type ScopedTokenResult, type SecretStore, type StoppedSandboxResumeFailure, type StoppedSandboxResumeRecovery, type StreamSandboxPromptOptions, type TerminalConnectionBoxLike, WORKSPACE_SANDBOX_HOST_EXHAUSTED, WORKSPACE_SANDBOX_MISSING, WORKSPACE_SANDBOX_RECOVERY_ACTIONS, WORKSPACE_SANDBOX_RECOVERY_CODES, WORKSPACE_SANDBOX_SNAPSHOT_MAX_AGE_MS, WORKSPACE_SANDBOX_UNRECOVERABLE, type WorkspaceSandboxEnsureContext, type WorkspaceSandboxInstanceLike, type WorkspaceSandboxManager, type WorkspaceSandboxManagerOptions, type WorkspaceSandboxRecoveryAction, type WorkspaceSandboxRecoveryCode, type WorkspaceSandboxRecoveryDecision, type WorkspaceSandboxRecoveryManager, WorkspaceSandboxRecoveryRequiredError, type WorkspaceSandboxRecoveryState, type WorkspaceSandboxRecoveryStore, type WorkspaceSandboxSnapshot, type WorkspaceSandboxSnapshotAssessment, type WorkspaceSandboxSnapshotAvailability, type WorkspaceSandboxSnapshotFreshness, type WriteProfileFilesOptions, adaptSandboxStream, assertEnvWithinLimits, assertProvisionPayloadWithinCap, assessWorkspaceSandboxSnapshot, attachReasoningEffort, buildAppToolMcpServers, buildProductEgressPolicy, buildSandboxToolFileMounts, buildSandboxToolPathSetupScript, classifySeveredStream, collectSandboxPromptText, createD1PrewarmClaimStore, createSandboxPrewarmer, createSandboxTerminalConnectionRoute, createWorkspaceSandboxManager, createWorkspaceSandboxRecoveryManager, deferredCorpusHash, deleteSecret, detectInteractiveQuestion, driveSandboxTurn, ensureWorkspaceSandbox, flattenHistory, formatSandboxProvisioningSupportDetails, formatSandboxProvisioningUserMessage, getClient, isEgressProxyRecoveryRequiredError, isSandboxApiBearerAuthFailure, isSandboxApiSandboxMissingFailure, isSandboxAuthFailure, isSandboxHostCapacityFailure, isTerminalPromptEvent, isWorkspaceSandboxRecoveryAction, isWorkspaceSandboxRecoveryCode, isWorkspaceSandboxRecoveryState, isWorkspaceSandboxSnapshotRestoreError, mergeExtraMcp, mergeHistoryIntoParts, mintSandboxScopedToken, peekWorkspaceSandbox, preferredWorkspaceSandboxRecoveryBoxKey, readSandboxBinaryBytes, readSecret, requireTransportableModel, resetClientCache, resolveModel, resolveModelSelection, resolveSandboxClientCredentials, runSandboxPrompt, runSandboxToolPathSetup, sandboxToolBinDir, sandboxToolPath, sandboxToolRootDir, secretStoreFromClient, serializeSandboxProvisioningError, shellQuote, shouldRestoreWorkspaceSandboxRecovery, splitDeferredProfileFiles, statSandboxFileSize, storeSecret, streamSandboxPrompt, syncSandboxMemberAdd, syncSandboxMemberRemove, syncSandboxMemberRole, workspaceSandboxRecoveryDiagnostic, workspaceSandboxRecoveryFromError, workspaceSandboxRecoveryMessage, workspaceSandboxRecoveryRecommendedActions, writeProfileFilesToBox };
@@ -89,7 +89,7 @@ import {
89
89
  workspaceSandboxRecoveryMessage,
90
90
  workspaceSandboxRecoveryRecommendedActions,
91
91
  writeProfileFilesToBox
92
- } from "../chunk-7V2I3JGZ.js";
92
+ } from "../chunk-OTLEYHOG.js";
93
93
  import "../chunk-TH7L265V.js";
94
94
  import "../chunk-LWSJK546.js";
95
95
  import "../chunk-P5PIAQVS.js";
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { R as RunSignoffOptions } from '../run-tEsZUhAf.js';
3
- import '../types-U7Nz-txa.js';
2
+ import { R as RunSignoffOptions } from '../run-D80hoLKh.js';
3
+ import '../types-Bd6uW9vQ.js';
4
4
 
5
5
  interface ParsedArgs extends RunSignoffOptions {
6
6
  readonly jsonPath?: string;
@@ -1,28 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ formatSignoffLine,
3
4
  formatSignoffReport,
4
5
  runSignoff
5
- } from "../chunk-WD2B6HGY.js";
6
+ } from "../chunk-K4RA5T3A.js";
7
+ import {
8
+ invokedAsScript
9
+ } from "../chunk-C3CAYGGQ.js";
6
10
 
7
11
  // src/signoff/cli.ts
8
12
  import { mkdirSync, writeFileSync } from "fs";
9
13
  import { dirname, resolve } from "path";
10
-
11
- // src/signoff/invoked-as-script.ts
12
- import { realpathSync } from "fs";
13
- import { pathToFileURL } from "url";
14
- function invokedAsScript(moduleUrl, entry) {
15
- if (entry === void 0 || entry.length === 0) return false;
16
- let resolved;
17
- try {
18
- resolved = realpathSync(entry);
19
- } catch {
20
- return false;
21
- }
22
- return moduleUrl === pathToFileURL(resolved).href;
23
- }
24
-
25
- // src/signoff/cli.ts
26
14
  function usage() {
27
15
  return [
28
16
  "Usage: agent-app-signoff [repoDir] [options]",
@@ -148,7 +136,7 @@ async function runSignoffCli(argv) {
148
136
  writeFileSync(abs, `${JSON.stringify(report, null, 2)}
149
137
  `);
150
138
  }
151
- process.stdout.write(`${formatSignoffReport(report)}
139
+ process.stdout.write(`${args.quiet ? formatSignoffLine(report) : formatSignoffReport(report)}
152
140
  `);
153
141
  return report.ok ? 0 : 1;
154
142
  }