@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
@@ -2,11 +2,11 @@ import { randomUUID } from "node:crypto";
2
2
  import { lstat, readFile } from "node:fs/promises";
3
3
  import { dirname } from "node:path";
4
4
  import { getDataPaths } from "../config/dataPaths.js";
5
- import { atomicWriteJson, ensurePrivateDirectory, withFileLock } from "../storage/fileStore.js";
5
+ import { atomicWriteJson, ensurePrivateDirectory, withFileLock, } from "../storage/fileStore.js";
6
6
  import { sanitizeSecretString } from "../security/secrets.js";
7
7
  import { normalizeFreshnessDays } from "./analysis.js";
8
- import { InventoryError, INVENTORY_LIMITS, buildRecordFreshness, buildRecordProvenance, canonicalPayloadDigest, clone, edgeIdFor, emptyInventoryState, freshnessStatusFor, validateImportPayload, validateOperator, validateState } from "./types.js";
9
- import { computeBlastRadius, computeFindings, computeRemovalAnalysis, computeRoiReport, findAttackPaths, findingKey, graphEdges } from "./analysis.js";
8
+ import { InventoryError, INVENTORY_LIMITS, buildRecordFreshness, buildRecordProvenance, canonicalPayloadDigest, clone, edgeIdFor, emptyInventoryState, freshnessStatusFor, validateImportPayload, validateOperator, validateState, } from "./types.js";
9
+ import { computeBlastRadius, computeFindings, computeRemovalAnalysis, computeRoiReport, findAttackPaths, findingKey, graphEdges, } from "./analysis.js";
10
10
  export class LocalInventoryController {
11
11
  path;
12
12
  now;
@@ -16,7 +16,8 @@ export class LocalInventoryController {
16
16
  constructor(options = {}) {
17
17
  this.path = options.path ?? getDataPaths().inventoryStore;
18
18
  this.now = options.now ?? (() => new Date());
19
- this.operatorAuthorizer = options.operatorAuthorizer ?? createDisabledInventoryOperatorAuthorizer();
19
+ this.operatorAuthorizer =
20
+ options.operatorAuthorizer ?? createDisabledInventoryOperatorAuthorizer();
20
21
  this.freshnessDays = options.freshnessDays;
21
22
  this.evalScenarioExists = options.evalScenarioExists;
22
23
  }
@@ -32,7 +33,16 @@ export class LocalInventoryController {
32
33
  for (const edge of state.edges)
33
34
  before.set(edge.edgeId, edge);
34
35
  const upserted = [];
35
- const counts = { agents: 0, tools: 0, identities: 0, providers: 0, resources: 0, sideEffects: 0, credentials: 0, policies: 0 };
36
+ const counts = {
37
+ agents: 0,
38
+ tools: 0,
39
+ identities: 0,
40
+ providers: 0,
41
+ resources: 0,
42
+ sideEffects: 0,
43
+ credentials: 0,
44
+ policies: 0,
45
+ };
36
46
  const imported = new ImportedRecords(payload);
37
47
  for (const input of payload.agents ?? []) {
38
48
  upsertAgent(state, operator.tenantId, input, payload, operator.principalId, now);
@@ -85,17 +95,33 @@ export class LocalInventoryController {
85
95
  runId,
86
96
  importedAt: now,
87
97
  importedBy: operator.principalId,
88
- source: { sourceId: payload.source.sourceId, sourceType: payload.source.sourceType, sourceName: payload.source.sourceName, ...(payload.source.sourceVersion === undefined ? {} : { sourceVersion: payload.source.sourceVersion }) },
98
+ source: {
99
+ sourceId: payload.source.sourceId,
100
+ sourceType: payload.source.sourceType,
101
+ sourceName: payload.source.sourceName,
102
+ ...(payload.source.sourceVersion === undefined
103
+ ? {}
104
+ : { sourceVersion: payload.source.sourceVersion }),
105
+ },
89
106
  digest,
90
107
  expectedPolicyHashes: payload.expectedPolicyHashes ?? [],
91
- ...(payload.counters === undefined ? {} : { counters: payload.counters }),
108
+ ...(payload.counters === undefined
109
+ ? {}
110
+ : { counters: payload.counters }),
92
111
  recordCounts: counts,
93
112
  edgesCreated: outcome.edgesCreated,
94
113
  edgesRefreshed: outcome.edgesRefreshed,
95
- status: "completed"
114
+ status: "completed",
96
115
  });
97
116
  this.rotateImportRuns(state, operator.tenantId);
98
- return { runId, importedAt: now, digest, recordCounts: counts, edgesCreated: outcome.edgesCreated, edgesRefreshed: outcome.edgesRefreshed };
117
+ return {
118
+ runId,
119
+ importedAt: now,
120
+ digest,
121
+ recordCounts: counts,
122
+ edgesCreated: outcome.edgesCreated,
123
+ edgesRefreshed: outcome.edgesRefreshed,
124
+ };
99
125
  });
100
126
  }
101
127
  async inspect(identityValue) {
@@ -114,23 +140,41 @@ export class LocalInventoryController {
114
140
  }
115
141
  const computed = computeFindings(state, operator.tenantId, now, this.freshnessDays);
116
142
  let resolved = 0;
117
- const appliedRemediations = state.remediations.filter((remediation) => remediation.tenantId === operator.tenantId && remediation.status === "applied");
143
+ const appliedRemediations = state.remediations.filter((remediation) => remediation.tenantId === operator.tenantId &&
144
+ remediation.status === "applied");
118
145
  const nextFindings = computed.map((finding) => {
119
146
  const prior = previous.get(findingKey(finding));
120
- const matchingRemediation = appliedRemediations.find((remediation) => remediation.findingId === finding.findingId && remediation.targetKind === finding.targetKind && remediation.targetId === finding.targetId);
147
+ const matchingRemediation = appliedRemediations.find((remediation) => remediation.findingId === finding.findingId &&
148
+ remediation.targetKind === finding.targetKind &&
149
+ remediation.targetId === finding.targetId);
121
150
  if (matchingRemediation !== undefined) {
122
151
  if (this.isRemediationEffective(state, operator.tenantId, matchingRemediation)) {
123
152
  resolved += 1;
124
- return { ...finding, status: "resolved", resolvedAt: matchingRemediation.appliedAt, remediationId: matchingRemediation.remediationId };
153
+ return {
154
+ ...finding,
155
+ status: "resolved",
156
+ resolvedAt: matchingRemediation.appliedAt,
157
+ remediationId: matchingRemediation.remediationId,
158
+ };
125
159
  }
126
160
  return finding;
127
161
  }
128
162
  if (prior !== undefined && prior.status === "resolved") {
129
- return { ...finding, status: "resolved", resolvedAt: prior.resolvedAt, ...(prior.remediationId === undefined ? {} : { remediationId: prior.remediationId }) };
163
+ return {
164
+ ...finding,
165
+ status: "resolved",
166
+ resolvedAt: prior.resolvedAt,
167
+ ...(prior.remediationId === undefined
168
+ ? {}
169
+ : { remediationId: prior.remediationId }),
170
+ };
130
171
  }
131
172
  return finding;
132
173
  });
133
- state.findings = [...state.findings.filter((finding) => finding.tenantId !== operator.tenantId), ...nextFindings];
174
+ state.findings = [
175
+ ...state.findings.filter((finding) => finding.tenantId !== operator.tenantId),
176
+ ...nextFindings,
177
+ ];
134
178
  this.assertStoreBounds(state);
135
179
  return { findings: nextFindings.length, resolved };
136
180
  });
@@ -165,18 +209,21 @@ export class LocalInventoryController {
165
209
  const proposal = validateRemediationProposal(input);
166
210
  const now = this.timestamp();
167
211
  return this.mutate((state) => {
168
- const finding = state.findings.find((candidate) => candidate.tenantId === operator.tenantId && candidate.findingId === proposal.findingId);
212
+ const finding = state.findings.find((candidate) => candidate.tenantId === operator.tenantId &&
213
+ candidate.findingId === proposal.findingId);
169
214
  if (finding === undefined)
170
215
  throw new InventoryError("Remediation finding was not found for the tenant.");
171
216
  if (finding.status === "resolved")
172
217
  throw new InventoryError("Cannot propose remediation for a resolved finding.");
173
- if (proposal.targetKind !== finding.targetKind || proposal.targetId !== finding.targetId)
218
+ if (proposal.targetKind !== finding.targetKind ||
219
+ proposal.targetId !== finding.targetId)
174
220
  throw new InventoryError("Remediation target does not match the finding target.");
175
221
  this.assertRemediationTargetExists(state, operator.tenantId, proposal.targetKind, proposal.targetId);
176
222
  if (proposal.kind === "reduce_scope") {
177
223
  if (proposal.targetKind !== "credential")
178
224
  throw new InventoryError("reduce_scope remediation only supports credential targets.");
179
- const credential = state.credentials.find((candidate) => candidate.tenantId === operator.tenantId && candidate.credentialId === proposal.targetId);
225
+ const credential = state.credentials.find((candidate) => candidate.tenantId === operator.tenantId &&
226
+ candidate.credentialId === proposal.targetId);
180
227
  if (credential === undefined)
181
228
  throw new InventoryError("Remediation target credential does not exist.");
182
229
  if (credential.status !== "active")
@@ -188,7 +235,9 @@ export class LocalInventoryController {
188
235
  if (reduced.length >= credential.grantScopes.length)
189
236
  throw new InventoryError("reduce_scope must remove at least one scope; permissions are never expanded.");
190
237
  }
191
- if (proposal.kind === "create_eval" && this.evalScenarioExists !== undefined && !this.evalScenarioExists(proposal.evalScenarioId))
238
+ if (proposal.kind === "create_eval" &&
239
+ this.evalScenarioExists !== undefined &&
240
+ !this.evalScenarioExists(proposal.evalScenarioId))
192
241
  throw new InventoryError("Eval scenario was not found for create_eval remediation.");
193
242
  if (state.remediations.length >= INVENTORY_LIMITS.maxRemediations)
194
243
  throw new InventoryError("Remediation limit was reached.");
@@ -205,7 +254,11 @@ export class LocalInventoryController {
205
254
  proposedBy: operator.principalId,
206
255
  createdAt: now,
207
256
  status: "proposed",
208
- ...(proposal.ownerId === undefined && proposal.reducedScopes === undefined && proposal.evalScenarioId === undefined ? {} : { result: remediationResult(proposal, operator.principalId) })
257
+ ...(proposal.ownerId === undefined &&
258
+ proposal.reducedScopes === undefined &&
259
+ proposal.evalScenarioId === undefined
260
+ ? {}
261
+ : { result: remediationResult(proposal, operator.principalId) }),
209
262
  };
210
263
  state.remediations.push(record);
211
264
  this.assertStoreBounds(state);
@@ -217,7 +270,8 @@ export class LocalInventoryController {
217
270
  const now = this.timestamp();
218
271
  const safeRemediationId = safeIdentifier(remediationId, "Remediation id");
219
272
  return this.mutate((state) => {
220
- const index = state.remediations.findIndex((candidate) => candidate.tenantId === operator.tenantId && candidate.remediationId === safeRemediationId);
273
+ const index = state.remediations.findIndex((candidate) => candidate.tenantId === operator.tenantId &&
274
+ candidate.remediationId === safeRemediationId);
221
275
  if (index === -1)
222
276
  throw new InventoryError("Remediation was not found for the tenant.");
223
277
  const remediation = state.remediations[index];
@@ -226,9 +280,14 @@ export class LocalInventoryController {
226
280
  if (remediation.status === "rejected" || remediation.status === "expired")
227
281
  throw new InventoryError(`Remediation is ${remediation.status} and cannot be applied.`);
228
282
  applyRemediationEffect(state, operator.tenantId, remediation, now);
229
- const applied = { ...remediation, status: "applied", appliedAt: now };
283
+ const applied = {
284
+ ...remediation,
285
+ status: "applied",
286
+ appliedAt: now,
287
+ };
230
288
  state.remediations[index] = applied;
231
- const finding = state.findings.find((candidate) => candidate.tenantId === operator.tenantId && candidate.findingId === remediation.findingId);
289
+ const finding = state.findings.find((candidate) => candidate.tenantId === operator.tenantId &&
290
+ candidate.findingId === remediation.findingId);
232
291
  if (finding !== undefined) {
233
292
  finding.status = "resolved";
234
293
  finding.resolvedAt = now;
@@ -240,10 +299,10 @@ export class LocalInventoryController {
240
299
  }
241
300
  async rejectRemediation(identityValue, remediationId) {
242
301
  const operator = await this.authorize(identityValue, "inventory.remediate");
243
- const now = this.timestamp();
244
302
  const safeRemediationId = safeIdentifier(remediationId, "Remediation id");
245
303
  return this.mutate((state) => {
246
- const index = state.remediations.findIndex((candidate) => candidate.tenantId === operator.tenantId && candidate.remediationId === safeRemediationId);
304
+ const index = state.remediations.findIndex((candidate) => candidate.tenantId === operator.tenantId &&
305
+ candidate.remediationId === safeRemediationId);
247
306
  if (index === -1)
248
307
  throw new InventoryError("Remediation was not found for the tenant.");
249
308
  const remediation = state.remediations[index];
@@ -266,13 +325,22 @@ export class LocalInventoryController {
266
325
  const tenantId = operator.tenantId;
267
326
  const snapshot = snapshotFor(state, tenantId, now, this.freshnessDays);
268
327
  const scenarioRefs = state.remediations
269
- .filter((remediation) => remediation.tenantId === tenantId && remediation.status === "applied" && remediation.kind === "create_eval" && remediation.result?.evalScenarioId !== undefined)
270
- .map((remediation) => ({ evalScenarioId: remediation.result.evalScenarioId, forFindingId: remediation.findingId, createdBy: remediation.proposedBy }));
328
+ .filter((remediation) => remediation.tenantId === tenantId &&
329
+ remediation.status === "applied" &&
330
+ remediation.kind === "create_eval" &&
331
+ remediation.result?.evalScenarioId !== undefined)
332
+ .map((remediation) => ({
333
+ evalScenarioId: remediation.result.evalScenarioId,
334
+ forFindingId: remediation.findingId,
335
+ createdBy: remediation.proposedBy,
336
+ }));
271
337
  const normalized = normalizeFreshnessDays(this.freshnessDays);
272
338
  const evidenceMetadata = snapshot.agents.map((agent) => ({
273
339
  agentId: agent.agentId,
274
340
  lastEvidenceAt: agent.lastEvidenceAt,
275
- status: agent.lastEvidenceAt === null ? "missing" : freshnessStatusFor(agent.lastEvidenceAt, now, normalized.evidenceFreshDays)
341
+ status: agent.lastEvidenceAt === null
342
+ ? "missing"
343
+ : freshnessStatusFor(agent.lastEvidenceAt, now, normalized.evidenceFreshDays),
276
344
  }));
277
345
  return {
278
346
  schemaVersion: 1,
@@ -284,7 +352,7 @@ export class LocalInventoryController {
284
352
  scenarioRefs,
285
353
  evidenceMetadata,
286
354
  removalAnalysis: computeRemovalAnalysis(state, tenantId, now, this.freshnessDays),
287
- roi: computeRoiReport(state, tenantId, now, this.freshnessDays)
355
+ roi: computeRoiReport(state, tenantId, now, this.freshnessDays),
288
356
  };
289
357
  }
290
358
  async authorize(identity, permission) {
@@ -300,7 +368,8 @@ export class LocalInventoryController {
300
368
  return value.toISOString();
301
369
  }
302
370
  upsertSource(state, tenantId, payload, now) {
303
- const existing = state.sources.find((source) => source.tenantId === tenantId && source.sourceId === payload.source.sourceId);
371
+ const existing = state.sources.find((source) => source.tenantId === tenantId &&
372
+ source.sourceId === payload.source.sourceId);
304
373
  if (existing === undefined) {
305
374
  if (state.sources.length >= INVENTORY_LIMITS.maxSources)
306
375
  throw new InventoryError("Import source limit was reached.");
@@ -309,10 +378,12 @@ export class LocalInventoryController {
309
378
  sourceId: payload.source.sourceId,
310
379
  sourceType: payload.source.sourceType,
311
380
  sourceName: payload.source.sourceName,
312
- ...(payload.source.sourceVersion === undefined ? {} : { sourceVersion: payload.source.sourceVersion }),
381
+ ...(payload.source.sourceVersion === undefined
382
+ ? {}
383
+ : { sourceVersion: payload.source.sourceVersion }),
313
384
  firstSeenAt: now,
314
385
  lastSeenAt: now,
315
- lastDigest: canonicalPayloadDigest(payload)
386
+ lastDigest: canonicalPayloadDigest(payload),
316
387
  });
317
388
  }
318
389
  else {
@@ -323,20 +394,40 @@ export class LocalInventoryController {
323
394
  }
324
395
  }
325
396
  assertRemediationTargetExists(state, tenantId, targetKind, targetId) {
326
- const exists = targetKind === "agent" ? state.agents.some((record) => record.tenantId === tenantId && record.agentId === targetId)
327
- : targetKind === "identity" ? state.identities.some((record) => record.tenantId === tenantId && record.identityId === targetId)
328
- : targetKind === "tool" ? state.tools.some((record) => record.tenantId === tenantId && record.toolId === targetId)
329
- : targetKind === "provider" ? state.providers.some((record) => record.tenantId === tenantId && record.providerId === targetId)
330
- : targetKind === "resource" ? state.resources.some((record) => record.tenantId === tenantId && record.resourceId === targetId)
331
- : targetKind === "side_effect" ? state.sideEffects.some((record) => record.tenantId === tenantId && record.sideEffectId === targetId)
332
- : targetKind === "credential" ? state.credentials.some((record) => record.tenantId === tenantId && record.credentialId === targetId)
333
- : targetKind === "environment" ? (state.agents.some((record) => record.tenantId === tenantId && record.environmentIds.includes(targetId)) ||
334
- state.identities.some((record) => record.tenantId === tenantId && record.environmentIds.includes(targetId)) ||
335
- state.providers.some((record) => record.tenantId === tenantId && record.environmentIds.includes(targetId)) ||
336
- state.resources.some((record) => record.tenantId === tenantId && record.environmentIds.includes(targetId)) ||
337
- state.credentials.some((record) => record.tenantId === tenantId && record.environmentIds.includes(targetId)) ||
338
- state.policies.some((record) => record.tenantId === tenantId && record.environmentIds.includes(targetId)))
339
- : targetKind === "policy" ? state.policies.some((record) => record.tenantId === tenantId && record.policyId === targetId)
397
+ const exists = targetKind === "agent"
398
+ ? state.agents.some((record) => record.tenantId === tenantId && record.agentId === targetId)
399
+ : targetKind === "identity"
400
+ ? state.identities.some((record) => record.tenantId === tenantId && record.identityId === targetId)
401
+ : targetKind === "tool"
402
+ ? state.tools.some((record) => record.tenantId === tenantId && record.toolId === targetId)
403
+ : targetKind === "provider"
404
+ ? state.providers.some((record) => record.tenantId === tenantId &&
405
+ record.providerId === targetId)
406
+ : targetKind === "resource"
407
+ ? state.resources.some((record) => record.tenantId === tenantId &&
408
+ record.resourceId === targetId)
409
+ : targetKind === "side_effect"
410
+ ? state.sideEffects.some((record) => record.tenantId === tenantId &&
411
+ record.sideEffectId === targetId)
412
+ : targetKind === "credential"
413
+ ? state.credentials.some((record) => record.tenantId === tenantId &&
414
+ record.credentialId === targetId)
415
+ : targetKind === "environment"
416
+ ? state.agents.some((record) => record.tenantId === tenantId &&
417
+ record.environmentIds.includes(targetId)) ||
418
+ state.identities.some((record) => record.tenantId === tenantId &&
419
+ record.environmentIds.includes(targetId)) ||
420
+ state.providers.some((record) => record.tenantId === tenantId &&
421
+ record.environmentIds.includes(targetId)) ||
422
+ state.resources.some((record) => record.tenantId === tenantId &&
423
+ record.environmentIds.includes(targetId)) ||
424
+ state.credentials.some((record) => record.tenantId === tenantId &&
425
+ record.environmentIds.includes(targetId)) ||
426
+ state.policies.some((record) => record.tenantId === tenantId &&
427
+ record.environmentIds.includes(targetId))
428
+ : targetKind === "policy"
429
+ ? state.policies.some((record) => record.tenantId === tenantId &&
430
+ record.policyId === targetId)
340
431
  : false;
341
432
  if (!exists)
342
433
  throw new InventoryError(`Remediation target ${targetKind} ${targetId} does not exist for the tenant.`);
@@ -350,21 +441,26 @@ export class LocalInventoryController {
350
441
  pruneStaleEdges(state, tenantId, now) {
351
442
  const normalized = normalizeFreshnessDays(this.freshnessDays);
352
443
  const staleBeforeMs = Date.parse(now) - normalized.edgeStaleDays * 24 * 60 * 60 * 1000;
353
- state.edges = state.edges.filter((edge) => edge.tenantId !== tenantId || Date.parse(edge.lastSeenAt) >= staleBeforeMs);
444
+ state.edges = state.edges.filter((edge) => edge.tenantId !== tenantId ||
445
+ Date.parse(edge.lastSeenAt) >= staleBeforeMs);
354
446
  }
355
447
  rotateImportRuns(state, tenantId) {
356
448
  const tenantRuns = state.importRuns.filter((run) => run.tenantId === tenantId);
357
449
  if (tenantRuns.length <= INVENTORY_LIMITS.maxImports)
358
450
  return;
359
451
  const excess = tenantRuns.length - INVENTORY_LIMITS.maxImports;
360
- const oldestRunIds = new Set([...tenantRuns].sort((a, b) => Date.parse(a.importedAt) - Date.parse(b.importedAt)).slice(0, excess).map((run) => run.runId));
452
+ const oldestRunIds = new Set([...tenantRuns]
453
+ .sort((a, b) => Date.parse(a.importedAt) - Date.parse(b.importedAt))
454
+ .slice(0, excess)
455
+ .map((run) => run.runId));
361
456
  state.importRuns = state.importRuns.filter((run) => !oldestRunIds.has(run.runId));
362
457
  }
363
458
  isRemediationEffective(state, tenantId, remediation) {
364
459
  if (remediation.kind === "reduce_scope") {
365
460
  if (remediation.targetKind !== "credential")
366
461
  return false;
367
- const credential = state.credentials.find((candidate) => candidate.tenantId === tenantId && candidate.credentialId === remediation.targetId);
462
+ const credential = state.credentials.find((candidate) => candidate.tenantId === tenantId &&
463
+ candidate.credentialId === remediation.targetId);
368
464
  const reduced = remediation.result?.reducedScopes;
369
465
  if (credential === undefined || reduced === undefined)
370
466
  return false;
@@ -373,7 +469,8 @@ export class LocalInventoryController {
373
469
  if (remediation.kind === "revoke") {
374
470
  if (remediation.targetKind !== "credential")
375
471
  return false;
376
- const credential = state.credentials.find((candidate) => candidate.tenantId === tenantId && candidate.credentialId === remediation.targetId);
472
+ const credential = state.credentials.find((candidate) => candidate.tenantId === tenantId &&
473
+ candidate.credentialId === remediation.targetId);
377
474
  return credential !== undefined && credential.status === "revoked";
378
475
  }
379
476
  if (remediation.kind === "assign_owner") {
@@ -381,19 +478,23 @@ export class LocalInventoryController {
381
478
  if (ownerId === undefined)
382
479
  return false;
383
480
  if (remediation.targetKind === "agent") {
384
- const record = state.agents.find((candidate) => candidate.tenantId === tenantId && candidate.agentId === remediation.targetId);
481
+ const record = state.agents.find((candidate) => candidate.tenantId === tenantId &&
482
+ candidate.agentId === remediation.targetId);
385
483
  return record !== undefined && record.ownerId === ownerId;
386
484
  }
387
485
  if (remediation.targetKind === "provider") {
388
- const record = state.providers.find((candidate) => candidate.tenantId === tenantId && candidate.providerId === remediation.targetId);
486
+ const record = state.providers.find((candidate) => candidate.tenantId === tenantId &&
487
+ candidate.providerId === remediation.targetId);
389
488
  return record !== undefined && record.ownerId === ownerId;
390
489
  }
391
490
  if (remediation.targetKind === "resource") {
392
- const record = state.resources.find((candidate) => candidate.tenantId === tenantId && candidate.resourceId === remediation.targetId);
491
+ const record = state.resources.find((candidate) => candidate.tenantId === tenantId &&
492
+ candidate.resourceId === remediation.targetId);
393
493
  return record !== undefined && record.ownerId === ownerId;
394
494
  }
395
495
  if (remediation.targetKind === "credential") {
396
- const record = state.credentials.find((candidate) => candidate.tenantId === tenantId && candidate.credentialId === remediation.targetId);
496
+ const record = state.credentials.find((candidate) => candidate.tenantId === tenantId &&
497
+ candidate.credentialId === remediation.targetId);
397
498
  return record !== undefined && record.ownerId === ownerId;
398
499
  }
399
500
  return false;
@@ -401,14 +502,17 @@ export class LocalInventoryController {
401
502
  if (remediation.kind === "onboard_through_gateway") {
402
503
  if (remediation.targetKind !== "agent")
403
504
  return false;
404
- const record = state.agents.find((candidate) => candidate.tenantId === tenantId && candidate.agentId === remediation.targetId);
505
+ const record = state.agents.find((candidate) => candidate.tenantId === tenantId &&
506
+ candidate.agentId === remediation.targetId);
405
507
  return record !== undefined && record.gatewayManaged === true;
406
508
  }
407
509
  if (remediation.kind === "create_eval") {
408
510
  const scenarioId = remediation.result?.evalScenarioId;
409
511
  if (scenarioId === undefined)
410
512
  return false;
411
- return this.evalScenarioExists === undefined ? true : this.evalScenarioExists(scenarioId);
513
+ return this.evalScenarioExists === undefined
514
+ ? true
515
+ : this.evalScenarioExists(scenarioId);
412
516
  }
413
517
  return false;
414
518
  }
@@ -444,41 +548,57 @@ export function createLocalInventoryController(options = {}) {
444
548
  return new LocalInventoryController(options);
445
549
  }
446
550
  export function createDisabledInventoryOperatorAuthorizer() {
447
- return { async authenticate() { throw new InventoryError("Inventory operator authorization is not configured."); } };
551
+ return {
552
+ async authenticate() {
553
+ throw new InventoryError("Inventory operator authorization is not configured.");
554
+ },
555
+ };
448
556
  }
449
557
  export function createTestInventoryOperatorAuthorizer() {
450
558
  const issued = new WeakSet();
451
559
  return {
452
560
  authorizer: {
453
561
  async authenticate(identity) {
454
- if (identity === null || typeof identity !== "object" || !issued.has(identity))
562
+ if (identity === null ||
563
+ typeof identity !== "object" ||
564
+ !issued.has(identity))
455
565
  throw new InventoryError("Inventory operator identity is not authenticated.");
456
566
  return validateOperator(identity);
457
- }
567
+ },
458
568
  },
459
569
  issue(input) {
460
570
  const operator = Object.freeze(validateOperator(input));
461
571
  issued.add(operator);
462
572
  return operator;
463
- }
573
+ },
464
574
  };
465
575
  }
466
576
  export function formatImportSummary(summary) {
467
577
  return `Inventory import ${summary.runId} at ${summary.importedAt} (digest ${summary.digest.slice(0, 16)}...) added ${summary.recordCounts.agents} agents, ${summary.recordCounts.tools} tools, ${summary.recordCounts.identities} identities, ${summary.recordCounts.providers} providers, ${summary.recordCounts.resources} resources, ${summary.recordCounts.sideEffects} side effects, ${summary.recordCounts.credentials} credentials, ${summary.recordCounts.policies} policies; edges created ${summary.edgesCreated}, refreshed ${summary.edgesRefreshed}.`;
468
578
  }
469
579
  export function formatAttackPathReport(report) {
470
- const lines = [`Attack paths for agent ${report.agentId}: ${report.paths.length} path(s), ${report.incompletePaths} incomplete`];
580
+ const lines = [
581
+ `Attack paths for agent ${report.agentId}: ${report.paths.length} path(s), ${report.incompletePaths} incomplete`,
582
+ ];
471
583
  for (const path of report.paths) {
472
- const chain = path.steps.map((step) => `${step.kind}:${step.id}`).join(" -> ");
473
- const edgeNotes = path.edges.length === 0 ? " (no persisted edges)" : ` [edges ${path.edges.map((edge) => `${edge.sourceId}->${edge.targetId} (${edge.relation})`).join(", ")}]`;
584
+ const chain = path.steps
585
+ .map((step) => `${step.kind}:${step.id}`)
586
+ .join(" -> ");
587
+ const edgeNotes = path.edges.length === 0
588
+ ? " (no persisted edges)"
589
+ : ` [edges ${path.edges.map((edge) => `${edge.sourceId}->${edge.targetId} (${edge.relation})`).join(", ")}]`;
474
590
  lines.push(` ${path.complete ? "complete" : "INCOMPLETE"} ${chain}${edgeNotes}`);
475
591
  }
476
592
  return lines.join("\n");
477
593
  }
478
594
  export function formatBlastRadiusReport(report) {
479
- const lines = [`Blast radius for agent ${report.agentId}: ${report.resources.length} reachable resource(s), ${report.incompletePaths} incomplete path(s)`];
595
+ const lines = [
596
+ `Blast radius for agent ${report.agentId}: ${report.resources.length} reachable resource(s), ${report.incompletePaths} incomplete path(s)`,
597
+ ];
480
598
  for (const resource of report.resources) {
481
- const effects = resource.sideEffects.map((sideEffect) => `${sideEffect.kind} (${sideEffect.severity})`).join(", ") || "none";
599
+ const effects = resource.sideEffects
600
+ .map((sideEffect) => `${sideEffect.kind} (${sideEffect.severity})`)
601
+ .join(", ") || "none";
482
602
  lines.push(` resource ${resource.resourceId} via provider ${resource.providerId}: ${effects}${resource.sensitive ? " [sensitive]" : ""} (${resource.pathCount} complete path(s))`);
483
603
  }
484
604
  lines.push(" Rules:");
@@ -488,7 +608,9 @@ export function formatBlastRadiusReport(report) {
488
608
  return lines.join("\n");
489
609
  }
490
610
  export function formatRoiReport(report) {
491
- const lines = [`ROI report for tenant ${report.tenantId} at ${report.generatedAt} (${report.basis})`];
611
+ const lines = [
612
+ `ROI report for tenant ${report.tenantId} at ${report.generatedAt} (${report.basis})`,
613
+ ];
492
614
  lines.push(` Active agents: ${report.activeAgentCount}; production providers: ${report.productionProviderCount}`);
493
615
  lines.push(` Incidents: prevented egress attempts ${report.incidents.preventedEgressAttempts}${report.incidents.preventedEgressSources.length === 0 ? " (not measured)" : ""}`);
494
616
  lines.push(` Review time: ${report.incidents.reviewTimeMinutes === null ? "not measured" : `${report.incidents.reviewTimeMinutes} minutes`}`);
@@ -500,7 +622,9 @@ export function formatRoiReport(report) {
500
622
  return lines.join("\n");
501
623
  }
502
624
  export function formatRemovalAnalysis(report) {
503
- const lines = [`Removal analysis for tenant ${report.tenantId} at ${report.generatedAt} (${report.basis})`];
625
+ const lines = [
626
+ `Removal analysis for tenant ${report.tenantId} at ${report.generatedAt} (${report.basis})`,
627
+ ];
504
628
  const coverage = report.coverage;
505
629
  lines.push(` Coverage: policy ${coverage.policyCoverageBps} bps, evidence ${coverage.evidenceCoverageBps} bps, gateway ${coverage.gatewayCoverageBps} bps, kill switch ${coverage.killSwitchCoverageBps} bps`);
506
630
  lines.push(` Direct credentials: ${coverage.directCredentials}; outside-gateway agents: ${coverage.outsideGatewayAgents}; unowned production: ${coverage.unownedProduction}`);
@@ -541,20 +665,28 @@ class ImportedRecords {
541
665
  function validateCrossReferences(state, tenantId, imported) {
542
666
  const has = (kind, id) => {
543
667
  if (kind === "agent")
544
- return imported.agentIds.has(id) || state.agents.some((record) => record.tenantId === tenantId && record.agentId === id);
668
+ return (imported.agentIds.has(id) ||
669
+ state.agents.some((record) => record.tenantId === tenantId && record.agentId === id));
545
670
  if (kind === "tool")
546
- return imported.toolIds.has(id) || state.tools.some((record) => record.tenantId === tenantId && record.toolId === id);
671
+ return (imported.toolIds.has(id) ||
672
+ state.tools.some((record) => record.tenantId === tenantId && record.toolId === id));
547
673
  if (kind === "identity")
548
- return imported.identityIds.has(id) || state.identities.some((record) => record.tenantId === tenantId && record.identityId === id);
674
+ return (imported.identityIds.has(id) ||
675
+ state.identities.some((record) => record.tenantId === tenantId && record.identityId === id));
549
676
  if (kind === "provider")
550
- return imported.providerIds.has(id) || state.providers.some((record) => record.tenantId === tenantId && record.providerId === id);
677
+ return (imported.providerIds.has(id) ||
678
+ state.providers.some((record) => record.tenantId === tenantId && record.providerId === id));
551
679
  if (kind === "resource")
552
- return imported.resourceIds.has(id) || state.resources.some((record) => record.tenantId === tenantId && record.resourceId === id);
680
+ return (imported.resourceIds.has(id) ||
681
+ state.resources.some((record) => record.tenantId === tenantId && record.resourceId === id));
553
682
  if (kind === "side_effect")
554
- return imported.sideEffectIds.has(id) || state.sideEffects.some((record) => record.tenantId === tenantId && record.sideEffectId === id);
683
+ return (imported.sideEffectIds.has(id) ||
684
+ state.sideEffects.some((record) => record.tenantId === tenantId && record.sideEffectId === id));
555
685
  if (kind === "credential")
556
- return imported.credentialIds.has(id) || state.credentials.some((record) => record.tenantId === tenantId && record.credentialId === id);
557
- return imported.policyIds.has(id) || state.policies.some((record) => record.tenantId === tenantId && record.policyId === id);
686
+ return (imported.credentialIds.has(id) ||
687
+ state.credentials.some((record) => record.tenantId === tenantId && record.credentialId === id));
688
+ return (imported.policyIds.has(id) ||
689
+ state.policies.some((record) => record.tenantId === tenantId && record.policyId === id));
558
690
  };
559
691
  for (const agent of importedAgents(state, tenantId)) {
560
692
  for (const identityId of agent.identityIds)
@@ -596,25 +728,62 @@ function validateCrossReferences(state, tenantId, imported) {
596
728
  }
597
729
  }
598
730
  function importedAgents(state, tenantId) {
599
- return state.agents.filter((record) => record.tenantId === tenantId).map((record) => ({ agentId: record.agentId, identityIds: record.identityIds }));
731
+ return state.agents
732
+ .filter((record) => record.tenantId === tenantId)
733
+ .map((record) => ({
734
+ agentId: record.agentId,
735
+ identityIds: record.identityIds,
736
+ }));
600
737
  }
601
738
  function importedIdentities(state, tenantId) {
602
- return state.identities.filter((record) => record.tenantId === tenantId).map((record) => ({ identityId: record.identityId, toolIds: record.toolIds }));
739
+ return state.identities
740
+ .filter((record) => record.tenantId === tenantId)
741
+ .map((record) => ({
742
+ identityId: record.identityId,
743
+ toolIds: record.toolIds,
744
+ }));
603
745
  }
604
746
  function importedTools(state, tenantId) {
605
- return state.tools.filter((record) => record.tenantId === tenantId).map((record) => ({ toolId: record.toolId, providerId: record.providerId, credentialIds: record.credentialIds }));
747
+ return state.tools
748
+ .filter((record) => record.tenantId === tenantId)
749
+ .map((record) => ({
750
+ toolId: record.toolId,
751
+ providerId: record.providerId,
752
+ credentialIds: record.credentialIds,
753
+ }));
606
754
  }
607
755
  function importedProviders(state, tenantId) {
608
- return state.providers.filter((record) => record.tenantId === tenantId).map((record) => ({ providerId: record.providerId, resourceIds: record.resourceIds }));
756
+ return state.providers
757
+ .filter((record) => record.tenantId === tenantId)
758
+ .map((record) => ({
759
+ providerId: record.providerId,
760
+ resourceIds: record.resourceIds,
761
+ }));
609
762
  }
610
763
  function importedResources(state, tenantId) {
611
- return state.resources.filter((record) => record.tenantId === tenantId).map((record) => ({ resourceId: record.resourceId, sideEffectIds: record.sideEffectIds }));
764
+ return state.resources
765
+ .filter((record) => record.tenantId === tenantId)
766
+ .map((record) => ({
767
+ resourceId: record.resourceId,
768
+ sideEffectIds: record.sideEffectIds,
769
+ }));
612
770
  }
613
771
  function importedSideEffects(state, tenantId) {
614
- return state.sideEffects.filter((record) => record.tenantId === tenantId).map((record) => ({ sideEffectId: record.sideEffectId, resourceId: record.resourceId }));
772
+ return state.sideEffects
773
+ .filter((record) => record.tenantId === tenantId)
774
+ .map((record) => ({
775
+ sideEffectId: record.sideEffectId,
776
+ resourceId: record.resourceId,
777
+ }));
615
778
  }
616
779
  function importedCredentials(state, tenantId) {
617
- return state.credentials.filter((record) => record.tenantId === tenantId).map((record) => ({ credentialId: record.credentialId, providerId: record.providerId, toolIds: record.toolIds }));
780
+ return state.credentials
781
+ .filter((record) => record.tenantId === tenantId)
782
+ .map((record) => ({
783
+ credentialId: record.credentialId,
784
+ providerId: record.providerId,
785
+ toolIds: record.toolIds,
786
+ }));
618
787
  }
619
788
  function upsertAgent(state, tenantId, input, payload, importedBy, now) {
620
789
  const record = {
@@ -624,7 +793,9 @@ function upsertAgent(state, tenantId, input, payload, importedBy, now) {
624
793
  agentId: input.agentId,
625
794
  name: input.name,
626
795
  ownerId: input.ownerId === undefined ? null : input.ownerId,
627
- ...(input.businessPurpose === undefined ? {} : { businessPurpose: input.businessPurpose }),
796
+ ...(input.businessPurpose === undefined
797
+ ? {}
798
+ : { businessPurpose: input.businessPurpose }),
628
799
  ...(input.model === undefined ? {} : { model: input.model }),
629
800
  ...(input.runtime === undefined ? {} : { runtime: input.runtime }),
630
801
  ...(input.version === undefined ? {} : { version: input.version }),
@@ -632,7 +803,7 @@ function upsertAgent(state, tenantId, input, payload, importedBy, now) {
632
803
  environmentIds: input.environmentIds,
633
804
  gatewayManaged: input.gatewayManaged,
634
805
  killSwitchEnabled: input.killSwitchEnabled,
635
- lastEvidenceAt: input.lastEvidenceAt === undefined ? null : input.lastEvidenceAt
806
+ lastEvidenceAt: input.lastEvidenceAt === undefined ? null : input.lastEvidenceAt,
636
807
  };
637
808
  merge(state, "agent", input.agentId, tenantId, record, payload.source, importedBy, now);
638
809
  }
@@ -645,7 +816,7 @@ function upsertTool(state, tenantId, input, payload, importedBy, now) {
645
816
  kind: input.kind,
646
817
  providerId: input.providerId === undefined ? null : input.providerId,
647
818
  requiredScopes: input.requiredScopes,
648
- credentialIds: input.credentialIds
819
+ credentialIds: input.credentialIds,
649
820
  };
650
821
  merge(state, "tool", input.toolId, tenantId, record, payload.source, importedBy, now);
651
822
  }
@@ -660,7 +831,7 @@ function upsertIdentity(state, tenantId, input, payload, importedBy, now) {
660
831
  agentId: input.agentId === undefined ? null : input.agentId,
661
832
  toolIds: input.toolIds,
662
833
  environmentIds: input.environmentIds,
663
- scopes: input.scopes
834
+ scopes: input.scopes,
664
835
  };
665
836
  merge(state, "identity", input.identityId, tenantId, record, payload.source, importedBy, now);
666
837
  }
@@ -676,7 +847,7 @@ function upsertProvider(state, tenantId, input, payload, importedBy, now) {
676
847
  environmentIds: input.environmentIds,
677
848
  resourceIds: input.resourceIds,
678
849
  gatewayBound: input.gatewayBound,
679
- killSwitchApplied: input.killSwitchApplied
850
+ killSwitchApplied: input.killSwitchApplied,
680
851
  };
681
852
  merge(state, "provider", input.providerId, tenantId, record, payload.source, importedBy, now);
682
853
  }
@@ -692,8 +863,10 @@ function upsertResource(state, tenantId, input, payload, importedBy, now) {
692
863
  environmentIds: input.environmentIds,
693
864
  sensitive: input.sensitive,
694
865
  ownerId: input.ownerId === undefined ? null : input.ownerId,
695
- ...(input.businessPurpose === undefined ? {} : { businessPurpose: input.businessPurpose }),
696
- sideEffectIds: input.sideEffectIds
866
+ ...(input.businessPurpose === undefined
867
+ ? {}
868
+ : { businessPurpose: input.businessPurpose }),
869
+ sideEffectIds: input.sideEffectIds,
697
870
  };
698
871
  merge(state, "resource", input.resourceId, tenantId, record, payload.source, importedBy, now);
699
872
  }
@@ -704,7 +877,7 @@ function upsertSideEffect(state, tenantId, input, payload, importedBy, now) {
704
877
  sideEffectId: input.sideEffectId,
705
878
  resourceId: input.resourceId,
706
879
  kind: input.kind,
707
- severity: input.severity
880
+ severity: input.severity,
708
881
  };
709
882
  merge(state, "side_effect", input.sideEffectId, tenantId, record, payload.source, importedBy, now);
710
883
  }
@@ -724,7 +897,7 @@ function upsertCredential(state, tenantId, input, payload, importedBy, now) {
724
897
  issuedAt: input.issuedAt,
725
898
  lastUsedAt: input.lastUsedAt === undefined ? null : input.lastUsedAt,
726
899
  ...(input.rotatedAt === undefined ? {} : { rotatedAt: input.rotatedAt }),
727
- gatewayBound: input.gatewayBound
900
+ gatewayBound: input.gatewayBound,
728
901
  };
729
902
  merge(state, "credential", input.credentialId, tenantId, record, payload.source, importedBy, now);
730
903
  }
@@ -737,7 +910,7 @@ function upsertPolicy(state, tenantId, input, payload, importedBy, now) {
737
910
  name: input.name,
738
911
  version: input.version,
739
912
  hash: input.hash,
740
- environmentIds: input.environmentIds
913
+ environmentIds: input.environmentIds,
741
914
  };
742
915
  merge(state, "policy", input.policyId, tenantId, record, payload.source, importedBy, now);
743
916
  }
@@ -754,7 +927,10 @@ function merge(state, kind, id, tenantId, record, source, importedBy, now) {
754
927
  }
755
928
  else {
756
929
  const prior = list[index];
757
- const mergedFreshness = { firstSeenAt: prior.freshness?.firstSeenAt ?? now, lastSeenAt: now };
930
+ const mergedFreshness = {
931
+ firstSeenAt: prior.freshness?.firstSeenAt ?? now,
932
+ lastSeenAt: now,
933
+ };
758
934
  list[index] = { ...stored, freshness: mergedFreshness, provenance };
759
935
  }
760
936
  }
@@ -823,11 +999,22 @@ function upsertEdges(state, tenantId, upserted, payload, importedBy, now, before
823
999
  const edgeId = edgeIdFor(sourceKind, sourceId, targetKind, targetId, relation);
824
1000
  const prior = before.get(edgeId);
825
1001
  if (prior === undefined) {
826
- state.edges.push({ tenantId, edgeId, sourceKind, sourceId, targetKind, targetId, relation, provenance, firstSeenAt: now, lastSeenAt: now });
1002
+ state.edges.push({
1003
+ tenantId,
1004
+ edgeId,
1005
+ sourceKind,
1006
+ sourceId,
1007
+ targetKind,
1008
+ targetId,
1009
+ relation,
1010
+ provenance,
1011
+ firstSeenAt: now,
1012
+ lastSeenAt: now,
1013
+ });
827
1014
  edgesCreated += 1;
828
1015
  }
829
1016
  else {
830
- state.edges = state.edges.map((edge) => (edge.edgeId === edgeId ? { ...edge, lastSeenAt: now } : edge));
1017
+ state.edges = state.edges.map((edge) => edge.edgeId === edgeId ? { ...edge, lastSeenAt: now } : edge);
831
1018
  edgesRefreshed += 1;
832
1019
  }
833
1020
  };
@@ -870,7 +1057,20 @@ function upsertEdges(state, tenantId, upserted, payload, importedBy, now, before
870
1057
  apply("resource", resource.resourceId, "side_effect", sideEffectId, "permits");
871
1058
  }
872
1059
  }
873
- return { recordCounts: { agents: 0, tools: 0, identities: 0, providers: 0, resources: 0, sideEffects: 0, credentials: 0, policies: 0 }, edgesCreated, edgesRefreshed };
1060
+ return {
1061
+ recordCounts: {
1062
+ agents: 0,
1063
+ tools: 0,
1064
+ identities: 0,
1065
+ providers: 0,
1066
+ resources: 0,
1067
+ sideEffects: 0,
1068
+ credentials: 0,
1069
+ policies: 0,
1070
+ },
1071
+ edgesCreated,
1072
+ edgesRefreshed,
1073
+ };
874
1074
  }
875
1075
  function snapshotFor(state, tenantId, now, freshnessDays) {
876
1076
  return {
@@ -889,7 +1089,7 @@ function snapshotFor(state, tenantId, now, freshnessDays) {
889
1089
  findings: clone(state.findings.filter((record) => record.tenantId === tenantId)),
890
1090
  remediations: clone(state.remediations.filter((record) => record.tenantId === tenantId)),
891
1091
  sources: clone(state.sources.filter((record) => record.tenantId === tenantId)),
892
- importRuns: clone(state.importRuns.filter((record) => record.tenantId === tenantId))
1092
+ importRuns: clone(state.importRuns.filter((record) => record.tenantId === tenantId)),
893
1093
  };
894
1094
  }
895
1095
  function applyRemediationEffect(state, tenantId, remediation, now) {
@@ -898,25 +1098,29 @@ function applyRemediationEffect(state, tenantId, remediation, now) {
898
1098
  if (result === undefined || result.ownerId === undefined)
899
1099
  throw new InventoryError("assign_owner remediation requires an owner id.");
900
1100
  if (remediation.targetKind === "agent") {
901
- const record = state.agents.find((candidate) => candidate.tenantId === tenantId && candidate.agentId === remediation.targetId);
1101
+ const record = state.agents.find((candidate) => candidate.tenantId === tenantId &&
1102
+ candidate.agentId === remediation.targetId);
902
1103
  if (record === undefined)
903
1104
  throw new InventoryError("Remediation target agent does not exist.");
904
1105
  record.ownerId = result.ownerId;
905
1106
  }
906
1107
  else if (remediation.targetKind === "provider") {
907
- const record = state.providers.find((candidate) => candidate.tenantId === tenantId && candidate.providerId === remediation.targetId);
1108
+ const record = state.providers.find((candidate) => candidate.tenantId === tenantId &&
1109
+ candidate.providerId === remediation.targetId);
908
1110
  if (record === undefined)
909
1111
  throw new InventoryError("Remediation target provider does not exist.");
910
1112
  record.ownerId = result.ownerId;
911
1113
  }
912
1114
  else if (remediation.targetKind === "resource") {
913
- const record = state.resources.find((candidate) => candidate.tenantId === tenantId && candidate.resourceId === remediation.targetId);
1115
+ const record = state.resources.find((candidate) => candidate.tenantId === tenantId &&
1116
+ candidate.resourceId === remediation.targetId);
914
1117
  if (record === undefined)
915
1118
  throw new InventoryError("Remediation target resource does not exist.");
916
1119
  record.ownerId = result.ownerId;
917
1120
  }
918
1121
  else if (remediation.targetKind === "credential") {
919
- const record = state.credentials.find((candidate) => candidate.tenantId === tenantId && candidate.credentialId === remediation.targetId);
1122
+ const record = state.credentials.find((candidate) => candidate.tenantId === tenantId &&
1123
+ candidate.credentialId === remediation.targetId);
920
1124
  if (record === undefined)
921
1125
  throw new InventoryError("Remediation target credential does not exist.");
922
1126
  record.ownerId = result.ownerId;
@@ -930,7 +1134,8 @@ function applyRemediationEffect(state, tenantId, remediation, now) {
930
1134
  throw new InventoryError("reduce_scope remediation only supports credential targets.");
931
1135
  if (result === undefined || result.reducedScopes === undefined)
932
1136
  throw new InventoryError("reduce_scope remediation requires the reduced scope list.");
933
- const record = state.credentials.find((candidate) => candidate.tenantId === tenantId && candidate.credentialId === remediation.targetId);
1137
+ const record = state.credentials.find((candidate) => candidate.tenantId === tenantId &&
1138
+ candidate.credentialId === remediation.targetId);
934
1139
  if (record === undefined)
935
1140
  throw new InventoryError("Remediation target credential does not exist.");
936
1141
  if (record.status !== "active")
@@ -947,7 +1152,8 @@ function applyRemediationEffect(state, tenantId, remediation, now) {
947
1152
  else if (remediation.kind === "revoke") {
948
1153
  if (remediation.targetKind !== "credential")
949
1154
  throw new InventoryError("revoke remediation only supports credential targets.");
950
- const record = state.credentials.find((candidate) => candidate.tenantId === tenantId && candidate.credentialId === remediation.targetId);
1155
+ const record = state.credentials.find((candidate) => candidate.tenantId === tenantId &&
1156
+ candidate.credentialId === remediation.targetId);
951
1157
  if (record === undefined)
952
1158
  throw new InventoryError("Remediation target credential does not exist.");
953
1159
  record.status = "revoked";
@@ -955,7 +1161,8 @@ function applyRemediationEffect(state, tenantId, remediation, now) {
955
1161
  else if (remediation.kind === "onboard_through_gateway") {
956
1162
  if (remediation.targetKind !== "agent")
957
1163
  throw new InventoryError("onboard_through_gateway remediation only supports agent targets.");
958
- const record = state.agents.find((candidate) => candidate.tenantId === tenantId && candidate.agentId === remediation.targetId);
1164
+ const record = state.agents.find((candidate) => candidate.tenantId === tenantId &&
1165
+ candidate.agentId === remediation.targetId);
959
1166
  if (record === undefined)
960
1167
  throw new InventoryError("Remediation target agent does not exist.");
961
1168
  record.gatewayManaged = true;
@@ -966,7 +1173,8 @@ function applyRemediationEffect(state, tenantId, remediation, now) {
966
1173
  throw new InventoryError("create_eval remediation only supports agent targets.");
967
1174
  if (result === undefined || result.evalScenarioId === undefined)
968
1175
  throw new InventoryError("create_eval remediation requires an eval scenario id.");
969
- if (!state.agents.some((record) => record.tenantId === tenantId && record.agentId === remediation.targetId))
1176
+ if (!state.agents.some((record) => record.tenantId === tenantId &&
1177
+ record.agentId === remediation.targetId))
970
1178
  throw new InventoryError("Remediation target agent does not exist.");
971
1179
  }
972
1180
  }
@@ -974,25 +1182,54 @@ function remediationResult(proposal, proposedBy) {
974
1182
  return {
975
1183
  description: `Proposed by ${proposedBy}`,
976
1184
  ...(proposal.ownerId === undefined ? {} : { ownerId: proposal.ownerId }),
977
- ...(proposal.reducedScopes === undefined ? {} : { reducedScopes: proposal.reducedScopes }),
978
- ...(proposal.evalScenarioId === undefined ? {} : { evalScenarioId: proposal.evalScenarioId })
1185
+ ...(proposal.reducedScopes === undefined
1186
+ ? {}
1187
+ : { reducedScopes: proposal.reducedScopes }),
1188
+ ...(proposal.evalScenarioId === undefined
1189
+ ? {}
1190
+ : { evalScenarioId: proposal.evalScenarioId }),
979
1191
  };
980
1192
  }
981
1193
  function validateRemediationProposal(value) {
982
1194
  const proposal = object(value, "Remediation proposal must be an object.");
983
- exactKeys(proposal, ["findingId", "kind", "targetKind", "targetId", "rationale", "ownerId", "reducedScopes", "evalScenarioId"], "remediation proposal", ["ownerId", "reducedScopes", "evalScenarioId"]);
1195
+ exactKeys(proposal, [
1196
+ "findingId",
1197
+ "kind",
1198
+ "targetKind",
1199
+ "targetId",
1200
+ "rationale",
1201
+ "ownerId",
1202
+ "reducedScopes",
1203
+ "evalScenarioId",
1204
+ ], "remediation proposal", ["ownerId", "reducedScopes", "evalScenarioId"]);
984
1205
  const kind = proposal.kind;
985
- if (!["assign_owner", "reduce_scope", "revoke", "onboard_through_gateway", "create_eval"].includes(kind))
1206
+ if (![
1207
+ "assign_owner",
1208
+ "reduce_scope",
1209
+ "revoke",
1210
+ "onboard_through_gateway",
1211
+ "create_eval",
1212
+ ].includes(kind))
986
1213
  throw new InventoryError("Remediation kind is invalid.");
987
1214
  const targetKind = proposal.targetKind;
988
- if (!["agent", "identity", "tool", "provider", "resource", "side_effect", "credential", "environment", "policy"].includes(targetKind))
1215
+ if (![
1216
+ "agent",
1217
+ "identity",
1218
+ "tool",
1219
+ "provider",
1220
+ "resource",
1221
+ "side_effect",
1222
+ "credential",
1223
+ "environment",
1224
+ "policy",
1225
+ ].includes(targetKind))
989
1226
  throw new InventoryError("Remediation target kind is invalid.");
990
1227
  const result = {
991
1228
  findingId: safeIdentifier(proposal.findingId, "Remediation finding id"),
992
1229
  kind: kind,
993
1230
  targetKind: targetKind,
994
1231
  targetId: safeIdentifier(proposal.targetId, "Remediation target id"),
995
- rationale: safeText(proposal.rationale, "Remediation rationale", 400)
1232
+ rationale: safeText(proposal.rationale, "Remediation rationale", 400),
996
1233
  };
997
1234
  if (proposal.ownerId !== undefined)
998
1235
  result.ownerId = safeIdentifier(proposal.ownerId, "Remediation owner id");
@@ -1009,21 +1246,30 @@ function validateRemediationProposal(value) {
1009
1246
  return result;
1010
1247
  }
1011
1248
  function safeIdentifier(value, label) {
1012
- if (typeof value !== "string" || !/^[a-z0-9][a-z0-9._-]{0,127}$/.test(value) || sanitizeSecretString(value) !== value)
1249
+ if (typeof value !== "string" ||
1250
+ !/^[a-z0-9][a-z0-9._-]{0,127}$/.test(value) ||
1251
+ sanitizeSecretString(value) !== value)
1013
1252
  throw new InventoryError(`${label} must be a safe identifier.`);
1014
1253
  return value;
1015
1254
  }
1016
1255
  function safeScopes(value, label) {
1017
- if (!Array.isArray(value) || value.length > INVENTORY_LIMITS.maxScopesPerRecord)
1256
+ if (!Array.isArray(value) ||
1257
+ value.length > INVENTORY_LIMITS.maxScopesPerRecord)
1018
1258
  throw new InventoryError(`${label} is invalid.`);
1019
1259
  return value.map((entry) => {
1020
- if (typeof entry !== "string" || !/^[a-z0-9][a-z0-9._:\-]{0,127}$/.test(entry) || sanitizeSecretString(entry) !== entry)
1260
+ if (typeof entry !== "string" ||
1261
+ !/^[a-z0-9][a-z0-9._:\-]{0,127}$/.test(entry) ||
1262
+ sanitizeSecretString(entry) !== entry)
1021
1263
  throw new InventoryError(`${label} must be safe scope identifiers.`);
1022
1264
  return entry;
1023
1265
  });
1024
1266
  }
1025
1267
  function safeText(value, label, max) {
1026
- if (typeof value !== "string" || value.trim() === "" || value.length > max || /[\u0000-\u001f]/.test(value) || sanitizeSecretString(value) !== value)
1268
+ if (typeof value !== "string" ||
1269
+ value.trim() === "" ||
1270
+ value.length > max ||
1271
+ /[\u0000-\u001f]/.test(value) ||
1272
+ sanitizeSecretString(value) !== value)
1027
1273
  throw new InventoryError(`${label} is invalid.`);
1028
1274
  return value.trim();
1029
1275
  }
@@ -1034,7 +1280,8 @@ function object(value, message) {
1034
1280
  }
1035
1281
  function exactKeys(value, keys, label, optional = []) {
1036
1282
  for (const key of Object.keys(value)) {
1037
- if (!keys.includes(key) || (value[key] === undefined && !optional.includes(key)))
1283
+ if (!keys.includes(key) ||
1284
+ (value[key] === undefined && !optional.includes(key)))
1038
1285
  throw new InventoryError(`${label} contains unsupported field: ${key}`);
1039
1286
  }
1040
1287
  }