@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
@@ -2,8 +2,8 @@ import { createHash } 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 { isSecretFieldName, sanitizeSecretString } from "../security/secrets.js";
6
- import { atomicWriteJson, ensurePrivateDirectory, withFileLock } from "../storage/fileStore.js";
5
+ import { isSecretFieldName, sanitizeSecretString, } from "../security/secrets.js";
6
+ import { atomicWriteJson, ensurePrivateDirectory, withFileLock, } from "../storage/fileStore.js";
7
7
  const WORLD_SCHEMA_VERSION = 1;
8
8
  const WORLD_KIND = "ghostapi.world";
9
9
  const WORLD_VERSION = "1.0.0";
@@ -35,22 +35,62 @@ export function createSyntheticWorld(options) {
35
35
  version: WORLD_VERSION,
36
36
  seed,
37
37
  clock: { initialAt, timezone: "UTC" },
38
- personas: [{ id: personaId, displayName: `Operator ${suffix.toUpperCase()}`, handle: `operator-${suffix}` }],
39
- organizations: [{ id: organizationId, name: `Sandbox ${suffix.toUpperCase()}`, slug }],
38
+ personas: [
39
+ {
40
+ id: personaId,
41
+ displayName: `Operator ${suffix.toUpperCase()}`,
42
+ handle: `operator-${suffix}`,
43
+ },
44
+ ],
45
+ organizations: [
46
+ { id: organizationId, name: `Sandbox ${suffix.toUpperCase()}`, slug },
47
+ ],
40
48
  accounts: {
41
- stripe: { accountId: `acct_world_${suffix}`, customerPrefix: `cus_world_${suffix}` },
42
- github: { userLogin: `world-${suffix}`, owner: `sandbox-${suffix}`, repository: `integration-${suffix}` },
43
- email: { domain: "ghostapi.invalid", inboxAddress: `operator-${suffix}@ghostapi.invalid` },
44
- genericRest: { tenantId: `tenant_world_${suffix}`, basePath: `/tenants/tenant_world_${suffix}` }
49
+ stripe: {
50
+ accountId: `acct_world_${suffix}`,
51
+ customerPrefix: `cus_world_${suffix}`,
52
+ },
53
+ github: {
54
+ userLogin: `world-${suffix}`,
55
+ owner: `sandbox-${suffix}`,
56
+ repository: `integration-${suffix}`,
57
+ },
58
+ email: {
59
+ domain: "ghostapi.invalid",
60
+ inboxAddress: `operator-${suffix}@ghostapi.invalid`,
61
+ },
62
+ genericRest: {
63
+ tenantId: `tenant_world_${suffix}`,
64
+ basePath: `/tenants/tenant_world_${suffix}`,
65
+ },
66
+ },
67
+ resources: {
68
+ defaultPriceId: `price_world_${suffix}`,
69
+ recoveryIssueLabel: "payment-recovery",
45
70
  },
46
- resources: { defaultPriceId: `price_world_${suffix}`, recoveryIssueLabel: "payment-recovery" },
47
71
  relationships: [{ personaId, organizationId, role: "owner" }],
48
72
  providerProjections: {
49
- stripe: { personaId, organizationId, customerPrefix: `cus_world_${suffix}` },
50
- github: { personaId, organizationId, repository: `sandbox-${suffix}/integration-${suffix}` },
51
- email: { personaId, organizationId, inboxAddress: `operator-${suffix}@ghostapi.invalid` },
52
- genericRest: { personaId, organizationId, tenantId: `tenant_world_${suffix}` }
53
- }
73
+ stripe: {
74
+ personaId,
75
+ organizationId,
76
+ customerPrefix: `cus_world_${suffix}`,
77
+ },
78
+ github: {
79
+ personaId,
80
+ organizationId,
81
+ repository: `sandbox-${suffix}/integration-${suffix}`,
82
+ },
83
+ email: {
84
+ personaId,
85
+ organizationId,
86
+ inboxAddress: `operator-${suffix}@ghostapi.invalid`,
87
+ },
88
+ genericRest: {
89
+ personaId,
90
+ organizationId,
91
+ tenantId: `tenant_world_${suffix}`,
92
+ },
93
+ },
54
94
  };
55
95
  const initialState = {
56
96
  clockAt: initialAt,
@@ -58,9 +98,16 @@ export function createSyntheticWorld(options) {
58
98
  github: { issues: [] },
59
99
  email: { messages: [] },
60
100
  genericRest: { failures: [] },
61
- receipts: []
101
+ receipts: [],
62
102
  };
63
- return validateSyntheticWorld({ schemaVersion: WORLD_SCHEMA_VERSION, kind: WORLD_KIND, manifest, revision: 0, baseline: initialState, state: initialState });
103
+ return validateSyntheticWorld({
104
+ schemaVersion: WORLD_SCHEMA_VERSION,
105
+ kind: WORLD_KIND,
106
+ manifest,
107
+ revision: 0,
108
+ baseline: initialState,
109
+ state: initialState,
110
+ });
64
111
  }
65
112
  export async function createWorld(options) {
66
113
  const world = createSyntheticWorld(options);
@@ -81,7 +128,7 @@ export async function resetWorld(id) {
81
128
  return mutateWorld(normalizeIdentifier(id, "World id"), (world) => ({
82
129
  ...world,
83
130
  revision: world.revision + 1,
84
- state: structuredClone(world.baseline)
131
+ state: structuredClone(world.baseline),
85
132
  }));
86
133
  }
87
134
  export async function forkWorld(sourceId, options) {
@@ -92,7 +139,10 @@ export async function forkWorld(sourceId, options) {
92
139
  const fork = structuredClone(source);
93
140
  fork.manifest.id = id;
94
141
  fork.manifest.title = title;
95
- fork.manifest.lineage = { parentId: source.manifest.id, parentRevision: source.revision };
142
+ fork.manifest.lineage = {
143
+ parentId: source.manifest.id,
144
+ parentRevision: source.revision,
145
+ };
96
146
  fork.revision = 0;
97
147
  fork.baseline = structuredClone(source.state);
98
148
  fork.state = structuredClone(source.state);
@@ -131,18 +181,65 @@ export async function runSubscriptionFailureWorkflow(id, actionId, commit) {
131
181
  emailId,
132
182
  issueNumber: world.state.github.issues.length + 1,
133
183
  genericFailureId,
134
- completedAt: nextTimestamp(world.state.clockAt)
184
+ completedAt: nextTimestamp(world.state.clockAt),
135
185
  };
136
186
  const nextState = {
137
187
  clockAt: receipt.completedAt,
138
188
  stripe: {
139
- customers: [...world.state.stripe.customers, { id: customerId, personaId: persona.id, organizationId: organization.id }],
140
- subscriptions: [...world.state.stripe.subscriptions, { id: subscriptionId, customerId, priceId: world.manifest.resources.defaultPriceId, status: "past_due", failureCode: "card_declined" }]
189
+ customers: [
190
+ ...world.state.stripe.customers,
191
+ {
192
+ id: customerId,
193
+ personaId: persona.id,
194
+ organizationId: organization.id,
195
+ },
196
+ ],
197
+ subscriptions: [
198
+ ...world.state.stripe.subscriptions,
199
+ {
200
+ id: subscriptionId,
201
+ customerId,
202
+ priceId: world.manifest.resources.defaultPriceId,
203
+ status: "past_due",
204
+ failureCode: "card_declined",
205
+ },
206
+ ],
207
+ },
208
+ github: {
209
+ issues: [
210
+ ...world.state.github.issues,
211
+ {
212
+ number: receipt.issueNumber,
213
+ repository: world.manifest.providerProjections.github.repository,
214
+ title: `Recover ${subscriptionId}`,
215
+ status: "open",
216
+ relatedSubscriptionId: subscriptionId,
217
+ },
218
+ ],
141
219
  },
142
- github: { issues: [...world.state.github.issues, { number: receipt.issueNumber, repository: world.manifest.providerProjections.github.repository, title: `Recover ${subscriptionId}`, status: "open", relatedSubscriptionId: subscriptionId }] },
143
- email: { messages: [...world.state.email.messages, { id: emailId, to: world.manifest.accounts.email.inboxAddress, template: "subscription-payment-failed", relatedSubscriptionId: subscriptionId }] },
144
- genericRest: { failures: [...world.state.genericRest.failures, { id: genericFailureId, tenantId: world.manifest.accounts.genericRest.tenantId, code: "payment_failed", relatedSubscriptionId: subscriptionId }] },
145
- receipts: [...world.state.receipts, receipt]
220
+ email: {
221
+ messages: [
222
+ ...world.state.email.messages,
223
+ {
224
+ id: emailId,
225
+ to: world.manifest.accounts.email.inboxAddress,
226
+ template: "subscription-payment-failed",
227
+ relatedSubscriptionId: subscriptionId,
228
+ },
229
+ ],
230
+ },
231
+ genericRest: {
232
+ failures: [
233
+ ...world.state.genericRest.failures,
234
+ {
235
+ id: genericFailureId,
236
+ tenantId: world.manifest.accounts.genericRest.tenantId,
237
+ code: "payment_failed",
238
+ relatedSubscriptionId: subscriptionId,
239
+ },
240
+ ],
241
+ },
242
+ receipts: [...world.state.receipts, receipt],
146
243
  };
147
244
  return { ...world, revision: world.revision + 1, state: nextState };
148
245
  }, commit);
@@ -163,7 +260,7 @@ export function formatWorld(world) {
163
260
  `Persona: ${world.manifest.personas[0].id}`,
164
261
  `Organization: ${world.manifest.organizations[0].id}`,
165
262
  `Providers: stripe, github, email, generic-rest`,
166
- `Workflow receipts: ${world.state.receipts.length}`
263
+ `Workflow receipts: ${world.state.receipts.length}`,
167
264
  ].join("\n");
168
265
  }
169
266
  export function validateSyntheticWorld(value) {
@@ -171,12 +268,21 @@ export function validateSyntheticWorld(value) {
171
268
  assertExactKeys(root, ["schemaVersion", "kind", "manifest", "revision", "baseline", "state"], "Synthetic world");
172
269
  if (root.schemaVersion !== WORLD_SCHEMA_VERSION || root.kind !== WORLD_KIND)
173
270
  throw new SyntheticWorldError("Unsupported synthetic world schema.");
174
- if (!Number.isInteger(root.revision) || root.revision < 0 || root.revision > 1_000_000)
271
+ if (!Number.isInteger(root.revision) ||
272
+ root.revision < 0 ||
273
+ root.revision > 1_000_000)
175
274
  throw new SyntheticWorldError("Synthetic world revision is invalid.");
176
275
  const manifest = normalizeManifest(root.manifest);
177
276
  const baseline = normalizeState(root.baseline, manifest);
178
277
  const state = normalizeState(root.state, manifest);
179
- const world = { schemaVersion: 1, kind: "ghostapi.world", manifest, revision: root.revision, baseline, state };
278
+ const world = {
279
+ schemaVersion: 1,
280
+ kind: "ghostapi.world",
281
+ manifest,
282
+ revision: root.revision,
283
+ baseline,
284
+ state,
285
+ };
180
286
  assertWorldSize(world);
181
287
  return world;
182
288
  }
@@ -224,7 +330,20 @@ async function ensureWorldFileIsSafe(path, required) {
224
330
  }
225
331
  function normalizeManifest(value) {
226
332
  const manifest = readObject(value, "Synthetic world manifest is invalid.");
227
- assertExactKeys(manifest, ["id", "title", "version", "seed", "clock", "personas", "organizations", "accounts", "resources", "relationships", "providerProjections", "lineage"], "Synthetic world manifest");
333
+ assertExactKeys(manifest, [
334
+ "id",
335
+ "title",
336
+ "version",
337
+ "seed",
338
+ "clock",
339
+ "personas",
340
+ "organizations",
341
+ "accounts",
342
+ "resources",
343
+ "relationships",
344
+ "providerProjections",
345
+ "lineage",
346
+ ], "Synthetic world manifest");
228
347
  const id = normalizeIdentifier(manifest.id, "World id");
229
348
  const title = normalizeTitle(manifest.title);
230
349
  if (manifest.version !== WORLD_VERSION)
@@ -240,22 +359,49 @@ function normalizeManifest(value) {
240
359
  const resources = normalizeResources(manifest.resources);
241
360
  const relationships = normalizeRelationships(manifest.relationships, personas[0].id, organizations[0].id);
242
361
  const providerProjections = normalizeProjections(manifest.providerProjections, personas[0].id, organizations[0].id, accounts);
243
- const lineage = manifest.lineage === undefined ? undefined : normalizeLineage(manifest.lineage);
244
- return { id, title, version: WORLD_VERSION, seed, clock: { initialAt: clock.initialAt, timezone: "UTC" }, personas, organizations, accounts, resources, relationships, providerProjections, ...(lineage === undefined ? {} : { lineage }) };
362
+ const lineage = manifest.lineage === undefined
363
+ ? undefined
364
+ : normalizeLineage(manifest.lineage);
365
+ return {
366
+ id,
367
+ title,
368
+ version: WORLD_VERSION,
369
+ seed,
370
+ clock: { initialAt: clock.initialAt, timezone: "UTC" },
371
+ personas,
372
+ organizations,
373
+ accounts,
374
+ resources,
375
+ relationships,
376
+ providerProjections,
377
+ ...(lineage === undefined ? {} : { lineage }),
378
+ };
245
379
  }
246
380
  function normalizePersonas(value) {
247
381
  if (!Array.isArray(value) || value.length !== 1)
248
382
  throw new SyntheticWorldError("Synthetic world requires exactly one synthetic persona.");
249
383
  const persona = readObject(value[0], "Synthetic persona is invalid.");
250
384
  assertExactKeys(persona, ["id", "displayName", "handle"], "Synthetic persona");
251
- return [{ id: normalizeIdentifier(persona.id, "Synthetic persona id"), displayName: normalizeSafeText(persona.displayName, "Synthetic persona displayName", 80), handle: normalizeIdentifier(persona.handle, "Synthetic persona handle") }];
385
+ return [
386
+ {
387
+ id: normalizeIdentifier(persona.id, "Synthetic persona id"),
388
+ displayName: normalizeSafeText(persona.displayName, "Synthetic persona displayName", 80),
389
+ handle: normalizeIdentifier(persona.handle, "Synthetic persona handle"),
390
+ },
391
+ ];
252
392
  }
253
393
  function normalizeOrganizations(value) {
254
394
  if (!Array.isArray(value) || value.length !== 1)
255
395
  throw new SyntheticWorldError("Synthetic world requires exactly one synthetic organization.");
256
396
  const organization = readObject(value[0], "Synthetic organization is invalid.");
257
397
  assertExactKeys(organization, ["id", "name", "slug"], "Synthetic organization");
258
- return [{ id: normalizeIdentifier(organization.id, "Synthetic organization id"), name: normalizeSafeText(organization.name, "Synthetic organization name", 80), slug: normalizeIdentifier(organization.slug, "Synthetic organization slug") }];
398
+ return [
399
+ {
400
+ id: normalizeIdentifier(organization.id, "Synthetic organization id"),
401
+ name: normalizeSafeText(organization.name, "Synthetic organization name", 80),
402
+ slug: normalizeIdentifier(organization.slug, "Synthetic organization slug"),
403
+ },
404
+ ];
259
405
  }
260
406
  function normalizeAccounts(value) {
261
407
  const accounts = readObject(value, "Synthetic world accounts are invalid.");
@@ -273,23 +419,38 @@ function normalizeAccounts(value) {
273
419
  if (domain !== "ghostapi.invalid")
274
420
  throw new SyntheticWorldError("Synthetic world email addresses must use ghostapi.invalid.");
275
421
  return {
276
- stripe: { accountId: normalizeIdentifier(stripe.accountId, "Synthetic Stripe account id"), customerPrefix: normalizeIdentifier(stripe.customerPrefix, "Synthetic Stripe customer prefix") },
277
- github: { userLogin: normalizeIdentifier(github.userLogin, "Synthetic GitHub login"), owner: normalizeIdentifier(github.owner, "Synthetic GitHub owner"), repository: normalizeIdentifier(github.repository, "Synthetic GitHub repository") },
422
+ stripe: {
423
+ accountId: normalizeIdentifier(stripe.accountId, "Synthetic Stripe account id"),
424
+ customerPrefix: normalizeIdentifier(stripe.customerPrefix, "Synthetic Stripe customer prefix"),
425
+ },
426
+ github: {
427
+ userLogin: normalizeIdentifier(github.userLogin, "Synthetic GitHub login"),
428
+ owner: normalizeIdentifier(github.owner, "Synthetic GitHub owner"),
429
+ repository: normalizeIdentifier(github.repository, "Synthetic GitHub repository"),
430
+ },
278
431
  email: { domain, inboxAddress },
279
- genericRest: { tenantId: normalizeIdentifier(genericRest.tenantId, "Synthetic tenant id"), basePath: normalizeBasePath(genericRest.basePath) }
432
+ genericRest: {
433
+ tenantId: normalizeIdentifier(genericRest.tenantId, "Synthetic tenant id"),
434
+ basePath: normalizeBasePath(genericRest.basePath),
435
+ },
280
436
  };
281
437
  }
282
438
  function normalizeResources(value) {
283
439
  const resources = readObject(value, "Synthetic world resources are invalid.");
284
440
  assertExactKeys(resources, ["defaultPriceId", "recoveryIssueLabel"], "Synthetic world resources");
285
- return { defaultPriceId: normalizeIdentifier(resources.defaultPriceId, "Synthetic price id"), recoveryIssueLabel: normalizeIdentifier(resources.recoveryIssueLabel, "Synthetic issue label") };
441
+ return {
442
+ defaultPriceId: normalizeIdentifier(resources.defaultPriceId, "Synthetic price id"),
443
+ recoveryIssueLabel: normalizeIdentifier(resources.recoveryIssueLabel, "Synthetic issue label"),
444
+ };
286
445
  }
287
446
  function normalizeRelationships(value, personaId, organizationId) {
288
447
  if (!Array.isArray(value) || value.length !== 1)
289
448
  throw new SyntheticWorldError("Synthetic world requires one persona-to-organization relationship.");
290
449
  const relationship = readObject(value[0], "Synthetic world relationship is invalid.");
291
450
  assertExactKeys(relationship, ["personaId", "organizationId", "role"], "Synthetic world relationship");
292
- if (relationship.personaId !== personaId || relationship.organizationId !== organizationId || relationship.role !== "owner")
451
+ if (relationship.personaId !== personaId ||
452
+ relationship.organizationId !== organizationId ||
453
+ relationship.role !== "owner")
293
454
  throw new SyntheticWorldError("Synthetic world relationship does not match canonical identity.");
294
455
  return [{ personaId, organizationId, role: "owner" }];
295
456
  }
@@ -304,17 +465,54 @@ function normalizeProjections(value, personaId, organizationId, accounts) {
304
465
  assertExactKeys(github, ["personaId", "organizationId", "repository"], "Synthetic GitHub projection");
305
466
  assertExactKeys(email, ["personaId", "organizationId", "inboxAddress"], "Synthetic email projection");
306
467
  assertExactKeys(genericRest, ["personaId", "organizationId", "tenantId"], "Synthetic generic REST projection");
307
- if (stripe.personaId !== personaId || stripe.organizationId !== organizationId || stripe.customerPrefix !== accounts.stripe.customerPrefix || github.personaId !== personaId || github.organizationId !== organizationId || github.repository !== `${accounts.github.owner}/${accounts.github.repository}` || email.personaId !== personaId || email.organizationId !== organizationId || email.inboxAddress !== accounts.email.inboxAddress || genericRest.personaId !== personaId || genericRest.organizationId !== organizationId || genericRest.tenantId !== accounts.genericRest.tenantId) {
468
+ if (stripe.personaId !== personaId ||
469
+ stripe.organizationId !== organizationId ||
470
+ stripe.customerPrefix !== accounts.stripe.customerPrefix ||
471
+ github.personaId !== personaId ||
472
+ github.organizationId !== organizationId ||
473
+ github.repository !==
474
+ `${accounts.github.owner}/${accounts.github.repository}` ||
475
+ email.personaId !== personaId ||
476
+ email.organizationId !== organizationId ||
477
+ email.inboxAddress !== accounts.email.inboxAddress ||
478
+ genericRest.personaId !== personaId ||
479
+ genericRest.organizationId !== organizationId ||
480
+ genericRest.tenantId !== accounts.genericRest.tenantId) {
308
481
  throw new SyntheticWorldError("Provider projections do not match canonical synthetic identity.");
309
482
  }
310
- return { stripe: { personaId, organizationId, customerPrefix: accounts.stripe.customerPrefix }, github: { personaId, organizationId, repository: `${accounts.github.owner}/${accounts.github.repository}` }, email: { personaId, organizationId, inboxAddress: accounts.email.inboxAddress }, genericRest: { personaId, organizationId, tenantId: accounts.genericRest.tenantId } };
483
+ return {
484
+ stripe: {
485
+ personaId,
486
+ organizationId,
487
+ customerPrefix: accounts.stripe.customerPrefix,
488
+ },
489
+ github: {
490
+ personaId,
491
+ organizationId,
492
+ repository: `${accounts.github.owner}/${accounts.github.repository}`,
493
+ },
494
+ email: {
495
+ personaId,
496
+ organizationId,
497
+ inboxAddress: accounts.email.inboxAddress,
498
+ },
499
+ genericRest: {
500
+ personaId,
501
+ organizationId,
502
+ tenantId: accounts.genericRest.tenantId,
503
+ },
504
+ };
311
505
  }
312
506
  function normalizeLineage(value) {
313
507
  const lineage = readObject(value, "Synthetic world lineage is invalid.");
314
508
  assertExactKeys(lineage, ["parentId", "parentRevision"], "Synthetic world lineage");
315
- if (!Number.isInteger(lineage.parentRevision) || lineage.parentRevision < 0)
509
+ if (!Number.isInteger(lineage.parentRevision) ||
510
+ lineage.parentRevision < 0)
316
511
  throw new SyntheticWorldError("Synthetic world lineage revision is invalid.");
317
- return { parentId: normalizeIdentifier(lineage.parentId, "Synthetic world parent id"), parentRevision: lineage.parentRevision };
512
+ return {
513
+ parentId: normalizeIdentifier(lineage.parentId, "Synthetic world parent id"),
514
+ parentRevision: lineage.parentRevision,
515
+ };
318
516
  }
319
517
  function normalizeState(value, manifest) {
320
518
  const state = readObject(value, "Synthetic world state is invalid.");
@@ -335,9 +533,19 @@ function normalizeState(value, manifest) {
335
533
  const messages = normalizeMessages(email.messages, subscriptions, manifest);
336
534
  const failures = normalizeFailures(genericRest.failures, subscriptions, manifest);
337
535
  const receipts = normalizeReceipts(state.receipts, customers, subscriptions, messages, issues, failures, manifest);
338
- if (customers.length !== subscriptions.length || customers.length !== issues.length || customers.length !== messages.length || customers.length !== failures.length || customers.length !== receipts.length)
536
+ if (customers.length !== subscriptions.length ||
537
+ customers.length !== issues.length ||
538
+ customers.length !== messages.length ||
539
+ customers.length !== failures.length ||
540
+ customers.length !== receipts.length)
339
541
  throw new SyntheticWorldError("Synthetic world providers have inconsistent workflow state.");
340
- if (new Set(customers.map((customer) => customer.id)).size !== customers.length || new Set(subscriptions.map((subscription) => subscription.id)).size !== subscriptions.length || new Set(messages.map((message) => message.id)).size !== messages.length || new Set(issues.map((issue) => issue.number)).size !== issues.length || new Set(failures.map((failure) => failure.id)).size !== failures.length) {
542
+ if (new Set(customers.map((customer) => customer.id)).size !==
543
+ customers.length ||
544
+ new Set(subscriptions.map((subscription) => subscription.id)).size !==
545
+ subscriptions.length ||
546
+ new Set(messages.map((message) => message.id)).size !== messages.length ||
547
+ new Set(issues.map((issue) => issue.number)).size !== issues.length ||
548
+ new Set(failures.map((failure) => failure.id)).size !== failures.length) {
341
549
  throw new SyntheticWorldError("Synthetic world contains duplicate provider resources.");
342
550
  }
343
551
  const subscriptionsById = new Map(subscriptions.map((subscription) => [subscription.id, subscription]));
@@ -346,19 +554,37 @@ function normalizeState(value, manifest) {
346
554
  const failuresById = new Map(failures.map((failure) => [failure.id, failure]));
347
555
  for (const receipt of receipts) {
348
556
  const subscription = subscriptionsById.get(receipt.subscriptionId);
349
- if (subscription?.customerId !== receipt.customerId || messagesById.get(receipt.emailId)?.relatedSubscriptionId !== receipt.subscriptionId || issuesByNumber.get(receipt.issueNumber)?.relatedSubscriptionId !== receipt.subscriptionId || failuresById.get(receipt.genericFailureId)?.relatedSubscriptionId !== receipt.subscriptionId) {
557
+ if (subscription?.customerId !== receipt.customerId ||
558
+ messagesById.get(receipt.emailId)?.relatedSubscriptionId !==
559
+ receipt.subscriptionId ||
560
+ issuesByNumber.get(receipt.issueNumber)?.relatedSubscriptionId !==
561
+ receipt.subscriptionId ||
562
+ failuresById.get(receipt.genericFailureId)?.relatedSubscriptionId !==
563
+ receipt.subscriptionId) {
350
564
  throw new SyntheticWorldError("Synthetic workflow receipt does not link one consistent cross-provider transition.");
351
565
  }
352
566
  }
353
- return { clockAt: state.clockAt, stripe: { customers, subscriptions }, github: { issues }, email: { messages }, genericRest: { failures }, receipts };
567
+ return {
568
+ clockAt: state.clockAt,
569
+ stripe: { customers, subscriptions },
570
+ github: { issues },
571
+ email: { messages },
572
+ genericRest: { failures },
573
+ receipts,
574
+ };
354
575
  }
355
576
  function normalizeCustomers(value, manifest) {
356
577
  return readArray(value, "Synthetic Stripe customers", MAX_WORKFLOW_RECEIPTS).map((entry) => {
357
578
  const customer = readObject(entry, "Synthetic Stripe customer is invalid.");
358
579
  assertExactKeys(customer, ["id", "personaId", "organizationId"], "Synthetic Stripe customer");
359
- if (customer.personaId !== manifest.personas[0].id || customer.organizationId !== manifest.organizations[0].id)
580
+ if (customer.personaId !== manifest.personas[0].id ||
581
+ customer.organizationId !== manifest.organizations[0].id)
360
582
  throw new SyntheticWorldError("Synthetic Stripe customer identity is inconsistent.");
361
- return { id: normalizeIdentifier(customer.id, "Synthetic Stripe customer id"), personaId: manifest.personas[0].id, organizationId: manifest.organizations[0].id };
583
+ return {
584
+ id: normalizeIdentifier(customer.id, "Synthetic Stripe customer id"),
585
+ personaId: manifest.personas[0].id,
586
+ organizationId: manifest.organizations[0].id,
587
+ };
362
588
  });
363
589
  }
364
590
  function normalizeSubscriptions(value, customers, manifest) {
@@ -366,9 +592,18 @@ function normalizeSubscriptions(value, customers, manifest) {
366
592
  return readArray(value, "Synthetic Stripe subscriptions", MAX_WORKFLOW_RECEIPTS).map((entry) => {
367
593
  const subscription = readObject(entry, "Synthetic Stripe subscription is invalid.");
368
594
  assertExactKeys(subscription, ["id", "customerId", "priceId", "status", "failureCode"], "Synthetic Stripe subscription");
369
- if (!customerIds.has(subscription.customerId) || subscription.priceId !== manifest.resources.defaultPriceId || subscription.status !== "past_due" || subscription.failureCode !== "card_declined")
595
+ if (!customerIds.has(subscription.customerId) ||
596
+ subscription.priceId !== manifest.resources.defaultPriceId ||
597
+ subscription.status !== "past_due" ||
598
+ subscription.failureCode !== "card_declined")
370
599
  throw new SyntheticWorldError("Synthetic Stripe subscription is inconsistent.");
371
- return { id: normalizeIdentifier(subscription.id, "Synthetic Stripe subscription id"), customerId: subscription.customerId, priceId: manifest.resources.defaultPriceId, status: "past_due", failureCode: "card_declined" };
600
+ return {
601
+ id: normalizeIdentifier(subscription.id, "Synthetic Stripe subscription id"),
602
+ customerId: subscription.customerId,
603
+ priceId: manifest.resources.defaultPriceId,
604
+ status: "past_due",
605
+ failureCode: "card_declined",
606
+ };
372
607
  });
373
608
  }
374
609
  function normalizeIssues(value, subscriptions, manifest) {
@@ -376,9 +611,20 @@ function normalizeIssues(value, subscriptions, manifest) {
376
611
  return readArray(value, "Synthetic GitHub issues", MAX_WORKFLOW_RECEIPTS).map((entry) => {
377
612
  const issue = readObject(entry, "Synthetic GitHub issue is invalid.");
378
613
  assertExactKeys(issue, ["number", "repository", "title", "status", "relatedSubscriptionId"], "Synthetic GitHub issue");
379
- if (!Number.isInteger(issue.number) || issue.number < 1 || issue.repository !== manifest.providerProjections.github.repository || typeof issue.title !== "string" || issue.status !== "open" || !subscriptionsById.has(issue.relatedSubscriptionId))
614
+ if (!Number.isInteger(issue.number) ||
615
+ issue.number < 1 ||
616
+ issue.repository !== manifest.providerProjections.github.repository ||
617
+ typeof issue.title !== "string" ||
618
+ issue.status !== "open" ||
619
+ !subscriptionsById.has(issue.relatedSubscriptionId))
380
620
  throw new SyntheticWorldError("Synthetic GitHub issue is inconsistent.");
381
- return { number: issue.number, repository: manifest.providerProjections.github.repository, title: normalizeSafeText(issue.title, "Synthetic GitHub issue title", 160), status: "open", relatedSubscriptionId: issue.relatedSubscriptionId };
621
+ return {
622
+ number: issue.number,
623
+ repository: manifest.providerProjections.github.repository,
624
+ title: normalizeSafeText(issue.title, "Synthetic GitHub issue title", 160),
625
+ status: "open",
626
+ relatedSubscriptionId: issue.relatedSubscriptionId,
627
+ };
382
628
  });
383
629
  }
384
630
  function normalizeMessages(value, subscriptions, manifest) {
@@ -386,9 +632,16 @@ function normalizeMessages(value, subscriptions, manifest) {
386
632
  return readArray(value, "Synthetic email messages", MAX_WORKFLOW_RECEIPTS).map((entry) => {
387
633
  const message = readObject(entry, "Synthetic email message is invalid.");
388
634
  assertExactKeys(message, ["id", "to", "template", "relatedSubscriptionId"], "Synthetic email message");
389
- if (message.to !== manifest.accounts.email.inboxAddress || message.template !== "subscription-payment-failed" || !subscriptionsById.has(message.relatedSubscriptionId))
635
+ if (message.to !== manifest.accounts.email.inboxAddress ||
636
+ message.template !== "subscription-payment-failed" ||
637
+ !subscriptionsById.has(message.relatedSubscriptionId))
390
638
  throw new SyntheticWorldError("Synthetic email message is inconsistent.");
391
- return { id: normalizeIdentifier(message.id, "Synthetic email message id"), to: manifest.accounts.email.inboxAddress, template: "subscription-payment-failed", relatedSubscriptionId: message.relatedSubscriptionId };
639
+ return {
640
+ id: normalizeIdentifier(message.id, "Synthetic email message id"),
641
+ to: manifest.accounts.email.inboxAddress,
642
+ template: "subscription-payment-failed",
643
+ relatedSubscriptionId: message.relatedSubscriptionId,
644
+ };
392
645
  });
393
646
  }
394
647
  function normalizeFailures(value, subscriptions, manifest) {
@@ -396,9 +649,16 @@ function normalizeFailures(value, subscriptions, manifest) {
396
649
  return readArray(value, "Synthetic generic REST failures", MAX_WORKFLOW_RECEIPTS).map((entry) => {
397
650
  const failure = readObject(entry, "Synthetic generic REST failure is invalid.");
398
651
  assertExactKeys(failure, ["id", "tenantId", "code", "relatedSubscriptionId"], "Synthetic generic REST failure");
399
- if (failure.tenantId !== manifest.accounts.genericRest.tenantId || failure.code !== "payment_failed" || !subscriptionsById.has(failure.relatedSubscriptionId))
652
+ if (failure.tenantId !== manifest.accounts.genericRest.tenantId ||
653
+ failure.code !== "payment_failed" ||
654
+ !subscriptionsById.has(failure.relatedSubscriptionId))
400
655
  throw new SyntheticWorldError("Synthetic generic REST failure is inconsistent.");
401
- return { id: normalizeIdentifier(failure.id, "Synthetic generic REST failure id"), tenantId: manifest.accounts.genericRest.tenantId, code: "payment_failed", relatedSubscriptionId: failure.relatedSubscriptionId };
656
+ return {
657
+ id: normalizeIdentifier(failure.id, "Synthetic generic REST failure id"),
658
+ tenantId: manifest.accounts.genericRest.tenantId,
659
+ code: "payment_failed",
660
+ relatedSubscriptionId: failure.relatedSubscriptionId,
661
+ };
402
662
  });
403
663
  }
404
664
  function normalizeReceipts(value, customers, subscriptions, messages, issues, failures, manifest) {
@@ -410,21 +670,51 @@ function normalizeReceipts(value, customers, subscriptions, messages, issues, fa
410
670
  const actionIds = new Set();
411
671
  return readArray(value, "Synthetic workflow receipts", MAX_WORKFLOW_RECEIPTS).map((entry) => {
412
672
  const receipt = readObject(entry, "Synthetic workflow receipt is invalid.");
413
- assertExactKeys(receipt, ["actionId", "customerId", "subscriptionId", "emailId", "issueNumber", "genericFailureId", "completedAt"], "Synthetic workflow receipt");
673
+ assertExactKeys(receipt, [
674
+ "actionId",
675
+ "customerId",
676
+ "subscriptionId",
677
+ "emailId",
678
+ "issueNumber",
679
+ "genericFailureId",
680
+ "completedAt",
681
+ ], "Synthetic workflow receipt");
414
682
  const actionId = normalizeIdentifier(receipt.actionId, "Synthetic workflow action id");
415
- if (actionIds.has(actionId) || !customerIds.has(receipt.customerId) || !subscriptionIds.has(receipt.subscriptionId) || !messageIds.has(receipt.emailId) || !issueNumbers.has(receipt.issueNumber) || !failureIds.has(receipt.genericFailureId) || !isTimestamp(receipt.completedAt) || receipt.completedAt < manifest.clock.initialAt)
683
+ if (actionIds.has(actionId) ||
684
+ !customerIds.has(receipt.customerId) ||
685
+ !subscriptionIds.has(receipt.subscriptionId) ||
686
+ !messageIds.has(receipt.emailId) ||
687
+ !issueNumbers.has(receipt.issueNumber) ||
688
+ !failureIds.has(receipt.genericFailureId) ||
689
+ !isTimestamp(receipt.completedAt) ||
690
+ receipt.completedAt < manifest.clock.initialAt)
416
691
  throw new SyntheticWorldError("Synthetic workflow receipt is inconsistent.");
417
692
  actionIds.add(actionId);
418
- return { actionId, customerId: receipt.customerId, subscriptionId: receipt.subscriptionId, emailId: receipt.emailId, issueNumber: receipt.issueNumber, genericFailureId: receipt.genericFailureId, completedAt: receipt.completedAt };
693
+ return {
694
+ actionId,
695
+ customerId: receipt.customerId,
696
+ subscriptionId: receipt.subscriptionId,
697
+ emailId: receipt.emailId,
698
+ issueNumber: receipt.issueNumber,
699
+ genericFailureId: receipt.genericFailureId,
700
+ completedAt: receipt.completedAt,
701
+ };
419
702
  });
420
703
  }
421
704
  function normalizeIdentifier(value, label) {
422
- if (typeof value !== "string" || !IDENTIFIER.test(value) || sanitizeSecretString(value) !== value)
705
+ if (typeof value !== "string" ||
706
+ !IDENTIFIER.test(value) ||
707
+ sanitizeSecretString(value) !== value)
423
708
  throw new SyntheticWorldError(`${label} must be a safe stable identifier.`);
424
709
  return value;
425
710
  }
426
711
  function normalizeSeed(value) {
427
- if (typeof value !== "string" || value.trim() === "" || value.length > MAX_SEED_LENGTH || /[\r\n\t]/.test(value) || sanitizeSecretString(value) !== value || hasObviousPii(value))
712
+ if (typeof value !== "string" ||
713
+ value.trim() === "" ||
714
+ value.length > MAX_SEED_LENGTH ||
715
+ /[\r\n\t]/.test(value) ||
716
+ sanitizeSecretString(value) !== value ||
717
+ hasObviousPii(value))
428
718
  throw new SyntheticWorldError("World seed must be a non-secret, non-PII 1-128 character string.");
429
719
  return value;
430
720
  }
@@ -432,7 +722,12 @@ function normalizeTitle(value) {
432
722
  return normalizeSafeText(value, "World title", 120);
433
723
  }
434
724
  function normalizeSafeText(value, label, maximumLength) {
435
- if (typeof value !== "string" || value.trim() === "" || value.trim().length > maximumLength || /[\r\n\t]/.test(value) || sanitizeSecretString(value) !== value || hasObviousPii(value))
725
+ if (typeof value !== "string" ||
726
+ value.trim() === "" ||
727
+ value.trim().length > maximumLength ||
728
+ /[\r\n\t]/.test(value) ||
729
+ sanitizeSecretString(value) !== value ||
730
+ hasObviousPii(value))
436
731
  throw new SyntheticWorldError(`${label} must be safe non-PII plain text.`);
437
732
  return value.trim();
438
733
  }
@@ -442,7 +737,8 @@ function normalizeBasePath(value) {
442
737
  return value;
443
738
  }
444
739
  function normalizeSyntheticInboxAddress(value) {
445
- if (typeof value !== "string" || !/^operator-[a-f0-9]{12}@ghostapi\.invalid$/.test(value))
740
+ if (typeof value !== "string" ||
741
+ !/^operator-[a-f0-9]{12}@ghostapi\.invalid$/.test(value))
446
742
  throw new SyntheticWorldError("Synthetic inbox address must use the generated ghostapi.invalid format.");
447
743
  return value;
448
744
  }
@@ -480,10 +776,13 @@ function nextTimestamp(value) {
480
776
  return new Date(Date.parse(value) + 1000).toISOString();
481
777
  }
482
778
  function isTimestamp(value) {
483
- return 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));
779
+ return (typeof value === "string" &&
780
+ /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(value) &&
781
+ Number.isFinite(Date.parse(value)));
484
782
  }
485
783
  function hasObviousPii(value) {
486
- return /[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/i.test(value) || /(?:\+\d[\d(). -]{7,}\d|\b\d{3}[ .-]\d{3}[ .-]\d{4}\b)/.test(value);
784
+ return (/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/i.test(value) ||
785
+ /(?:\+\d[\d(). -]{7,}\d|\b\d{3}[ .-]\d{3}[ .-]\d{4}\b)/.test(value));
487
786
  }
488
787
  function isErrorCode(error, code) {
489
788
  return error instanceof Error && "code" in error && error.code === code;