@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,11 @@
1
- import { createHash, createPrivateKey, createPublicKey, generateKeyPairSync, KeyObject, randomUUID, sign, verify } from "node:crypto";
1
+ import { createHash, createPrivateKey, createPublicKey, generateKeyPairSync, KeyObject, randomUUID, sign, verify, } from "node:crypto";
2
2
  import { lstat, readFile } from "node:fs/promises";
3
3
  import { join } from "node:path";
4
4
  import { getDataPaths } from "../config/dataPaths.js";
5
- import { createLocalSafetyController } from "../safety/index.js";
5
+ import { createLocalSafetyController, } from "../safety/index.js";
6
6
  import { sanitizeSecretString } from "../security/secrets.js";
7
- import { atomicWriteJson, ensurePrivateDirectory, withFileLock } from "../storage/fileStore.js";
8
- import { inspectWorld, runSubscriptionFailureWorkflow } from "../worlds/index.js";
7
+ import { atomicWriteJson, ensurePrivateDirectory, withFileLock, } from "../storage/fileStore.js";
8
+ import { inspectWorld, runSubscriptionFailureWorkflow, } from "../worlds/index.js";
9
9
  const ACTION_SCHEMA_VERSION = 1;
10
10
  const ACTION_KIND = "ghostapi.action";
11
11
  const APPROVAL_KIND = "ghostapi.action-approval";
@@ -31,8 +31,27 @@ export function actionApprovalHash(value) {
31
31
  }
32
32
  export function validateActionEnvelope(value) {
33
33
  const action = object(value, "Action envelope must be an object.");
34
- exactKeys(action, ["schemaVersion", "kind", "actionId", "idempotencyKey", "actor", "project", "provider", "operation", "resource", "arguments", "expectedSideEffects", "riskClass", "reversibility", "policy", "evidence", "expiresAt", "nonce"], "Action envelope");
35
- if (action.schemaVersion !== ACTION_SCHEMA_VERSION || action.kind !== ACTION_KIND)
34
+ exactKeys(action, [
35
+ "schemaVersion",
36
+ "kind",
37
+ "actionId",
38
+ "idempotencyKey",
39
+ "actor",
40
+ "project",
41
+ "provider",
42
+ "operation",
43
+ "resource",
44
+ "arguments",
45
+ "expectedSideEffects",
46
+ "riskClass",
47
+ "reversibility",
48
+ "policy",
49
+ "evidence",
50
+ "expiresAt",
51
+ "nonce",
52
+ ], "Action envelope");
53
+ if (action.schemaVersion !== ACTION_SCHEMA_VERSION ||
54
+ action.kind !== ACTION_KIND)
36
55
  throw new ActionGatewayError("Unsupported action envelope schema.");
37
56
  const actor = object(action.actor, "Action actor is invalid.");
38
57
  exactKeys(actor, ["id", "workloadId", "type"], "Action actor");
@@ -52,14 +71,32 @@ export function validateActionEnvelope(value) {
52
71
  exactKeys(args, ["worldId"], "Action arguments");
53
72
  const operation = identifier(action.operation, "Action operation");
54
73
  const sideEffects = action.expectedSideEffects;
55
- const expected = ["stripe.subscription.past_due", "email.subscription_payment_failed", "github.recovery_issue", "generic_rest.payment_failed"];
56
- if (!Array.isArray(sideEffects) || sideEffects.length > 16 || sideEffects.some((value) => typeof value !== "string" || !IDENTIFIER.test(value) || sanitizeSecretString(value) !== value) || new Set(sideEffects).size !== sideEffects.length)
74
+ const expected = [
75
+ "stripe.subscription.past_due",
76
+ "email.subscription_payment_failed",
77
+ "github.recovery_issue",
78
+ "generic_rest.payment_failed",
79
+ ];
80
+ if (!Array.isArray(sideEffects) ||
81
+ sideEffects.length > 16 ||
82
+ sideEffects.some((value) => typeof value !== "string" ||
83
+ !IDENTIFIER.test(value) ||
84
+ sanitizeSecretString(value) !== value) ||
85
+ new Set(sideEffects).size !== sideEffects.length)
57
86
  throw new ActionGatewayError("Action expected side effects must be a bounded unique list of safe identifiers.");
58
- if (operation === "synthetic.subscription_failure" && (sideEffects.length !== expected.length || sideEffects.some((value, index) => value !== expected[index])))
87
+ if (operation === "synthetic.subscription_failure" &&
88
+ (sideEffects.length !== expected.length ||
89
+ sideEffects.some((value, index) => value !== expected[index])))
59
90
  throw new ActionGatewayError("Action expected side effects are invalid for the synthetic operation.");
60
- if (action.riskClass !== "read" && action.riskClass !== "write" && action.riskClass !== "money_movement" && action.riskClass !== "external_communication" && action.riskClass !== "delete" && action.riskClass !== "deploy")
91
+ if (action.riskClass !== "read" &&
92
+ action.riskClass !== "write" &&
93
+ action.riskClass !== "money_movement" &&
94
+ action.riskClass !== "external_communication" &&
95
+ action.riskClass !== "delete" &&
96
+ action.riskClass !== "deploy")
61
97
  throw new ActionGatewayError("Action risk class is invalid.");
62
- if (action.reversibility !== "none" && action.reversibility !== "compensatable")
98
+ if (action.reversibility !== "none" &&
99
+ action.reversibility !== "compensatable")
63
100
  throw new ActionGatewayError("Action reversibility is invalid.");
64
101
  const policy = object(action.policy, "Action policy reference is invalid.");
65
102
  exactKeys(policy, ["version", "hash"], "Action policy reference");
@@ -73,8 +110,15 @@ export function validateActionEnvelope(value) {
73
110
  kind: "ghostapi.action",
74
111
  actionId: identifier(action.actionId, "Action id"),
75
112
  idempotencyKey: identifier(action.idempotencyKey, "Action idempotency key"),
76
- actor: { id: identifier(actor.id, "Action actor id"), workloadId: identifier(actor.workloadId, "Action workload id"), type: actor.type },
77
- project: { id: identifier(project.id, "Action project id"), environment: "synthetic" },
113
+ actor: {
114
+ id: identifier(actor.id, "Action actor id"),
115
+ workloadId: identifier(actor.workloadId, "Action workload id"),
116
+ type: actor.type,
117
+ },
118
+ project: {
119
+ id: identifier(project.id, "Action project id"),
120
+ environment: "synthetic",
121
+ },
78
122
  provider: "ghostapi-synthetic",
79
123
  operation,
80
124
  resource: { type: "synthetic-world", id: worldId },
@@ -82,16 +126,30 @@ export function validateActionEnvelope(value) {
82
126
  expectedSideEffects: [...sideEffects],
83
127
  riskClass: action.riskClass,
84
128
  reversibility: action.reversibility,
85
- policy: { version: positiveInteger(policy.version, "Action policy version"), hash: hash(policy.hash, "Action policy hash") },
129
+ policy: {
130
+ version: positiveInteger(policy.version, "Action policy version"),
131
+ hash: hash(policy.hash, "Action policy hash"),
132
+ },
86
133
  evidence: { hash: hash(evidence.hash, "Action evidence hash") },
87
134
  expiresAt: futureTimestamp(action.expiresAt, "Action expiry"),
88
- nonce: identifier(action.nonce, "Action nonce")
135
+ nonce: identifier(action.nonce, "Action nonce"),
89
136
  };
90
137
  }
91
138
  export function validateActionApproval(value) {
92
139
  const approval = object(value, "Action approval must be an object.");
93
- exactKeys(approval, ["schemaVersion", "kind", "approvalId", "actionHash", "approvedBy", "approvedAt", "expiresAt", "nonce", "provenance"], "Action approval");
94
- if (approval.schemaVersion !== ACTION_SCHEMA_VERSION || approval.kind !== APPROVAL_KIND)
140
+ exactKeys(approval, [
141
+ "schemaVersion",
142
+ "kind",
143
+ "approvalId",
144
+ "actionHash",
145
+ "approvedBy",
146
+ "approvedAt",
147
+ "expiresAt",
148
+ "nonce",
149
+ "provenance",
150
+ ], "Action approval");
151
+ if (approval.schemaVersion !== ACTION_SCHEMA_VERSION ||
152
+ approval.kind !== APPROVAL_KIND)
95
153
  throw new ActionGatewayError("Unsupported action approval schema.");
96
154
  const approvedAt = timestamp(approval.approvedAt, "Action approval timestamp");
97
155
  const expiresAt = futureTimestamp(approval.expiresAt, "Action approval expiry");
@@ -99,9 +157,25 @@ export function validateActionApproval(value) {
99
157
  throw new ActionGatewayError("Action approval must expire after it was issued.");
100
158
  const provenance = object(approval.provenance, "Action approval provenance is invalid.");
101
159
  exactKeys(provenance, ["kind", "keyId", "signature"], "Action approval provenance");
102
- if (provenance.kind !== "ghostapi.ed25519-signature" || typeof provenance.signature !== "string" || !/^[A-Za-z0-9_-]{86}$/.test(provenance.signature))
160
+ if (provenance.kind !== "ghostapi.ed25519-signature" ||
161
+ typeof provenance.signature !== "string" ||
162
+ !/^[A-Za-z0-9_-]{86}$/.test(provenance.signature))
103
163
  throw new ActionGatewayError("Action approval provenance is invalid.");
104
- return { schemaVersion: 1, kind: "ghostapi.action-approval", approvalId: identifier(approval.approvalId, "Action approval id"), actionHash: hash(approval.actionHash, "Action approval hash"), approvedBy: identifier(approval.approvedBy, "Action approver id"), approvedAt, expiresAt, nonce: identifier(approval.nonce, "Action approval nonce"), provenance: { kind: "ghostapi.ed25519-signature", keyId: identifier(provenance.keyId, "Action approval provenance key id"), signature: provenance.signature } };
164
+ return {
165
+ schemaVersion: 1,
166
+ kind: "ghostapi.action-approval",
167
+ approvalId: identifier(approval.approvalId, "Action approval id"),
168
+ actionHash: hash(approval.actionHash, "Action approval hash"),
169
+ approvedBy: identifier(approval.approvedBy, "Action approver id"),
170
+ approvedAt,
171
+ expiresAt,
172
+ nonce: identifier(approval.nonce, "Action approval nonce"),
173
+ provenance: {
174
+ kind: "ghostapi.ed25519-signature",
175
+ keyId: identifier(provenance.keyId, "Action approval provenance key id"),
176
+ signature: provenance.signature,
177
+ },
178
+ };
105
179
  }
106
180
  export function createSyntheticActionAdapter() {
107
181
  return {
@@ -116,15 +190,31 @@ export function createSyntheticActionAdapter() {
116
190
  },
117
191
  async execute(action, controls) {
118
192
  const receipt = await runSubscriptionFailureWorkflow(action.arguments.worldId, action.actionId, controls?.commit);
119
- return { outcome: "committed", providerRequestId: `synthetic_${receipt.actionId}`, result: { worldId: action.arguments.worldId, subscriptionId: receipt.subscriptionId, syntheticReceiptId: receipt.actionId } };
193
+ return {
194
+ outcome: "committed",
195
+ providerRequestId: `synthetic_${receipt.actionId}`,
196
+ result: {
197
+ worldId: action.arguments.worldId,
198
+ subscriptionId: receipt.subscriptionId,
199
+ syntheticReceiptId: receipt.actionId,
200
+ },
201
+ };
120
202
  },
121
203
  async verify(action) {
122
204
  const world = await inspectWorld(action.arguments.worldId);
123
205
  const receipt = world.state.receipts.find((candidate) => candidate.actionId === action.actionId);
124
206
  return receipt === undefined
125
207
  ? { committed: false }
126
- : { committed: true, providerRequestId: `synthetic_${receipt.actionId}`, result: { worldId: action.arguments.worldId, subscriptionId: receipt.subscriptionId, syntheticReceiptId: receipt.actionId } };
127
- }
208
+ : {
209
+ committed: true,
210
+ providerRequestId: `synthetic_${receipt.actionId}`,
211
+ result: {
212
+ worldId: action.arguments.worldId,
213
+ subscriptionId: receipt.subscriptionId,
214
+ syntheticReceiptId: receipt.actionId,
215
+ },
216
+ };
217
+ },
128
218
  };
129
219
  }
130
220
  export class LocalActionGateway {
@@ -137,13 +227,18 @@ export class LocalActionGateway {
137
227
  this.now = options.now ?? (() => new Date());
138
228
  this.pathForAction = options.pathForAction ?? getActionPath;
139
229
  this.adapter = options.adapter ?? createSyntheticActionAdapter();
140
- this.safetyController = options.safetyController ?? createLocalSafetyController({ now: this.now });
141
- this.approvalVerifier = options.approvalVerifier ?? createDisabledActionApprovalVerifier();
230
+ this.safetyController =
231
+ options.safetyController ??
232
+ createLocalSafetyController({ now: this.now });
233
+ this.approvalVerifier =
234
+ options.approvalVerifier ?? createDisabledActionApprovalVerifier();
142
235
  }
143
236
  async submit(actionValue, approvalValue, policy) {
144
237
  const action = validateActionEnvelope(actionValue);
145
238
  const approval = validateActionApproval(approvalValue);
146
- const verification = await this.approvalVerifier.verify(action, approval, { phase: "submit" });
239
+ const verification = await this.approvalVerifier.verify(action, approval, {
240
+ phase: "submit",
241
+ });
147
242
  requireApprovalInboxVerification(approval, verification);
148
243
  const current = this.now().toISOString();
149
244
  verifyPolicy(action, policy);
@@ -152,14 +247,22 @@ export class LocalActionGateway {
152
247
  return withFileLock(path, async () => {
153
248
  const existing = await readStoredAction(path, false);
154
249
  if (existing !== null) {
155
- if (existing.actionHash === actionDigest && actionApprovalHash(existing.approval) === actionApprovalHash(approval))
250
+ if (existing.actionHash === actionDigest &&
251
+ actionApprovalHash(existing.approval) === actionApprovalHash(approval))
156
252
  return existing;
157
253
  throw new ActionGatewayError("Action id already exists with a different immutable envelope or approval.");
158
254
  }
159
255
  if (Date.parse(action.expiresAt) <= Date.parse(current))
160
256
  throw new ActionGatewayError("Action has expired.");
161
257
  verifyApproval(action, approval, current);
162
- const record = { schemaVersion: 1, kind: "ghostapi.action-record", envelope: action, actionHash: actionDigest, approval, receipts: [] };
258
+ const record = {
259
+ schemaVersion: 1,
260
+ kind: "ghostapi.action-record",
261
+ envelope: action,
262
+ actionHash: actionDigest,
263
+ approval,
264
+ receipts: [],
265
+ };
163
266
  record.receipts.push(createReceipt(record, "requested", current));
164
267
  await writeStoredAction(path, record);
165
268
  return record;
@@ -178,11 +281,14 @@ export class LocalActionGateway {
178
281
  const approvalVerification = await this.approvalVerifier.verify(record.envelope, record.approval, { phase: "execute" });
179
282
  requireApprovalInboxVerification(record.approval, approvalVerification);
180
283
  const current = this.now().toISOString();
181
- if (actionHash(requested) !== record.actionHash || canonicalizeActionEnvelope(requested) !== canonicalizeActionEnvelope(record.envelope))
284
+ if (actionHash(requested) !== record.actionHash ||
285
+ canonicalizeActionEnvelope(requested) !==
286
+ canonicalizeActionEnvelope(record.envelope))
182
287
  throw new ActionGatewayError("Action envelope changed after approval; execution is blocked.");
183
288
  verifyApproval(record.envelope, record.approval, current, false);
184
289
  verifyPolicy(record.envelope, policy);
185
- if (identity.actorId !== record.envelope.actor.id || identity.workloadId !== record.envelope.actor.workloadId)
290
+ if (identity.actorId !== record.envelope.actor.id ||
291
+ identity.workloadId !== record.envelope.actor.workloadId)
186
292
  throw new ActionGatewayError("Execution identity does not match the approved action.");
187
293
  if (!this.adapter.supports(record.envelope.operation))
188
294
  throw new ActionGatewayError(`Execution adapter does not support action operation: ${record.envelope.operation}`);
@@ -192,7 +298,9 @@ export class LocalActionGateway {
192
298
  const committed = findLatestReceipt(record.receipts, "committed");
193
299
  if (committed !== undefined) {
194
300
  const reconciliation = await this.adapter.verify(record.envelope);
195
- if (!reconciliation.committed || reconciliation.providerRequestId === undefined || reconciliation.result === undefined)
301
+ if (!reconciliation.committed ||
302
+ reconciliation.providerRequestId === undefined ||
303
+ reconciliation.result === undefined)
196
304
  throw new ActionGatewayError("Committed action verification is incomplete; execution will not retry without idempotency proof.");
197
305
  const verifiedReceipt = createReceipt(record, "verified", current, reconciliation.providerRequestId, reconciliation.result);
198
306
  appendReceipt(record, verifiedReceipt);
@@ -202,7 +310,9 @@ export class LocalActionGateway {
202
310
  const attempted = findLatestReceipt(record.receipts, "attempted");
203
311
  if (attempted !== undefined) {
204
312
  const reconciliation = await this.adapter.verify(record.envelope);
205
- if (!reconciliation.committed || reconciliation.providerRequestId === undefined || reconciliation.result === undefined)
313
+ if (!reconciliation.committed ||
314
+ reconciliation.providerRequestId === undefined ||
315
+ reconciliation.result === undefined)
206
316
  throw new ActionGatewayError("Prior action outcome is unknown; execution will not retry without idempotency proof.");
207
317
  appendReceipt(record, createReceipt(record, "committed", current, reconciliation.providerRequestId, reconciliation.result));
208
318
  const verifiedReceipt = createReceipt(record, "verified", current, reconciliation.providerRequestId, reconciliation.result);
@@ -222,17 +332,27 @@ export class LocalActionGateway {
222
332
  await writeStoredAction(path, record);
223
333
  let execution;
224
334
  try {
225
- execution = await this.adapter.execute(record.envelope, { commit: safety.commit });
335
+ execution = await this.adapter.execute(record.envelope, {
336
+ commit: safety.commit,
337
+ });
226
338
  }
227
339
  catch {
228
- await safety.complete({ success: false, latencyMs: 0, reason: "synthetic execution failed or was stopped" });
340
+ await safety.complete({
341
+ success: false,
342
+ latencyMs: 0,
343
+ reason: "synthetic execution failed or was stopped",
344
+ });
229
345
  const failed = createReceipt(record, "failed", this.now().toISOString(), undefined, undefined, { code: "execution_error", retrySafe: false });
230
346
  appendReceipt(record, failed);
231
347
  await writeStoredAction(path, record);
232
348
  throw new ActionGatewayError("Action execution failed; it was not automatically retried.");
233
349
  }
234
350
  if (execution.outcome === "unknown") {
235
- await safety.complete({ success: false, latencyMs: 0, reason: "synthetic execution outcome is unknown" });
351
+ await safety.complete({
352
+ success: false,
353
+ latencyMs: 0,
354
+ reason: "synthetic execution outcome is unknown",
355
+ });
236
356
  const failed = createReceipt(record, "failed", this.now().toISOString(), undefined, undefined, { code: "unknown_outcome", retrySafe: false });
237
357
  appendReceipt(record, failed);
238
358
  await writeStoredAction(path, record);
@@ -241,8 +361,15 @@ export class LocalActionGateway {
241
361
  appendReceipt(record, createReceipt(record, "committed", this.now().toISOString(), execution.providerRequestId, execution.result));
242
362
  await writeStoredAction(path, record);
243
363
  const verification = await this.adapter.verify(record.envelope);
244
- if (!verification.committed || verification.providerRequestId === undefined || verification.result === undefined) {
245
- await safety.complete({ success: false, reconciliationMismatch: true, latencyMs: 0, reason: "synthetic verification did not prove the result" });
364
+ if (!verification.committed ||
365
+ verification.providerRequestId === undefined ||
366
+ verification.result === undefined) {
367
+ await safety.complete({
368
+ success: false,
369
+ reconciliationMismatch: true,
370
+ latencyMs: 0,
371
+ reason: "synthetic verification did not prove the result",
372
+ });
246
373
  const failed = createReceipt(record, "failed", this.now().toISOString(), execution.providerRequestId, execution.result, { code: "verification_failed", retrySafe: false });
247
374
  appendReceipt(record, failed);
248
375
  await writeStoredAction(path, record);
@@ -257,7 +384,8 @@ export class LocalActionGateway {
257
384
  }
258
385
  async compensate(actionId) {
259
386
  const action = await this.inspect(actionId);
260
- if (this.adapter.compensate === undefined || action.envelope.reversibility !== "compensatable")
387
+ if (this.adapter.compensate === undefined ||
388
+ action.envelope.reversibility !== "compensatable")
261
389
  throw new ActionGatewayError("Compensation is unsupported for this action; no rollback is claimed.");
262
390
  await this.adapter.compensate(action.envelope);
263
391
  throw new ActionGatewayError("Compensation adapters must return a durable compensation receipt before this operation can be exposed.");
@@ -275,32 +403,51 @@ function safetyAction(action, actionDigest) {
275
403
  riskClass: action.riskClass,
276
404
  idempotencyKey: action.idempotencyKey,
277
405
  actionHash: actionDigest,
278
- costs: { monetaryAmountMinor: 0, requests: 1, messages: 1, mutations: 1, deletes: 0, tokenCost: 0 }
406
+ costs: {
407
+ monetaryAmountMinor: 0,
408
+ requests: 1,
409
+ messages: 1,
410
+ mutations: 1,
411
+ deletes: 0,
412
+ tokenCost: 0,
413
+ },
279
414
  };
280
415
  }
281
416
  export function createLocalActionGateway(options = {}) {
282
417
  return new LocalActionGateway(options);
283
418
  }
284
419
  export function createDisabledActionApprovalVerifier() {
285
- return { async verify() { throw new ActionGatewayError("Action approval authenticity could not be verified."); } };
420
+ return {
421
+ async verify() {
422
+ throw new ActionGatewayError("Action approval authenticity could not be verified.");
423
+ },
424
+ };
286
425
  }
287
426
  export function createEd25519ActionApprovalSigner(options) {
288
427
  const keyId = identifier(options.keyId, "Action approval signing key id");
289
- const privateKey = options.privateKey instanceof KeyObject ? options.privateKey : createPrivateKey(options.privateKey);
290
- if (privateKey.type !== "private" || privateKey.asymmetricKeyType !== "ed25519")
428
+ const privateKey = options.privateKey instanceof KeyObject
429
+ ? options.privateKey
430
+ : createPrivateKey(options.privateKey);
431
+ if (privateKey.type !== "private" ||
432
+ privateKey.asymmetricKeyType !== "ed25519")
291
433
  throw new ActionGatewayError("Action approval signing key must be an Ed25519 private key.");
292
434
  return {
293
435
  issue(value) {
294
436
  const approval = validateUnsignedActionApproval(value);
295
437
  const signature = sign(null, Buffer.from(canonicalJson(approval), "utf8"), privateKey).toString("base64url");
296
- return { ...approval, provenance: { kind: "ghostapi.ed25519-signature", keyId, signature } };
297
- }
438
+ return {
439
+ ...approval,
440
+ provenance: { kind: "ghostapi.ed25519-signature", keyId, signature },
441
+ };
442
+ },
298
443
  };
299
444
  }
300
445
  export function createEd25519ActionApprovalVerifier(options) {
301
446
  const keys = new Map(Object.entries(options.trustedKeys).map(([keyId, key]) => {
302
447
  const parsedId = identifier(keyId, "Action approval verification key id");
303
- const publicKey = key instanceof KeyObject && key.type === "public" ? key : createPublicKey(key);
448
+ const publicKey = key instanceof KeyObject && key.type === "public"
449
+ ? key
450
+ : createPublicKey(key);
304
451
  if (publicKey.asymmetricKeyType !== "ed25519")
305
452
  throw new ActionGatewayError("Action approval verification key must be Ed25519.");
306
453
  return [parsedId, publicKey];
@@ -309,20 +456,26 @@ export function createEd25519ActionApprovalVerifier(options) {
309
456
  async verify(action, approval) {
310
457
  const parsed = validateActionApproval(approval);
311
458
  const publicKey = keys.get(parsed.provenance.keyId);
312
- const valid = publicKey !== undefined && verify(null, Buffer.from(canonicalJson(unsignedActionApproval(parsed)), "utf8"), publicKey, Buffer.from(parsed.provenance.signature, "base64url"));
459
+ const valid = publicKey !== undefined &&
460
+ verify(null, Buffer.from(canonicalJson(unsignedActionApproval(parsed)), "utf8"), publicKey, Buffer.from(parsed.provenance.signature, "base64url"));
313
461
  if (!valid || parsed.actionHash !== actionHash(action))
314
462
  throw new ActionGatewayError("Action approval authenticity could not be verified.");
315
463
  return { provenance: "verified" };
316
- }
464
+ },
317
465
  };
318
466
  }
319
467
  export function createTestActionApprovalVerifier() {
320
468
  const { privateKey, publicKey } = generateKeyPairSync("ed25519");
321
- const signer = createEd25519ActionApprovalSigner({ keyId: "test-approval-key", privateKey });
469
+ const signer = createEd25519ActionApprovalSigner({
470
+ keyId: "test-approval-key",
471
+ privateKey,
472
+ });
322
473
  return {
323
- verifier: createEd25519ActionApprovalVerifier({ trustedKeys: { "test-approval-key": publicKey } }),
474
+ verifier: createEd25519ActionApprovalVerifier({
475
+ trustedKeys: { "test-approval-key": publicKey },
476
+ }),
324
477
  signer,
325
- issue: (approval) => signer.issue(approval)
478
+ issue: (approval) => signer.issue(approval),
326
479
  };
327
480
  }
328
481
  export function getActionPath(actionId) {
@@ -357,7 +510,8 @@ async function writeStoredAction(path, record) {
357
510
  export function validateStoredAction(value) {
358
511
  const record = object(value, "Action record must be an object.");
359
512
  exactKeys(record, ["schemaVersion", "kind", "envelope", "actionHash", "approval", "receipts"], "Action record");
360
- if (record.schemaVersion !== ACTION_SCHEMA_VERSION || record.kind !== RECORD_KIND)
513
+ if (record.schemaVersion !== ACTION_SCHEMA_VERSION ||
514
+ record.kind !== RECORD_KIND)
361
515
  throw new ActionGatewayError("Unsupported action record schema.");
362
516
  const envelope = validateActionEnvelope(record.envelope);
363
517
  const digest = hash(record.actionHash, "Action record hash");
@@ -366,16 +520,43 @@ export function validateStoredAction(value) {
366
520
  const approval = validateActionApproval(record.approval);
367
521
  verifyApproval(envelope, approval, new Date(0).toISOString(), false);
368
522
  const rawReceipts = record.receipts;
369
- if (!Array.isArray(rawReceipts) || rawReceipts.length < 1 || rawReceipts.length > MAX_RECEIPTS)
523
+ if (!Array.isArray(rawReceipts) ||
524
+ rawReceipts.length < 1 ||
525
+ rawReceipts.length > MAX_RECEIPTS)
370
526
  throw new ActionGatewayError("Action record receipts are invalid.");
371
- const receipts = rawReceipts.map((receipt, index) => validateReceipt(receipt, envelope.actionId, digest, index === 0 ? receiptGenesisHash(digest) : rawReceipts[index - 1].receiptHash));
527
+ const receipts = rawReceipts.map((receipt, index) => validateReceipt(receipt, envelope.actionId, digest, index === 0
528
+ ? receiptGenesisHash(digest)
529
+ : rawReceipts[index - 1].receiptHash));
372
530
  if (receipts[0].status !== "requested")
373
531
  throw new ActionGatewayError("Action record must begin with a requested receipt.");
374
- return { schemaVersion: 1, kind: "ghostapi.action-record", envelope, actionHash: digest, approval, receipts };
532
+ return {
533
+ schemaVersion: 1,
534
+ kind: "ghostapi.action-record",
535
+ envelope,
536
+ actionHash: digest,
537
+ approval,
538
+ receipts,
539
+ };
375
540
  }
376
541
  function createReceipt(record, status, timestamp, providerRequestId, result, failure) {
377
- const previousReceiptHash = record.receipts.length === 0 ? receiptGenesisHash(record.actionHash) : record.receipts.at(-1).receiptHash;
378
- const base = { receiptId: identifier(`receipt-${randomUUID().replace(/-/g, "")}`, "Receipt id"), actionId: record.envelope.actionId, actionHash: record.actionHash, status, timestamp: timestampValue(timestamp, "Action receipt timestamp"), ...(providerRequestId === undefined ? {} : { providerRequestId: identifier(providerRequestId, "Provider request id") }), ...(result === undefined ? {} : { result }), ...(failure === undefined ? {} : { failure }), previousReceiptHash };
542
+ const previousReceiptHash = record.receipts.length === 0
543
+ ? receiptGenesisHash(record.actionHash)
544
+ : record.receipts.at(-1).receiptHash;
545
+ const base = {
546
+ receiptId: identifier(`receipt-${randomUUID().replace(/-/g, "")}`, "Receipt id"),
547
+ actionId: record.envelope.actionId,
548
+ actionHash: record.actionHash,
549
+ status,
550
+ timestamp: timestampValue(timestamp, "Action receipt timestamp"),
551
+ ...(providerRequestId === undefined
552
+ ? {}
553
+ : {
554
+ providerRequestId: identifier(providerRequestId, "Provider request id"),
555
+ }),
556
+ ...(result === undefined ? {} : { result }),
557
+ ...(failure === undefined ? {} : { failure }),
558
+ previousReceiptHash,
559
+ };
379
560
  return { ...base, receiptHash: sha256(canonicalJson(base)) };
380
561
  }
381
562
  function appendReceipt(record, receipt) {
@@ -385,38 +566,80 @@ function appendReceipt(record, receipt) {
385
566
  }
386
567
  function validateReceipt(value, actionId, actionDigest, previousReceiptHash) {
387
568
  const receipt = object(value, "Action receipt is invalid.");
388
- exactKeys(receipt, ["receiptId", "actionId", "actionHash", "status", "timestamp", "providerRequestId", "result", "failure", "previousReceiptHash", "receiptHash"], "Action receipt");
389
- if (receipt.actionId !== actionId || receipt.actionHash !== actionDigest || receipt.previousReceiptHash !== previousReceiptHash || (receipt.status !== "requested" && receipt.status !== "attempted" && receipt.status !== "committed" && receipt.status !== "verified" && receipt.status !== "failed"))
569
+ exactKeys(receipt, [
570
+ "receiptId",
571
+ "actionId",
572
+ "actionHash",
573
+ "status",
574
+ "timestamp",
575
+ "providerRequestId",
576
+ "result",
577
+ "failure",
578
+ "previousReceiptHash",
579
+ "receiptHash",
580
+ ], "Action receipt");
581
+ if (receipt.actionId !== actionId ||
582
+ receipt.actionHash !== actionDigest ||
583
+ receipt.previousReceiptHash !== previousReceiptHash ||
584
+ (receipt.status !== "requested" &&
585
+ receipt.status !== "attempted" &&
586
+ receipt.status !== "committed" &&
587
+ receipt.status !== "verified" &&
588
+ receipt.status !== "failed"))
390
589
  throw new ActionGatewayError("Action receipt linkage is invalid.");
391
- const providerRequestId = receipt.providerRequestId === undefined ? undefined : identifier(receipt.providerRequestId, "Provider request id");
590
+ const providerRequestId = receipt.providerRequestId === undefined
591
+ ? undefined
592
+ : identifier(receipt.providerRequestId, "Provider request id");
392
593
  const result = receipt.result === undefined ? undefined : normalizeResult(receipt.result);
393
- const failure = receipt.failure === undefined ? undefined : normalizeFailure(receipt.failure);
394
- if ((receipt.status === "committed" || receipt.status === "verified") && (providerRequestId === undefined || result === undefined))
594
+ const failure = receipt.failure === undefined
595
+ ? undefined
596
+ : normalizeFailure(receipt.failure);
597
+ if ((receipt.status === "committed" || receipt.status === "verified") &&
598
+ (providerRequestId === undefined || result === undefined))
395
599
  throw new ActionGatewayError("Committed and verified receipts require a provider result.");
396
600
  if (receipt.status === "failed" && failure === undefined)
397
601
  throw new ActionGatewayError("Failed receipt requires a failure classification.");
398
602
  const status = receipt.status;
399
- const base = { receiptId: identifier(receipt.receiptId, "Receipt id"), actionId, actionHash: actionDigest, status, timestamp: timestampValue(receipt.timestamp, "Action receipt timestamp"), ...(providerRequestId === undefined ? {} : { providerRequestId }), ...(result === undefined ? {} : { result }), ...(failure === undefined ? {} : { failure }), previousReceiptHash };
400
- if (hash(receipt.receiptHash, "Action receipt hash") !== sha256(canonicalJson(base)))
603
+ const base = {
604
+ receiptId: identifier(receipt.receiptId, "Receipt id"),
605
+ actionId,
606
+ actionHash: actionDigest,
607
+ status,
608
+ timestamp: timestampValue(receipt.timestamp, "Action receipt timestamp"),
609
+ ...(providerRequestId === undefined ? {} : { providerRequestId }),
610
+ ...(result === undefined ? {} : { result }),
611
+ ...(failure === undefined ? {} : { failure }),
612
+ previousReceiptHash,
613
+ };
614
+ if (hash(receipt.receiptHash, "Action receipt hash") !==
615
+ sha256(canonicalJson(base)))
401
616
  throw new ActionGatewayError("Action receipt hash is invalid.");
402
617
  return { ...base, receiptHash: receipt.receiptHash };
403
618
  }
404
619
  function normalizeResult(value) {
405
620
  const result = object(value, "Action receipt result is invalid.");
406
621
  exactKeys(result, ["worldId", "subscriptionId", "syntheticReceiptId"], "Action receipt result");
407
- return { worldId: identifier(result.worldId, "Action receipt world id"), subscriptionId: identifier(result.subscriptionId, "Action receipt subscription id"), syntheticReceiptId: identifier(result.syntheticReceiptId, "Action receipt synthetic receipt id") };
622
+ return {
623
+ worldId: identifier(result.worldId, "Action receipt world id"),
624
+ subscriptionId: identifier(result.subscriptionId, "Action receipt subscription id"),
625
+ syntheticReceiptId: identifier(result.syntheticReceiptId, "Action receipt synthetic receipt id"),
626
+ };
408
627
  }
409
628
  function normalizeFailure(value) {
410
629
  const failure = object(value, "Action receipt failure is invalid.");
411
630
  exactKeys(failure, ["code", "retrySafe"], "Action receipt failure");
412
- if ((failure.code !== "execution_error" && failure.code !== "unknown_outcome" && failure.code !== "verification_failed") || failure.retrySafe !== false)
631
+ if ((failure.code !== "execution_error" &&
632
+ failure.code !== "unknown_outcome" &&
633
+ failure.code !== "verification_failed") ||
634
+ failure.retrySafe !== false)
413
635
  throw new ActionGatewayError("Action receipt failure is invalid.");
414
636
  return { code: failure.code, retrySafe: false };
415
637
  }
416
638
  function verifyApproval(action, approval, now, enforceExpiry = true) {
417
639
  if (approval.actionHash !== actionHash(action))
418
640
  throw new ActionGatewayError("Action approval is not bound to the exact canonical action hash.");
419
- if (approval.approvedBy === action.actor.id || approval.approvedBy === action.actor.workloadId)
641
+ if (approval.approvedBy === action.actor.id ||
642
+ approval.approvedBy === action.actor.workloadId)
420
643
  throw new ActionGatewayError("An action actor cannot approve its own high-risk action.");
421
644
  if (enforceExpiry && Date.parse(approval.approvedAt) > Date.parse(now))
422
645
  throw new ActionGatewayError("Action approval timestamp is in the future.");
@@ -426,7 +649,8 @@ function verifyApproval(action, approval, now, enforceExpiry = true) {
426
649
  function verifyPolicy(action, policy) {
427
650
  if (!policy.allowed)
428
651
  throw new ActionGatewayError("Current policy denies action execution.");
429
- if (policy.version !== action.policy.version || policy.hash !== action.policy.hash)
652
+ if (policy.version !== action.policy.version ||
653
+ policy.hash !== action.policy.hash)
430
654
  throw new ActionGatewayError("Current policy reference does not match the approved action.");
431
655
  }
432
656
  function receiptGenesisHash(actionDigest) {
@@ -451,7 +675,9 @@ function exactKeys(value, keys, label) {
451
675
  throw new ActionGatewayError(`${label} contains unsupported field: ${key}`);
452
676
  }
453
677
  function identifier(value, label) {
454
- if (typeof value !== "string" || !IDENTIFIER.test(value) || sanitizeSecretString(value) !== value)
678
+ if (typeof value !== "string" ||
679
+ !IDENTIFIER.test(value) ||
680
+ sanitizeSecretString(value) !== value)
455
681
  throw new ActionGatewayError(`${label} must be a safe stable identifier.`);
456
682
  return value;
457
683
  }
@@ -469,7 +695,9 @@ function timestamp(value, label) {
469
695
  return timestampValue(value, label);
470
696
  }
471
697
  function timestampValue(value, label) {
472
- 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)))
698
+ if (typeof value !== "string" ||
699
+ !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(value) ||
700
+ !Number.isFinite(Date.parse(value)))
473
701
  throw new ActionGatewayError(`${label} must be an ISO UTC timestamp.`);
474
702
  return value;
475
703
  }
@@ -488,18 +716,38 @@ function canonicalJson(value) {
488
716
  return `[${value.map(canonicalJson).join(",")}]`;
489
717
  if (typeof value === "object") {
490
718
  const record = value;
491
- return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`).join(",")}}`;
719
+ return `{${Object.keys(record)
720
+ .sort()
721
+ .map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`)
722
+ .join(",")}}`;
492
723
  }
493
724
  throw new ActionGatewayError("Canonical action values must be JSON data.");
494
725
  }
495
726
  function requireApprovalInboxVerification(approval, verification) {
496
- if (approval.approvedBy === "approval-inbox" && verification.approvalInboxRequestId !== approval.approvalId)
727
+ if (approval.approvedBy === "approval-inbox" &&
728
+ verification.approvalInboxRequestId !== approval.approvalId)
497
729
  throw new ActionGatewayError("Approval inbox provenance is not active for execution.");
498
730
  }
499
731
  function validateUnsignedActionApproval(value) {
500
732
  const approval = object(value, "Action approval must be an object.");
501
- exactKeys(approval, ["schemaVersion", "kind", "approvalId", "actionHash", "approvedBy", "approvedAt", "expiresAt", "nonce"], "Unsigned action approval");
502
- return unsignedActionApproval(validateActionApproval({ ...approval, provenance: { kind: "ghostapi.ed25519-signature", keyId: "validation-key", signature: "A".repeat(86) } }));
733
+ exactKeys(approval, [
734
+ "schemaVersion",
735
+ "kind",
736
+ "approvalId",
737
+ "actionHash",
738
+ "approvedBy",
739
+ "approvedAt",
740
+ "expiresAt",
741
+ "nonce",
742
+ ], "Unsigned action approval");
743
+ return unsignedActionApproval(validateActionApproval({
744
+ ...approval,
745
+ provenance: {
746
+ kind: "ghostapi.ed25519-signature",
747
+ keyId: "validation-key",
748
+ signature: "A".repeat(86),
749
+ },
750
+ }));
503
751
  }
504
752
  function unsignedActionApproval(approval) {
505
753
  const { provenance: _provenance, ...unsigned } = approval;