@yiaany/ghostapi 0.1.8 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (293) 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 -80
  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 +67 -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/assets/ghostapi-avatar.png +0 -0
  206. package/docs/ci.md +49 -49
  207. package/docs/github-actions.md +84 -84
  208. package/docs/mcp.md +58 -52
  209. package/docs/policy.md +63 -61
  210. package/docs/providers/authoring-packs.md +180 -175
  211. package/docs/providers/stripe-core-pack.md +84 -82
  212. package/docs/releases/README.md +13 -0
  213. package/docs/security/action-gateway-threat-model.md +34 -34
  214. package/docs/security/action-ledger-incident-replay-threat-model.md +42 -42
  215. package/docs/security/approval-inbox-threat-model.md +35 -35
  216. package/docs/security/credential-broker-threat-model.md +52 -52
  217. package/docs/security/egress-threat-model.md +126 -126
  218. package/docs/security/inventory-threat-model.md +104 -104
  219. package/docs/security/kill-switch-budgets-threat-model.md +37 -37
  220. package/docs/security/reliability-threat-model.md +111 -111
  221. package/docs/security/trust-ladder-threat-model.md +33 -33
  222. package/docs/telemetry.md +14 -0
  223. package/docs/usage.md +680 -571
  224. package/examples/README.md +10 -10
  225. package/examples/agent-instructions/README.md +39 -39
  226. package/examples/ci-smoke/README.md +21 -21
  227. package/examples/ci-smoke/ghostapi.policy.yaml +22 -22
  228. package/examples/ci-smoke/package.json +9 -9
  229. package/examples/ci-smoke/production-egress.mjs +4 -2
  230. package/examples/ci-smoke/safe.mjs +21 -17
  231. package/examples/evals/README.md +24 -24
  232. package/examples/evals/retry-after.eval.json +71 -52
  233. package/examples/generic-rest/README.md +27 -27
  234. package/examples/github-fetch/README.md +38 -35
  235. package/examples/openai-streaming/README.md +16 -16
  236. package/examples/openai-streaming/streaming-tool-call.mjs +44 -31
  237. package/examples/policy/ghostapi.policy.yaml +27 -27
  238. package/examples/record-replay/README.md +19 -19
  239. package/examples/record-replay/replay-requests.json +22 -22
  240. package/examples/record-replay/stripe-checkout.har.json +30 -30
  241. package/examples/resend-node/README.md +22 -22
  242. package/examples/stripe-node/README.md +35 -35
  243. package/examples/stripe-node/checkout-flow.mjs +87 -61
  244. package/examples/twilio-node/README.md +21 -21
  245. package/examples/worlds/README.md +19 -19
  246. package/examples/worlds/subscription-recovery.mjs +21 -12
  247. package/package.json +28 -6
  248. package/dist/landing/assets/index-BXR41fAD.css +0 -1
  249. package/dist/landing/assets/index-CxWsopzP.css +0 -1
  250. package/dist/landing/assets/index-D8_ggMRE.js +0 -55
  251. package/dist/landing/assets/index-DZlpyhqd.js +0 -55
  252. package/dist/landing/assets/index-Dk-Xezs3.js +0 -55
  253. package/dist/landing/index.html +0 -15
  254. package/dist/landing/landing.d.ts +0 -3
  255. package/dist/landing/landing.js +0 -12
  256. package/dist/landing/landing.js.map +0 -1
  257. package/docs/commercial/README.md +0 -29
  258. package/docs/commercial/data-inventory.md +0 -36
  259. package/docs/commercial/manual-invoicing.md +0 -44
  260. package/docs/commercial/metrics.md +0 -51
  261. package/docs/commercial/pricing.md +0 -103
  262. package/docs/commercial/privacy-policy-draft.md +0 -41
  263. package/docs/commercial/terms-topics-for-counsel.md +0 -51
  264. package/docs/design-partners/README.md +0 -52
  265. package/docs/design-partners/demo-narratives.md +0 -33
  266. package/docs/design-partners/design-partner-offer.md +0 -39
  267. package/docs/design-partners/discovery-questions.md +0 -28
  268. package/docs/design-partners/feedback-capture-template.md +0 -48
  269. package/docs/design-partners/icp-one-pager.md +0 -43
  270. package/docs/design-partners/onboarding-checklist.md +0 -33
  271. package/docs/design-partners/pilot-success-criteria.md +0 -27
  272. package/docs/design-partners/pricing-interview-script.md +0 -18
  273. package/docs/design-partners/telemetry-plan.md +0 -48
  274. package/docs/development/baseline.md +0 -114
  275. package/docs/development/onboarding-smoke.md +0 -37
  276. package/docs/development/verification-0.1.8.md +0 -25
  277. package/docs/enterprise-product-roadmap-ru.md +0 -1879
  278. package/docs/fundraising/README.md +0 -87
  279. package/docs/fundraising/data-room-checklist.md +0 -72
  280. package/docs/fundraising/demo-script.md +0 -52
  281. package/docs/fundraising/design-partners-50.md +0 -74
  282. package/docs/fundraising/launch-posts.md +0 -37
  283. package/docs/fundraising/metrics-and-evidence.md +0 -30
  284. package/docs/fundraising/roadmap-12-month.md +0 -25
  285. package/docs/fundraising/technical-due-diligence-index.md +0 -37
  286. package/docs/fundraising/yc-application.md +0 -25
  287. package/docs/hosted-pilot.md +0 -111
  288. package/docs/operations/disaster-recovery-runbook.md +0 -81
  289. package/docs/operations/kill-switch-runbook.md +0 -32
  290. package/docs/release-checklist.md +0 -83
  291. package/docs/release-migration-and-rollback.md +0 -63
  292. package/docs/release-readiness.md +0 -39
  293. package/docs/team-control-plane.md +0 -72
@@ -3,7 +3,7 @@ import { lstat, readFile } from "node:fs/promises";
3
3
  import { dirname } from "node:path";
4
4
  import { getDataPaths } from "../config/dataPaths.js";
5
5
  import { sanitizeSecretString } from "../security/secrets.js";
6
- import { atomicWriteJson, ensurePrivateDirectory, withFileLock } from "../storage/fileStore.js";
6
+ import { atomicWriteJson, ensurePrivateDirectory, withFileLock, } from "../storage/fileStore.js";
7
7
  const SCHEMA_VERSION = 1;
8
8
  const MAX_STORE_BYTES = 1024 * 1024;
9
9
  const MAX_TARGETS = 1_000;
@@ -12,7 +12,14 @@ const MAX_EVALS = 100;
12
12
  const IDENTIFIER = /^[a-z0-9][a-z0-9._-]{0,127}$/;
13
13
  const HASH = /^[a-f0-9]{64}$/;
14
14
  const MAX_FRESHNESS_MS = 30 * 24 * 60 * 60 * 1000;
15
- export const TRUST_LEVELS = ["simulate", "shadow", "dry-run", "approve", "bounded-auto", "trusted"];
15
+ export const TRUST_LEVELS = [
16
+ "simulate",
17
+ "shadow",
18
+ "dry-run",
19
+ "approve",
20
+ "bounded-auto",
21
+ "trusted",
22
+ ];
16
23
  export class TrustLadderError extends Error {
17
24
  constructor(message) {
18
25
  super(message);
@@ -27,12 +34,39 @@ export function createLocalSyntheticTrustCapabilities() {
27
34
  environment: "synthetic",
28
35
  levels: [
29
36
  { level: "simulate", status: "supported", externalSideEffects: false },
30
- { level: "shadow", status: "supported", externalSideEffects: false, requiresActualContext: true },
31
- { level: "dry-run", status: "unsupported", externalSideEffects: false, requiresOfficialProviderSemantics: true, reason: "ghostapi-synthetic has no provider-official dry-run semantic." },
32
- { level: "approve", status: "supported", externalSideEffects: false, requiresApproval: true },
33
- { level: "bounded-auto", status: "supported", externalSideEffects: false, requiresCanary: true, requiresOutcomeComparison: true },
34
- { level: "trusted", status: "unsupported", externalSideEffects: false, reason: "The local synthetic runtime is not production authorization." }
35
- ]
37
+ {
38
+ level: "shadow",
39
+ status: "supported",
40
+ externalSideEffects: false,
41
+ requiresActualContext: true,
42
+ },
43
+ {
44
+ level: "dry-run",
45
+ status: "unsupported",
46
+ externalSideEffects: false,
47
+ requiresOfficialProviderSemantics: true,
48
+ reason: "ghostapi-synthetic has no provider-official dry-run semantic.",
49
+ },
50
+ {
51
+ level: "approve",
52
+ status: "supported",
53
+ externalSideEffects: false,
54
+ requiresApproval: true,
55
+ },
56
+ {
57
+ level: "bounded-auto",
58
+ status: "supported",
59
+ externalSideEffects: false,
60
+ requiresCanary: true,
61
+ requiresOutcomeComparison: true,
62
+ },
63
+ {
64
+ level: "trusted",
65
+ status: "unsupported",
66
+ externalSideEffects: false,
67
+ reason: "The local synthetic runtime is not production authorization.",
68
+ },
69
+ ],
36
70
  };
37
71
  }
38
72
  export function createLocalTrustLadder(options) {
@@ -106,7 +140,9 @@ export class LocalTrustLadder {
106
140
  const current = findOrCreateTarget(state, prediction.target, now);
107
141
  assertActiveLevel(current, "shadow");
108
142
  const evidence = comparisonEvidence(prediction.target, "shadow", observationHash(prediction), observationHash(actual), now);
109
- appendAudit(state, "shadow.compared", current, undefined, undefined, evidence.matched ? "predicted action matches actual input context" : "predicted action differs from actual input context", now);
143
+ appendAudit(state, "shadow.compared", current, undefined, undefined, evidence.matched
144
+ ? "predicted action matches actual input context"
145
+ : "predicted action differs from actual input context", now);
110
146
  return evidence;
111
147
  });
112
148
  }
@@ -119,7 +155,9 @@ export class LocalTrustLadder {
119
155
  const current = findOrCreateTarget(state, prediction.target, now);
120
156
  assertActiveLevel(current, "bounded-auto");
121
157
  const evidence = comparisonEvidence(prediction.target, "bounded-auto", outcomeHash(prediction), outcomeHash(actual), now);
122
- appendAudit(state, "bounded.outcome_compared", current, undefined, undefined, evidence.matched ? "predicted and actual bounded outcomes match" : "predicted and actual bounded outcomes differ", now);
158
+ appendAudit(state, "bounded.outcome_compared", current, undefined, undefined, evidence.matched
159
+ ? "predicted and actual bounded outcomes match"
160
+ : "predicted and actual bounded outcomes differ", now);
123
161
  return evidence;
124
162
  });
125
163
  }
@@ -144,14 +182,23 @@ export class LocalTrustLadder {
144
182
  current.canary.errors += 1;
145
183
  current.updatedAt = now;
146
184
  appendAudit(state, "canary.outcome", current, undefined, undefined, reason, now);
147
- if (outcome === "violation" && policy.violationResponse === "demote_to_approve") {
185
+ if (outcome === "violation" &&
186
+ policy.violationResponse === "demote_to_approve") {
148
187
  const previous = current.level;
149
188
  current.level = "approve";
150
189
  appendAudit(state, "trust.auto_demoted", current, previous, "approve", "policy violation automatically returned trust to approval mode", now);
151
190
  }
152
- if ((outcome === "violation" && policy.violationResponse === "open_circuit_breaker") || (current.canary.violations > 0 && current.canary.violations >= policy.stopConditions.maxViolations) || (current.canary.errors > 0 && current.canary.errors >= policy.stopConditions.maxErrors)) {
191
+ if ((outcome === "violation" &&
192
+ policy.violationResponse === "open_circuit_breaker") ||
193
+ (current.canary.violations > 0 &&
194
+ current.canary.violations >= policy.stopConditions.maxViolations) ||
195
+ (current.canary.errors > 0 &&
196
+ current.canary.errors >= policy.stopConditions.maxErrors)) {
153
197
  current.circuitBreaker = "open";
154
- appendAudit(state, "canary.circuit_opened", current, undefined, undefined, outcome === "violation" && policy.violationResponse === "open_circuit_breaker" ? "policy violation opened the configured circuit breaker" : "configured canary stop condition was breached", now);
198
+ appendAudit(state, "canary.circuit_opened", current, undefined, undefined, outcome === "violation" &&
199
+ policy.violationResponse === "open_circuit_breaker"
200
+ ? "policy violation opened the configured circuit breaker"
201
+ : "configured canary stop condition was breached", now);
155
202
  }
156
203
  return clone(current);
157
204
  });
@@ -214,33 +261,62 @@ export function createTestTrustOwnerVerifier() {
214
261
  return {
215
262
  verifier: {
216
263
  async authenticate(identity) {
217
- if (identity === null || typeof identity !== "object" || !issued.has(identity))
264
+ if (identity === null ||
265
+ typeof identity !== "object" ||
266
+ !issued.has(identity))
218
267
  throw new TrustLadderError("Trust owner identity is not authenticated.");
219
268
  return validateOwner(identity);
220
- }
269
+ },
221
270
  },
222
271
  issue(input) {
223
272
  const owner = Object.freeze(validateOwner({ ...input, principalId: input.principalId ?? input.id }));
224
273
  issued.add(owner);
225
274
  return owner;
226
- }
275
+ },
227
276
  };
228
277
  }
229
278
  function validateCapabilities(value) {
230
279
  const capabilities = object(value, "Trust capabilities are invalid.");
231
280
  exactKeys(capabilities, ["schemaVersion", "kind", "provider", "environment", "levels"], "Trust capabilities");
232
- if (capabilities.schemaVersion !== SCHEMA_VERSION || capabilities.kind !== "ghostapi.trust-capabilities" || capabilities.provider !== "ghostapi-synthetic" || capabilities.environment !== "synthetic")
281
+ if (capabilities.schemaVersion !== SCHEMA_VERSION ||
282
+ capabilities.kind !== "ghostapi.trust-capabilities" ||
283
+ capabilities.provider !== "ghostapi-synthetic" ||
284
+ capabilities.environment !== "synthetic")
233
285
  throw new TrustLadderError("Only local synthetic trust capabilities are available.");
234
286
  const levels = array(capabilities.levels, "Trust level capabilities", TRUST_LEVELS.length).map(validateCapability);
235
- if (levels.length !== TRUST_LEVELS.length || levels.some((capability, index) => capability.level !== TRUST_LEVELS[index]))
287
+ if (levels.length !== TRUST_LEVELS.length ||
288
+ levels.some((capability, index) => capability.level !== TRUST_LEVELS[index]))
236
289
  throw new TrustLadderError("Trust capabilities must define every level in stable order.");
237
290
  if (levels.some((capability) => capability.externalSideEffects))
238
291
  throw new TrustLadderError("Local trust capabilities cannot enable external side effects.");
239
- return { schemaVersion: 1, kind: "ghostapi.trust-capabilities", provider: "ghostapi-synthetic", environment: "synthetic", levels };
292
+ return {
293
+ schemaVersion: 1,
294
+ kind: "ghostapi.trust-capabilities",
295
+ provider: "ghostapi-synthetic",
296
+ environment: "synthetic",
297
+ levels,
298
+ };
240
299
  }
241
300
  function validateCapability(value) {
242
301
  const capability = object(value, "Trust level capability is invalid.");
243
- exactKeys(capability, ["level", "status", "externalSideEffects", "requiresActualContext", "requiresOfficialProviderSemantics", "requiresApproval", "requiresCanary", "requiresOutcomeComparison", "reason"], "Trust level capability", ["requiresActualContext", "requiresOfficialProviderSemantics", "requiresApproval", "requiresCanary", "requiresOutcomeComparison", "reason"]);
302
+ exactKeys(capability, [
303
+ "level",
304
+ "status",
305
+ "externalSideEffects",
306
+ "requiresActualContext",
307
+ "requiresOfficialProviderSemantics",
308
+ "requiresApproval",
309
+ "requiresCanary",
310
+ "requiresOutcomeComparison",
311
+ "reason",
312
+ ], "Trust level capability", [
313
+ "requiresActualContext",
314
+ "requiresOfficialProviderSemantics",
315
+ "requiresApproval",
316
+ "requiresCanary",
317
+ "requiresOutcomeComparison",
318
+ "reason",
319
+ ]);
244
320
  const level = trustLevel(capability.level, "Trust capability level");
245
321
  if (capability.status !== "supported" && capability.status !== "unsupported")
246
322
  throw new TrustLadderError("Trust capability status is invalid.");
@@ -251,21 +327,54 @@ function validateCapability(value) {
251
327
  const requiresApproval = booleanFlag(capability.requiresApproval, "Trust capability flag");
252
328
  const requiresCanary = booleanFlag(capability.requiresCanary, "Trust capability flag");
253
329
  const requiresOutcomeComparison = booleanFlag(capability.requiresOutcomeComparison, "Trust capability flag");
254
- const reason = capability.reason === undefined ? undefined : text(capability.reason, "Trust capability reason", 200);
330
+ const reason = capability.reason === undefined
331
+ ? undefined
332
+ : text(capability.reason, "Trust capability reason", 200);
255
333
  if (capability.status === "unsupported" && reason === undefined)
256
334
  throw new TrustLadderError("Unsupported trust capability requires a reason.");
257
- return { level, status: capability.status, externalSideEffects: false, ...(requiresActualContext === undefined ? {} : { requiresActualContext }), ...(requiresOfficialProviderSemantics === undefined ? {} : { requiresOfficialProviderSemantics }), ...(requiresApproval === undefined ? {} : { requiresApproval }), ...(requiresCanary === undefined ? {} : { requiresCanary }), ...(requiresOutcomeComparison === undefined ? {} : { requiresOutcomeComparison }), ...(reason === undefined ? {} : { reason }) };
335
+ return {
336
+ level,
337
+ status: capability.status,
338
+ externalSideEffects: false,
339
+ ...(requiresActualContext === undefined ? {} : { requiresActualContext }),
340
+ ...(requiresOfficialProviderSemantics === undefined
341
+ ? {}
342
+ : { requiresOfficialProviderSemantics }),
343
+ ...(requiresApproval === undefined ? {} : { requiresApproval }),
344
+ ...(requiresCanary === undefined ? {} : { requiresCanary }),
345
+ ...(requiresOutcomeComparison === undefined
346
+ ? {}
347
+ : { requiresOutcomeComparison }),
348
+ ...(reason === undefined ? {} : { reason }),
349
+ };
258
350
  }
259
351
  function validatePolicy(value) {
260
352
  const policy = object(value, "Trust promotion policy is invalid.");
261
- exactKeys(policy, ["schemaVersion", "kind", "id", "ownerPrincipalId", "automaticPromotion", "minimumRuns", "requiredEvalIds", "maxViolationRateBps", "maxErrorRateBps", "evidenceFreshnessMs", "canary", "stopConditions", "violationResponse"], "Trust promotion policy");
262
- if (policy.schemaVersion !== SCHEMA_VERSION || policy.kind !== "ghostapi.trust-promotion-policy" || policy.automaticPromotion !== false)
353
+ exactKeys(policy, [
354
+ "schemaVersion",
355
+ "kind",
356
+ "id",
357
+ "ownerPrincipalId",
358
+ "automaticPromotion",
359
+ "minimumRuns",
360
+ "requiredEvalIds",
361
+ "maxViolationRateBps",
362
+ "maxErrorRateBps",
363
+ "evidenceFreshnessMs",
364
+ "canary",
365
+ "stopConditions",
366
+ "violationResponse",
367
+ ], "Trust promotion policy");
368
+ if (policy.schemaVersion !== SCHEMA_VERSION ||
369
+ policy.kind !== "ghostapi.trust-promotion-policy" ||
370
+ policy.automaticPromotion !== false)
263
371
  throw new TrustLadderError("Automatic trust promotion is disabled.");
264
372
  const canary = object(policy.canary, "Trust canary scope is invalid.");
265
373
  exactKeys(canary, ["tenantIds", "resourceIds", "percentageBps"], "Trust canary scope");
266
374
  const stopConditions = object(policy.stopConditions, "Trust canary stop conditions are invalid.");
267
375
  exactKeys(stopConditions, ["maxViolations", "maxErrors"], "Trust canary stop conditions");
268
- if (policy.violationResponse !== "demote_to_approve" && policy.violationResponse !== "open_circuit_breaker")
376
+ if (policy.violationResponse !== "demote_to_approve" &&
377
+ policy.violationResponse !== "open_circuit_breaker")
269
378
  throw new TrustLadderError("Trust violation response is invalid.");
270
379
  return {
271
380
  schemaVersion: 1,
@@ -278,9 +387,16 @@ function validatePolicy(value) {
278
387
  maxViolationRateBps: bps(policy.maxViolationRateBps, "Trust maximum violation rate"),
279
388
  maxErrorRateBps: bps(policy.maxErrorRateBps, "Trust maximum error rate"),
280
389
  evidenceFreshnessMs: positive(policy.evidenceFreshnessMs, "Trust evidence freshness", MAX_FRESHNESS_MS),
281
- canary: { tenantIds: identifiers(canary.tenantIds, "Trust canary tenant ids", MAX_TARGETS, true), resourceIds: identifiers(canary.resourceIds, "Trust canary resource ids", MAX_TARGETS, true), percentageBps: bps(canary.percentageBps, "Trust canary percentage") },
282
- stopConditions: { maxViolations: nonNegative(stopConditions.maxViolations, "Trust canary maximum violations", 1_000_000), maxErrors: nonNegative(stopConditions.maxErrors, "Trust canary maximum errors", 1_000_000) },
283
- violationResponse: policy.violationResponse
390
+ canary: {
391
+ tenantIds: identifiers(canary.tenantIds, "Trust canary tenant ids", MAX_TARGETS, true),
392
+ resourceIds: identifiers(canary.resourceIds, "Trust canary resource ids", MAX_TARGETS, true),
393
+ percentageBps: bps(canary.percentageBps, "Trust canary percentage"),
394
+ },
395
+ stopConditions: {
396
+ maxViolations: nonNegative(stopConditions.maxViolations, "Trust canary maximum violations", 1_000_000),
397
+ maxErrors: nonNegative(stopConditions.maxErrors, "Trust canary maximum errors", 1_000_000),
398
+ },
399
+ violationResponse: policy.violationResponse,
284
400
  };
285
401
  }
286
402
  function validateEvidence(value) {
@@ -294,32 +410,61 @@ function validateEvidence(value) {
294
410
  exactKeys(evaluation, ["id", "status", "completedAt"], "Trust evidence eval");
295
411
  if (evaluation.status !== "passed" && evaluation.status !== "failed")
296
412
  throw new TrustLadderError("Trust evidence eval status is invalid.");
297
- return { id: identifier(evaluation.id, "Trust evidence eval id"), status: evaluation.status, completedAt: timestamp(evaluation.completedAt, "Trust evidence eval completion") };
413
+ return {
414
+ id: identifier(evaluation.id, "Trust evidence eval id"),
415
+ status: evaluation.status,
416
+ completedAt: timestamp(evaluation.completedAt, "Trust evidence eval completion"),
417
+ };
298
418
  });
299
419
  unique(evals.map((evaluation) => evaluation.id), "Trust evidence eval ids");
300
- return { runCount, violations, errors, evals, observedAt: timestamp(evidence.observedAt, "Trust evidence observation time") };
420
+ return {
421
+ runCount,
422
+ violations,
423
+ errors,
424
+ evals,
425
+ observedAt: timestamp(evidence.observedAt, "Trust evidence observation time"),
426
+ };
301
427
  }
302
428
  function validateTarget(value) {
303
429
  const target = object(value, "Trust target is invalid.");
304
430
  exactKeys(target, ["provider", "environment", "tenantId", "resourceId"], "Trust target");
305
- if (target.provider !== "ghostapi-synthetic" || target.environment !== "synthetic")
431
+ if (target.provider !== "ghostapi-synthetic" ||
432
+ target.environment !== "synthetic")
306
433
  throw new TrustLadderError("Local trust preparation accepts synthetic identities only; production identities are unsupported.");
307
- return { provider: "ghostapi-synthetic", environment: "synthetic", tenantId: identifier(target.tenantId, "Trust target tenant id"), resourceId: identifier(target.resourceId, "Trust target resource id") };
434
+ return {
435
+ provider: "ghostapi-synthetic",
436
+ environment: "synthetic",
437
+ tenantId: identifier(target.tenantId, "Trust target tenant id"),
438
+ resourceId: identifier(target.resourceId, "Trust target resource id"),
439
+ };
308
440
  }
309
441
  function validateObservation(value) {
310
442
  const observation = object(value, "Shadow observation is invalid.");
311
443
  exactKeys(observation, ["target", "actionHash", "operation", "contextHash"], "Shadow observation");
312
- return { target: validateTarget(observation.target), actionHash: hash(observation.actionHash, "Shadow action hash"), operation: identifier(observation.operation, "Shadow operation"), contextHash: hash(observation.contextHash, "Shadow context hash") };
444
+ return {
445
+ target: validateTarget(observation.target),
446
+ actionHash: hash(observation.actionHash, "Shadow action hash"),
447
+ operation: identifier(observation.operation, "Shadow operation"),
448
+ contextHash: hash(observation.contextHash, "Shadow context hash"),
449
+ };
313
450
  }
314
451
  function validateOutcomeObservation(value) {
315
452
  const observation = object(value, "Bounded outcome observation is invalid.");
316
453
  exactKeys(observation, ["target", "actionHash", "outcomeHash", "executionReceiptHash"], "Bounded outcome observation");
317
- return { target: validateTarget(observation.target), actionHash: hash(observation.actionHash, "Bounded action hash"), outcomeHash: hash(observation.outcomeHash, "Bounded outcome hash"), executionReceiptHash: hash(observation.executionReceiptHash, "Bounded execution receipt hash") };
454
+ return {
455
+ target: validateTarget(observation.target),
456
+ actionHash: hash(observation.actionHash, "Bounded action hash"),
457
+ outcomeHash: hash(observation.outcomeHash, "Bounded outcome hash"),
458
+ executionReceiptHash: hash(observation.executionReceiptHash, "Bounded execution receipt hash"),
459
+ };
318
460
  }
319
461
  function validateOwner(value) {
320
462
  const owner = object(value, "Trust owner identity is invalid.");
321
463
  exactKeys(owner, ["id", "principalId"], "Trust owner identity");
322
- return { id: identifier(owner.id, "Trust owner id"), principalId: identifier(owner.principalId, "Trust owner principal id") };
464
+ return {
465
+ id: identifier(owner.id, "Trust owner id"),
466
+ principalId: identifier(owner.principalId, "Trust owner principal id"),
467
+ };
323
468
  }
324
469
  function validateState(value) {
325
470
  const state = object(value, "Trust ladder store must be an object.");
@@ -343,22 +488,43 @@ function validateTargetState(value) {
343
488
  const errors = nonNegative(canary.errors, "Trust canary error count", observed);
344
489
  if (state.circuitBreaker !== "closed" && state.circuitBreaker !== "open")
345
490
  throw new TrustLadderError("Trust circuit breaker state is invalid.");
346
- return { target: validateTarget(state.target), level: trustLevel(state.level, "Trust target level"), circuitBreaker: state.circuitBreaker, canary: { observed, violations, errors }, updatedAt: timestamp(state.updatedAt, "Trust target update time") };
491
+ return {
492
+ target: validateTarget(state.target),
493
+ level: trustLevel(state.level, "Trust target level"),
494
+ circuitBreaker: state.circuitBreaker,
495
+ canary: { observed, violations, errors },
496
+ updatedAt: timestamp(state.updatedAt, "Trust target update time"),
497
+ };
347
498
  }
348
499
  function validateAudit(value) {
349
500
  const record = object(value, "Trust audit record is invalid.");
350
- exactKeys(record, ["sequence", "event", "targetKey", "fromLevel", "toLevel", "reason", "timestamp", "previousHash", "recordHash"], "Trust audit record", ["fromLevel", "toLevel"]);
501
+ exactKeys(record, [
502
+ "sequence",
503
+ "event",
504
+ "targetKey",
505
+ "fromLevel",
506
+ "toLevel",
507
+ "reason",
508
+ "timestamp",
509
+ "previousHash",
510
+ "recordHash",
511
+ ], "Trust audit record", ["fromLevel", "toLevel"]);
351
512
  const base = {
352
513
  sequence: positive(record.sequence, "Trust audit sequence", MAX_AUDIT),
353
514
  event: text(record.event, "Trust audit event", 80),
354
515
  targetKey: identifier(record.targetKey, "Trust audit target key"),
355
- ...(record.fromLevel === undefined ? {} : { fromLevel: trustLevel(record.fromLevel, "Trust audit from level") }),
356
- ...(record.toLevel === undefined ? {} : { toLevel: trustLevel(record.toLevel, "Trust audit to level") }),
516
+ ...(record.fromLevel === undefined
517
+ ? {}
518
+ : { fromLevel: trustLevel(record.fromLevel, "Trust audit from level") }),
519
+ ...(record.toLevel === undefined
520
+ ? {}
521
+ : { toLevel: trustLevel(record.toLevel, "Trust audit to level") }),
357
522
  reason: text(record.reason, "Trust audit reason", 200),
358
523
  timestamp: timestamp(record.timestamp, "Trust audit timestamp"),
359
- previousHash: hash(record.previousHash, "Trust audit previous hash")
524
+ previousHash: hash(record.previousHash, "Trust audit previous hash"),
360
525
  };
361
- if (hash(record.recordHash, "Trust audit record hash") !== sha256(canonical(base)))
526
+ if (hash(record.recordHash, "Trust audit record hash") !==
527
+ sha256(canonical(base)))
362
528
  throw new TrustLadderError("Trust audit record hash is invalid.");
363
529
  return { ...base, recordHash: record.recordHash };
364
530
  }
@@ -373,7 +539,12 @@ function validateAuditChain(anchor, records) {
373
539
  }
374
540
  }
375
541
  function emptyState() {
376
- return { schemaVersion: 1, targets: [], auditAnchor: sha256("ghostapi.trust.audit.v1"), audit: [] };
542
+ return {
543
+ schemaVersion: 1,
544
+ targets: [],
545
+ auditAnchor: sha256("ghostapi.trust.audit.v1"),
546
+ audit: [],
547
+ };
377
548
  }
378
549
  function findOrCreateTarget(state, target, now) {
379
550
  const existing = state.targets.find((candidate) => sameTarget(candidate.target, target));
@@ -381,7 +552,13 @@ function findOrCreateTarget(state, target, now) {
381
552
  return existing;
382
553
  if (state.targets.length >= MAX_TARGETS)
383
554
  throw new TrustLadderError("Trust target limit was reached.");
384
- const created = { target: clone(target), level: "simulate", circuitBreaker: "closed", canary: { observed: 0, violations: 0, errors: 0 }, updatedAt: now };
555
+ const created = {
556
+ target: clone(target),
557
+ level: "simulate",
558
+ circuitBreaker: "closed",
559
+ canary: { observed: 0, violations: 0, errors: 0 },
560
+ updatedAt: now,
561
+ };
385
562
  state.targets.push(created);
386
563
  return created;
387
564
  }
@@ -390,7 +567,8 @@ function assertPromotionEvidence(evidence, policy, now) {
390
567
  throw new TrustLadderError("Trust promotion evidence has too few runs.");
391
568
  if (Date.parse(evidence.observedAt) > Date.parse(now))
392
569
  throw new TrustLadderError("Trust promotion evidence is from the future.");
393
- if (Date.parse(now) - Date.parse(evidence.observedAt) > policy.evidenceFreshnessMs)
570
+ if (Date.parse(now) - Date.parse(evidence.observedAt) >
571
+ policy.evidenceFreshnessMs)
394
572
  throw new TrustLadderError("Trust promotion evidence is stale.");
395
573
  for (const required of policy.requiredEvalIds) {
396
574
  const evaluation = evidence.evals.find((candidate) => candidate.id === required);
@@ -398,7 +576,8 @@ function assertPromotionEvidence(evidence, policy, now) {
398
576
  throw new TrustLadderError(`Trust promotion requires passing eval: ${required}`);
399
577
  if (Date.parse(evaluation.completedAt) > Date.parse(now))
400
578
  throw new TrustLadderError(`Trust promotion eval is from the future: ${required}`);
401
- if (Date.parse(now) - Date.parse(evaluation.completedAt) > policy.evidenceFreshnessMs)
579
+ if (Date.parse(now) - Date.parse(evaluation.completedAt) >
580
+ policy.evidenceFreshnessMs)
402
581
  throw new TrustLadderError(`Trust promotion eval is stale: ${required}`);
403
582
  }
404
583
  if (rateBps(evidence.violations, evidence.runCount) > policy.maxViolationRateBps)
@@ -420,20 +599,54 @@ function capabilityFor(capabilities, level) {
420
599
  return capability;
421
600
  }
422
601
  function canaryDecision(target, policy) {
423
- if (policy.canary.tenantIds.length > 0 && !policy.canary.tenantIds.includes(target.tenantId))
424
- return { target: clone(target), assigned: false, bucket: deterministicBucket(policy.id, target), reason: "tenant is outside the configured canary scope" };
425
- if (policy.canary.resourceIds.length > 0 && !policy.canary.resourceIds.includes(target.resourceId))
426
- return { target: clone(target), assigned: false, bucket: deterministicBucket(policy.id, target), reason: "resource is outside the configured canary scope" };
602
+ if (policy.canary.tenantIds.length > 0 &&
603
+ !policy.canary.tenantIds.includes(target.tenantId))
604
+ return {
605
+ target: clone(target),
606
+ assigned: false,
607
+ bucket: deterministicBucket(policy.id, target),
608
+ reason: "tenant is outside the configured canary scope",
609
+ };
610
+ if (policy.canary.resourceIds.length > 0 &&
611
+ !policy.canary.resourceIds.includes(target.resourceId))
612
+ return {
613
+ target: clone(target),
614
+ assigned: false,
615
+ bucket: deterministicBucket(policy.id, target),
616
+ reason: "resource is outside the configured canary scope",
617
+ };
427
618
  const bucket = deterministicBucket(policy.id, target);
428
619
  return bucket < policy.canary.percentageBps
429
- ? { target: clone(target), assigned: true, bucket, reason: "target is inside deterministic canary scope" }
430
- : { target: clone(target), assigned: false, bucket, reason: "target is outside the configured canary percentage" };
620
+ ? {
621
+ target: clone(target),
622
+ assigned: true,
623
+ bucket,
624
+ reason: "target is inside deterministic canary scope",
625
+ }
626
+ : {
627
+ target: clone(target),
628
+ assigned: false,
629
+ bucket,
630
+ reason: "target is outside the configured canary percentage",
631
+ };
431
632
  }
432
633
  function deterministicBucket(policyId, target) {
433
- return createHash("sha256").update(`ghostapi.trust.canary.v1:${policyId}:${targetKey(target)}`, "utf8").digest().readUInt32BE(0) % 10_000;
634
+ return (createHash("sha256")
635
+ .update(`ghostapi.trust.canary.v1:${policyId}:${targetKey(target)}`, "utf8")
636
+ .digest()
637
+ .readUInt32BE(0) % 10_000);
434
638
  }
435
639
  function comparisonEvidence(target, level, predictedHash, actualHash, observedAt) {
436
- return { schemaVersion: 1, kind: "ghostapi.trust-comparison", target: clone(target), level, matched: predictedHash === actualHash, predictedHash, actualHash, observedAt };
640
+ return {
641
+ schemaVersion: 1,
642
+ kind: "ghostapi.trust-comparison",
643
+ target: clone(target),
644
+ level,
645
+ matched: predictedHash === actualHash,
646
+ predictedHash,
647
+ actualHash,
648
+ observedAt,
649
+ };
437
650
  }
438
651
  function observationHash(value) {
439
652
  return sha256(canonical(value));
@@ -445,7 +658,16 @@ function appendAudit(state, event, target, fromLevel, toLevel, reason, timestamp
445
658
  if (state.audit.length >= MAX_AUDIT)
446
659
  throw new TrustLadderError("Trust audit limit was reached.");
447
660
  const previousHash = state.audit.at(-1)?.recordHash ?? state.auditAnchor;
448
- const base = { sequence: state.audit.length + 1, event: text(event, "Trust audit event", 80), targetKey: targetKey(target.target), ...(fromLevel === undefined ? {} : { fromLevel }), ...(toLevel === undefined ? {} : { toLevel }), reason: text(reason, "Trust audit reason", 200), timestamp: timestampValue, previousHash };
661
+ const base = {
662
+ sequence: state.audit.length + 1,
663
+ event: text(event, "Trust audit event", 80),
664
+ targetKey: targetKey(target.target),
665
+ ...(fromLevel === undefined ? {} : { fromLevel }),
666
+ ...(toLevel === undefined ? {} : { toLevel }),
667
+ reason: text(reason, "Trust audit reason", 200),
668
+ timestamp: timestampValue,
669
+ previousHash,
670
+ };
449
671
  state.audit.push({ ...base, recordHash: sha256(canonical(base)) });
450
672
  }
451
673
  function assertActiveLevel(state, level) {
@@ -455,7 +677,10 @@ function assertActiveLevel(state, level) {
455
677
  throw new TrustLadderError(`Trust target is not active at ${level}.`);
456
678
  }
457
679
  function sameTarget(left, right) {
458
- return left.provider === right.provider && left.environment === right.environment && left.tenantId === right.tenantId && left.resourceId === right.resourceId;
680
+ return (left.provider === right.provider &&
681
+ left.environment === right.environment &&
682
+ left.tenantId === right.tenantId &&
683
+ left.resourceId === right.resourceId);
459
684
  }
460
685
  function targetKey(target) {
461
686
  return `target-${sha256(canonical(target)).slice(0, 32)}`;
@@ -471,7 +696,9 @@ function canaryOutcome(value) {
471
696
  return value;
472
697
  }
473
698
  function identifier(value, label) {
474
- if (typeof value !== "string" || !IDENTIFIER.test(value) || sanitizeSecretString(value) !== value)
699
+ if (typeof value !== "string" ||
700
+ !IDENTIFIER.test(value) ||
701
+ sanitizeSecretString(value) !== value)
475
702
  throw new TrustLadderError(`${label} must be a safe identifier.`);
476
703
  return value;
477
704
  }
@@ -488,17 +715,25 @@ function hash(value, label) {
488
715
  return value;
489
716
  }
490
717
  function timestamp(value, label) {
491
- 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)))
718
+ if (typeof value !== "string" ||
719
+ !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(value) ||
720
+ !Number.isFinite(Date.parse(value)))
492
721
  throw new TrustLadderError(`${label} must be an ISO UTC timestamp.`);
493
722
  return value;
494
723
  }
495
724
  function positive(value, label, max) {
496
- if (typeof value !== "number" || !Number.isInteger(value) || value < 1 || value > max)
725
+ if (typeof value !== "number" ||
726
+ !Number.isInteger(value) ||
727
+ value < 1 ||
728
+ value > max)
497
729
  throw new TrustLadderError(`${label} is invalid.`);
498
730
  return value;
499
731
  }
500
732
  function nonNegative(value, label, max) {
501
- if (typeof value !== "number" || !Number.isInteger(value) || value < 0 || value > max)
733
+ if (typeof value !== "number" ||
734
+ !Number.isInteger(value) ||
735
+ value < 0 ||
736
+ value > max)
502
737
  throw new TrustLadderError(`${label} is invalid.`);
503
738
  return value;
504
739
  }
@@ -516,7 +751,11 @@ function rateBps(count, total) {
516
751
  return total === 0 ? 10_000 : Math.floor((count * 10_000) / total);
517
752
  }
518
753
  function text(value, label, maxLength) {
519
- if (typeof value !== "string" || value.trim() === "" || value.length > maxLength || /[\u0000-\u001f]/.test(value) || sanitizeSecretString(value) !== value)
754
+ if (typeof value !== "string" ||
755
+ value.trim() === "" ||
756
+ value.length > maxLength ||
757
+ /[\u0000-\u001f]/.test(value) ||
758
+ sanitizeSecretString(value) !== value)
520
759
  throw new TrustLadderError(`${label} is invalid.`);
521
760
  return value.trim();
522
761
  }
@@ -532,7 +771,8 @@ function array(value, label, max) {
532
771
  }
533
772
  function exactKeys(value, keys, label, optional = []) {
534
773
  for (const key of Object.keys(value))
535
- if (!keys.includes(key) || (value[key] === undefined && !optional.includes(key)))
774
+ if (!keys.includes(key) ||
775
+ (value[key] === undefined && !optional.includes(key)))
536
776
  throw new TrustLadderError(`${label} contains unsupported field: ${key}`);
537
777
  }
538
778
  function unique(values, label) {
@@ -540,12 +780,18 @@ function unique(values, label) {
540
780
  throw new TrustLadderError(`${label} must be unique.`);
541
781
  }
542
782
  function canonical(value) {
543
- if (value === null || typeof value === "boolean" || typeof value === "string" || typeof value === "number")
783
+ if (value === null ||
784
+ typeof value === "boolean" ||
785
+ typeof value === "string" ||
786
+ typeof value === "number")
544
787
  return JSON.stringify(value);
545
788
  if (Array.isArray(value))
546
789
  return `[${value.map(canonical).join(",")}]`;
547
790
  if (typeof value === "object")
548
- return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`).join(",")}}`;
791
+ return `{${Object.keys(value)
792
+ .sort()
793
+ .map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`)
794
+ .join(",")}}`;
549
795
  throw new TrustLadderError("Trust data must be JSON.");
550
796
  }
551
797
  function sha256(value) {