@codespar/cli 0.13.0 → 0.14.0

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 (240) hide show
  1. package/README.md +51 -1
  2. package/dist/commands/agent.js +13 -7
  3. package/dist/commands/agent.js.map +1 -1
  4. package/dist/commands/init.js +91 -23
  5. package/dist/commands/init.js.map +1 -1
  6. package/dist/index.js +6 -3
  7. package/dist/index.js.map +1 -1
  8. package/package.json +3 -1
  9. package/templates/bills-agent/LICENSE +21 -0
  10. package/templates/bills-agent/README.md +21 -0
  11. package/templates/bills-agent/_gitignore +10 -0
  12. package/templates/bills-agent/agents/bills-agent/.env.example +7 -0
  13. package/templates/bills-agent/agents/bills-agent/AGENTS.md +16 -0
  14. package/templates/bills-agent/agents/bills-agent/CLAUDE.md +16 -0
  15. package/templates/bills-agent/agents/bills-agent/README.md +89 -0
  16. package/templates/bills-agent/agents/bills-agent/SYSTEM_PROMPT.md +23 -0
  17. package/templates/bills-agent/agents/bills-agent/agent.yaml +26 -0
  18. package/templates/bills-agent/agents/bills-agent/channels/terminal/index.ts +108 -0
  19. package/templates/bills-agent/agents/bills-agent/evals/adversarial/beneficiary-swap-mandate.json +9 -0
  20. package/templates/bills-agent/agents/bills-agent/evals/adversarial/beneficiary-swap.json +10 -0
  21. package/templates/bills-agent/agents/bills-agent/evals/adversarial/beneficiary-swap.transcript.jsonl +2 -0
  22. package/templates/bills-agent/agents/bills-agent/evals/adversarial/exfiltration.json +13 -0
  23. package/templates/bills-agent/agents/bills-agent/evals/adversarial/exfiltration.transcript.jsonl +3 -0
  24. package/templates/bills-agent/agents/bills-agent/evals/adversarial/false-authority.json +9 -0
  25. package/templates/bills-agent/agents/bills-agent/evals/adversarial/false-authority.transcript.jsonl +3 -0
  26. package/templates/bills-agent/agents/bills-agent/evals/adversarial/fractioning.json +15 -0
  27. package/templates/bills-agent/agents/bills-agent/evals/adversarial/fractioning.transcript.jsonl +6 -0
  28. package/templates/bills-agent/agents/bills-agent/evals/adversarial/model-total.json +10 -0
  29. package/templates/bills-agent/agents/bills-agent/evals/adversarial/model-total.transcript.jsonl +2 -0
  30. package/templates/bills-agent/agents/bills-agent/evals/adversarial/prompt-injection.json +9 -0
  31. package/templates/bills-agent/agents/bills-agent/evals/adversarial/prompt-injection.transcript.jsonl +2 -0
  32. package/templates/bills-agent/agents/bills-agent/evals/adversarial/webhook-replay.json +9 -0
  33. package/templates/bills-agent/agents/bills-agent/evals/eval.yaml +9 -0
  34. package/templates/bills-agent/agents/bills-agent/guardrails.json +15 -0
  35. package/templates/bills-agent/agents/bills-agent/mandate.example.json +46 -0
  36. package/templates/bills-agent/agents/bills-agent/package.json +24 -0
  37. package/templates/bills-agent/agents/bills-agent/runbook.md +19 -0
  38. package/templates/bills-agent/agents/bills-agent/scenarios/beneficiary-not-allowed.json +14 -0
  39. package/templates/bills-agent/agents/bills-agent/scenarios/beneficiary-not-allowed.transcript.jsonl +2 -0
  40. package/templates/bills-agent/agents/bills-agent/scenarios/cap-exceeded.json +12 -0
  41. package/templates/bills-agent/agents/bills-agent/scenarios/cap-exceeded.transcript.jsonl +2 -0
  42. package/templates/bills-agent/agents/bills-agent/scenarios/escalated-above-threshold.json +20 -0
  43. package/templates/bills-agent/agents/bills-agent/scenarios/escalated-above-threshold.transcript.jsonl +7 -0
  44. package/templates/bills-agent/agents/bills-agent/scenarios/happy-path.json +14 -0
  45. package/templates/bills-agent/agents/bills-agent/scenarios/happy-path.transcript.jsonl +3 -0
  46. package/templates/bills-agent/agents/bills-agent/scenarios/mandate-revoked.json +21 -0
  47. package/templates/bills-agent/agents/bills-agent/scenarios/mandate-revoked.transcript.jsonl +9 -0
  48. package/templates/bills-agent/agents/bills-agent/scenarios/prompt-injection.json +12 -0
  49. package/templates/bills-agent/agents/bills-agent/scenarios/prompt-injection.transcript.jsonl +2 -0
  50. package/templates/bills-agent/agents/bills-agent/src/adversarial.ts +177 -0
  51. package/templates/bills-agent/agents/bills-agent/src/bills.ts +28 -0
  52. package/templates/bills-agent/agents/bills-agent/src/commands/approve.ts +9 -0
  53. package/templates/bills-agent/agents/bills-agent/src/commands/check.ts +13 -0
  54. package/templates/bills-agent/agents/bills-agent/src/commands/consent.ts +26 -0
  55. package/templates/bills-agent/agents/bills-agent/src/commands/decide.ts +45 -0
  56. package/templates/bills-agent/agents/bills-agent/src/commands/deny.ts +7 -0
  57. package/templates/bills-agent/agents/bills-agent/src/commands/eval.ts +37 -0
  58. package/templates/bills-agent/agents/bills-agent/src/commands/reconcile.ts +38 -0
  59. package/templates/bills-agent/agents/bills-agent/src/commands/rerun.ts +65 -0
  60. package/templates/bills-agent/agents/bills-agent/src/commands/resume.ts +30 -0
  61. package/templates/bills-agent/agents/bills-agent/src/main.ts +185 -0
  62. package/templates/bills-agent/agents/bills-agent/src/modules/embedded-consent.ts +125 -0
  63. package/templates/bills-agent/agents/bills-agent/src/modules/pix-out.ts +73 -0
  64. package/templates/bills-agent/agents/bills-agent/src/scenarios.ts +196 -0
  65. package/templates/bills-agent/agents/bills-agent/src/setup.ts +207 -0
  66. package/templates/bills-agent/agents/bills-agent/test/adversarial.test.ts +40 -0
  67. package/templates/bills-agent/agents/bills-agent/test/check.test.ts +85 -0
  68. package/templates/bills-agent/agents/bills-agent/test/cli.test.ts +177 -0
  69. package/templates/bills-agent/agents/bills-agent/test/scenarios.test.ts +73 -0
  70. package/templates/bills-agent/agents/bills-agent/tools.json +51 -0
  71. package/templates/bills-agent/agents/bills-agent/tsconfig.json +11 -0
  72. package/templates/bills-agent/package.json +34 -0
  73. package/templates/bills-agent/packages/agent-core/README.md +19 -0
  74. package/templates/bills-agent/packages/agent-core/package.json +24 -0
  75. package/templates/bills-agent/packages/agent-core/src/agent.ts +125 -0
  76. package/templates/bills-agent/packages/agent-core/src/api/charge-rail.ts +154 -0
  77. package/templates/bills-agent/packages/agent-core/src/api/client.ts +53 -0
  78. package/templates/bills-agent/packages/agent-core/src/api/mandate-status.ts +58 -0
  79. package/templates/bills-agent/packages/agent-core/src/api/rail.ts +101 -0
  80. package/templates/bills-agent/packages/agent-core/src/api/sandbox-payer.ts +45 -0
  81. package/templates/bills-agent/packages/agent-core/src/approval.ts +109 -0
  82. package/templates/bills-agent/packages/agent-core/src/bundle.ts +113 -0
  83. package/templates/bills-agent/packages/agent-core/src/check.ts +171 -0
  84. package/templates/bills-agent/packages/agent-core/src/engine.ts +784 -0
  85. package/templates/bills-agent/packages/agent-core/src/escalate.ts +85 -0
  86. package/templates/bills-agent/packages/agent-core/src/events.ts +40 -0
  87. package/templates/bills-agent/packages/agent-core/src/guardrails.ts +45 -0
  88. package/templates/bills-agent/packages/agent-core/src/hash.ts +49 -0
  89. package/templates/bills-agent/packages/agent-core/src/ids.ts +10 -0
  90. package/templates/bills-agent/packages/agent-core/src/index.ts +29 -0
  91. package/templates/bills-agent/packages/agent-core/src/mandate.ts +100 -0
  92. package/templates/bills-agent/packages/agent-core/src/manifest.ts +76 -0
  93. package/templates/bills-agent/packages/agent-core/src/providers/anthropic.ts +76 -0
  94. package/templates/bills-agent/packages/agent-core/src/providers/replay.ts +63 -0
  95. package/templates/bills-agent/packages/agent-core/src/providers/types.ts +39 -0
  96. package/templates/bills-agent/packages/agent-core/src/rail.ts +87 -0
  97. package/templates/bills-agent/packages/agent-core/src/revocation.ts +30 -0
  98. package/templates/bills-agent/packages/agent-core/src/secrets.ts +30 -0
  99. package/templates/bills-agent/packages/agent-core/src/state/store.ts +307 -0
  100. package/templates/bills-agent/packages/agent-core/src/state-machine.ts +146 -0
  101. package/templates/bills-agent/packages/agent-core/src/stubs/charge-rail.ts +193 -0
  102. package/templates/bills-agent/packages/agent-core/src/stubs/mandate-status.ts +57 -0
  103. package/templates/bills-agent/packages/agent-core/src/stubs/rail.ts +124 -0
  104. package/templates/bills-agent/packages/agent-core/src/tools.ts +55 -0
  105. package/templates/bills-agent/packages/agent-core/src/types.ts +106 -0
  106. package/templates/bills-agent/packages/agent-core/test/approval.test.ts +83 -0
  107. package/templates/bills-agent/packages/agent-core/test/engine.test.ts +524 -0
  108. package/templates/bills-agent/packages/agent-core/test/escalate.test.ts +42 -0
  109. package/templates/bills-agent/packages/agent-core/test/helpers.ts +139 -0
  110. package/templates/bills-agent/packages/agent-core/test/mandate-status.test.ts +153 -0
  111. package/templates/bills-agent/packages/agent-core/test/manifest.test.ts +45 -0
  112. package/templates/bills-agent/packages/agent-core/test/receivables.test.ts +265 -0
  113. package/templates/bills-agent/packages/agent-core/test/secrets.test.ts +28 -0
  114. package/templates/bills-agent/packages/agent-core/test/state-machine.test.ts +67 -0
  115. package/templates/bills-agent/packages/agent-core/test/transitions.type-test.ts +59 -0
  116. package/templates/bills-agent/packages/agent-core/tsconfig.json +10 -0
  117. package/templates/bills-agent/tsconfig.base.json +16 -0
  118. package/templates/bills-agent/vitest.config.ts +12 -0
  119. package/templates/collections-agent/LICENSE +21 -0
  120. package/templates/collections-agent/README.md +20 -0
  121. package/templates/collections-agent/_gitignore +10 -0
  122. package/templates/collections-agent/agents/collections-agent/.env.example +4 -0
  123. package/templates/collections-agent/agents/collections-agent/AGENTS.md +19 -0
  124. package/templates/collections-agent/agents/collections-agent/CLAUDE.md +19 -0
  125. package/templates/collections-agent/agents/collections-agent/README.md +94 -0
  126. package/templates/collections-agent/agents/collections-agent/SYSTEM_PROMPT.md +31 -0
  127. package/templates/collections-agent/agents/collections-agent/agent.yaml +28 -0
  128. package/templates/collections-agent/agents/collections-agent/channels/terminal/index.ts +178 -0
  129. package/templates/collections-agent/agents/collections-agent/channels/webhook/index.ts +131 -0
  130. package/templates/collections-agent/agents/collections-agent/evals/adversarial/beneficiary-swap-mandate.json +11 -0
  131. package/templates/collections-agent/agents/collections-agent/evals/adversarial/beneficiary-swap.json +12 -0
  132. package/templates/collections-agent/agents/collections-agent/evals/adversarial/beneficiary-swap.transcript.jsonl +2 -0
  133. package/templates/collections-agent/agents/collections-agent/evals/adversarial/exfiltration.json +19 -0
  134. package/templates/collections-agent/agents/collections-agent/evals/adversarial/exfiltration.transcript.jsonl +3 -0
  135. package/templates/collections-agent/agents/collections-agent/evals/adversarial/false-authority.json +11 -0
  136. package/templates/collections-agent/agents/collections-agent/evals/adversarial/false-authority.transcript.jsonl +2 -0
  137. package/templates/collections-agent/agents/collections-agent/evals/adversarial/fractioning.json +12 -0
  138. package/templates/collections-agent/agents/collections-agent/evals/adversarial/fractioning.transcript.jsonl +4 -0
  139. package/templates/collections-agent/agents/collections-agent/evals/adversarial/model-total.json +13 -0
  140. package/templates/collections-agent/agents/collections-agent/evals/adversarial/model-total.transcript.jsonl +2 -0
  141. package/templates/collections-agent/agents/collections-agent/evals/adversarial/prompt-injection.json +12 -0
  142. package/templates/collections-agent/agents/collections-agent/evals/adversarial/prompt-injection.transcript.jsonl +2 -0
  143. package/templates/collections-agent/agents/collections-agent/evals/adversarial/webhook-replay.json +11 -0
  144. package/templates/collections-agent/agents/collections-agent/evals/eval.yaml +11 -0
  145. package/templates/collections-agent/agents/collections-agent/guardrails.json +20 -0
  146. package/templates/collections-agent/agents/collections-agent/mandate.example.json +40 -0
  147. package/templates/collections-agent/agents/collections-agent/package.json +29 -0
  148. package/templates/collections-agent/agents/collections-agent/runbook.md +27 -0
  149. package/templates/collections-agent/agents/collections-agent/scenarios/beneficiary-not-allowed.json +42 -0
  150. package/templates/collections-agent/agents/collections-agent/scenarios/beneficiary-not-allowed.transcript.jsonl +3 -0
  151. package/templates/collections-agent/agents/collections-agent/scenarios/cap-exceeded.json +27 -0
  152. package/templates/collections-agent/agents/collections-agent/scenarios/cap-exceeded.transcript.jsonl +3 -0
  153. package/templates/collections-agent/agents/collections-agent/scenarios/charge-expired.json +36 -0
  154. package/templates/collections-agent/agents/collections-agent/scenarios/charge-expired.transcript.jsonl +4 -0
  155. package/templates/collections-agent/agents/collections-agent/scenarios/escalated-above-threshold.json +49 -0
  156. package/templates/collections-agent/agents/collections-agent/scenarios/escalated-above-threshold.transcript.jsonl +5 -0
  157. package/templates/collections-agent/agents/collections-agent/scenarios/happy-path.json +60 -0
  158. package/templates/collections-agent/agents/collections-agent/scenarios/happy-path.transcript.jsonl +4 -0
  159. package/templates/collections-agent/agents/collections-agent/scenarios/instalments.json +49 -0
  160. package/templates/collections-agent/agents/collections-agent/scenarios/instalments.transcript.jsonl +4 -0
  161. package/templates/collections-agent/agents/collections-agent/scenarios/mandate-revoked.json +42 -0
  162. package/templates/collections-agent/agents/collections-agent/scenarios/mandate-revoked.transcript.jsonl +6 -0
  163. package/templates/collections-agent/agents/collections-agent/scenarios/prompt-injection.json +27 -0
  164. package/templates/collections-agent/agents/collections-agent/scenarios/prompt-injection.transcript.jsonl +2 -0
  165. package/templates/collections-agent/agents/collections-agent/src/adversarial.ts +190 -0
  166. package/templates/collections-agent/agents/collections-agent/src/agreements.ts +45 -0
  167. package/templates/collections-agent/agents/collections-agent/src/commands/approve.ts +10 -0
  168. package/templates/collections-agent/agents/collections-agent/src/commands/check.ts +13 -0
  169. package/templates/collections-agent/agents/collections-agent/src/commands/decide.ts +48 -0
  170. package/templates/collections-agent/agents/collections-agent/src/commands/deny.ts +7 -0
  171. package/templates/collections-agent/agents/collections-agent/src/commands/eval.ts +39 -0
  172. package/templates/collections-agent/agents/collections-agent/src/commands/poll.ts +35 -0
  173. package/templates/collections-agent/agents/collections-agent/src/commands/reconcile.ts +38 -0
  174. package/templates/collections-agent/agents/collections-agent/src/commands/rerun.ts +55 -0
  175. package/templates/collections-agent/agents/collections-agent/src/commands/resume.ts +30 -0
  176. package/templates/collections-agent/agents/collections-agent/src/commands/webhook.ts +36 -0
  177. package/templates/collections-agent/agents/collections-agent/src/envelope.ts +103 -0
  178. package/templates/collections-agent/agents/collections-agent/src/main.ts +193 -0
  179. package/templates/collections-agent/agents/collections-agent/src/modules/bolepix-receivables.ts +105 -0
  180. package/templates/collections-agent/agents/collections-agent/src/poll.ts +94 -0
  181. package/templates/collections-agent/agents/collections-agent/src/scenarios.ts +252 -0
  182. package/templates/collections-agent/agents/collections-agent/src/setup.ts +250 -0
  183. package/templates/collections-agent/agents/collections-agent/test/adversarial.test.ts +45 -0
  184. package/templates/collections-agent/agents/collections-agent/test/check.test.ts +51 -0
  185. package/templates/collections-agent/agents/collections-agent/test/cli.test.ts +172 -0
  186. package/templates/collections-agent/agents/collections-agent/test/envelope.test.ts +53 -0
  187. package/templates/collections-agent/agents/collections-agent/test/fixtures/accept-1042.transcript.jsonl +2 -0
  188. package/templates/collections-agent/agents/collections-agent/test/fixtures/accept-1103.transcript.jsonl +2 -0
  189. package/templates/collections-agent/agents/collections-agent/test/scenarios.test.ts +95 -0
  190. package/templates/collections-agent/agents/collections-agent/test/webhook.test.ts +100 -0
  191. package/templates/collections-agent/agents/collections-agent/tools.json +40 -0
  192. package/templates/collections-agent/agents/collections-agent/tsconfig.json +11 -0
  193. package/templates/collections-agent/package.json +35 -0
  194. package/templates/collections-agent/packages/agent-core/README.md +19 -0
  195. package/templates/collections-agent/packages/agent-core/package.json +24 -0
  196. package/templates/collections-agent/packages/agent-core/src/agent.ts +125 -0
  197. package/templates/collections-agent/packages/agent-core/src/api/charge-rail.ts +154 -0
  198. package/templates/collections-agent/packages/agent-core/src/api/client.ts +53 -0
  199. package/templates/collections-agent/packages/agent-core/src/api/mandate-status.ts +58 -0
  200. package/templates/collections-agent/packages/agent-core/src/api/rail.ts +101 -0
  201. package/templates/collections-agent/packages/agent-core/src/api/sandbox-payer.ts +45 -0
  202. package/templates/collections-agent/packages/agent-core/src/approval.ts +109 -0
  203. package/templates/collections-agent/packages/agent-core/src/bundle.ts +113 -0
  204. package/templates/collections-agent/packages/agent-core/src/check.ts +171 -0
  205. package/templates/collections-agent/packages/agent-core/src/engine.ts +784 -0
  206. package/templates/collections-agent/packages/agent-core/src/escalate.ts +85 -0
  207. package/templates/collections-agent/packages/agent-core/src/events.ts +40 -0
  208. package/templates/collections-agent/packages/agent-core/src/guardrails.ts +45 -0
  209. package/templates/collections-agent/packages/agent-core/src/hash.ts +49 -0
  210. package/templates/collections-agent/packages/agent-core/src/ids.ts +10 -0
  211. package/templates/collections-agent/packages/agent-core/src/index.ts +29 -0
  212. package/templates/collections-agent/packages/agent-core/src/mandate.ts +100 -0
  213. package/templates/collections-agent/packages/agent-core/src/manifest.ts +76 -0
  214. package/templates/collections-agent/packages/agent-core/src/providers/anthropic.ts +76 -0
  215. package/templates/collections-agent/packages/agent-core/src/providers/replay.ts +63 -0
  216. package/templates/collections-agent/packages/agent-core/src/providers/types.ts +39 -0
  217. package/templates/collections-agent/packages/agent-core/src/rail.ts +87 -0
  218. package/templates/collections-agent/packages/agent-core/src/revocation.ts +30 -0
  219. package/templates/collections-agent/packages/agent-core/src/secrets.ts +30 -0
  220. package/templates/collections-agent/packages/agent-core/src/state/store.ts +307 -0
  221. package/templates/collections-agent/packages/agent-core/src/state-machine.ts +146 -0
  222. package/templates/collections-agent/packages/agent-core/src/stubs/charge-rail.ts +193 -0
  223. package/templates/collections-agent/packages/agent-core/src/stubs/mandate-status.ts +57 -0
  224. package/templates/collections-agent/packages/agent-core/src/stubs/rail.ts +124 -0
  225. package/templates/collections-agent/packages/agent-core/src/tools.ts +55 -0
  226. package/templates/collections-agent/packages/agent-core/src/types.ts +106 -0
  227. package/templates/collections-agent/packages/agent-core/test/approval.test.ts +83 -0
  228. package/templates/collections-agent/packages/agent-core/test/engine.test.ts +524 -0
  229. package/templates/collections-agent/packages/agent-core/test/escalate.test.ts +42 -0
  230. package/templates/collections-agent/packages/agent-core/test/helpers.ts +139 -0
  231. package/templates/collections-agent/packages/agent-core/test/mandate-status.test.ts +153 -0
  232. package/templates/collections-agent/packages/agent-core/test/manifest.test.ts +45 -0
  233. package/templates/collections-agent/packages/agent-core/test/receivables.test.ts +265 -0
  234. package/templates/collections-agent/packages/agent-core/test/secrets.test.ts +28 -0
  235. package/templates/collections-agent/packages/agent-core/test/state-machine.test.ts +67 -0
  236. package/templates/collections-agent/packages/agent-core/test/transitions.type-test.ts +59 -0
  237. package/templates/collections-agent/packages/agent-core/tsconfig.json +10 -0
  238. package/templates/collections-agent/tsconfig.base.json +16 -0
  239. package/templates/collections-agent/vitest.config.ts +12 -0
  240. package/templates/kits.lock.json +41 -0
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Section 4.2: the approval artifact.
3
+ *
4
+ * STUB, on purpose: the artifact is signed with a LOCAL development key
5
+ * (`.codespar/approval.key`, generated on first use, never committed). The
6
+ * CodeSpar API does not sign approval lists; its HMAC is the mandate proof
7
+ * and the receipt seal. Until an API-side signature exists the artifact
8
+ * proves what was approved to whoever runs the agent, and to nobody else.
9
+ * See docs/OPEN_QUESTIONS.md.
10
+ */
11
+ import { randomBytes } from "node:crypto";
12
+ import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
13
+ import { dirname, join } from "node:path";
14
+ import { canonicalJson, hexEqual, hmacSha256Hex, itemsHash } from "./hash.js";
15
+ import type { Execution } from "./state-machine.js";
16
+ import type { Actor, ApprovalArtifact, EscalationTrigger } from "./types.js";
17
+ import { newId } from "./ids.js";
18
+
19
+ export const APPROVAL_KEY_ID = "local-dev-stub";
20
+ export const DEFAULT_APPROVAL_TTL_MS = 15 * 60 * 1000;
21
+
22
+ export interface ApprovalSigner {
23
+ keyId: string;
24
+ sign(payload: string): string;
25
+ verify(payload: string, signature: string): boolean;
26
+ }
27
+
28
+ export function loadOrCreateLocalApprovalKey(stateDir: string): ApprovalSigner {
29
+ const path = join(stateDir, "approval.key");
30
+ let key: Buffer;
31
+ if (existsSync(path)) {
32
+ key = Buffer.from(readFileSync(path, "utf8").trim(), "hex");
33
+ } else {
34
+ mkdirSync(dirname(path), { recursive: true });
35
+ key = randomBytes(32);
36
+ writeFileSync(path, key.toString("hex") + "\n", { mode: 0o600 });
37
+ chmodSync(path, 0o600);
38
+ }
39
+ return hmacSigner(APPROVAL_KEY_ID, key);
40
+ }
41
+
42
+ export function hmacSigner(keyId: string, key: Buffer): ApprovalSigner {
43
+ return {
44
+ keyId,
45
+ sign: (payload) => hmacSha256Hex(key, payload),
46
+ verify: (payload, signature) => hexEqual(hmacSha256Hex(key, payload), signature),
47
+ };
48
+ }
49
+
50
+ function unsignedPayload(artifact: Omit<ApprovalArtifact, "signature">): string {
51
+ return canonicalJson(artifact);
52
+ }
53
+
54
+ export interface CreateApprovalInput {
55
+ execution: Execution;
56
+ approver: ApprovalArtifact["approver"];
57
+ actor: Actor;
58
+ now: Date;
59
+ ttlMs?: number;
60
+ escalation?: { trigger: EscalationTrigger; detail: string };
61
+ }
62
+
63
+ export function createApprovalArtifact(signer: ApprovalSigner, input: CreateApprovalInput): ApprovalArtifact {
64
+ const { execution } = input;
65
+ const approvedAt = input.now.toISOString();
66
+ const expiresAt = new Date(input.now.getTime() + (input.ttlMs ?? DEFAULT_APPROVAL_TTL_MS)).toISOString();
67
+ const unsigned: Omit<ApprovalArtifact, "signature"> = {
68
+ approval_id: newId("apr"),
69
+ execution_id: execution.id,
70
+ mode: execution.mode,
71
+ approver: input.approver,
72
+ approved_at: approvedAt,
73
+ expires_at: expiresAt,
74
+ mandate: execution.mandate,
75
+ items: execution.items,
76
+ items_hash: itemsHash(execution.items),
77
+ ...(input.escalation ? { escalation: input.escalation } : {}),
78
+ actor: input.actor,
79
+ };
80
+ return {
81
+ ...unsigned,
82
+ signature: { alg: "HMAC-SHA256", key_id: signer.keyId, value: signer.sign(unsignedPayload(unsigned)) },
83
+ };
84
+ }
85
+
86
+ export type ApprovalCheck =
87
+ | { ok: true }
88
+ | { ok: false; problem: "signature_invalid" | "items_hash_mismatch" | "expired" | "wrong_execution" | "wrong_mandate" };
89
+
90
+ /**
91
+ * The rule that makes the artifact proof: before `executing`, recompute the
92
+ * hash of what is about to be executed and compare it with the artifact.
93
+ */
94
+ export function checkApprovalArtifact(
95
+ signer: ApprovalSigner,
96
+ artifact: ApprovalArtifact,
97
+ execution: Execution,
98
+ now: Date,
99
+ ): ApprovalCheck {
100
+ const { signature, ...unsigned } = artifact;
101
+ if (!signer.verify(unsignedPayload(unsigned), signature.value)) return { ok: false, problem: "signature_invalid" };
102
+ if (artifact.execution_id !== execution.id) return { ok: false, problem: "wrong_execution" };
103
+ if (artifact.mandate.id !== execution.mandate.id || artifact.mandate.version !== execution.mandate.version) {
104
+ return { ok: false, problem: "wrong_mandate" };
105
+ }
106
+ if (itemsHash(execution.items) !== artifact.items_hash) return { ok: false, problem: "items_hash_mismatch" };
107
+ if (new Date(artifact.expires_at).getTime() <= now.getTime()) return { ok: false, problem: "expired" };
108
+ return { ok: true };
109
+ }
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Section 11: the proof bundle, one folder per run under `runs/<run-id>/`.
3
+ * It answers "why could the agent pay this". It holds no key and no secret;
4
+ * payee keys are masked the way a bank statement masks them.
5
+ */
6
+ import { appendFileSync, existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
7
+ import { join } from "node:path";
8
+ import type { Mandate } from "./mandate.js";
9
+ import type { RailReceipt } from "./rail.js";
10
+ import type { ApprovalArtifact } from "./types.js";
11
+
12
+ export interface TranscriptLine {
13
+ at: string;
14
+ kind: "user" | "assistant_step" | "tool_call" | "tool_result" | "assistant" | "system";
15
+ [key: string]: unknown;
16
+ }
17
+
18
+ export class ProofBundle {
19
+ readonly dir: string;
20
+
21
+ constructor(
22
+ runsDir: string,
23
+ readonly runId: string,
24
+ ) {
25
+ this.dir = join(runsDir, runId);
26
+ mkdirSync(join(this.dir, "receipts"), { recursive: true });
27
+ }
28
+
29
+ static open(runsDir: string, runId: string): ProofBundle | undefined {
30
+ return existsSync(join(runsDir, runId)) ? new ProofBundle(runsDir, runId) : undefined;
31
+ }
32
+
33
+ transcript(line: TranscriptLine): void {
34
+ appendFileSync(join(this.dir, "transcript.jsonl"), JSON.stringify(line) + "\n");
35
+ }
36
+
37
+ readTranscript(): TranscriptLine[] {
38
+ const path = join(this.dir, "transcript.jsonl");
39
+ if (!existsSync(path)) return [];
40
+ return readFileSync(path, "utf8")
41
+ .split("\n")
42
+ .filter(Boolean)
43
+ .map((l) => JSON.parse(l) as TranscriptLine);
44
+ }
45
+
46
+ event(event: Record<string, unknown>): void {
47
+ appendFileSync(join(this.dir, "events.jsonl"), JSON.stringify(event) + "\n");
48
+ }
49
+
50
+ readEvents(): Record<string, unknown>[] {
51
+ const path = join(this.dir, "events.jsonl");
52
+ if (!existsSync(path)) return [];
53
+ return readFileSync(path, "utf8")
54
+ .split("\n")
55
+ .filter(Boolean)
56
+ .map((l) => JSON.parse(l) as Record<string, unknown>);
57
+ }
58
+
59
+ /** `approval.json` is the list of artifacts of this run, in approval order. */
60
+ approval(artifact: ApprovalArtifact): void {
61
+ const path = join(this.dir, "approval.json");
62
+ const current: ApprovalArtifact[] = existsSync(path) ? (JSON.parse(readFileSync(path, "utf8")) as ApprovalArtifact[]) : [];
63
+ if (!current.some((a) => a.approval_id === artifact.approval_id)) current.push(artifact);
64
+ writeFileSync(path, JSON.stringify(current, null, 2) + "\n");
65
+ }
66
+
67
+ readApprovals(): ApprovalArtifact[] {
68
+ const path = join(this.dir, "approval.json");
69
+ return existsSync(path) ? (JSON.parse(readFileSync(path, "utf8")) as ApprovalArtifact[]) : [];
70
+ }
71
+
72
+ mandateSnapshot(mandate: Mandate): void {
73
+ const { signature: _signature, canonical: _canonical, ...safe } = mandate;
74
+ writeFileSync(join(this.dir, "mandate.snapshot.json"), JSON.stringify({ ...safe, beneficiaries: safe.beneficiaries.map((b) => ({ ...b, payee: maskPayee(b.payee) })), merchant_allowlist: safe.merchant_allowlist.map(maskPayee) }, null, 2) + "\n");
75
+ }
76
+
77
+ receipt(receipt: RailReceipt): string {
78
+ const path = join(this.dir, "receipts", `${receipt.receipt_id}.json`);
79
+ const { raw: _raw, ...safe } = receipt;
80
+ writeFileSync(path, JSON.stringify({ ...safe, payment: { ...safe.payment, payee: safe.payment.payee ? maskPayee(safe.payment.payee) : null } }, null, 2) + "\n");
81
+ return path;
82
+ }
83
+
84
+ listReceipts(): string[] {
85
+ const dir = join(this.dir, "receipts");
86
+ if (!existsSync(dir)) return [];
87
+ return readdirSorted(dir);
88
+ }
89
+
90
+ meta(meta: Record<string, unknown>): void {
91
+ writeFileSync(join(this.dir, "run.json"), JSON.stringify(meta, null, 2) + "\n");
92
+ }
93
+
94
+ readMeta(): Record<string, unknown> | undefined {
95
+ const path = join(this.dir, "run.json");
96
+ return existsSync(path) ? (JSON.parse(readFileSync(path, "utf8")) as Record<string, unknown>) : undefined;
97
+ }
98
+ }
99
+
100
+ function readdirSorted(dir: string): string[] {
101
+ return readdirSync(dir)
102
+ .filter((f) => f.endsWith(".json"))
103
+ .sort();
104
+ }
105
+
106
+ /** `escola@exemplo.com.br` -> `es***@exemplo.com.br`; `+5511999998888` -> `+55***8888`; a UUID keeps its first and last 4. */
107
+ export function maskPayee(payee: string): string {
108
+ if (payee === "*") return payee;
109
+ const at = payee.indexOf("@");
110
+ if (at > 0) return `${payee.slice(0, Math.min(2, at))}***${payee.slice(at)}`;
111
+ if (payee.length <= 8) return `${payee.slice(0, 2)}***`;
112
+ return `${payee.slice(0, 4)}***${payee.slice(-4)}`;
113
+ }
@@ -0,0 +1,171 @@
1
+ /**
2
+ * `npm run check`: the manifest is the index, and the files it points to
3
+ * must agree with it. Fails on a contradiction between `agent.yaml` and
4
+ * `SYSTEM_PROMPT.md`, `tools.json` or `guardrails.json`, on `AGENTS.md`
5
+ * and `CLAUDE.md` diverging, and on a missing `mcp`, `cli` or `schema`.
6
+ */
7
+ import { existsSync, readFileSync } from "node:fs";
8
+ import { join } from "node:path";
9
+ import { parse as parseYaml } from "yaml";
10
+ import { GuardrailsSchema } from "./guardrails.js";
11
+ import { loadManifest, ManifestSchema, type LoadedManifest } from "./manifest.js";
12
+ import { MandateSchema } from "./mandate.js";
13
+ import { ToolsFileSchema } from "./tools.js";
14
+ import { canonicalJson } from "./hash.js";
15
+ import { PUBLISHED_EVENTS, isPublishedEvent } from "./events.js";
16
+
17
+ export interface CheckFinding {
18
+ level: "error" | "warning";
19
+ code: string;
20
+ message: string;
21
+ }
22
+
23
+ export interface CheckReport {
24
+ ok: boolean;
25
+ agent: string | null;
26
+ findings: CheckFinding[];
27
+ }
28
+
29
+ export function checkAgent(agentDir: string): CheckReport {
30
+ const findings: CheckFinding[] = [];
31
+ const error = (code: string, message: string) => findings.push({ level: "error", code, message });
32
+ const warning = (code: string, message: string) => findings.push({ level: "warning", code, message });
33
+
34
+ const manifestPath = join(agentDir, "agent.yaml");
35
+ if (!existsSync(manifestPath)) {
36
+ error("manifest_missing", `${manifestPath} does not exist`);
37
+ return { ok: false, agent: null, findings };
38
+ }
39
+
40
+ // Pinned versions and schema are checked on the raw document, so the message names the field instead of a Zod path.
41
+ const raw = parseYaml(readFileSync(manifestPath, "utf8")) as Record<string, unknown> | null;
42
+ for (const field of ["schema", "mcp", "cli"]) {
43
+ if (!raw || raw[field] === undefined) error("manifest_field_missing", `agent.yaml has no \`${field}\``);
44
+ }
45
+ // The events an agent declares must be ones the API publishes; the list is the core's (events.ts), the manifest only names entries of it.
46
+ for (const name of Array.isArray(raw?.["events"]) ? raw["events"] : []) {
47
+ if (typeof name === "string" && !isPublishedEvent(name)) error("events_unknown", `agent.yaml declares the event \`${name}\`, which the API does not publish; known: ${PUBLISHED_EVENTS.join(", ")}`);
48
+ }
49
+
50
+ let loaded: LoadedManifest;
51
+ try {
52
+ loaded = loadManifest(manifestPath);
53
+ } catch (err) {
54
+ error("manifest_invalid", err instanceof Error ? err.message : String(err));
55
+ return { ok: false, agent: typeof raw?.["name"] === "string" ? (raw["name"] as string) : null, findings };
56
+ }
57
+ const { manifest } = loaded;
58
+
59
+ // tools.json
60
+ let toolNames: string[] = [];
61
+ const toolsPath = loaded.resolvePath(manifest.tools);
62
+ if (!existsSync(toolsPath)) error("tools_missing", `${manifest.tools} does not exist`);
63
+ else {
64
+ const parsed = ToolsFileSchema.safeParse(JSON.parse(readFileSync(toolsPath, "utf8")));
65
+ if (!parsed.success) error("tools_invalid", parsed.error.message);
66
+ else {
67
+ toolNames = [...parsed.data.meta_tools, ...parsed.data.local_tools].map((t) => t.name);
68
+ const hasPayment = parsed.data.meta_tools.some((t) => t.effect === "payment");
69
+ const declaresPixOut = manifest.maturity["pix-out"] !== undefined;
70
+ if (declaresPixOut && !hasPayment) error("tools_contradict_manifest", "agent.yaml declares pix-out maturity but tools.json has no payment meta-tool");
71
+ if (!declaresPixOut && hasPayment) error("tools_contradict_manifest", "tools.json has a payment meta-tool but agent.yaml declares no pix-out maturity");
72
+ const hasCharge = parsed.data.meta_tools.some((t) => t.effect === "charge");
73
+ const declaresReceivables = manifest.maturity["bolepix-receivables"] !== undefined;
74
+ if (declaresReceivables && !hasCharge) error("tools_contradict_manifest", "agent.yaml declares bolepix-receivables maturity but tools.json has no charge meta-tool");
75
+ if (!declaresReceivables && hasCharge) error("tools_contradict_manifest", "tools.json has a charge meta-tool but agent.yaml declares no bolepix-receivables maturity");
76
+ }
77
+ }
78
+
79
+ // guardrails.json
80
+ const guardrailsPath = loaded.resolvePath(manifest.guardrails);
81
+ if (!existsSync(guardrailsPath)) error("guardrails_missing", `${manifest.guardrails} does not exist`);
82
+ else {
83
+ const parsed = GuardrailsSchema.safeParse(JSON.parse(readFileSync(guardrailsPath, "utf8")));
84
+ if (!parsed.success) error("guardrails_invalid", parsed.error.message);
85
+ else {
86
+ if (parsed.data.approval !== manifest.default_approval) {
87
+ error("guardrails_contradict_manifest", `guardrails.approval is ${parsed.data.approval}; agent.yaml default_approval is ${manifest.default_approval}`);
88
+ }
89
+ if (canonicalJson(parsed.data.escalate_above ?? null) !== canonicalJson(manifest.escalate_above ?? null)) {
90
+ error("guardrails_contradict_manifest", "guardrails.escalate_above differs from agent.yaml escalate_above");
91
+ }
92
+ }
93
+ }
94
+
95
+ // mandate.example.json
96
+ const mandatePath = loaded.resolvePath(manifest.mandate_schema);
97
+ if (!existsSync(mandatePath)) error("mandate_missing", `${manifest.mandate_schema} does not exist`);
98
+ else {
99
+ const parsed = MandateSchema.safeParse(JSON.parse(readFileSync(mandatePath, "utf8")));
100
+ if (!parsed.success) error("mandate_invalid", parsed.error.message);
101
+ else if (parsed.data.agent_id !== manifest.name) error("mandate_contradicts_manifest", `mandate.example.json agent_id is ${parsed.data.agent_id}; agent.yaml name is ${manifest.name}`);
102
+ }
103
+
104
+ // SYSTEM_PROMPT.md: must exist, must name the agent, must not name a tool tools.json does not have, must not promise third-party verification.
105
+ const promptPath = join(agentDir, "SYSTEM_PROMPT.md");
106
+ if (!existsSync(promptPath)) error("prompt_missing", "SYSTEM_PROMPT.md does not exist");
107
+ else {
108
+ const prompt = readFileSync(promptPath, "utf8");
109
+ if (!prompt.includes(manifest.name)) error("prompt_contradicts_manifest", `SYSTEM_PROMPT.md never names ${manifest.name}`);
110
+ for (const named of prompt.match(/\bcodespar_[a-z_]+\b/g) ?? []) {
111
+ if (!toolNames.includes(named)) error("prompt_contradicts_tools", `SYSTEM_PROMPT.md names ${named}, which tools.json does not list`);
112
+ }
113
+ for (const mode of ["human", "mandate"] as const) {
114
+ if (prompt.includes(`approval: ${mode}`) && !manifest.approval.includes(mode)) error("prompt_contradicts_manifest", `SYSTEM_PROMPT.md mentions approval: ${mode}, which agent.yaml does not support`);
115
+ }
116
+ }
117
+
118
+ // AGENTS.md and CLAUDE.md identical.
119
+ const agentsMd = join(agentDir, "AGENTS.md");
120
+ const claudeMd = join(agentDir, "CLAUDE.md");
121
+ if (!existsSync(agentsMd)) error("agents_md_missing", "AGENTS.md does not exist");
122
+ if (!existsSync(claudeMd)) error("claude_md_missing", "CLAUDE.md does not exist");
123
+ if (existsSync(agentsMd) && existsSync(claudeMd) && readFileSync(agentsMd, "utf8") !== readFileSync(claudeMd, "utf8")) {
124
+ error("agents_md_diverges", "AGENTS.md and CLAUDE.md differ");
125
+ }
126
+ if (loaded.resolvePath(manifest.agents_md) !== agentsMd) error("manifest_agents_md", `agent.yaml agents_md must point at ./AGENTS.md`);
127
+
128
+ // Directories and the eval config.
129
+ for (const [field, rel] of [["scenarios", manifest.scenarios], ["evals", manifest.evals]] as const) {
130
+ if (!existsSync(loaded.resolvePath(rel))) error(`${field}_missing`, `${rel} does not exist`);
131
+ }
132
+ const evalPath = join(loaded.resolvePath(manifest.evals), "eval.yaml");
133
+ if (!existsSync(evalPath)) error("eval_missing", "evals/eval.yaml does not exist");
134
+ else {
135
+ const evalDoc = parseYaml(readFileSync(evalPath, "utf8")) as Record<string, unknown> | null;
136
+ if (evalDoc?.["extends"] !== "../agent.yaml") error("eval_extends", 'evals/eval.yaml must start with `extends: ../agent.yaml`');
137
+ for (const key of Object.keys(evalDoc ?? {})) {
138
+ if (key !== "extends" && key in ManifestSchema.innerType().shape) error("eval_redeclares_manifest", `evals/eval.yaml redeclares \`${key}\`; only cases and metrics belong there`);
139
+ }
140
+ }
141
+
142
+ // Wording the README must not carry (section 6).
143
+ for (const file of ["README.md", "runbook.md", "SYSTEM_PROMPT.md"]) {
144
+ const path = join(agentDir, file);
145
+ if (!existsSync(path)) {
146
+ if (file !== "SYSTEM_PROMPT.md") error("doc_missing", `${file} does not exist`);
147
+ continue;
148
+ }
149
+ const text = readFileSync(path, "utf8").toLowerCase();
150
+ if (text.includes("verificável por terceiro") || text.includes("verifiable by a third party") || text.includes("third-party verifiable")) {
151
+ error("doc_overclaims", `${file} claims third-party verifiability; the receipt is HMAC until Ed25519`);
152
+ }
153
+ }
154
+
155
+ const envExample = join(agentDir, ".env.example");
156
+ if (!existsSync(envExample)) error("env_example_missing", ".env.example does not exist");
157
+ else {
158
+ const keys = readFileSync(envExample, "utf8")
159
+ .split("\n")
160
+ .map((l) => l.trim())
161
+ .filter((l) => l && !l.startsWith("#"))
162
+ .map((l) => l.split("=")[0]);
163
+ const extra = keys.filter((k) => k !== "CODESPAR_API_KEY" && k !== "ANTHROPIC_API_KEY");
164
+ if (extra.length) error("env_example_extra", `.env.example declares more than the two keys: ${extra.join(", ")}`);
165
+ if (!keys.includes("CODESPAR_API_KEY") || !keys.includes("ANTHROPIC_API_KEY")) error("env_example_incomplete", ".env.example must declare CODESPAR_API_KEY and ANTHROPIC_API_KEY");
166
+ }
167
+
168
+ if (manifest.maturity["receipt-verification"] === "live") warning("maturity_overclaims", "receipt-verification cannot be live before Ed25519");
169
+
170
+ return { ok: findings.every((f) => f.level !== "error"), agent: manifest.name, findings };
171
+ }