@yiaany/ghostapi 0.1.9 → 0.2.1

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 (292) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/CONTRIBUTING.md +132 -119
  3. package/LICENSE +21 -21
  4. package/PROVENANCE.md +13 -0
  5. package/README.md +78 -84
  6. package/SECURITY.md +56 -53
  7. package/dist/actions/gateway.js +320 -72
  8. package/dist/actions/gateway.js.map +1 -1
  9. package/dist/actions/index.d.ts +2 -2
  10. package/dist/actions/index.js +1 -1
  11. package/dist/actions/index.js.map +1 -1
  12. package/dist/agents/agentPrompt.js +7 -5
  13. package/dist/agents/agentPrompt.js.map +1 -1
  14. package/dist/ai/aiClient.js +5 -5
  15. package/dist/ai/aiClient.js.map +1 -1
  16. package/dist/ai/aiGenerator.js +66 -26
  17. package/dist/ai/aiGenerator.js.map +1 -1
  18. package/dist/ai/genericInference.js +151 -36
  19. package/dist/ai/genericInference.js.map +1 -1
  20. package/dist/ai/prompts.js +55 -49
  21. package/dist/ai/prompts.js.map +1 -1
  22. package/dist/approvals/approvalInbox.js +649 -133
  23. package/dist/approvals/approvalInbox.js.map +1 -1
  24. package/dist/approvals/index.d.ts +2 -2
  25. package/dist/approvals/index.js +1 -1
  26. package/dist/approvals/index.js.map +1 -1
  27. package/dist/behavior/behaviorStore.js +34 -14
  28. package/dist/behavior/behaviorStore.js.map +1 -1
  29. package/dist/cache/index.js +14 -5
  30. package/dist/cache/index.js.map +1 -1
  31. package/dist/cli/index.js +226 -93
  32. package/dist/cli/index.js.map +1 -1
  33. package/dist/cli/openUrl.js +17 -3
  34. package/dist/cli/openUrl.js.map +1 -1
  35. package/dist/cli/parser.js +79 -18
  36. package/dist/cli/parser.js.map +1 -1
  37. package/dist/config/dataPaths.js +4 -2
  38. package/dist/config/dataPaths.js.map +1 -1
  39. package/dist/config/localConfig.js +5 -2
  40. package/dist/config/localConfig.js.map +1 -1
  41. package/dist/config/serverConfig.js +11 -5
  42. package/dist/config/serverConfig.js.map +1 -1
  43. package/dist/contracts/contract.js +311 -83
  44. package/dist/contracts/contract.js.map +1 -1
  45. package/dist/contracts/index.d.ts +2 -2
  46. package/dist/contracts/index.js +1 -1
  47. package/dist/contracts/index.js.map +1 -1
  48. package/dist/credentials/credentialBroker.js +391 -79
  49. package/dist/credentials/credentialBroker.js.map +1 -1
  50. package/dist/credentials/index.d.ts +2 -2
  51. package/dist/credentials/index.js +1 -1
  52. package/dist/credentials/index.js.map +1 -1
  53. package/dist/dashboard/app.js +823 -685
  54. package/dist/dashboard/dashboard.js.map +1 -1
  55. package/dist/dashboard/index.html +704 -267
  56. package/dist/dashboard/styles.css +1040 -349
  57. package/dist/egress/capabilities.js +55 -25
  58. package/dist/egress/capabilities.js.map +1 -1
  59. package/dist/egress/linuxBootstrap.js +47 -11
  60. package/dist/egress/linuxBootstrap.js.map +1 -1
  61. package/dist/egress/run.js +170 -31
  62. package/dist/egress/run.js.map +1 -1
  63. package/dist/errors/errorEngine.js +15 -3
  64. package/dist/errors/errorEngine.js.map +1 -1
  65. package/dist/errors/providerErrors.js.map +1 -1
  66. package/dist/evals/evals.js +304 -78
  67. package/dist/evals/evals.js.map +1 -1
  68. package/dist/evals/index.d.ts +2 -2
  69. package/dist/evals/index.js +1 -1
  70. package/dist/evals/index.js.map +1 -1
  71. package/dist/evidence/index.d.ts +2 -2
  72. package/dist/evidence/index.js +1 -1
  73. package/dist/evidence/index.js.map +1 -1
  74. package/dist/evidence/report.d.ts +1 -1
  75. package/dist/evidence/report.js +263 -79
  76. package/dist/evidence/report.js.map +1 -1
  77. package/dist/fault/faultLab.js +24 -9
  78. package/dist/fault/faultLab.js.map +1 -1
  79. package/dist/index.d.ts +42 -42
  80. package/dist/index.js +21 -21
  81. package/dist/index.js.map +1 -1
  82. package/dist/inventory/analysis.d.ts +1 -1
  83. package/dist/inventory/analysis.js +174 -50
  84. package/dist/inventory/analysis.js.map +1 -1
  85. package/dist/inventory/index.d.ts +4 -4
  86. package/dist/inventory/index.js +3 -3
  87. package/dist/inventory/index.js.map +1 -1
  88. package/dist/inventory/inventoryController.js +362 -115
  89. package/dist/inventory/inventoryController.js.map +1 -1
  90. package/dist/inventory/types.js +680 -140
  91. package/dist/inventory/types.js.map +1 -1
  92. package/dist/ledger/actionLedger.js +355 -78
  93. package/dist/ledger/actionLedger.js.map +1 -1
  94. package/dist/ledger/index.d.ts +2 -2
  95. package/dist/ledger/index.js +1 -1
  96. package/dist/ledger/index.js.map +1 -1
  97. package/dist/mcp/server.js +26 -11
  98. package/dist/mcp/server.js.map +1 -1
  99. package/dist/policy/index.d.ts +2 -2
  100. package/dist/policy/index.js +1 -1
  101. package/dist/policy/index.js.map +1 -1
  102. package/dist/policy/policy.js +112 -32
  103. package/dist/policy/policy.js.map +1 -1
  104. package/dist/productTelemetry/index.js +71 -20
  105. package/dist/productTelemetry/index.js.map +1 -1
  106. package/dist/providers/conformance.js +10 -5
  107. package/dist/providers/conformance.js.map +1 -1
  108. package/dist/providers/discord.js +2 -2
  109. package/dist/providers/generic.js +3 -3
  110. package/dist/providers/github.js +2 -2
  111. package/dist/providers/index.d.ts +3 -3
  112. package/dist/providers/index.js +2 -2
  113. package/dist/providers/index.js.map +1 -1
  114. package/dist/providers/openai.js +3 -3
  115. package/dist/providers/packs/resendPack.js +51 -25
  116. package/dist/providers/packs/resendPack.js.map +1 -1
  117. package/dist/providers/packs/stripePack.js +590 -121
  118. package/dist/providers/packs/stripePack.js.map +1 -1
  119. package/dist/providers/registry.js +4 -4
  120. package/dist/providers/registry.js.map +1 -1
  121. package/dist/providers/runtime.js +7 -6
  122. package/dist/providers/runtime.js.map +1 -1
  123. package/dist/providers/stripeWebhook.js +39 -10
  124. package/dist/providers/stripeWebhook.js.map +1 -1
  125. package/dist/providers/twilio.js +2 -2
  126. package/dist/proxy/cacheKey.js +9 -2
  127. package/dist/proxy/cacheKey.js.map +1 -1
  128. package/dist/proxy/providerDetector.js +22 -9
  129. package/dist/proxy/providerDetector.js.map +1 -1
  130. package/dist/proxy/proxyHandler.js +57 -22
  131. package/dist/proxy/proxyHandler.js.map +1 -1
  132. package/dist/proxy/requestNormalizer.js +4 -2
  133. package/dist/proxy/requestNormalizer.js.map +1 -1
  134. package/dist/reliability/costGovernance.js +211 -49
  135. package/dist/reliability/costGovernance.js.map +1 -1
  136. package/dist/reliability/index.d.ts +8 -8
  137. package/dist/reliability/index.js +4 -4
  138. package/dist/reliability/index.js.map +1 -1
  139. package/dist/reliability/reconciliation.js +274 -65
  140. package/dist/reliability/reconciliation.js.map +1 -1
  141. package/dist/reliability/runtimeHealth.js +127 -35
  142. package/dist/reliability/runtimeHealth.js.map +1 -1
  143. package/dist/reliability/slo.js +257 -50
  144. package/dist/reliability/slo.js.map +1 -1
  145. package/dist/report/safetyReport.d.ts +6 -0
  146. package/dist/report/safetyReport.js +56 -12
  147. package/dist/report/safetyReport.js.map +1 -1
  148. package/dist/rules/aiRules.js +5 -5
  149. package/dist/rules/aiRules.js.map +1 -1
  150. package/dist/safety/index.d.ts +2 -2
  151. package/dist/safety/index.js +1 -1
  152. package/dist/safety/index.js.map +1 -1
  153. package/dist/safety/safetyController.js +675 -165
  154. package/dist/safety/safetyController.js.map +1 -1
  155. package/dist/scenarios/scenarioBundle.js +204 -71
  156. package/dist/scenarios/scenarioBundle.js.map +1 -1
  157. package/dist/scenarios/scenarioStore.js +88 -29
  158. package/dist/scenarios/scenarioStore.js.map +1 -1
  159. package/dist/security/headerSanitizer.js +52 -10
  160. package/dist/security/headerSanitizer.js.map +1 -1
  161. package/dist/security/secrets.js +10 -5
  162. package/dist/security/secrets.js.map +1 -1
  163. package/dist/server/accessControl.js +37 -10
  164. package/dist/server/accessControl.js.map +1 -1
  165. package/dist/server/createServer.js +1 -1
  166. package/dist/server/createServer.js.map +1 -1
  167. package/dist/server/eventsStore.js +15 -5
  168. package/dist/server/eventsStore.js.map +1 -1
  169. package/dist/server/routes.js +61 -19
  170. package/dist/server/routes.js.map +1 -1
  171. package/dist/server/sse.js +1 -1
  172. package/dist/server/sse.js.map +1 -1
  173. package/dist/setup/sdkDetector.js +7 -2
  174. package/dist/setup/sdkDetector.js.map +1 -1
  175. package/dist/setup/setupGenerator.js +136 -48
  176. package/dist/setup/setupGenerator.js.map +1 -1
  177. package/dist/state/stateExtractor.js +3 -1
  178. package/dist/state/stateExtractor.js.map +1 -1
  179. package/dist/state/stateResolver.js +10 -7
  180. package/dist/state/stateResolver.js.map +1 -1
  181. package/dist/state/stateStore.js +13 -4
  182. package/dist/state/stateStore.js.map +1 -1
  183. package/dist/storage/fileStore.js +12 -4
  184. package/dist/storage/fileStore.js.map +1 -1
  185. package/dist/teamControl/controlPlane.js +846 -174
  186. package/dist/teamControl/controlPlane.js.map +1 -1
  187. package/dist/teamControl/deployment.js +19 -5
  188. package/dist/teamControl/deployment.js.map +1 -1
  189. package/dist/teamControl/index.d.ts +4 -4
  190. package/dist/teamControl/index.js +2 -2
  191. package/dist/teamControl/index.js.map +1 -1
  192. package/dist/tests/testGenerator.js +15 -7
  193. package/dist/tests/testGenerator.js.map +1 -1
  194. package/dist/trust/index.d.ts +2 -2
  195. package/dist/trust/index.js +1 -1
  196. package/dist/trust/index.js.map +1 -1
  197. package/dist/trust/trustLadder.js +309 -63
  198. package/dist/trust/trustLadder.js.map +1 -1
  199. package/dist/worlds/index.d.ts +2 -2
  200. package/dist/worlds/index.js +1 -1
  201. package/dist/worlds/index.js.map +1 -1
  202. package/dist/worlds/worlds.js +364 -65
  203. package/dist/worlds/worlds.js.map +1 -1
  204. package/docs/assets/dashboard.png +0 -0
  205. package/docs/ci.md +49 -49
  206. package/docs/github-actions.md +84 -84
  207. package/docs/mcp.md +58 -52
  208. package/docs/policy.md +63 -61
  209. package/docs/providers/authoring-packs.md +180 -175
  210. package/docs/providers/stripe-core-pack.md +84 -82
  211. package/docs/releases/README.md +13 -0
  212. package/docs/security/action-gateway-threat-model.md +34 -34
  213. package/docs/security/action-ledger-incident-replay-threat-model.md +42 -42
  214. package/docs/security/approval-inbox-threat-model.md +35 -35
  215. package/docs/security/credential-broker-threat-model.md +52 -52
  216. package/docs/security/egress-threat-model.md +126 -126
  217. package/docs/security/inventory-threat-model.md +104 -104
  218. package/docs/security/kill-switch-budgets-threat-model.md +37 -37
  219. package/docs/security/reliability-threat-model.md +111 -111
  220. package/docs/security/trust-ladder-threat-model.md +33 -33
  221. package/docs/telemetry.md +14 -0
  222. package/docs/usage.md +680 -571
  223. package/examples/README.md +10 -10
  224. package/examples/agent-instructions/README.md +39 -39
  225. package/examples/ci-smoke/README.md +21 -21
  226. package/examples/ci-smoke/ghostapi.policy.yaml +22 -22
  227. package/examples/ci-smoke/package.json +9 -9
  228. package/examples/ci-smoke/production-egress.mjs +4 -2
  229. package/examples/ci-smoke/safe.mjs +21 -17
  230. package/examples/evals/README.md +24 -24
  231. package/examples/evals/retry-after.eval.json +71 -52
  232. package/examples/generic-rest/README.md +27 -27
  233. package/examples/github-fetch/README.md +38 -35
  234. package/examples/openai-streaming/README.md +16 -16
  235. package/examples/openai-streaming/streaming-tool-call.mjs +44 -31
  236. package/examples/policy/ghostapi.policy.yaml +27 -27
  237. package/examples/record-replay/README.md +19 -19
  238. package/examples/record-replay/replay-requests.json +22 -22
  239. package/examples/record-replay/stripe-checkout.har.json +30 -30
  240. package/examples/resend-node/README.md +22 -22
  241. package/examples/stripe-node/README.md +35 -35
  242. package/examples/stripe-node/checkout-flow.mjs +87 -61
  243. package/examples/twilio-node/README.md +21 -21
  244. package/examples/worlds/README.md +19 -19
  245. package/examples/worlds/subscription-recovery.mjs +21 -12
  246. package/package.json +28 -6
  247. package/dist/landing/assets/index-BXR41fAD.css +0 -1
  248. package/dist/landing/assets/index-CxWsopzP.css +0 -1
  249. package/dist/landing/assets/index-D8_ggMRE.js +0 -55
  250. package/dist/landing/assets/index-DZlpyhqd.js +0 -55
  251. package/dist/landing/assets/index-Dk-Xezs3.js +0 -55
  252. package/dist/landing/index.html +0 -15
  253. package/dist/landing/landing.d.ts +0 -3
  254. package/dist/landing/landing.js +0 -12
  255. package/dist/landing/landing.js.map +0 -1
  256. package/docs/commercial/README.md +0 -29
  257. package/docs/commercial/data-inventory.md +0 -36
  258. package/docs/commercial/manual-invoicing.md +0 -44
  259. package/docs/commercial/metrics.md +0 -51
  260. package/docs/commercial/pricing.md +0 -103
  261. package/docs/commercial/privacy-policy-draft.md +0 -41
  262. package/docs/commercial/terms-topics-for-counsel.md +0 -51
  263. package/docs/design-partners/README.md +0 -52
  264. package/docs/design-partners/demo-narratives.md +0 -33
  265. package/docs/design-partners/design-partner-offer.md +0 -39
  266. package/docs/design-partners/discovery-questions.md +0 -28
  267. package/docs/design-partners/feedback-capture-template.md +0 -48
  268. package/docs/design-partners/icp-one-pager.md +0 -43
  269. package/docs/design-partners/onboarding-checklist.md +0 -33
  270. package/docs/design-partners/pilot-success-criteria.md +0 -27
  271. package/docs/design-partners/pricing-interview-script.md +0 -18
  272. package/docs/design-partners/telemetry-plan.md +0 -48
  273. package/docs/development/baseline.md +0 -114
  274. package/docs/development/onboarding-smoke.md +0 -37
  275. package/docs/development/verification-0.1.8.md +0 -25
  276. package/docs/enterprise-product-roadmap-ru.md +0 -1879
  277. package/docs/fundraising/README.md +0 -87
  278. package/docs/fundraising/data-room-checklist.md +0 -72
  279. package/docs/fundraising/demo-script.md +0 -52
  280. package/docs/fundraising/design-partners-50.md +0 -74
  281. package/docs/fundraising/launch-posts.md +0 -37
  282. package/docs/fundraising/metrics-and-evidence.md +0 -30
  283. package/docs/fundraising/roadmap-12-month.md +0 -25
  284. package/docs/fundraising/technical-due-diligence-index.md +0 -37
  285. package/docs/fundraising/yc-application.md +0 -25
  286. package/docs/hosted-pilot.md +0 -111
  287. package/docs/operations/disaster-recovery-runbook.md +0 -81
  288. package/docs/operations/kill-switch-runbook.md +0 -32
  289. package/docs/release-checklist.md +0 -83
  290. package/docs/release-migration-and-rollback.md +0 -63
  291. package/docs/release-readiness.md +0 -39
  292. package/docs/team-control-plane.md +0 -72
@@ -1,11 +1,10 @@
1
1
  import { createHash, randomUUID } from "node:crypto";
2
2
  import { lstat, readFile } from "node:fs/promises";
3
3
  import { dirname } from "node:path";
4
- import { actionApprovalHash, actionHash, createLocalActionGateway, createSyntheticActionAdapter, validateActionApproval, validateActionEnvelope } from "../actions/index.js";
4
+ import { actionApprovalHash, actionHash, createLocalActionGateway, createSyntheticActionAdapter, validateActionApproval, validateActionEnvelope, } from "../actions/index.js";
5
5
  import { getDataPaths } from "../config/dataPaths.js";
6
6
  import { sanitizeSecretString } from "../security/secrets.js";
7
- import { atomicWriteJson, ensurePrivateDirectory, withFileLock } from "../storage/fileStore.js";
8
- const SCHEMA_VERSION = 1;
7
+ import { atomicWriteJson, ensurePrivateDirectory, withFileLock, } from "../storage/fileStore.js";
9
8
  const MAX_STORE_BYTES = 1024 * 1024;
10
9
  const MAX_REQUESTS = 1_000;
11
10
  const MAX_DECISIONS = 4;
@@ -13,7 +12,10 @@ const MAX_AUDIT = 10_000;
13
12
  const IDENTIFIER = /^[a-z0-9][a-z0-9._-]{0,127}$/;
14
13
  const HASH = /^[a-f0-9]{64}$/;
15
14
  export class ApprovalInboxError extends Error {
16
- constructor(message) { super(message); this.name = "ApprovalInboxError"; }
15
+ constructor(message) {
16
+ super(message);
17
+ this.name = "ApprovalInboxError";
18
+ }
17
19
  }
18
20
  export function createLocalApprovalInbox(options) {
19
21
  return new LocalApprovalInbox(options);
@@ -40,13 +42,24 @@ export class LocalApprovalInbox {
40
42
  const approval = validateActionApproval(approvalValue);
41
43
  const state = await this.read();
42
44
  const request = state.requests.find((candidate) => candidate.id === approval.approvalId);
43
- const active = request?.status === "executing" || (request?.status === "executed" && request.executionReceiptHash !== undefined);
44
- if (request === undefined || !active || request.consumedAt === undefined || request.artifact === undefined || request.actionHash !== actionHash(action) || actionApprovalHash(request.artifact) !== actionApprovalHash(approval))
45
+ const active = request?.status === "executing" ||
46
+ (request?.status === "executed" &&
47
+ request.executionReceiptHash !== undefined);
48
+ if (request === undefined ||
49
+ !active ||
50
+ request.consumedAt === undefined ||
51
+ request.artifact === undefined ||
52
+ request.actionHash !== actionHash(action) ||
53
+ actionApprovalHash(request.artifact) !== actionApprovalHash(approval))
45
54
  throw new ApprovalInboxError("Approval inbox provenance is not active for execution.");
46
55
  return { ...verified, approvalInboxRequestId: request.id };
47
- }
56
+ },
48
57
  };
49
- this.actionGateway = createLocalActionGateway({ ...options.actionGatewayOptions, now: this.now, approvalVerifier: inboxVerifier });
58
+ this.actionGateway = createLocalActionGateway({
59
+ ...options.actionGatewayOptions,
60
+ now: this.now,
61
+ approvalVerifier: inboxVerifier,
62
+ });
50
63
  }
51
64
  async request(actionValue, policyValue, contextValue) {
52
65
  const action = validateActionEnvelope(actionValue);
@@ -60,7 +73,8 @@ export class LocalApprovalInbox {
60
73
  return this.mutate((state, timestamp) => {
61
74
  refresh(state, timestamp);
62
75
  const actionDigest = actionHash(action);
63
- const existing = state.requests.find((request) => request.actionHash === actionDigest && request.status !== "superseded");
76
+ const existing = state.requests.find((request) => request.actionHash === actionDigest &&
77
+ request.status !== "superseded");
64
78
  if (existing !== undefined)
65
79
  return clone(existing);
66
80
  const decision = evaluate(policy, action, context, state.requests, timestamp);
@@ -68,10 +82,23 @@ export class LocalApprovalInbox {
68
82
  throw new ApprovalInboxError(`Approval policy denies this action: ${decision.reasons.join("; ")}`);
69
83
  const expiresAt = new Date(Math.min(Date.parse(action.expiresAt), Date.parse(timestamp) + policy.approvalTtlMs)).toISOString();
70
84
  const request = {
71
- id: identifier(`approval-${randomUUID().replace(/-/g, "")}`, "Approval request id"), action, actionHash: actionDigest, risk: classify(action), context,
72
- policy: { id: policy.id, version: policy.version, hash: policyHash(policy), requiredApprovals: decision.requiredApprovals },
73
- display: display(action, context, decision.reasons), status: "pending", createdAt: timestamp, expiresAt,
74
- escalationAt: new Date(Date.parse(timestamp) + policy.escalationTimeoutMs).toISOString(), decisions: []
85
+ id: identifier(`approval-${randomUUID().replace(/-/g, "")}`, "Approval request id"),
86
+ action,
87
+ actionHash: actionDigest,
88
+ risk: classify(action),
89
+ context,
90
+ policy: {
91
+ id: policy.id,
92
+ version: policy.version,
93
+ hash: policyHash(policy),
94
+ requiredApprovals: decision.requiredApprovals,
95
+ },
96
+ display: display(action, context, decision.reasons),
97
+ status: "pending",
98
+ createdAt: timestamp,
99
+ expiresAt,
100
+ escalationAt: new Date(Date.parse(timestamp) + policy.escalationTimeoutMs).toISOString(),
101
+ decisions: [],
75
102
  };
76
103
  state.requests.push(request);
77
104
  appendAudit(state, "request.created", request, action.actor.id, timestamp);
@@ -86,12 +113,23 @@ export class LocalApprovalInbox {
86
113
  const request = findRequest(state, requestId);
87
114
  requirePending(request);
88
115
  assertIndependent(request, approver);
89
- if (request.decisions.some((decision) => decision.kind === "approve" && (decision.approverId === approver.id || decision.principalId === approver.principalId || decision.independenceKey === approver.independenceKey)))
116
+ if (request.decisions.some((decision) => decision.kind === "approve" &&
117
+ (decision.approverId === approver.id ||
118
+ decision.principalId === approver.principalId ||
119
+ decision.independenceKey === approver.independenceKey)))
90
120
  throw new ApprovalInboxError("An independent approver can approve this action only once.");
91
- const decision = { id: identifier(`decision-${randomUUID().replace(/-/g, "")}`, "Approval decision id"), kind: "approve", approverId: approver.id, principalId: approver.principalId, independenceKey: approver.independenceKey, decidedAt: now };
121
+ const decision = {
122
+ id: identifier(`decision-${randomUUID().replace(/-/g, "")}`, "Approval decision id"),
123
+ kind: "approve",
124
+ approverId: approver.id,
125
+ principalId: approver.principalId,
126
+ independenceKey: approver.independenceKey,
127
+ decidedAt: now,
128
+ };
92
129
  request.decisions.push(decision);
93
130
  appendAudit(state, "request.approved", request, approver.id, now);
94
- if (request.decisions.filter((entry) => entry.kind === "approve").length >= request.policy.requiredApprovals) {
131
+ if (request.decisions.filter((entry) => entry.kind === "approve").length >=
132
+ request.policy.requiredApprovals) {
95
133
  request.status = "approved";
96
134
  request.artifact = await createArtifact(request, now, this.approvalSigner);
97
135
  appendAudit(state, "artifact.issued", request, "approval-inbox", now);
@@ -111,7 +149,9 @@ export class LocalApprovalInbox {
111
149
  const old = await this.mutate((state, now) => {
112
150
  refresh(state, now);
113
151
  const request = findRequest(state, requestId);
114
- if (request.status === "executing" || request.status === "executed" || request.consumedAt !== undefined)
152
+ if (request.status === "executing" ||
153
+ request.status === "executed" ||
154
+ request.consumedAt !== undefined)
115
155
  throw new ApprovalInboxError("Executed or executing approvals cannot be edited.");
116
156
  request.status = "superseded";
117
157
  appendAudit(state, "request.superseded", request, approver.id, now);
@@ -134,18 +174,26 @@ export class LocalApprovalInbox {
134
174
  const now = this.timestamp();
135
175
  refresh(state, now);
136
176
  const request = findRequest(state, requestId);
137
- if ((request.status !== "approved" && request.status !== "executing" && request.status !== "executed") || request.artifact === undefined)
177
+ if ((request.status !== "approved" &&
178
+ request.status !== "executing" &&
179
+ request.status !== "executed") ||
180
+ request.artifact === undefined)
138
181
  throw new ApprovalInboxError("Approval is not active for execution.");
139
- if (request.policy.id !== policy.id || request.policy.version !== policy.version || request.policy.hash !== policyHash(policy))
182
+ if (request.policy.id !== policy.id ||
183
+ request.policy.version !== policy.version ||
184
+ request.policy.hash !== policyHash(policy))
140
185
  throw new ApprovalInboxError("Approval policy changed after approval.");
141
186
  const decision = evaluate(policy, request.action, request.context, state.requests, now, request.id);
142
- if (!decision.allowed || decision.requiredApprovals !== request.policy.requiredApprovals)
187
+ if (!decision.allowed ||
188
+ decision.requiredApprovals !== request.policy.requiredApprovals)
143
189
  throw new ApprovalInboxError("Current approval policy no longer authorizes execution.");
144
- if (identity.actorId !== request.action.actor.id || identity.workloadId !== request.action.actor.workloadId)
190
+ if (identity.actorId !== request.action.actor.id ||
191
+ identity.workloadId !== request.action.actor.workloadId)
145
192
  throw new ApprovalInboxError("Execution identity does not match the approved action.");
146
193
  if (request.status === "executed") {
147
194
  const receipt = await this.actionGateway.execute(request.action, identity, actionPolicy);
148
- if (receipt.status !== "verified" || receipt.receiptHash !== request.executionReceiptHash)
195
+ if (receipt.status !== "verified" ||
196
+ receipt.receiptHash !== request.executionReceiptHash)
149
197
  throw new ApprovalInboxError("Executed approval does not match its durable action receipt.");
150
198
  return receipt;
151
199
  }
@@ -185,12 +233,22 @@ export class LocalApprovalInbox {
185
233
  }
186
234
  async get(requestIdValue) {
187
235
  const requestId = identifier(requestIdValue, "Approval request id");
188
- return this.mutate((state, now) => { refresh(state, now); return clone(findRequest(state, requestId)); });
236
+ return this.mutate((state, now) => {
237
+ refresh(state, now);
238
+ return clone(findRequest(state, requestId));
239
+ });
189
240
  }
190
241
  async list(status) {
191
- return this.mutate((state, now) => { refresh(state, now); return state.requests.filter((request) => status === undefined || request.status === status).map(clone); });
242
+ return this.mutate((state, now) => {
243
+ refresh(state, now);
244
+ return state.requests
245
+ .filter((request) => status === undefined || request.status === status)
246
+ .map(clone);
247
+ });
248
+ }
249
+ async readStateForTesting() {
250
+ return this.read();
192
251
  }
193
- async readStateForTesting() { return this.read(); }
194
252
  async decideTerminal(requestIdValue, identity, reasonValue, kind) {
195
253
  const approver = await this.approverVerifier.authenticate(identity);
196
254
  const requestId = identifier(requestIdValue, "Approval request id");
@@ -198,11 +256,21 @@ export class LocalApprovalInbox {
198
256
  return this.mutate((state, now) => {
199
257
  refresh(state, now);
200
258
  const request = findRequest(state, requestId);
201
- if (request.status === "executing" || request.status === "executed" || request.status === "execution_failed")
259
+ if (request.status === "executing" ||
260
+ request.status === "executed" ||
261
+ request.status === "execution_failed")
202
262
  throw new ApprovalInboxError("Execution has already started; approval cannot be revoked.");
203
263
  if (request.status !== "pending" && request.status !== "approved")
204
264
  throw new ApprovalInboxError("Approval is not active.");
205
- const decision = { id: identifier(`decision-${randomUUID().replace(/-/g, "")}`, "Approval decision id"), kind, approverId: approver.id, principalId: approver.principalId, independenceKey: approver.independenceKey, reason, decidedAt: now };
265
+ const decision = {
266
+ id: identifier(`decision-${randomUUID().replace(/-/g, "")}`, "Approval decision id"),
267
+ kind,
268
+ approverId: approver.id,
269
+ principalId: approver.principalId,
270
+ independenceKey: approver.independenceKey,
271
+ reason,
272
+ decidedAt: now,
273
+ };
206
274
  request.decisions.push(decision);
207
275
  request.status = kind === "reject" ? "rejected" : "revoked";
208
276
  appendAudit(state, kind === "reject" ? "request.rejected" : "request.revoked", request, approver.id, now);
@@ -229,31 +297,67 @@ export class LocalApprovalInbox {
229
297
  }
230
298
  }
231
299
  async mutate(operation) {
232
- return withFileLock(this.path, async () => { const state = await this.read(); const result = await operation(state, this.timestamp()); await this.write(state); return result; });
300
+ return withFileLock(this.path, async () => {
301
+ const state = await this.read();
302
+ const result = await operation(state, this.timestamp());
303
+ await this.write(state);
304
+ return result;
305
+ });
306
+ }
307
+ async write(state) {
308
+ await atomicWriteJson(this.path, validateState(state));
233
309
  }
234
- async write(state) { await atomicWriteJson(this.path, validateState(state)); }
235
310
  async latestDurableFailedReceipt(actionId) {
236
311
  try {
237
312
  const record = await this.actionGateway.inspect(actionId);
238
- return [...record.receipts].reverse().find((receipt) => receipt.status === "failed");
313
+ return [...record.receipts]
314
+ .reverse()
315
+ .find((receipt) => receipt.status === "failed");
239
316
  }
240
317
  catch {
241
318
  return undefined;
242
319
  }
243
320
  }
244
- timestamp() { const value = this.now(); if (!(value instanceof Date) || !Number.isFinite(value.getTime()))
245
- throw new ApprovalInboxError("Approval inbox clock is invalid."); return value.toISOString(); }
321
+ timestamp() {
322
+ const value = this.now();
323
+ if (!(value instanceof Date) || !Number.isFinite(value.getTime()))
324
+ throw new ApprovalInboxError("Approval inbox clock is invalid.");
325
+ return value.toISOString();
326
+ }
246
327
  }
247
328
  export function createTestApprovalApproverVerifier() {
248
329
  const issued = new WeakSet();
249
330
  return {
250
- verifier: { async authenticate(identity) { if (identity === null || typeof identity !== "object" || !issued.has(identity))
251
- throw new ApprovalInboxError("Approver identity is not authenticated."); return validateApprover(identity); } },
252
- issue(input) { const result = Object.freeze(validateApprover({ ...input, principalId: input.principalId ?? input.id })); issued.add(result); return result; }
331
+ verifier: {
332
+ async authenticate(identity) {
333
+ if (identity === null ||
334
+ typeof identity !== "object" ||
335
+ !issued.has(identity))
336
+ throw new ApprovalInboxError("Approver identity is not authenticated.");
337
+ return validateApprover(identity);
338
+ },
339
+ },
340
+ issue(input) {
341
+ const result = Object.freeze(validateApprover({
342
+ ...input,
343
+ principalId: input.principalId ?? input.id,
344
+ }));
345
+ issued.add(result);
346
+ return result;
347
+ },
348
+ };
349
+ }
350
+ export function approvalPolicyHash(value) {
351
+ return policyHash(validatePolicy(value));
352
+ }
353
+ function emptyState() {
354
+ return {
355
+ schemaVersion: 1,
356
+ requests: [],
357
+ auditAnchor: sha256("ghostapi.approval.audit.v1"),
358
+ audit: [],
253
359
  };
254
360
  }
255
- export function approvalPolicyHash(value) { return policyHash(validatePolicy(value)); }
256
- function emptyState() { return { schemaVersion: 1, requests: [], auditAnchor: sha256("ghostapi.approval.audit.v1"), audit: [] }; }
257
361
  function validateState(value) {
258
362
  const state = object(value, "Approval inbox store must be an object.");
259
363
  exactKeys(state, ["schemaVersion", "requests", "auditAnchor", "audit"], "Approval inbox store");
@@ -269,10 +373,28 @@ function validateState(value) {
269
373
  }
270
374
  function validateRequest(value) {
271
375
  const request = object(value, "Approval request is invalid.");
272
- exactKeys(request, ["id", "action", "actionHash", "risk", "context", "policy", "display", "status", "createdAt", "expiresAt", "escalationAt", "decisions", "artifact", "consumedAt", "executionReceiptHash", "supersedesRequestId"], "Approval request", ["artifact", "consumedAt", "executionReceiptHash", "supersedesRequestId"]);
376
+ exactKeys(request, [
377
+ "id",
378
+ "action",
379
+ "actionHash",
380
+ "risk",
381
+ "context",
382
+ "policy",
383
+ "display",
384
+ "status",
385
+ "createdAt",
386
+ "expiresAt",
387
+ "escalationAt",
388
+ "decisions",
389
+ "artifact",
390
+ "consumedAt",
391
+ "executionReceiptHash",
392
+ "supersedesRequestId",
393
+ ], "Approval request", ["artifact", "consumedAt", "executionReceiptHash", "supersedesRequestId"]);
273
394
  const action = validateActionEnvelope(request.action);
274
395
  const actionDigest = hash(request.actionHash, "Approval action hash");
275
- if (actionHash(action) !== actionDigest || classify(action) !== risk(request.risk))
396
+ if (actionHash(action) !== actionDigest ||
397
+ classify(action) !== risk(request.risk))
276
398
  throw new ApprovalInboxError("Approval request action is invalid.");
277
399
  const context = validateContext(request.context);
278
400
  const policy = policyRecord(request.policy);
@@ -281,41 +403,148 @@ function validateRequest(value) {
281
403
  const createdAt = timestamp(request.createdAt, "Approval creation time");
282
404
  const expiresAt = timestamp(request.expiresAt, "Approval expiry");
283
405
  const escalationAt = timestamp(request.escalationAt, "Approval escalation time");
284
- if (!validStatus(request.status) || Date.parse(expiresAt) <= Date.parse(createdAt) || Date.parse(escalationAt) > Date.parse(expiresAt))
406
+ if (!validStatus(request.status) ||
407
+ Date.parse(expiresAt) <= Date.parse(createdAt) ||
408
+ Date.parse(escalationAt) > Date.parse(expiresAt))
285
409
  throw new ApprovalInboxError("Approval request lifecycle is invalid.");
286
- const artifact = request.artifact === undefined ? undefined : validateArtifact(request.artifact, actionDigest);
287
- if ((request.status === "approved" || request.status === "executing" || request.status === "executed" || request.status === "execution_failed") && artifact === undefined)
410
+ const artifact = request.artifact === undefined
411
+ ? undefined
412
+ : validateArtifact(request.artifact, actionDigest);
413
+ if ((request.status === "approved" ||
414
+ request.status === "executing" ||
415
+ request.status === "executed" ||
416
+ request.status === "execution_failed") &&
417
+ artifact === undefined)
288
418
  throw new ApprovalInboxError("Approved requests require an approval artifact.");
289
- const consumedAt = request.consumedAt === undefined ? undefined : timestamp(request.consumedAt, "Approval consumption time");
290
- if ((request.status === "executing" || request.status === "executed" || request.status === "execution_failed") && consumedAt === undefined)
419
+ const consumedAt = request.consumedAt === undefined
420
+ ? undefined
421
+ : timestamp(request.consumedAt, "Approval consumption time");
422
+ if ((request.status === "executing" ||
423
+ request.status === "executed" ||
424
+ request.status === "execution_failed") &&
425
+ consumedAt === undefined)
291
426
  throw new ApprovalInboxError("Executed approvals require consumption metadata.");
292
- const executionReceiptHash = request.executionReceiptHash === undefined ? undefined : hash(request.executionReceiptHash, "Approval execution receipt hash");
427
+ const executionReceiptHash = request.executionReceiptHash === undefined
428
+ ? undefined
429
+ : hash(request.executionReceiptHash, "Approval execution receipt hash");
293
430
  if (request.status === "executed" && executionReceiptHash === undefined)
294
431
  throw new ApprovalInboxError("Executed approvals require an action receipt hash.");
295
- return { id: identifier(request.id, "Approval request id"), action, actionHash: actionDigest, risk: risk(request.risk), context, policy, display: displayValue, status: request.status, createdAt, expiresAt, escalationAt, decisions, ...(artifact === undefined ? {} : { artifact }), ...(consumedAt === undefined ? {} : { consumedAt }), ...(executionReceiptHash === undefined ? {} : { executionReceiptHash }), ...(request.supersedesRequestId === undefined ? {} : { supersedesRequestId: identifier(request.supersedesRequestId, "Superseded approval request id") }) };
432
+ return {
433
+ id: identifier(request.id, "Approval request id"),
434
+ action,
435
+ actionHash: actionDigest,
436
+ risk: risk(request.risk),
437
+ context,
438
+ policy,
439
+ display: displayValue,
440
+ status: request.status,
441
+ createdAt,
442
+ expiresAt,
443
+ escalationAt,
444
+ decisions,
445
+ ...(artifact === undefined ? {} : { artifact }),
446
+ ...(consumedAt === undefined ? {} : { consumedAt }),
447
+ ...(executionReceiptHash === undefined ? {} : { executionReceiptHash }),
448
+ ...(request.supersedesRequestId === undefined
449
+ ? {}
450
+ : {
451
+ supersedesRequestId: identifier(request.supersedesRequestId, "Superseded approval request id"),
452
+ }),
453
+ };
296
454
  }
297
455
  function validateAudit(value) {
298
456
  const record = object(value, "Approval audit record is invalid.");
299
- exactKeys(record, ["sequence", "event", "requestId", "actionHash", "actionReceiptHash", "actorId", "timestamp", "previousHash", "recordHash"], "Approval audit record", ["actionReceiptHash"]);
300
- const base = { sequence: positive(record.sequence, "Approval audit sequence"), event: text(record.event, "Approval audit event", 80), requestId: identifier(record.requestId, "Approval audit request id"), actionHash: hash(record.actionHash, "Approval audit action hash"), ...(record.actionReceiptHash === undefined ? {} : { actionReceiptHash: hash(record.actionReceiptHash, "Approval audit receipt hash") }), actorId: identifier(record.actorId, "Approval audit actor id"), timestamp: timestamp(record.timestamp, "Approval audit timestamp"), previousHash: hash(record.previousHash, "Approval audit previous hash") };
301
- if (hash(record.recordHash, "Approval audit record hash") !== sha256(canonical(base)))
457
+ exactKeys(record, [
458
+ "sequence",
459
+ "event",
460
+ "requestId",
461
+ "actionHash",
462
+ "actionReceiptHash",
463
+ "actorId",
464
+ "timestamp",
465
+ "previousHash",
466
+ "recordHash",
467
+ ], "Approval audit record", ["actionReceiptHash"]);
468
+ const base = {
469
+ sequence: positive(record.sequence, "Approval audit sequence"),
470
+ event: text(record.event, "Approval audit event", 80),
471
+ requestId: identifier(record.requestId, "Approval audit request id"),
472
+ actionHash: hash(record.actionHash, "Approval audit action hash"),
473
+ ...(record.actionReceiptHash === undefined
474
+ ? {}
475
+ : {
476
+ actionReceiptHash: hash(record.actionReceiptHash, "Approval audit receipt hash"),
477
+ }),
478
+ actorId: identifier(record.actorId, "Approval audit actor id"),
479
+ timestamp: timestamp(record.timestamp, "Approval audit timestamp"),
480
+ previousHash: hash(record.previousHash, "Approval audit previous hash"),
481
+ };
482
+ if (hash(record.recordHash, "Approval audit record hash") !==
483
+ sha256(canonical(base)))
302
484
  throw new ApprovalInboxError("Approval audit record hash is invalid.");
303
485
  return { ...base, recordHash: record.recordHash };
304
486
  }
305
- function validateAuditChain(anchor, audit) { let previous = anchor; let sequence = 1; for (const record of audit) {
306
- if (record.sequence !== sequence || record.previousHash !== previous)
307
- throw new ApprovalInboxError("Approval audit chain is invalid.");
308
- previous = record.recordHash;
309
- sequence += 1;
310
- } }
487
+ function validateAuditChain(anchor, audit) {
488
+ let previous = anchor;
489
+ let sequence = 1;
490
+ for (const record of audit) {
491
+ if (record.sequence !== sequence || record.previousHash !== previous)
492
+ throw new ApprovalInboxError("Approval audit chain is invalid.");
493
+ previous = record.recordHash;
494
+ sequence += 1;
495
+ }
496
+ }
311
497
  function validatePolicy(value) {
312
498
  const policy = object(value, "Approval policy is invalid.");
313
- exactKeys(policy, ["schemaVersion", "kind", "id", "version", "allowedEnvironments", "allowedActors", "allowedResources", "maxAmountMinor", "minimumConfidence", "criticalRisks", "velocity", "approvalTtlMs", "escalationTimeoutMs"], "Approval policy", ["allowedActors", "allowedResources", "maxAmountMinor"]);
499
+ exactKeys(policy, [
500
+ "schemaVersion",
501
+ "kind",
502
+ "id",
503
+ "version",
504
+ "allowedEnvironments",
505
+ "allowedActors",
506
+ "allowedResources",
507
+ "maxAmountMinor",
508
+ "minimumConfidence",
509
+ "criticalRisks",
510
+ "velocity",
511
+ "approvalTtlMs",
512
+ "escalationTimeoutMs",
513
+ ], "Approval policy", ["allowedActors", "allowedResources", "maxAmountMinor"]);
314
514
  if (policy.schemaVersion !== 1 || policy.kind !== "ghostapi.approval-policy")
315
515
  throw new ApprovalInboxError("Unsupported approval policy schema.");
316
516
  const velocity = object(policy.velocity, "Approval velocity policy is invalid.");
317
517
  exactKeys(velocity, ["maxActions", "windowMs"], "Approval velocity policy");
318
- const result = { schemaVersion: 1, kind: "ghostapi.approval-policy", id: identifier(policy.id, "Approval policy id"), version: positive(policy.version, "Approval policy version"), allowedEnvironments: identifiers(policy.allowedEnvironments, "Approval allowed environments"), ...(policy.allowedActors === undefined ? {} : { allowedActors: identifiers(policy.allowedActors, "Approval allowed actors") }), ...(policy.allowedResources === undefined ? {} : { allowedResources: identifiers(policy.allowedResources, "Approval allowed resources") }), ...(policy.maxAmountMinor === undefined ? {} : { maxAmountMinor: nonNegative(policy.maxAmountMinor, "Approval max amount") }), minimumConfidence: percentage(policy.minimumConfidence, "Approval minimum confidence"), criticalRisks: risks(policy.criticalRisks), velocity: { maxActions: positive(velocity.maxActions, "Approval velocity maximum"), windowMs: boundedMs(velocity.windowMs, "Approval velocity window") }, approvalTtlMs: boundedMs(policy.approvalTtlMs, "Approval TTL"), escalationTimeoutMs: boundedMs(policy.escalationTimeoutMs, "Approval escalation timeout") };
518
+ const result = {
519
+ schemaVersion: 1,
520
+ kind: "ghostapi.approval-policy",
521
+ id: identifier(policy.id, "Approval policy id"),
522
+ version: positive(policy.version, "Approval policy version"),
523
+ allowedEnvironments: identifiers(policy.allowedEnvironments, "Approval allowed environments"),
524
+ ...(policy.allowedActors === undefined
525
+ ? {}
526
+ : {
527
+ allowedActors: identifiers(policy.allowedActors, "Approval allowed actors"),
528
+ }),
529
+ ...(policy.allowedResources === undefined
530
+ ? {}
531
+ : {
532
+ allowedResources: identifiers(policy.allowedResources, "Approval allowed resources"),
533
+ }),
534
+ ...(policy.maxAmountMinor === undefined
535
+ ? {}
536
+ : {
537
+ maxAmountMinor: nonNegative(policy.maxAmountMinor, "Approval max amount"),
538
+ }),
539
+ minimumConfidence: percentage(policy.minimumConfidence, "Approval minimum confidence"),
540
+ criticalRisks: risks(policy.criticalRisks),
541
+ velocity: {
542
+ maxActions: positive(velocity.maxActions, "Approval velocity maximum"),
543
+ windowMs: boundedMs(velocity.windowMs, "Approval velocity window"),
544
+ },
545
+ approvalTtlMs: boundedMs(policy.approvalTtlMs, "Approval TTL"),
546
+ escalationTimeoutMs: boundedMs(policy.escalationTimeoutMs, "Approval escalation timeout"),
547
+ };
319
548
  if (result.escalationTimeoutMs > result.approvalTtlMs)
320
549
  throw new ApprovalInboxError("Approval escalation timeout cannot exceed approval TTL.");
321
550
  return result;
@@ -325,88 +554,375 @@ function evaluate(policy, action, context, history, now, currentId) {
325
554
  const actionRisk = classify(action);
326
555
  if (!policy.allowedEnvironments.includes(action.project.environment))
327
556
  reasons.push("environment is not approved");
328
- if (policy.allowedActors !== undefined && !policy.allowedActors.includes(action.actor.id))
557
+ if (policy.allowedActors !== undefined &&
558
+ !policy.allowedActors.includes(action.actor.id))
329
559
  reasons.push("actor is not approved");
330
- if (policy.allowedResources !== undefined && !policy.allowedResources.includes(action.resource.id))
560
+ if (policy.allowedResources !== undefined &&
561
+ !policy.allowedResources.includes(action.resource.id))
331
562
  reasons.push("resource is not approved");
332
- if (policy.maxAmountMinor !== undefined && (context.amountMinor === undefined || context.amountMinor > policy.maxAmountMinor))
563
+ if (policy.maxAmountMinor !== undefined &&
564
+ (context.amountMinor === undefined ||
565
+ context.amountMinor > policy.maxAmountMinor))
333
566
  reasons.push("amount is missing or exceeds the policy limit");
334
567
  const cutoff = Date.parse(now) - policy.velocity.windowMs;
335
- if (history.filter((request) => request.id !== currentId && request.action.actor.id === action.actor.id && Date.parse(request.createdAt) >= cutoff && request.status !== "superseded").length >= policy.velocity.maxActions)
568
+ if (history.filter((request) => request.id !== currentId &&
569
+ request.action.actor.id === action.actor.id &&
570
+ Date.parse(request.createdAt) >= cutoff &&
571
+ request.status !== "superseded").length >= policy.velocity.maxActions)
336
572
  reasons.push("action velocity limit was reached");
337
- const requiredApprovals = policy.criticalRisks.includes(actionRisk) || context.confidence < policy.minimumConfidence ? 2 : 1;
338
- reasons.push(requiredApprovals === 2 ? "independent two-person approval is required" : "independent human approval is required");
573
+ const requiredApprovals = policy.criticalRisks.includes(actionRisk) ||
574
+ context.confidence < policy.minimumConfidence
575
+ ? 2
576
+ : 1;
577
+ reasons.push(requiredApprovals === 2
578
+ ? "independent two-person approval is required"
579
+ : "independent human approval is required");
339
580
  return { allowed: reasons.length === 1, requiredApprovals, reasons };
340
581
  }
341
- function display(action, context, policyReason) { return { intent: "Update a local synthetic subscription failure workflow.", target: `${action.resource.type}/${action.resource.id}`, normalizedArgumentDiff: { before: {}, after: clone(action.arguments) }, expectedSideEffects: [...action.expectedSideEffects], reversibility: action.reversibility, impact: { amountMinor: context.amountMinor ?? null, amountKnown: context.amountMinor !== undefined, irreversible: action.reversibility === "none" }, policyReason, evidenceHash: action.evidence.hash, simulation: { status: "passed", summary: "Synthetic adapter preflight found the local target world." } }; }
342
- function displayRecord(value, action, context) { const candidate = object(value, "Approval display is invalid."); exactKeys(candidate, ["intent", "target", "normalizedArgumentDiff", "expectedSideEffects", "reversibility", "impact", "policyReason", "evidenceHash", "simulation"], "Approval display"); const generated = display(action, context, array(candidate.policyReason, "Approval policy reason", 16).map((item) => text(item, "Approval policy reason", 200))); if (canonical(candidate) !== canonical(generated))
343
- throw new ApprovalInboxError("Approval display does not match normalized action data."); return generated; }
344
- async function createArtifact(request, now, signer) { return signer.issue({ schemaVersion: 1, kind: "ghostapi.action-approval", approvalId: request.id, actionHash: request.actionHash, approvedBy: "approval-inbox", approvedAt: now, expiresAt: request.expiresAt, nonce: `inbox-${request.actionHash.slice(0, 32)}` }); }
345
- function validateArtifact(value, actionDigest) { const artifact = object(value, "Approval artifact is invalid."); exactKeys(artifact, ["schemaVersion", "kind", "approvalId", "actionHash", "approvedBy", "approvedAt", "expiresAt", "nonce", "provenance"], "Approval artifact"); if (artifact.schemaVersion !== 1 || artifact.kind !== "ghostapi.action-approval" || artifact.actionHash !== actionDigest || artifact.approvedBy !== "approval-inbox")
346
- throw new ApprovalInboxError("Approval artifact is invalid."); const provenance = object(artifact.provenance, "Approval artifact provenance is invalid."); exactKeys(provenance, ["kind", "keyId", "signature"], "Approval artifact provenance"); if (provenance.kind !== "ghostapi.ed25519-signature" || typeof provenance.signature !== "string" || !/^[A-Za-z0-9_-]{86}$/.test(provenance.signature))
347
- throw new ApprovalInboxError("Approval artifact provenance is invalid."); return { schemaVersion: 1, kind: "ghostapi.action-approval", approvalId: identifier(artifact.approvalId, "Approval artifact id"), actionHash: actionDigest, approvedBy: "approval-inbox", approvedAt: timestamp(artifact.approvedAt, "Approval artifact issue time"), expiresAt: timestamp(artifact.expiresAt, "Approval artifact expiry"), nonce: identifier(artifact.nonce, "Approval artifact nonce"), provenance: { kind: "ghostapi.ed25519-signature", keyId: identifier(provenance.keyId, "Approval artifact key id"), signature: provenance.signature } }; }
348
- function policyRecord(value) { const policy = object(value, "Approval policy record is invalid."); exactKeys(policy, ["id", "version", "hash", "requiredApprovals"], "Approval policy record"); const requiredApprovals = policy.requiredApprovals; if (requiredApprovals !== 1 && requiredApprovals !== 2)
349
- throw new ApprovalInboxError("Approval requirement is invalid."); return { id: identifier(policy.id, "Approval policy id"), version: positive(policy.version, "Approval policy version"), hash: hash(policy.hash, "Approval policy hash"), requiredApprovals }; }
350
- function validateContext(value) { const context = object(value, "Approval context is invalid."); exactKeys(context, ["confidence", "amountMinor"], "Approval context", ["amountMinor"]); return { confidence: percentage(context.confidence, "Approval confidence"), ...(context.amountMinor === undefined ? {} : { amountMinor: nonNegative(context.amountMinor, "Approval amount") }) }; }
351
- function validateApprover(value) { const approver = object(value, "Approver identity is invalid."); exactKeys(approver, ["id", "principalId", "independenceKey"], "Approver identity"); return { id: identifier(approver.id, "Approver id"), principalId: identifier(approver.principalId, "Approver principal id"), independenceKey: identifier(approver.independenceKey, "Approver independence key") }; }
352
- function validateDecision(value) { const decision = object(value, "Approval decision is invalid."); exactKeys(decision, ["id", "kind", "approverId", "principalId", "independenceKey", "reason", "decidedAt"], "Approval decision", ["reason"]); if (decision.kind !== "approve" && decision.kind !== "reject" && decision.kind !== "revoke")
353
- throw new ApprovalInboxError("Approval decision kind is invalid."); return { id: identifier(decision.id, "Approval decision id"), kind: decision.kind, approverId: identifier(decision.approverId, "Approver id"), principalId: identifier(decision.principalId, "Approver principal id"), independenceKey: identifier(decision.independenceKey, "Approver independence key"), ...(decision.reason === undefined ? {} : { reason: text(decision.reason, "Approval decision reason", 200) }), decidedAt: timestamp(decision.decidedAt, "Approval decision time") }; }
354
- function appendAudit(state, event, request, actorId, timestampValue, actionReceiptHash) { const previous = state.audit.at(-1)?.recordHash ?? state.auditAnchor; const base = { sequence: state.audit.length + 1, event, requestId: request.id, actionHash: request.actionHash, ...(actionReceiptHash === undefined ? {} : { actionReceiptHash }), actorId: identifier(actorId, "Approval audit actor id"), timestamp: timestampValue, previousHash: previous }; state.audit.push({ ...base, recordHash: sha256(canonical(base)) }); }
355
- function refresh(state, now) { for (const request of state.requests) {
356
- if (request.status === "pending" && Date.parse(now) >= Date.parse(request.escalationAt)) {
357
- request.status = "timed_out";
358
- appendAudit(state, "request.timed_out", request, "approval-inbox", now);
359
- }
360
- else if ((request.status === "pending" || request.status === "approved") && Date.parse(now) >= Date.parse(request.expiresAt)) {
361
- request.status = "expired";
362
- appendAudit(state, "request.expired", request, "approval-inbox", now);
582
+ function display(action, context, policyReason) {
583
+ return {
584
+ intent: "Update a local synthetic subscription failure workflow.",
585
+ target: `${action.resource.type}/${action.resource.id}`,
586
+ normalizedArgumentDiff: { before: {}, after: clone(action.arguments) },
587
+ expectedSideEffects: [...action.expectedSideEffects],
588
+ reversibility: action.reversibility,
589
+ impact: {
590
+ amountMinor: context.amountMinor ?? null,
591
+ amountKnown: context.amountMinor !== undefined,
592
+ irreversible: action.reversibility === "none",
593
+ },
594
+ policyReason,
595
+ evidenceHash: action.evidence.hash,
596
+ simulation: {
597
+ status: "passed",
598
+ summary: "Synthetic adapter preflight found the local target world.",
599
+ },
600
+ };
601
+ }
602
+ function displayRecord(value, action, context) {
603
+ const candidate = object(value, "Approval display is invalid.");
604
+ exactKeys(candidate, [
605
+ "intent",
606
+ "target",
607
+ "normalizedArgumentDiff",
608
+ "expectedSideEffects",
609
+ "reversibility",
610
+ "impact",
611
+ "policyReason",
612
+ "evidenceHash",
613
+ "simulation",
614
+ ], "Approval display");
615
+ const generated = display(action, context, array(candidate.policyReason, "Approval policy reason", 16).map((item) => text(item, "Approval policy reason", 200)));
616
+ if (canonical(candidate) !== canonical(generated))
617
+ throw new ApprovalInboxError("Approval display does not match normalized action data.");
618
+ return generated;
619
+ }
620
+ async function createArtifact(request, now, signer) {
621
+ return signer.issue({
622
+ schemaVersion: 1,
623
+ kind: "ghostapi.action-approval",
624
+ approvalId: request.id,
625
+ actionHash: request.actionHash,
626
+ approvedBy: "approval-inbox",
627
+ approvedAt: now,
628
+ expiresAt: request.expiresAt,
629
+ nonce: `inbox-${request.actionHash.slice(0, 32)}`,
630
+ });
631
+ }
632
+ function validateArtifact(value, actionDigest) {
633
+ const artifact = object(value, "Approval artifact is invalid.");
634
+ exactKeys(artifact, [
635
+ "schemaVersion",
636
+ "kind",
637
+ "approvalId",
638
+ "actionHash",
639
+ "approvedBy",
640
+ "approvedAt",
641
+ "expiresAt",
642
+ "nonce",
643
+ "provenance",
644
+ ], "Approval artifact");
645
+ if (artifact.schemaVersion !== 1 ||
646
+ artifact.kind !== "ghostapi.action-approval" ||
647
+ artifact.actionHash !== actionDigest ||
648
+ artifact.approvedBy !== "approval-inbox")
649
+ throw new ApprovalInboxError("Approval artifact is invalid.");
650
+ const provenance = object(artifact.provenance, "Approval artifact provenance is invalid.");
651
+ exactKeys(provenance, ["kind", "keyId", "signature"], "Approval artifact provenance");
652
+ if (provenance.kind !== "ghostapi.ed25519-signature" ||
653
+ typeof provenance.signature !== "string" ||
654
+ !/^[A-Za-z0-9_-]{86}$/.test(provenance.signature))
655
+ throw new ApprovalInboxError("Approval artifact provenance is invalid.");
656
+ return {
657
+ schemaVersion: 1,
658
+ kind: "ghostapi.action-approval",
659
+ approvalId: identifier(artifact.approvalId, "Approval artifact id"),
660
+ actionHash: actionDigest,
661
+ approvedBy: "approval-inbox",
662
+ approvedAt: timestamp(artifact.approvedAt, "Approval artifact issue time"),
663
+ expiresAt: timestamp(artifact.expiresAt, "Approval artifact expiry"),
664
+ nonce: identifier(artifact.nonce, "Approval artifact nonce"),
665
+ provenance: {
666
+ kind: "ghostapi.ed25519-signature",
667
+ keyId: identifier(provenance.keyId, "Approval artifact key id"),
668
+ signature: provenance.signature,
669
+ },
670
+ };
671
+ }
672
+ function policyRecord(value) {
673
+ const policy = object(value, "Approval policy record is invalid.");
674
+ exactKeys(policy, ["id", "version", "hash", "requiredApprovals"], "Approval policy record");
675
+ const requiredApprovals = policy.requiredApprovals;
676
+ if (requiredApprovals !== 1 && requiredApprovals !== 2)
677
+ throw new ApprovalInboxError("Approval requirement is invalid.");
678
+ return {
679
+ id: identifier(policy.id, "Approval policy id"),
680
+ version: positive(policy.version, "Approval policy version"),
681
+ hash: hash(policy.hash, "Approval policy hash"),
682
+ requiredApprovals,
683
+ };
684
+ }
685
+ function validateContext(value) {
686
+ const context = object(value, "Approval context is invalid.");
687
+ exactKeys(context, ["confidence", "amountMinor"], "Approval context", [
688
+ "amountMinor",
689
+ ]);
690
+ return {
691
+ confidence: percentage(context.confidence, "Approval confidence"),
692
+ ...(context.amountMinor === undefined
693
+ ? {}
694
+ : { amountMinor: nonNegative(context.amountMinor, "Approval amount") }),
695
+ };
696
+ }
697
+ function validateApprover(value) {
698
+ const approver = object(value, "Approver identity is invalid.");
699
+ exactKeys(approver, ["id", "principalId", "independenceKey"], "Approver identity");
700
+ return {
701
+ id: identifier(approver.id, "Approver id"),
702
+ principalId: identifier(approver.principalId, "Approver principal id"),
703
+ independenceKey: identifier(approver.independenceKey, "Approver independence key"),
704
+ };
705
+ }
706
+ function validateDecision(value) {
707
+ const decision = object(value, "Approval decision is invalid.");
708
+ exactKeys(decision, [
709
+ "id",
710
+ "kind",
711
+ "approverId",
712
+ "principalId",
713
+ "independenceKey",
714
+ "reason",
715
+ "decidedAt",
716
+ ], "Approval decision", ["reason"]);
717
+ if (decision.kind !== "approve" &&
718
+ decision.kind !== "reject" &&
719
+ decision.kind !== "revoke")
720
+ throw new ApprovalInboxError("Approval decision kind is invalid.");
721
+ return {
722
+ id: identifier(decision.id, "Approval decision id"),
723
+ kind: decision.kind,
724
+ approverId: identifier(decision.approverId, "Approver id"),
725
+ principalId: identifier(decision.principalId, "Approver principal id"),
726
+ independenceKey: identifier(decision.independenceKey, "Approver independence key"),
727
+ ...(decision.reason === undefined
728
+ ? {}
729
+ : { reason: text(decision.reason, "Approval decision reason", 200) }),
730
+ decidedAt: timestamp(decision.decidedAt, "Approval decision time"),
731
+ };
732
+ }
733
+ function appendAudit(state, event, request, actorId, timestampValue, actionReceiptHash) {
734
+ const previous = state.audit.at(-1)?.recordHash ?? state.auditAnchor;
735
+ const base = {
736
+ sequence: state.audit.length + 1,
737
+ event,
738
+ requestId: request.id,
739
+ actionHash: request.actionHash,
740
+ ...(actionReceiptHash === undefined ? {} : { actionReceiptHash }),
741
+ actorId: identifier(actorId, "Approval audit actor id"),
742
+ timestamp: timestampValue,
743
+ previousHash: previous,
744
+ };
745
+ state.audit.push({ ...base, recordHash: sha256(canonical(base)) });
746
+ }
747
+ function refresh(state, now) {
748
+ for (const request of state.requests) {
749
+ if (request.status === "pending" &&
750
+ Date.parse(now) >= Date.parse(request.escalationAt)) {
751
+ request.status = "timed_out";
752
+ appendAudit(state, "request.timed_out", request, "approval-inbox", now);
753
+ }
754
+ else if ((request.status === "pending" || request.status === "approved") &&
755
+ Date.parse(now) >= Date.parse(request.expiresAt)) {
756
+ request.status = "expired";
757
+ appendAudit(state, "request.expired", request, "approval-inbox", now);
758
+ }
363
759
  }
364
- } }
365
- function classify(action) { if (action.provider === "ghostapi-synthetic" && action.operation === "synthetic.subscription_failure")
366
- return "update"; throw new ApprovalInboxError("No approval risk taxonomy rule exists for this action."); }
367
- function risk(value) { if (value !== "read" && value !== "create" && value !== "update" && value !== "communicate" && value !== "money_movement" && value !== "delete" && value !== "permission_change" && value !== "deployment")
368
- throw new ApprovalInboxError("Approval risk is invalid."); return value; }
369
- function risks(value) { const parsed = array(value, "Approval critical risks", 8).map(risk); unique(parsed, "Approval critical risks"); return parsed.sort(); }
370
- function requirePending(request) { if (request.status !== "pending")
371
- throw new ApprovalInboxError("Approval is not pending."); }
372
- function assertIndependent(request, approver) { if (approver.id === request.action.actor.id || approver.principalId === request.action.actor.id || approver.principalId === request.action.actor.workloadId || approver.independenceKey === request.action.actor.id || approver.independenceKey === request.action.actor.workloadId)
373
- throw new ApprovalInboxError("Action workload cannot approve its own action."); }
374
- function findRequest(state, requestId) { const request = state.requests.find((candidate) => candidate.id === requestId); if (request === undefined)
375
- throw new ApprovalInboxError("Approval request was not found."); return request; }
376
- function validStatus(value) { return value === "pending" || value === "approved" || value === "rejected" || value === "revoked" || value === "timed_out" || value === "expired" || value === "executing" || value === "executed" || value === "execution_failed" || value === "superseded"; }
377
- function policyHash(policy) { return sha256(canonical(policy)); }
378
- function canonical(value) { if (value === null || typeof value === "boolean" || typeof value === "string" || typeof value === "number")
379
- return JSON.stringify(value); if (Array.isArray(value))
380
- return `[${value.map(canonical).join(",")}]`; if (typeof value === "object")
381
- return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`).join(",")}}`; throw new ApprovalInboxError("Approval data must be JSON."); }
382
- function sha256(value) { return createHash("sha256").update(value, "utf8").digest("hex"); }
383
- function identifier(value, label) { if (typeof value !== "string" || !IDENTIFIER.test(value) || sanitizeSecretString(value) !== value)
384
- throw new ApprovalInboxError(`${label} must be a safe identifier.`); return value; }
385
- function hash(value, label) { if (typeof value !== "string" || !HASH.test(value))
386
- throw new ApprovalInboxError(`${label} must be a SHA-256 hash.`); return value; }
387
- function timestamp(value, label) { if (typeof value !== "string" || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(value) || !Number.isFinite(Date.parse(value)))
388
- throw new ApprovalInboxError(`${label} must be an ISO UTC timestamp.`); return value; }
389
- function positive(value, label) { if (typeof value !== "number" || !Number.isInteger(value) || value < 1 || value > 100_000)
390
- throw new ApprovalInboxError(`${label} is invalid.`); return value; }
391
- function nonNegative(value, label) { if (typeof value !== "number" || !Number.isInteger(value) || value < 0 || value > Number.MAX_SAFE_INTEGER)
392
- throw new ApprovalInboxError(`${label} is invalid.`); return value; }
393
- function percentage(value, label) { if (typeof value !== "number" || !Number.isInteger(value) || value < 0 || value > 100)
394
- throw new ApprovalInboxError(`${label} must be an integer from 0 to 100.`); return value; }
395
- function boundedMs(value, label) { if (typeof value !== "number" || !Number.isInteger(value) || value < 1_000 || value > 24 * 60 * 60 * 1000)
396
- throw new ApprovalInboxError(`${label} is invalid.`); return value; }
397
- function text(value, label, max) { if (typeof value !== "string" || value.trim() === "" || value.length > max || /[\u0000-\u001f]/.test(value) || sanitizeSecretString(value) !== value)
398
- throw new ApprovalInboxError(`${label} is invalid.`); return value.trim(); }
399
- function identifiers(value, label) { const result = array(value, label, 100).map((entry) => identifier(entry, label)); if (result.length === 0)
400
- throw new ApprovalInboxError(`${label} must not be empty.`); unique(result, label); return result.sort(); }
401
- function object(value, message) { if (value === null || typeof value !== "object" || Array.isArray(value))
402
- throw new ApprovalInboxError(message); return value; }
403
- function array(value, label, max) { if (!Array.isArray(value) || value.length > max)
404
- throw new ApprovalInboxError(`${label} is invalid.`); return value; }
405
- function exactKeys(value, keys, label, optional = []) { for (const key of Object.keys(value))
406
- if (!keys.includes(key) || (value[key] === undefined && !optional.includes(key)))
407
- throw new ApprovalInboxError(`${label} contains unsupported field: ${key}`); }
408
- function unique(values, label) { if (new Set(values).size !== values.length)
409
- throw new ApprovalInboxError(`${label} must be unique.`); }
410
- function clone(value) { return structuredClone(value); }
411
- function isErrorCode(error, code) { return error instanceof Error && "code" in error && error.code === code; }
760
+ }
761
+ function classify(action) {
762
+ if (action.provider === "ghostapi-synthetic" &&
763
+ action.operation === "synthetic.subscription_failure")
764
+ return "update";
765
+ throw new ApprovalInboxError("No approval risk taxonomy rule exists for this action.");
766
+ }
767
+ function risk(value) {
768
+ if (value !== "read" &&
769
+ value !== "create" &&
770
+ value !== "update" &&
771
+ value !== "communicate" &&
772
+ value !== "money_movement" &&
773
+ value !== "delete" &&
774
+ value !== "permission_change" &&
775
+ value !== "deployment")
776
+ throw new ApprovalInboxError("Approval risk is invalid.");
777
+ return value;
778
+ }
779
+ function risks(value) {
780
+ const parsed = array(value, "Approval critical risks", 8).map(risk);
781
+ unique(parsed, "Approval critical risks");
782
+ return parsed.sort();
783
+ }
784
+ function requirePending(request) {
785
+ if (request.status !== "pending")
786
+ throw new ApprovalInboxError("Approval is not pending.");
787
+ }
788
+ function assertIndependent(request, approver) {
789
+ if (approver.id === request.action.actor.id ||
790
+ approver.principalId === request.action.actor.id ||
791
+ approver.principalId === request.action.actor.workloadId ||
792
+ approver.independenceKey === request.action.actor.id ||
793
+ approver.independenceKey === request.action.actor.workloadId)
794
+ throw new ApprovalInboxError("Action workload cannot approve its own action.");
795
+ }
796
+ function findRequest(state, requestId) {
797
+ const request = state.requests.find((candidate) => candidate.id === requestId);
798
+ if (request === undefined)
799
+ throw new ApprovalInboxError("Approval request was not found.");
800
+ return request;
801
+ }
802
+ function validStatus(value) {
803
+ return (value === "pending" ||
804
+ value === "approved" ||
805
+ value === "rejected" ||
806
+ value === "revoked" ||
807
+ value === "timed_out" ||
808
+ value === "expired" ||
809
+ value === "executing" ||
810
+ value === "executed" ||
811
+ value === "execution_failed" ||
812
+ value === "superseded");
813
+ }
814
+ function policyHash(policy) {
815
+ return sha256(canonical(policy));
816
+ }
817
+ function canonical(value) {
818
+ if (value === null ||
819
+ typeof value === "boolean" ||
820
+ typeof value === "string" ||
821
+ typeof value === "number")
822
+ return JSON.stringify(value);
823
+ if (Array.isArray(value))
824
+ return `[${value.map(canonical).join(",")}]`;
825
+ if (typeof value === "object")
826
+ return `{${Object.keys(value)
827
+ .sort()
828
+ .map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`)
829
+ .join(",")}}`;
830
+ throw new ApprovalInboxError("Approval data must be JSON.");
831
+ }
832
+ function sha256(value) {
833
+ return createHash("sha256").update(value, "utf8").digest("hex");
834
+ }
835
+ function identifier(value, label) {
836
+ if (typeof value !== "string" ||
837
+ !IDENTIFIER.test(value) ||
838
+ sanitizeSecretString(value) !== value)
839
+ throw new ApprovalInboxError(`${label} must be a safe identifier.`);
840
+ return value;
841
+ }
842
+ function hash(value, label) {
843
+ if (typeof value !== "string" || !HASH.test(value))
844
+ throw new ApprovalInboxError(`${label} must be a SHA-256 hash.`);
845
+ return value;
846
+ }
847
+ function timestamp(value, label) {
848
+ if (typeof value !== "string" ||
849
+ !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(value) ||
850
+ !Number.isFinite(Date.parse(value)))
851
+ throw new ApprovalInboxError(`${label} must be an ISO UTC timestamp.`);
852
+ return value;
853
+ }
854
+ function positive(value, label) {
855
+ if (typeof value !== "number" ||
856
+ !Number.isInteger(value) ||
857
+ value < 1 ||
858
+ value > 100_000)
859
+ throw new ApprovalInboxError(`${label} is invalid.`);
860
+ return value;
861
+ }
862
+ function nonNegative(value, label) {
863
+ if (typeof value !== "number" ||
864
+ !Number.isInteger(value) ||
865
+ value < 0 ||
866
+ value > Number.MAX_SAFE_INTEGER)
867
+ throw new ApprovalInboxError(`${label} is invalid.`);
868
+ return value;
869
+ }
870
+ function percentage(value, label) {
871
+ if (typeof value !== "number" ||
872
+ !Number.isInteger(value) ||
873
+ value < 0 ||
874
+ value > 100)
875
+ throw new ApprovalInboxError(`${label} must be an integer from 0 to 100.`);
876
+ return value;
877
+ }
878
+ function boundedMs(value, label) {
879
+ if (typeof value !== "number" ||
880
+ !Number.isInteger(value) ||
881
+ value < 1_000 ||
882
+ value > 24 * 60 * 60 * 1000)
883
+ throw new ApprovalInboxError(`${label} is invalid.`);
884
+ return value;
885
+ }
886
+ function text(value, label, max) {
887
+ if (typeof value !== "string" ||
888
+ value.trim() === "" ||
889
+ value.length > max ||
890
+ /[\u0000-\u001f]/.test(value) ||
891
+ sanitizeSecretString(value) !== value)
892
+ throw new ApprovalInboxError(`${label} is invalid.`);
893
+ return value.trim();
894
+ }
895
+ function identifiers(value, label) {
896
+ const result = array(value, label, 100).map((entry) => identifier(entry, label));
897
+ if (result.length === 0)
898
+ throw new ApprovalInboxError(`${label} must not be empty.`);
899
+ unique(result, label);
900
+ return result.sort();
901
+ }
902
+ function object(value, message) {
903
+ if (value === null || typeof value !== "object" || Array.isArray(value))
904
+ throw new ApprovalInboxError(message);
905
+ return value;
906
+ }
907
+ function array(value, label, max) {
908
+ if (!Array.isArray(value) || value.length > max)
909
+ throw new ApprovalInboxError(`${label} is invalid.`);
910
+ return value;
911
+ }
912
+ function exactKeys(value, keys, label, optional = []) {
913
+ for (const key of Object.keys(value))
914
+ if (!keys.includes(key) ||
915
+ (value[key] === undefined && !optional.includes(key)))
916
+ throw new ApprovalInboxError(`${label} contains unsupported field: ${key}`);
917
+ }
918
+ function unique(values, label) {
919
+ if (new Set(values).size !== values.length)
920
+ throw new ApprovalInboxError(`${label} must be unique.`);
921
+ }
922
+ function clone(value) {
923
+ return structuredClone(value);
924
+ }
925
+ function isErrorCode(error, code) {
926
+ return error instanceof Error && "code" in error && error.code === code;
927
+ }
412
928
  //# sourceMappingURL=approvalInbox.js.map