@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,13 +2,42 @@ import { createHash } from "node:crypto";
2
2
  import { sanitizeSecretString } from "../security/secrets.js";
3
3
  export const INVENTORY_SCHEMA_VERSION = 1;
4
4
  export const INVENTORY_KIND = "ghostapi.inventory";
5
- export const IMPORT_SOURCE_TYPES = ["config", "ci", "gateway", "cloud"];
5
+ export const IMPORT_SOURCE_TYPES = [
6
+ "config",
7
+ "ci",
8
+ "gateway",
9
+ "cloud",
10
+ ];
6
11
  export const TOOL_KINDS = ["mcp_server", "sdk_client", "http_client"];
7
12
  export const IDENTITY_ROLES = ["service_account", "user", "workload"];
8
- export const SIDE_EFFECT_KINDS = ["read", "create", "update", "communicate", "money_movement", "delete", "permission_change", "deployment"];
13
+ export const SIDE_EFFECT_KINDS = [
14
+ "read",
15
+ "create",
16
+ "update",
17
+ "communicate",
18
+ "money_movement",
19
+ "delete",
20
+ "permission_change",
21
+ "deployment",
22
+ ];
9
23
  export const IMPACT_SEVERITIES = ["low", "medium", "high", "critical"];
10
- export const EDGE_RELATIONS = ["owns", "uses", "connects", "exposes", "permits", "requires"];
11
- export const NODE_KINDS = ["agent", "identity", "tool", "provider", "resource", "side_effect", "credential"];
24
+ export const EDGE_RELATIONS = [
25
+ "owns",
26
+ "uses",
27
+ "connects",
28
+ "exposes",
29
+ "permits",
30
+ "requires",
31
+ ];
32
+ export const NODE_KINDS = [
33
+ "agent",
34
+ "identity",
35
+ "tool",
36
+ "provider",
37
+ "resource",
38
+ "side_effect",
39
+ "credential",
40
+ ];
12
41
  export const FINDING_KINDS = [
13
42
  "orphaned_agents",
14
43
  "stale_unused_credentials",
@@ -17,13 +46,33 @@ export const FINDING_KINDS = [
17
46
  "agents_outside_gateway",
18
47
  "missing_kill_switch",
19
48
  "missing_evidence",
20
- "policy_drift"
49
+ "policy_drift",
21
50
  ];
22
51
  export const FINDING_BASIS = ["evidence", "heuristic"];
23
- export const FINDING_SEVERITIES = ["critical", "high", "medium", "low"];
24
- export const REMEDIATION_KINDS = ["assign_owner", "reduce_scope", "revoke", "onboard_through_gateway", "create_eval"];
25
- export const REMEDIATION_STATUSES = ["proposed", "applied", "rejected", "expired"];
26
- export const FRESHNESS_DEFAULT_DAYS = { edgeStale: 90, credentialStale: 90, evidenceFresh: 30 };
52
+ export const FINDING_SEVERITIES = [
53
+ "critical",
54
+ "high",
55
+ "medium",
56
+ "low",
57
+ ];
58
+ export const REMEDIATION_KINDS = [
59
+ "assign_owner",
60
+ "reduce_scope",
61
+ "revoke",
62
+ "onboard_through_gateway",
63
+ "create_eval",
64
+ ];
65
+ export const REMEDIATION_STATUSES = [
66
+ "proposed",
67
+ "applied",
68
+ "rejected",
69
+ "expired",
70
+ ];
71
+ export const FRESHNESS_DEFAULT_DAYS = {
72
+ edgeStale: 90,
73
+ credentialStale: 90,
74
+ evidenceFresh: 30,
75
+ };
27
76
  const IDENTIFIER = /^[a-z0-9][a-z0-9._-]{0,127}$/;
28
77
  const SCOPE_IDENTIFIER = /^[a-z0-9][a-z0-9._:\-]{0,127}$/;
29
78
  const HASH = /^[a-f0-9]{64}$/;
@@ -45,7 +94,7 @@ export const INVENTORY_LIMITS = {
45
94
  maxRemediations: 1000,
46
95
  maxReferencesPerRecord: 64,
47
96
  maxScopesPerRecord: 32,
48
- maxCounters: 3
97
+ maxCounters: 3,
49
98
  };
50
99
  export class InventoryError extends Error {
51
100
  constructor(message) {
@@ -69,7 +118,7 @@ export function emptyInventoryState() {
69
118
  policies: [],
70
119
  edges: [],
71
120
  findings: [],
72
- remediations: []
121
+ remediations: [],
73
122
  };
74
123
  }
75
124
  export function sha256Digest(value) {
@@ -82,30 +131,74 @@ export function sortedKeysReplacer(_key, value) {
82
131
  if (Array.isArray(value))
83
132
  return value;
84
133
  if (value !== null && typeof value === "object") {
85
- return Object.fromEntries(Object.entries(value).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)));
134
+ return Object.fromEntries(Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0));
86
135
  }
87
136
  return value;
88
137
  }
89
138
  export function validateImportPayload(value) {
90
139
  const payload = object(value, "Inventory import must be an object.");
91
- exactKeys(payload, ["schemaVersion", "kind", "source", "expectedPolicyHashes", "counters", "agents", "tools", "identities", "providers", "resources", "sideEffects", "credentials", "policies"], "inventory import", [
92
- "expectedPolicyHashes", "counters", "agents", "tools", "identities", "providers", "resources", "sideEffects", "credentials", "policies"
140
+ exactKeys(payload, [
141
+ "schemaVersion",
142
+ "kind",
143
+ "source",
144
+ "expectedPolicyHashes",
145
+ "counters",
146
+ "agents",
147
+ "tools",
148
+ "identities",
149
+ "providers",
150
+ "resources",
151
+ "sideEffects",
152
+ "credentials",
153
+ "policies",
154
+ ], "inventory import", [
155
+ "expectedPolicyHashes",
156
+ "counters",
157
+ "agents",
158
+ "tools",
159
+ "identities",
160
+ "providers",
161
+ "resources",
162
+ "sideEffects",
163
+ "credentials",
164
+ "policies",
93
165
  ]);
94
- if (payload.schemaVersion !== INVENTORY_SCHEMA_VERSION || payload.kind !== "ghostapi.inventory-import")
166
+ if (payload.schemaVersion !== INVENTORY_SCHEMA_VERSION ||
167
+ payload.kind !== "ghostapi.inventory-import")
95
168
  throw new InventoryError("Unsupported inventory import schema.");
96
169
  const source = validateImportSource(payload.source);
97
170
  if (source.sourceType === "config" && payload.counters !== undefined)
98
171
  throw new InventoryError("Counters are only allowed for ci, gateway, or cloud imports.");
99
- const expectedPolicyHashes = payload.expectedPolicyHashes === undefined ? [] : array(payload.expectedPolicyHashes, "expected policy hashes", 64).map(validateExpectedPolicyHash);
100
- const counters = payload.counters === undefined ? undefined : validateCounters(payload.counters);
101
- const agents = payload.agents === undefined ? [] : array(payload.agents, "agents", 200).map(validateImportedAgent);
102
- const tools = payload.tools === undefined ? [] : array(payload.tools, "tools", 200).map(validateImportedTool);
103
- const identities = payload.identities === undefined ? [] : array(payload.identities, "identities", 200).map(validateImportedIdentity);
104
- const providers = payload.providers === undefined ? [] : array(payload.providers, "providers", 100).map(validateImportedProvider);
105
- const resources = payload.resources === undefined ? [] : array(payload.resources, "resources", 300).map(validateImportedResource);
106
- const sideEffects = payload.sideEffects === undefined ? [] : array(payload.sideEffects, "side effects", 400).map(validateImportedSideEffect);
107
- const credentials = payload.credentials === undefined ? [] : array(payload.credentials, "credentials", 200).map(validateImportedCredential);
108
- const policies = payload.policies === undefined ? [] : array(payload.policies, "policies", 50).map(validateImportedPolicy);
172
+ const expectedPolicyHashes = payload.expectedPolicyHashes === undefined
173
+ ? []
174
+ : array(payload.expectedPolicyHashes, "expected policy hashes", 64).map(validateExpectedPolicyHash);
175
+ const counters = payload.counters === undefined
176
+ ? undefined
177
+ : validateCounters(payload.counters);
178
+ const agents = payload.agents === undefined
179
+ ? []
180
+ : array(payload.agents, "agents", 200).map(validateImportedAgent);
181
+ const tools = payload.tools === undefined
182
+ ? []
183
+ : array(payload.tools, "tools", 200).map(validateImportedTool);
184
+ const identities = payload.identities === undefined
185
+ ? []
186
+ : array(payload.identities, "identities", 200).map(validateImportedIdentity);
187
+ const providers = payload.providers === undefined
188
+ ? []
189
+ : array(payload.providers, "providers", 100).map(validateImportedProvider);
190
+ const resources = payload.resources === undefined
191
+ ? []
192
+ : array(payload.resources, "resources", 300).map(validateImportedResource);
193
+ const sideEffects = payload.sideEffects === undefined
194
+ ? []
195
+ : array(payload.sideEffects, "side effects", 400).map(validateImportedSideEffect);
196
+ const credentials = payload.credentials === undefined
197
+ ? []
198
+ : array(payload.credentials, "credentials", 200).map(validateImportedCredential);
199
+ const policies = payload.policies === undefined
200
+ ? []
201
+ : array(payload.policies, "policies", 50).map(validateImportedPolicy);
109
202
  unique(agents.map((agent) => agent.agentId), "agent ids");
110
203
  unique(tools.map((tool) => tool.toolId), "tool ids");
111
204
  unique(identities.map((identity) => identity.identityId), "identity ids");
@@ -127,13 +220,30 @@ export function validateImportPayload(value) {
127
220
  resources,
128
221
  sideEffects,
129
222
  credentials,
130
- policies
223
+ policies,
131
224
  };
132
225
  }
133
226
  export function validateState(value) {
134
227
  const state = object(value, "Inventory store must be an object.");
135
- exactKeys(state, ["schemaVersion", "kind", "sources", "importRuns", "agents", "tools", "identities", "providers", "resources", "sideEffects", "credentials", "policies", "edges", "findings", "remediations"], "inventory store");
136
- if (state.schemaVersion !== INVENTORY_SCHEMA_VERSION || state.kind !== INVENTORY_KIND)
228
+ exactKeys(state, [
229
+ "schemaVersion",
230
+ "kind",
231
+ "sources",
232
+ "importRuns",
233
+ "agents",
234
+ "tools",
235
+ "identities",
236
+ "providers",
237
+ "resources",
238
+ "sideEffects",
239
+ "credentials",
240
+ "policies",
241
+ "edges",
242
+ "findings",
243
+ "remediations",
244
+ ], "inventory store");
245
+ if (state.schemaVersion !== INVENTORY_SCHEMA_VERSION ||
246
+ state.kind !== INVENTORY_KIND)
137
247
  throw new InventoryError("Unsupported inventory store schema.");
138
248
  const sources = array(state.sources, "import sources", INVENTORY_LIMITS.maxSources).map(validateSourceRecord);
139
249
  const importRuns = array(state.importRuns, "import runs", INVENTORY_LIMITS.maxImports).map(validateImportRunRecord);
@@ -148,7 +258,23 @@ export function validateState(value) {
148
258
  const edges = array(state.edges, "graph edges", INVENTORY_LIMITS.maxEdges).map(validateEdge);
149
259
  const findings = array(state.findings, "findings", INVENTORY_LIMITS.maxFindings).map(validateFindingRecord);
150
260
  const remediations = array(state.remediations, "remediations", INVENTORY_LIMITS.maxRemediations).map(validateRemediationRecord);
151
- return { schemaVersion: 1, kind: "ghostapi.inventory", sources, importRuns, agents, tools, identities, providers, resources, sideEffects, credentials, policies, edges, findings, remediations };
261
+ return {
262
+ schemaVersion: 1,
263
+ kind: "ghostapi.inventory",
264
+ sources,
265
+ importRuns,
266
+ agents,
267
+ tools,
268
+ identities,
269
+ providers,
270
+ resources,
271
+ sideEffects,
272
+ credentials,
273
+ policies,
274
+ edges,
275
+ findings,
276
+ remediations,
277
+ };
152
278
  }
153
279
  export function clone(value) {
154
280
  return structuredClone(value);
@@ -166,7 +292,7 @@ export function validateOperator(value) {
166
292
  id: identifier(operator.id, "Inventory operator id"),
167
293
  principalId: identifier(operator.principalId, "Inventory operator principal id"),
168
294
  tenantId: identifier(operator.tenantId, "Inventory operator tenant id"),
169
- permissions
295
+ permissions,
170
296
  };
171
297
  }
172
298
  export function validateFindingsAndRemediations(value) {
@@ -174,11 +300,15 @@ export function validateFindingsAndRemediations(value) {
174
300
  exactKeys(box, ["findings", "remediations"], "inventory findings box");
175
301
  return {
176
302
  findings: array(box.findings, "findings", INVENTORY_LIMITS.maxFindings).map(validateFindingRecord),
177
- remediations: array(box.remediations, "remediations", INVENTORY_LIMITS.maxRemediations).map(validateRemediationRecord)
303
+ remediations: array(box.remediations, "remediations", INVENTORY_LIMITS.maxRemediations).map(validateRemediationRecord),
178
304
  };
179
305
  }
180
306
  function isInventoryPermission(value) {
181
- return value === "inventory.import" || value === "inventory.inspect" || value === "inventory.analyze" || value === "inventory.remediate" || value === "inventory.export";
307
+ return (value === "inventory.import" ||
308
+ value === "inventory.inspect" ||
309
+ value === "inventory.analyze" ||
310
+ value === "inventory.remediate" ||
311
+ value === "inventory.export");
182
312
  }
183
313
  function validateImportSource(value) {
184
314
  const source = object(value, "Inventory import source is invalid.");
@@ -190,19 +320,34 @@ function validateImportSource(value) {
190
320
  sourceId: identifier(source.sourceId, "Import source id"),
191
321
  sourceType: sourceType,
192
322
  sourceName: text(source.sourceName, "Import source name", 120),
193
- ...(source.sourceVersion === undefined ? {} : { sourceVersion: text(source.sourceVersion, "Import source version", 64) })
323
+ ...(source.sourceVersion === undefined
324
+ ? {}
325
+ : {
326
+ sourceVersion: text(source.sourceVersion, "Import source version", 64),
327
+ }),
194
328
  };
195
329
  }
196
330
  function validateExpectedPolicyHash(value) {
197
331
  const expected = object(value, "Expected policy hash is invalid.");
198
332
  exactKeys(expected, ["environmentId", "policyHash"], "expected policy hash");
199
- return { environmentId: identifier(expected.environmentId, "Environment id"), policyHash: hash(expected.policyHash, "Policy hash") };
333
+ return {
334
+ environmentId: identifier(expected.environmentId, "Environment id"),
335
+ policyHash: hash(expected.policyHash, "Policy hash"),
336
+ };
200
337
  }
201
338
  function validateCounters(value) {
202
339
  const counters = object(value, "Import counters are invalid.");
203
- exactKeys(counters, ["preventedEgressAttempts", "reviewTimeMinutes", "meanTimeToContainMinutes"], "import counters");
340
+ exactKeys(counters, [
341
+ "preventedEgressAttempts",
342
+ "reviewTimeMinutes",
343
+ "meanTimeToContainMinutes",
344
+ ], "import counters");
204
345
  const result = {};
205
- for (const key of ["preventedEgressAttempts", "reviewTimeMinutes", "meanTimeToContainMinutes"]) {
346
+ for (const key of [
347
+ "preventedEgressAttempts",
348
+ "reviewTimeMinutes",
349
+ "meanTimeToContainMinutes",
350
+ ]) {
206
351
  if (counters[key] !== undefined)
207
352
  result[key] = nonNegative(counters[key], `Import counter ${key}`, 9_999_999_999);
208
353
  }
@@ -210,24 +355,59 @@ function validateCounters(value) {
210
355
  }
211
356
  function validateImportedAgent(value) {
212
357
  const record = object(value, "Imported agent is invalid.");
213
- exactKeys(record, ["agentId", "name", "ownerId", "businessPurpose", "model", "runtime", "version", "identityIds", "environmentIds", "gatewayManaged", "killSwitchEnabled", "lastEvidenceAt"], "imported agent", [
214
- "ownerId", "businessPurpose", "model", "runtime", "version", "lastEvidenceAt"
358
+ exactKeys(record, [
359
+ "agentId",
360
+ "name",
361
+ "ownerId",
362
+ "businessPurpose",
363
+ "model",
364
+ "runtime",
365
+ "version",
366
+ "identityIds",
367
+ "environmentIds",
368
+ "gatewayManaged",
369
+ "killSwitchEnabled",
370
+ "lastEvidenceAt",
371
+ ], "imported agent", [
372
+ "ownerId",
373
+ "businessPurpose",
374
+ "model",
375
+ "runtime",
376
+ "version",
377
+ "lastEvidenceAt",
215
378
  ]);
216
- if (typeof record.gatewayManaged !== "boolean" || typeof record.killSwitchEnabled !== "boolean")
379
+ if (typeof record.gatewayManaged !== "boolean" ||
380
+ typeof record.killSwitchEnabled !== "boolean")
217
381
  throw new InventoryError("Imported agent booleans are invalid.");
218
382
  return {
219
383
  agentId: identifier(record.agentId, "Agent id"),
220
384
  name: text(record.name, "Agent name", 160),
221
- ...(record.ownerId === undefined ? {} : { ownerId: identifier(record.ownerId, "Agent owner id") }),
222
- ...(record.businessPurpose === undefined ? {} : { businessPurpose: text(record.businessPurpose, "Agent business purpose", 240) }),
223
- ...(record.model === undefined ? {} : { model: text(record.model, "Agent model", 120) }),
224
- ...(record.runtime === undefined ? {} : { runtime: text(record.runtime, "Agent runtime", 120) }),
225
- ...(record.version === undefined ? {} : { version: text(record.version, "Agent version", 64) }),
385
+ ...(record.ownerId === undefined
386
+ ? {}
387
+ : { ownerId: identifier(record.ownerId, "Agent owner id") }),
388
+ ...(record.businessPurpose === undefined
389
+ ? {}
390
+ : {
391
+ businessPurpose: text(record.businessPurpose, "Agent business purpose", 240),
392
+ }),
393
+ ...(record.model === undefined
394
+ ? {}
395
+ : { model: text(record.model, "Agent model", 120) }),
396
+ ...(record.runtime === undefined
397
+ ? {}
398
+ : { runtime: text(record.runtime, "Agent runtime", 120) }),
399
+ ...(record.version === undefined
400
+ ? {}
401
+ : { version: text(record.version, "Agent version", 64) }),
226
402
  identityIds: identifiers(record.identityIds, "Agent identity ids", INVENTORY_LIMITS.maxReferencesPerRecord),
227
403
  environmentIds: identifiers(record.environmentIds, "Agent environment ids", 16),
228
404
  gatewayManaged: record.gatewayManaged,
229
405
  killSwitchEnabled: record.killSwitchEnabled,
230
- ...(record.lastEvidenceAt === undefined ? {} : { lastEvidenceAt: timestamp(record.lastEvidenceAt, "Agent last evidence time") })
406
+ ...(record.lastEvidenceAt === undefined
407
+ ? {}
408
+ : {
409
+ lastEvidenceAt: timestamp(record.lastEvidenceAt, "Agent last evidence time"),
410
+ }),
231
411
  };
232
412
  }
233
413
  function validateImportedTool(value) {
@@ -240,14 +420,24 @@ function validateImportedTool(value) {
240
420
  toolId: identifier(record.toolId, "Tool id"),
241
421
  name: text(record.name, "Tool name", 160),
242
422
  kind: kind,
243
- ...(record.providerId === undefined ? {} : { providerId: identifier(record.providerId, "Tool provider id") }),
423
+ ...(record.providerId === undefined
424
+ ? {}
425
+ : { providerId: identifier(record.providerId, "Tool provider id") }),
244
426
  requiredScopes: scopes(record.requiredScopes, "Tool required scopes", INVENTORY_LIMITS.maxScopesPerRecord),
245
- credentialIds: identifiers(record.credentialIds, "Tool credential ids", INVENTORY_LIMITS.maxReferencesPerRecord)
427
+ credentialIds: identifiers(record.credentialIds, "Tool credential ids", INVENTORY_LIMITS.maxReferencesPerRecord),
246
428
  };
247
429
  }
248
430
  function validateImportedIdentity(value) {
249
431
  const record = object(value, "Imported identity is invalid.");
250
- exactKeys(record, ["identityId", "principalId", "role", "agentId", "toolIds", "environmentIds", "scopes"], "imported identity", ["agentId"]);
432
+ exactKeys(record, [
433
+ "identityId",
434
+ "principalId",
435
+ "role",
436
+ "agentId",
437
+ "toolIds",
438
+ "environmentIds",
439
+ "scopes",
440
+ ], "imported identity", ["agentId"]);
251
441
  const role = record.role;
252
442
  if (!IDENTITY_ROLES.includes(role))
253
443
  throw new InventoryError("Imported identity role is invalid.");
@@ -255,31 +445,55 @@ function validateImportedIdentity(value) {
255
445
  identityId: identifier(record.identityId, "Identity id"),
256
446
  principalId: identifier(record.principalId, "Identity principal id"),
257
447
  role: role,
258
- ...(record.agentId === undefined ? {} : { agentId: identifier(record.agentId, "Identity agent id") }),
448
+ ...(record.agentId === undefined
449
+ ? {}
450
+ : { agentId: identifier(record.agentId, "Identity agent id") }),
259
451
  toolIds: identifiers(record.toolIds, "Identity tool ids", INVENTORY_LIMITS.maxReferencesPerRecord),
260
452
  environmentIds: identifiers(record.environmentIds, "Identity environment ids", 16),
261
- scopes: scopes(record.scopes, "Identity scopes", INVENTORY_LIMITS.maxScopesPerRecord)
453
+ scopes: scopes(record.scopes, "Identity scopes", INVENTORY_LIMITS.maxScopesPerRecord),
262
454
  };
263
455
  }
264
456
  function validateImportedProvider(value) {
265
457
  const record = object(value, "Imported provider is invalid.");
266
- exactKeys(record, ["providerId", "name", "providerType", "ownerId", "environmentIds", "resourceIds", "gatewayBound", "killSwitchApplied"], "imported provider", ["ownerId"]);
267
- if (typeof record.gatewayBound !== "boolean" || typeof record.killSwitchApplied !== "boolean")
458
+ exactKeys(record, [
459
+ "providerId",
460
+ "name",
461
+ "providerType",
462
+ "ownerId",
463
+ "environmentIds",
464
+ "resourceIds",
465
+ "gatewayBound",
466
+ "killSwitchApplied",
467
+ ], "imported provider", ["ownerId"]);
468
+ if (typeof record.gatewayBound !== "boolean" ||
469
+ typeof record.killSwitchApplied !== "boolean")
268
470
  throw new InventoryError("Imported provider booleans are invalid.");
269
471
  return {
270
472
  providerId: identifier(record.providerId, "Provider id"),
271
473
  name: text(record.name, "Provider name", 160),
272
474
  providerType: text(record.providerType, "Provider type", 120),
273
- ...(record.ownerId === undefined ? {} : { ownerId: identifier(record.ownerId, "Provider owner id") }),
475
+ ...(record.ownerId === undefined
476
+ ? {}
477
+ : { ownerId: identifier(record.ownerId, "Provider owner id") }),
274
478
  environmentIds: identifiers(record.environmentIds, "Provider environment ids", 16),
275
479
  resourceIds: identifiers(record.resourceIds, "Provider resource ids", INVENTORY_LIMITS.maxReferencesPerRecord),
276
480
  gatewayBound: record.gatewayBound,
277
- killSwitchApplied: record.killSwitchApplied
481
+ killSwitchApplied: record.killSwitchApplied,
278
482
  };
279
483
  }
280
484
  function validateImportedResource(value) {
281
485
  const record = object(value, "Imported resource is invalid.");
282
- exactKeys(record, ["resourceId", "providerId", "resourceType", "name", "environmentIds", "sensitive", "ownerId", "businessPurpose", "sideEffectIds"], "imported resource", ["ownerId", "businessPurpose"]);
486
+ exactKeys(record, [
487
+ "resourceId",
488
+ "providerId",
489
+ "resourceType",
490
+ "name",
491
+ "environmentIds",
492
+ "sensitive",
493
+ "ownerId",
494
+ "businessPurpose",
495
+ "sideEffectIds",
496
+ ], "imported resource", ["ownerId", "businessPurpose"]);
283
497
  if (typeof record.sensitive !== "boolean")
284
498
  throw new InventoryError("Imported resource sensitive flag is invalid.");
285
499
  return {
@@ -289,9 +503,15 @@ function validateImportedResource(value) {
289
503
  name: text(record.name, "Resource name", 160),
290
504
  environmentIds: identifiers(record.environmentIds, "Resource environment ids", 16),
291
505
  sensitive: record.sensitive,
292
- ...(record.ownerId === undefined ? {} : { ownerId: identifier(record.ownerId, "Resource owner id") }),
293
- ...(record.businessPurpose === undefined ? {} : { businessPurpose: text(record.businessPurpose, "Resource business purpose", 240) }),
294
- sideEffectIds: identifiers(record.sideEffectIds, "Resource side effect ids", INVENTORY_LIMITS.maxReferencesPerRecord)
506
+ ...(record.ownerId === undefined
507
+ ? {}
508
+ : { ownerId: identifier(record.ownerId, "Resource owner id") }),
509
+ ...(record.businessPurpose === undefined
510
+ ? {}
511
+ : {
512
+ businessPurpose: text(record.businessPurpose, "Resource business purpose", 240),
513
+ }),
514
+ sideEffectIds: identifiers(record.sideEffectIds, "Resource side effect ids", INVENTORY_LIMITS.maxReferencesPerRecord),
295
515
  };
296
516
  }
297
517
  function validateImportedSideEffect(value) {
@@ -307,14 +527,25 @@ function validateImportedSideEffect(value) {
307
527
  sideEffectId: identifier(record.sideEffectId, "Side effect id"),
308
528
  resourceId: identifier(record.resourceId, "Side effect resource id"),
309
529
  kind: kind,
310
- severity: severity
530
+ severity: severity,
311
531
  };
312
532
  }
313
533
  function validateImportedCredential(value) {
314
534
  const record = object(value, "Imported credential is invalid.");
315
- exactKeys(record, ["credentialId", "name", "providerId", "ownerId", "environmentIds", "grantScopes", "toolIds", "status", "issuedAt", "lastUsedAt", "rotatedAt", "gatewayBound"], "imported credential", [
316
- "ownerId", "lastUsedAt", "rotatedAt"
317
- ]);
535
+ exactKeys(record, [
536
+ "credentialId",
537
+ "name",
538
+ "providerId",
539
+ "ownerId",
540
+ "environmentIds",
541
+ "grantScopes",
542
+ "toolIds",
543
+ "status",
544
+ "issuedAt",
545
+ "lastUsedAt",
546
+ "rotatedAt",
547
+ "gatewayBound",
548
+ ], "imported credential", ["ownerId", "lastUsedAt", "rotatedAt"]);
318
549
  if (record.status !== "active" && record.status !== "revoked")
319
550
  throw new InventoryError("Imported credential status is invalid.");
320
551
  if (typeof record.gatewayBound !== "boolean")
@@ -323,15 +554,23 @@ function validateImportedCredential(value) {
323
554
  credentialId: identifier(record.credentialId, "Credential id"),
324
555
  name: text(record.name, "Credential name", 160),
325
556
  providerId: identifier(record.providerId, "Credential provider id"),
326
- ...(record.ownerId === undefined ? {} : { ownerId: identifier(record.ownerId, "Credential owner id") }),
557
+ ...(record.ownerId === undefined
558
+ ? {}
559
+ : { ownerId: identifier(record.ownerId, "Credential owner id") }),
327
560
  environmentIds: identifiers(record.environmentIds, "Credential environment ids", 16),
328
561
  grantScopes: scopes(record.grantScopes, "Credential grant scopes", INVENTORY_LIMITS.maxScopesPerRecord),
329
562
  toolIds: identifiers(record.toolIds, "Credential tool ids", INVENTORY_LIMITS.maxReferencesPerRecord),
330
563
  status: record.status,
331
564
  issuedAt: timestamp(record.issuedAt, "Credential issued time"),
332
- ...(record.lastUsedAt === undefined ? {} : { lastUsedAt: timestamp(record.lastUsedAt, "Credential last used time") }),
333
- ...(record.rotatedAt === undefined ? {} : { rotatedAt: timestamp(record.rotatedAt, "Credential rotated time") }),
334
- gatewayBound: record.gatewayBound
565
+ ...(record.lastUsedAt === undefined
566
+ ? {}
567
+ : {
568
+ lastUsedAt: timestamp(record.lastUsedAt, "Credential last used time"),
569
+ }),
570
+ ...(record.rotatedAt === undefined
571
+ ? {}
572
+ : { rotatedAt: timestamp(record.rotatedAt, "Credential rotated time") }),
573
+ gatewayBound: record.gatewayBound,
335
574
  };
336
575
  }
337
576
  function validateImportedPolicy(value) {
@@ -342,12 +581,21 @@ function validateImportedPolicy(value) {
342
581
  name: text(record.name, "Policy name", 160),
343
582
  version: text(record.version, "Policy version", 64),
344
583
  hash: hash(record.hash, "Policy hash"),
345
- environmentIds: identifiers(record.environmentIds, "Policy environment ids", 16)
584
+ environmentIds: identifiers(record.environmentIds, "Policy environment ids", 16),
346
585
  };
347
586
  }
348
587
  function validateSourceRecord(value) {
349
588
  const record = object(value, "Import source is invalid.");
350
- exactKeys(record, ["tenantId", "sourceId", "sourceType", "sourceName", "sourceVersion", "firstSeenAt", "lastSeenAt", "lastDigest"], "import source", ["sourceVersion"]);
589
+ exactKeys(record, [
590
+ "tenantId",
591
+ "sourceId",
592
+ "sourceType",
593
+ "sourceName",
594
+ "sourceVersion",
595
+ "firstSeenAt",
596
+ "lastSeenAt",
597
+ "lastDigest",
598
+ ], "import source", ["sourceVersion"]);
351
599
  const sourceType = record.sourceType;
352
600
  if (!IMPORT_SOURCE_TYPES.includes(sourceType))
353
601
  throw new InventoryError("Import source type is invalid.");
@@ -356,16 +604,34 @@ function validateSourceRecord(value) {
356
604
  sourceId: identifier(record.sourceId, "Source id"),
357
605
  sourceType: sourceType,
358
606
  sourceName: text(record.sourceName, "Source name", 120),
359
- ...(record.sourceVersion === undefined ? {} : { sourceVersion: text(record.sourceVersion, "Source version", 64) }),
607
+ ...(record.sourceVersion === undefined
608
+ ? {}
609
+ : { sourceVersion: text(record.sourceVersion, "Source version", 64) }),
360
610
  firstSeenAt: timestamp(record.firstSeenAt, "Source first seen time"),
361
611
  lastSeenAt: timestamp(record.lastSeenAt, "Source last seen time"),
362
- lastDigest: hash(record.lastDigest, "Source digest")
612
+ lastDigest: hash(record.lastDigest, "Source digest"),
363
613
  };
364
614
  }
365
615
  function validateImportRunRecord(value) {
366
616
  const record = object(value, "Import run is invalid.");
367
- exactKeys(record, ["schemaVersion", "kind", "tenantId", "runId", "importedAt", "importedBy", "source", "digest", "expectedPolicyHashes", "counters", "recordCounts", "edgesCreated", "edgesRefreshed", "status"], "import run", ["counters"]);
368
- if (record.schemaVersion !== INVENTORY_SCHEMA_VERSION || record.kind !== "ghostapi.inventory-import-run")
617
+ exactKeys(record, [
618
+ "schemaVersion",
619
+ "kind",
620
+ "tenantId",
621
+ "runId",
622
+ "importedAt",
623
+ "importedBy",
624
+ "source",
625
+ "digest",
626
+ "expectedPolicyHashes",
627
+ "counters",
628
+ "recordCounts",
629
+ "edgesCreated",
630
+ "edgesRefreshed",
631
+ "status",
632
+ ], "import run", ["counters"]);
633
+ if (record.schemaVersion !== INVENTORY_SCHEMA_VERSION ||
634
+ record.kind !== "ghostapi.inventory-import-run")
369
635
  throw new InventoryError("Unsupported import run schema.");
370
636
  if (record.status !== "completed")
371
637
  throw new InventoryError("Import run status is invalid.");
@@ -375,8 +641,19 @@ function validateImportRunRecord(value) {
375
641
  if (!IMPORT_SOURCE_TYPES.includes(sourceType))
376
642
  throw new InventoryError("Import run source type is invalid.");
377
643
  const recordCounts = object(record.recordCounts, "Import run record counts are invalid.");
378
- exactKeys(recordCounts, ["agents", "tools", "identities", "providers", "resources", "sideEffects", "credentials", "policies"], "import run record counts");
379
- const counters = record.counters === undefined ? undefined : validateCounters(record.counters);
644
+ exactKeys(recordCounts, [
645
+ "agents",
646
+ "tools",
647
+ "identities",
648
+ "providers",
649
+ "resources",
650
+ "sideEffects",
651
+ "credentials",
652
+ "policies",
653
+ ], "import run record counts");
654
+ const counters = record.counters === undefined
655
+ ? undefined
656
+ : validateCounters(record.counters);
380
657
  if (sourceType === "config" && counters !== undefined)
381
658
  throw new InventoryError("Counters are only allowed for ci, gateway, or cloud imports.");
382
659
  return {
@@ -390,7 +667,11 @@ function validateImportRunRecord(value) {
390
667
  sourceId: identifier(source.sourceId, "Import run source id"),
391
668
  sourceType: sourceType,
392
669
  sourceName: text(source.sourceName, "Import run source name", 120),
393
- ...(source.sourceVersion === undefined ? {} : { sourceVersion: text(source.sourceVersion, "Import run source version", 64) })
670
+ ...(source.sourceVersion === undefined
671
+ ? {}
672
+ : {
673
+ sourceVersion: text(source.sourceVersion, "Import run source version", 64),
674
+ }),
394
675
  },
395
676
  digest: hash(record.digest, "Import run digest"),
396
677
  expectedPolicyHashes: array(record.expectedPolicyHashes, "import run expected policy hashes", 64).map(validateExpectedPolicyHash),
@@ -403,21 +684,39 @@ function validateImportRunRecord(value) {
403
684
  resources: nonNegative(recordCounts.resources, "Import run resource count", 1000),
404
685
  sideEffects: nonNegative(recordCounts.sideEffects, "Import run side effect count", 2000),
405
686
  credentials: nonNegative(recordCounts.credentials, "Import run credential count", 500),
406
- policies: nonNegative(recordCounts.policies, "Import run policy count", 100)
687
+ policies: nonNegative(recordCounts.policies, "Import run policy count", 100),
407
688
  },
408
689
  edgesCreated: nonNegative(record.edgesCreated, "Import run edges created", 6000),
409
690
  edgesRefreshed: nonNegative(record.edgesRefreshed, "Import run edges refreshed", 6000),
410
- status: "completed"
691
+ status: "completed",
411
692
  };
412
693
  }
413
694
  function validateAgentRecord(value) {
414
695
  const record = object(value, "Agent record is invalid.");
415
- exactKeys(record, ["schemaVersion", "kind", "tenantId", "agentId", "name", "ownerId", "businessPurpose", "model", "runtime", "version", "identityIds", "environmentIds", "gatewayManaged", "killSwitchEnabled", "lastEvidenceAt", "provenance", "freshness"], "agent record", [
416
- "businessPurpose", "model", "runtime", "version"
417
- ]);
418
- if (record.schemaVersion !== INVENTORY_SCHEMA_VERSION || record.kind !== "ghostapi.inventory-agent")
696
+ exactKeys(record, [
697
+ "schemaVersion",
698
+ "kind",
699
+ "tenantId",
700
+ "agentId",
701
+ "name",
702
+ "ownerId",
703
+ "businessPurpose",
704
+ "model",
705
+ "runtime",
706
+ "version",
707
+ "identityIds",
708
+ "environmentIds",
709
+ "gatewayManaged",
710
+ "killSwitchEnabled",
711
+ "lastEvidenceAt",
712
+ "provenance",
713
+ "freshness",
714
+ ], "agent record", ["businessPurpose", "model", "runtime", "version"]);
715
+ if (record.schemaVersion !== INVENTORY_SCHEMA_VERSION ||
716
+ record.kind !== "ghostapi.inventory-agent")
419
717
  throw new InventoryError("Unsupported agent record schema.");
420
- if (typeof record.gatewayManaged !== "boolean" || typeof record.killSwitchEnabled !== "boolean")
718
+ if (typeof record.gatewayManaged !== "boolean" ||
719
+ typeof record.killSwitchEnabled !== "boolean")
421
720
  throw new InventoryError("Agent record booleans are invalid.");
422
721
  return {
423
722
  schemaVersion: 1,
@@ -425,23 +724,52 @@ function validateAgentRecord(value) {
425
724
  tenantId: identifier(record.tenantId, "Agent tenant id"),
426
725
  agentId: identifier(record.agentId, "Agent id"),
427
726
  name: text(record.name, "Agent name", 160),
428
- ownerId: record.ownerId === null ? null : record.ownerId === undefined ? null : identifier(record.ownerId, "Agent owner id"),
429
- ...(record.businessPurpose === undefined ? {} : { businessPurpose: text(record.businessPurpose, "Agent business purpose", 240) }),
430
- ...(record.model === undefined ? {} : { model: text(record.model, "Agent model", 120) }),
431
- ...(record.runtime === undefined ? {} : { runtime: text(record.runtime, "Agent runtime", 120) }),
432
- ...(record.version === undefined ? {} : { version: text(record.version, "Agent version", 64) }),
727
+ ownerId: record.ownerId === null
728
+ ? null
729
+ : record.ownerId === undefined
730
+ ? null
731
+ : identifier(record.ownerId, "Agent owner id"),
732
+ ...(record.businessPurpose === undefined
733
+ ? {}
734
+ : {
735
+ businessPurpose: text(record.businessPurpose, "Agent business purpose", 240),
736
+ }),
737
+ ...(record.model === undefined
738
+ ? {}
739
+ : { model: text(record.model, "Agent model", 120) }),
740
+ ...(record.runtime === undefined
741
+ ? {}
742
+ : { runtime: text(record.runtime, "Agent runtime", 120) }),
743
+ ...(record.version === undefined
744
+ ? {}
745
+ : { version: text(record.version, "Agent version", 64) }),
433
746
  identityIds: identifiers(record.identityIds, "Agent identity ids", INVENTORY_LIMITS.maxReferencesPerRecord),
434
747
  environmentIds: identifiers(record.environmentIds, "Agent environment ids", 16),
435
748
  gatewayManaged: record.gatewayManaged,
436
749
  killSwitchEnabled: record.killSwitchEnabled,
437
- lastEvidenceAt: record.lastEvidenceAt === null ? null : record.lastEvidenceAt === undefined ? null : timestamp(record.lastEvidenceAt, "Agent last evidence time"),
750
+ lastEvidenceAt: record.lastEvidenceAt === null
751
+ ? null
752
+ : record.lastEvidenceAt === undefined
753
+ ? null
754
+ : timestamp(record.lastEvidenceAt, "Agent last evidence time"),
438
755
  provenance: validateProvenance(record.provenance),
439
- freshness: validateFreshness(record.freshness)
756
+ freshness: validateFreshness(record.freshness),
440
757
  };
441
758
  }
442
759
  function validateToolRecord(value) {
443
760
  const record = object(value, "Tool record is invalid.");
444
- exactKeys(record, ["schemaVersion", "kind", "tenantId", "toolId", "name", "providerId", "requiredScopes", "credentialIds", "provenance", "freshness"], "tool record", ["providerId"]);
761
+ exactKeys(record, [
762
+ "schemaVersion",
763
+ "kind",
764
+ "tenantId",
765
+ "toolId",
766
+ "name",
767
+ "providerId",
768
+ "requiredScopes",
769
+ "credentialIds",
770
+ "provenance",
771
+ "freshness",
772
+ ], "tool record", ["providerId"]);
445
773
  if (record.schemaVersion !== INVENTORY_SCHEMA_VERSION)
446
774
  throw new InventoryError("Unsupported tool record schema.");
447
775
  const toolKind = record.kind;
@@ -453,17 +781,35 @@ function validateToolRecord(value) {
453
781
  toolId: identifier(record.toolId, "Tool id"),
454
782
  name: text(record.name, "Tool name", 160),
455
783
  kind: toolKind,
456
- providerId: record.providerId === null ? null : record.providerId === undefined ? null : identifier(record.providerId, "Tool provider id"),
784
+ providerId: record.providerId === null
785
+ ? null
786
+ : record.providerId === undefined
787
+ ? null
788
+ : identifier(record.providerId, "Tool provider id"),
457
789
  requiredScopes: scopes(record.requiredScopes, "Tool required scopes", INVENTORY_LIMITS.maxScopesPerRecord),
458
790
  credentialIds: identifiers(record.credentialIds, "Tool credential ids", INVENTORY_LIMITS.maxReferencesPerRecord),
459
791
  provenance: validateProvenance(record.provenance),
460
- freshness: validateFreshness(record.freshness)
792
+ freshness: validateFreshness(record.freshness),
461
793
  };
462
794
  }
463
795
  function validateIdentityRecord(value) {
464
796
  const record = object(value, "Identity record is invalid.");
465
- exactKeys(record, ["schemaVersion", "kind", "tenantId", "identityId", "principalId", "role", "agentId", "toolIds", "environmentIds", "scopes", "provenance", "freshness"], "identity record", ["agentId"]);
466
- if (record.schemaVersion !== INVENTORY_SCHEMA_VERSION || record.kind !== "ghostapi.inventory-identity")
797
+ exactKeys(record, [
798
+ "schemaVersion",
799
+ "kind",
800
+ "tenantId",
801
+ "identityId",
802
+ "principalId",
803
+ "role",
804
+ "agentId",
805
+ "toolIds",
806
+ "environmentIds",
807
+ "scopes",
808
+ "provenance",
809
+ "freshness",
810
+ ], "identity record", ["agentId"]);
811
+ if (record.schemaVersion !== INVENTORY_SCHEMA_VERSION ||
812
+ record.kind !== "ghostapi.inventory-identity")
467
813
  throw new InventoryError("Unsupported identity record schema.");
468
814
  const role = record.role;
469
815
  if (!IDENTITY_ROLES.includes(role))
@@ -475,20 +821,40 @@ function validateIdentityRecord(value) {
475
821
  identityId: identifier(record.identityId, "Identity id"),
476
822
  principalId: identifier(record.principalId, "Identity principal id"),
477
823
  role: role,
478
- agentId: record.agentId === null ? null : record.agentId === undefined ? null : identifier(record.agentId, "Identity agent id"),
824
+ agentId: record.agentId === null
825
+ ? null
826
+ : record.agentId === undefined
827
+ ? null
828
+ : identifier(record.agentId, "Identity agent id"),
479
829
  toolIds: identifiers(record.toolIds, "Identity tool ids", INVENTORY_LIMITS.maxReferencesPerRecord),
480
830
  environmentIds: identifiers(record.environmentIds, "Identity environment ids", 16),
481
831
  scopes: scopes(record.scopes, "Identity scopes", INVENTORY_LIMITS.maxScopesPerRecord),
482
832
  provenance: validateProvenance(record.provenance),
483
- freshness: validateFreshness(record.freshness)
833
+ freshness: validateFreshness(record.freshness),
484
834
  };
485
835
  }
486
836
  function validateProviderRecord(value) {
487
837
  const record = object(value, "Provider record is invalid.");
488
- exactKeys(record, ["schemaVersion", "kind", "tenantId", "providerId", "name", "providerType", "ownerId", "environmentIds", "resourceIds", "gatewayBound", "killSwitchApplied", "provenance", "freshness"], "provider record", ["ownerId"]);
489
- if (record.schemaVersion !== INVENTORY_SCHEMA_VERSION || record.kind !== "ghostapi.inventory-provider")
838
+ exactKeys(record, [
839
+ "schemaVersion",
840
+ "kind",
841
+ "tenantId",
842
+ "providerId",
843
+ "name",
844
+ "providerType",
845
+ "ownerId",
846
+ "environmentIds",
847
+ "resourceIds",
848
+ "gatewayBound",
849
+ "killSwitchApplied",
850
+ "provenance",
851
+ "freshness",
852
+ ], "provider record", ["ownerId"]);
853
+ if (record.schemaVersion !== INVENTORY_SCHEMA_VERSION ||
854
+ record.kind !== "ghostapi.inventory-provider")
490
855
  throw new InventoryError("Unsupported provider record schema.");
491
- if (typeof record.gatewayBound !== "boolean" || typeof record.killSwitchApplied !== "boolean")
856
+ if (typeof record.gatewayBound !== "boolean" ||
857
+ typeof record.killSwitchApplied !== "boolean")
492
858
  throw new InventoryError("Provider record booleans are invalid.");
493
859
  return {
494
860
  schemaVersion: 1,
@@ -497,19 +863,39 @@ function validateProviderRecord(value) {
497
863
  providerId: identifier(record.providerId, "Provider id"),
498
864
  name: text(record.name, "Provider name", 160),
499
865
  providerType: text(record.providerType, "Provider type", 120),
500
- ownerId: record.ownerId === null ? null : record.ownerId === undefined ? null : identifier(record.ownerId, "Provider owner id"),
866
+ ownerId: record.ownerId === null
867
+ ? null
868
+ : record.ownerId === undefined
869
+ ? null
870
+ : identifier(record.ownerId, "Provider owner id"),
501
871
  environmentIds: identifiers(record.environmentIds, "Provider environment ids", 16),
502
872
  resourceIds: identifiers(record.resourceIds, "Provider resource ids", INVENTORY_LIMITS.maxReferencesPerRecord),
503
873
  gatewayBound: record.gatewayBound,
504
874
  killSwitchApplied: record.killSwitchApplied,
505
875
  provenance: validateProvenance(record.provenance),
506
- freshness: validateFreshness(record.freshness)
876
+ freshness: validateFreshness(record.freshness),
507
877
  };
508
878
  }
509
879
  function validateResourceRecord(value) {
510
880
  const record = object(value, "Resource record is invalid.");
511
- exactKeys(record, ["schemaVersion", "kind", "tenantId", "resourceId", "providerId", "resourceType", "name", "environmentIds", "sensitive", "ownerId", "businessPurpose", "sideEffectIds", "provenance", "freshness"], "resource record", ["ownerId", "businessPurpose"]);
512
- if (record.schemaVersion !== INVENTORY_SCHEMA_VERSION || record.kind !== "ghostapi.inventory-resource")
881
+ exactKeys(record, [
882
+ "schemaVersion",
883
+ "kind",
884
+ "tenantId",
885
+ "resourceId",
886
+ "providerId",
887
+ "resourceType",
888
+ "name",
889
+ "environmentIds",
890
+ "sensitive",
891
+ "ownerId",
892
+ "businessPurpose",
893
+ "sideEffectIds",
894
+ "provenance",
895
+ "freshness",
896
+ ], "resource record", ["ownerId", "businessPurpose"]);
897
+ if (record.schemaVersion !== INVENTORY_SCHEMA_VERSION ||
898
+ record.kind !== "ghostapi.inventory-resource")
513
899
  throw new InventoryError("Unsupported resource record schema.");
514
900
  if (typeof record.sensitive !== "boolean")
515
901
  throw new InventoryError("Resource record sensitive flag is invalid.");
@@ -523,16 +909,33 @@ function validateResourceRecord(value) {
523
909
  name: text(record.name, "Resource name", 160),
524
910
  environmentIds: identifiers(record.environmentIds, "Resource environment ids", 16),
525
911
  sensitive: record.sensitive,
526
- ownerId: record.ownerId === null ? null : record.ownerId === undefined ? null : identifier(record.ownerId, "Resource owner id"),
527
- ...(record.businessPurpose === undefined ? {} : { businessPurpose: text(record.businessPurpose, "Resource business purpose", 240) }),
912
+ ownerId: record.ownerId === null
913
+ ? null
914
+ : record.ownerId === undefined
915
+ ? null
916
+ : identifier(record.ownerId, "Resource owner id"),
917
+ ...(record.businessPurpose === undefined
918
+ ? {}
919
+ : {
920
+ businessPurpose: text(record.businessPurpose, "Resource business purpose", 240),
921
+ }),
528
922
  sideEffectIds: identifiers(record.sideEffectIds, "Resource side effect ids", INVENTORY_LIMITS.maxReferencesPerRecord),
529
923
  provenance: validateProvenance(record.provenance),
530
- freshness: validateFreshness(record.freshness)
924
+ freshness: validateFreshness(record.freshness),
531
925
  };
532
926
  }
533
927
  function validateSideEffectRecord(value) {
534
928
  const record = object(value, "Side effect record is invalid.");
535
- exactKeys(record, ["schemaVersion", "tenantId", "sideEffectId", "resourceId", "kind", "severity", "provenance", "freshness"], "side effect record");
929
+ exactKeys(record, [
930
+ "schemaVersion",
931
+ "tenantId",
932
+ "sideEffectId",
933
+ "resourceId",
934
+ "kind",
935
+ "severity",
936
+ "provenance",
937
+ "freshness",
938
+ ], "side effect record");
536
939
  if (record.schemaVersion !== INVENTORY_SCHEMA_VERSION)
537
940
  throw new InventoryError("Unsupported side effect record schema.");
538
941
  const kind = record.kind;
@@ -549,13 +952,32 @@ function validateSideEffectRecord(value) {
549
952
  kind: kind,
550
953
  severity: severity,
551
954
  provenance: validateProvenance(record.provenance),
552
- freshness: validateFreshness(record.freshness)
955
+ freshness: validateFreshness(record.freshness),
553
956
  };
554
957
  }
555
958
  function validateCredentialRecord(value) {
556
959
  const record = object(value, "Credential record is invalid.");
557
- exactKeys(record, ["schemaVersion", "kind", "tenantId", "credentialId", "name", "providerId", "ownerId", "environmentIds", "grantScopes", "toolIds", "status", "issuedAt", "lastUsedAt", "rotatedAt", "gatewayBound", "provenance", "freshness"], "credential record", ["ownerId", "lastUsedAt", "rotatedAt"]);
558
- if (record.schemaVersion !== INVENTORY_SCHEMA_VERSION || record.kind !== "ghostapi.inventory-credential")
960
+ exactKeys(record, [
961
+ "schemaVersion",
962
+ "kind",
963
+ "tenantId",
964
+ "credentialId",
965
+ "name",
966
+ "providerId",
967
+ "ownerId",
968
+ "environmentIds",
969
+ "grantScopes",
970
+ "toolIds",
971
+ "status",
972
+ "issuedAt",
973
+ "lastUsedAt",
974
+ "rotatedAt",
975
+ "gatewayBound",
976
+ "provenance",
977
+ "freshness",
978
+ ], "credential record", ["ownerId", "lastUsedAt", "rotatedAt"]);
979
+ if (record.schemaVersion !== INVENTORY_SCHEMA_VERSION ||
980
+ record.kind !== "ghostapi.inventory-credential")
559
981
  throw new InventoryError("Unsupported credential record schema.");
560
982
  if (record.status !== "active" && record.status !== "revoked")
561
983
  throw new InventoryError("Credential record status is invalid.");
@@ -568,23 +990,45 @@ function validateCredentialRecord(value) {
568
990
  credentialId: identifier(record.credentialId, "Credential id"),
569
991
  name: text(record.name, "Credential name", 160),
570
992
  providerId: identifier(record.providerId, "Credential provider id"),
571
- ownerId: record.ownerId === null ? null : record.ownerId === undefined ? null : identifier(record.ownerId, "Credential owner id"),
993
+ ownerId: record.ownerId === null
994
+ ? null
995
+ : record.ownerId === undefined
996
+ ? null
997
+ : identifier(record.ownerId, "Credential owner id"),
572
998
  environmentIds: identifiers(record.environmentIds, "Credential environment ids", 16),
573
999
  grantScopes: scopes(record.grantScopes, "Credential grant scopes", INVENTORY_LIMITS.maxScopesPerRecord),
574
1000
  toolIds: identifiers(record.toolIds, "Credential tool ids", INVENTORY_LIMITS.maxReferencesPerRecord),
575
1001
  status: record.status,
576
1002
  issuedAt: timestamp(record.issuedAt, "Credential issued time"),
577
- lastUsedAt: record.lastUsedAt === null ? null : record.lastUsedAt === undefined ? null : timestamp(record.lastUsedAt, "Credential last used time"),
578
- ...(record.rotatedAt === undefined ? {} : { rotatedAt: timestamp(record.rotatedAt, "Credential rotated time") }),
1003
+ lastUsedAt: record.lastUsedAt === null
1004
+ ? null
1005
+ : record.lastUsedAt === undefined
1006
+ ? null
1007
+ : timestamp(record.lastUsedAt, "Credential last used time"),
1008
+ ...(record.rotatedAt === undefined
1009
+ ? {}
1010
+ : { rotatedAt: timestamp(record.rotatedAt, "Credential rotated time") }),
579
1011
  gatewayBound: record.gatewayBound,
580
1012
  provenance: validateProvenance(record.provenance),
581
- freshness: validateFreshness(record.freshness)
1013
+ freshness: validateFreshness(record.freshness),
582
1014
  };
583
1015
  }
584
1016
  function validatePolicyRecord(value) {
585
1017
  const record = object(value, "Policy record is invalid.");
586
- exactKeys(record, ["schemaVersion", "kind", "tenantId", "policyId", "name", "version", "hash", "environmentIds", "provenance", "freshness"], "policy record");
587
- if (record.schemaVersion !== INVENTORY_SCHEMA_VERSION || record.kind !== "ghostapi.inventory-policy")
1018
+ exactKeys(record, [
1019
+ "schemaVersion",
1020
+ "kind",
1021
+ "tenantId",
1022
+ "policyId",
1023
+ "name",
1024
+ "version",
1025
+ "hash",
1026
+ "environmentIds",
1027
+ "provenance",
1028
+ "freshness",
1029
+ ], "policy record");
1030
+ if (record.schemaVersion !== INVENTORY_SCHEMA_VERSION ||
1031
+ record.kind !== "ghostapi.inventory-policy")
588
1032
  throw new InventoryError("Unsupported policy record schema.");
589
1033
  return {
590
1034
  schemaVersion: 1,
@@ -596,12 +1040,23 @@ function validatePolicyRecord(value) {
596
1040
  hash: hash(record.hash, "Policy hash"),
597
1041
  environmentIds: identifiers(record.environmentIds, "Policy environment ids", 16),
598
1042
  provenance: validateProvenance(record.provenance),
599
- freshness: validateFreshness(record.freshness)
1043
+ freshness: validateFreshness(record.freshness),
600
1044
  };
601
1045
  }
602
1046
  function validateEdge(value) {
603
1047
  const record = object(value, "Graph edge is invalid.");
604
- exactKeys(record, ["tenantId", "edgeId", "sourceKind", "sourceId", "targetKind", "targetId", "relation", "provenance", "firstSeenAt", "lastSeenAt"], "graph edge");
1048
+ exactKeys(record, [
1049
+ "tenantId",
1050
+ "edgeId",
1051
+ "sourceKind",
1052
+ "sourceId",
1053
+ "targetKind",
1054
+ "targetId",
1055
+ "relation",
1056
+ "provenance",
1057
+ "firstSeenAt",
1058
+ "lastSeenAt",
1059
+ ], "graph edge");
605
1060
  if (!NODE_KINDS.includes(record.sourceKind))
606
1061
  throw new InventoryError("Graph edge source kind is invalid.");
607
1062
  if (!NODE_KINDS.includes(record.targetKind))
@@ -618,12 +1073,27 @@ function validateEdge(value) {
618
1073
  relation: record.relation,
619
1074
  provenance: validateProvenance(record.provenance),
620
1075
  firstSeenAt: timestamp(record.firstSeenAt, "Edge first seen time"),
621
- lastSeenAt: timestamp(record.lastSeenAt, "Edge last seen time")
1076
+ lastSeenAt: timestamp(record.lastSeenAt, "Edge last seen time"),
622
1077
  };
623
1078
  }
624
1079
  function validateFindingRecord(value) {
625
1080
  const record = object(value, "Finding record is invalid.");
626
- exactKeys(record, ["schemaVersion", "tenantId", "findingId", "kind", "severity", "targetKind", "targetId", "ruleId", "reason", "basis", "discoveredAt", "status", "resolvedAt", "remediationId"], "finding record", ["resolvedAt", "remediationId"]);
1081
+ exactKeys(record, [
1082
+ "schemaVersion",
1083
+ "tenantId",
1084
+ "findingId",
1085
+ "kind",
1086
+ "severity",
1087
+ "targetKind",
1088
+ "targetId",
1089
+ "ruleId",
1090
+ "reason",
1091
+ "basis",
1092
+ "discoveredAt",
1093
+ "status",
1094
+ "resolvedAt",
1095
+ "remediationId",
1096
+ ], "finding record", ["resolvedAt", "remediationId"]);
627
1097
  if (record.schemaVersion !== INVENTORY_SCHEMA_VERSION)
628
1098
  throw new InventoryError("Unsupported finding record schema.");
629
1099
  if (!FINDING_KINDS.includes(record.kind))
@@ -634,7 +1104,15 @@ function validateFindingRecord(value) {
634
1104
  throw new InventoryError("Finding basis is invalid.");
635
1105
  if (record.status !== "open" && record.status !== "resolved")
636
1106
  throw new InventoryError("Finding status is invalid.");
637
- if (record.targetKind !== "agent" && record.targetKind !== "identity" && record.targetKind !== "tool" && record.targetKind !== "provider" && record.targetKind !== "resource" && record.targetKind !== "side_effect" && record.targetKind !== "credential" && record.targetKind !== "environment" && record.targetKind !== "policy")
1107
+ if (record.targetKind !== "agent" &&
1108
+ record.targetKind !== "identity" &&
1109
+ record.targetKind !== "tool" &&
1110
+ record.targetKind !== "provider" &&
1111
+ record.targetKind !== "resource" &&
1112
+ record.targetKind !== "side_effect" &&
1113
+ record.targetKind !== "credential" &&
1114
+ record.targetKind !== "environment" &&
1115
+ record.targetKind !== "policy")
638
1116
  throw new InventoryError("Finding target kind is invalid.");
639
1117
  return {
640
1118
  schemaVersion: 1,
@@ -649,22 +1127,52 @@ function validateFindingRecord(value) {
649
1127
  basis: record.basis,
650
1128
  discoveredAt: timestamp(record.discoveredAt, "Finding discovery time"),
651
1129
  status: record.status,
652
- ...(record.resolvedAt === undefined ? {} : { resolvedAt: timestamp(record.resolvedAt, "Finding resolved time") }),
653
- ...(record.remediationId === undefined ? {} : { remediationId: identifier(record.remediationId, "Finding remediation id") })
1130
+ ...(record.resolvedAt === undefined
1131
+ ? {}
1132
+ : { resolvedAt: timestamp(record.resolvedAt, "Finding resolved time") }),
1133
+ ...(record.remediationId === undefined
1134
+ ? {}
1135
+ : {
1136
+ remediationId: identifier(record.remediationId, "Finding remediation id"),
1137
+ }),
654
1138
  };
655
1139
  }
656
1140
  function validateRemediationRecord(value) {
657
1141
  const record = object(value, "Remediation record is invalid.");
658
- exactKeys(record, ["schemaVersion", "tenantId", "remediationId", "findingId", "kind", "targetKind", "targetId", "rationale", "proposedBy", "createdAt", "status", "appliedAt", "result"], "remediation record", ["appliedAt", "result"]);
1142
+ exactKeys(record, [
1143
+ "schemaVersion",
1144
+ "tenantId",
1145
+ "remediationId",
1146
+ "findingId",
1147
+ "kind",
1148
+ "targetKind",
1149
+ "targetId",
1150
+ "rationale",
1151
+ "proposedBy",
1152
+ "createdAt",
1153
+ "status",
1154
+ "appliedAt",
1155
+ "result",
1156
+ ], "remediation record", ["appliedAt", "result"]);
659
1157
  if (record.schemaVersion !== INVENTORY_SCHEMA_VERSION)
660
1158
  throw new InventoryError("Unsupported remediation record schema.");
661
1159
  if (!REMEDIATION_KINDS.includes(record.kind))
662
1160
  throw new InventoryError("Remediation kind is invalid.");
663
1161
  if (!REMEDIATION_STATUSES.includes(record.status))
664
1162
  throw new InventoryError("Remediation status is invalid.");
665
- if (record.targetKind !== "agent" && record.targetKind !== "identity" && record.targetKind !== "tool" && record.targetKind !== "provider" && record.targetKind !== "resource" && record.targetKind !== "side_effect" && record.targetKind !== "credential" && record.targetKind !== "environment" && record.targetKind !== "policy")
1163
+ if (record.targetKind !== "agent" &&
1164
+ record.targetKind !== "identity" &&
1165
+ record.targetKind !== "tool" &&
1166
+ record.targetKind !== "provider" &&
1167
+ record.targetKind !== "resource" &&
1168
+ record.targetKind !== "side_effect" &&
1169
+ record.targetKind !== "credential" &&
1170
+ record.targetKind !== "environment" &&
1171
+ record.targetKind !== "policy")
666
1172
  throw new InventoryError("Remediation target kind is invalid.");
667
- const result = record.result === undefined ? undefined : validateRemediationResult(record.result);
1173
+ const result = record.result === undefined
1174
+ ? undefined
1175
+ : validateRemediationResult(record.result);
668
1176
  return {
669
1177
  schemaVersion: 1,
670
1178
  tenantId: identifier(record.tenantId, "Remediation tenant id"),
@@ -677,8 +1185,10 @@ function validateRemediationRecord(value) {
677
1185
  proposedBy: identifier(record.proposedBy, "Remediation proposer"),
678
1186
  createdAt: timestamp(record.createdAt, "Remediation creation time"),
679
1187
  status: record.status,
680
- ...(record.appliedAt === undefined ? {} : { appliedAt: timestamp(record.appliedAt, "Remediation applied time") }),
681
- ...(result === undefined ? {} : { result })
1188
+ ...(record.appliedAt === undefined
1189
+ ? {}
1190
+ : { appliedAt: timestamp(record.appliedAt, "Remediation applied time") }),
1191
+ ...(result === undefined ? {} : { result }),
682
1192
  };
683
1193
  }
684
1194
  function validateRemediationResult(value) {
@@ -686,9 +1196,19 @@ function validateRemediationResult(value) {
686
1196
  exactKeys(result, ["description", "reducedScopes", "evalScenarioId", "ownerId"], "remediation result", ["reducedScopes", "evalScenarioId", "ownerId"]);
687
1197
  return {
688
1198
  description: text(result.description, "Remediation result description", 400),
689
- ...(result.reducedScopes === undefined ? {} : { reducedScopes: scopes(result.reducedScopes, "Remediation reduced scopes", INVENTORY_LIMITS.maxScopesPerRecord) }),
690
- ...(result.evalScenarioId === undefined ? {} : { evalScenarioId: identifier(result.evalScenarioId, "Remediation eval scenario id") }),
691
- ...(result.ownerId === undefined ? {} : { ownerId: identifier(result.ownerId, "Remediation owner id") })
1199
+ ...(result.reducedScopes === undefined
1200
+ ? {}
1201
+ : {
1202
+ reducedScopes: scopes(result.reducedScopes, "Remediation reduced scopes", INVENTORY_LIMITS.maxScopesPerRecord),
1203
+ }),
1204
+ ...(result.evalScenarioId === undefined
1205
+ ? {}
1206
+ : {
1207
+ evalScenarioId: identifier(result.evalScenarioId, "Remediation eval scenario id"),
1208
+ }),
1209
+ ...(result.ownerId === undefined
1210
+ ? {}
1211
+ : { ownerId: identifier(result.ownerId, "Remediation owner id") }),
692
1212
  };
693
1213
  }
694
1214
  function validateProvenance(value) {
@@ -702,7 +1222,7 @@ function validateProvenance(value) {
702
1222
  sourceType: sourceType,
703
1223
  sourceName: text(provenance.sourceName, "Provenance source name", 120),
704
1224
  importedAt: timestamp(provenance.importedAt, "Provenance import time"),
705
- importedBy: identifier(provenance.importedBy, "Provenance importer")
1225
+ importedBy: identifier(provenance.importedBy, "Provenance importer"),
706
1226
  };
707
1227
  }
708
1228
  function validateFreshness(value) {
@@ -710,11 +1230,17 @@ function validateFreshness(value) {
710
1230
  exactKeys(freshness, ["firstSeenAt", "lastSeenAt"], "record freshness");
711
1231
  return {
712
1232
  firstSeenAt: timestamp(freshness.firstSeenAt, "Record first seen time"),
713
- lastSeenAt: timestamp(freshness.lastSeenAt, "Record last seen time")
1233
+ lastSeenAt: timestamp(freshness.lastSeenAt, "Record last seen time"),
714
1234
  };
715
1235
  }
716
1236
  export function buildRecordProvenance(source, importedAt, importedBy) {
717
- return { sourceId: source.sourceId, sourceType: source.sourceType, sourceName: source.sourceName, importedAt, importedBy };
1237
+ return {
1238
+ sourceId: source.sourceId,
1239
+ sourceType: source.sourceType,
1240
+ sourceName: source.sourceName,
1241
+ importedAt,
1242
+ importedBy,
1243
+ };
718
1244
  }
719
1245
  export function buildRecordFreshness(now) {
720
1246
  return { firstSeenAt: now, lastSeenAt: now };
@@ -741,7 +1267,8 @@ function array(value, label, max) {
741
1267
  }
742
1268
  function exactKeys(value, keys, label, optional = []) {
743
1269
  for (const key of Object.keys(value)) {
744
- if (!keys.includes(key) || (value[key] === undefined && !optional.includes(key)))
1270
+ if (!keys.includes(key) ||
1271
+ (value[key] === undefined && !optional.includes(key)))
745
1272
  throw new InventoryError(`${label} contains unsupported field: ${key}`);
746
1273
  }
747
1274
  }
@@ -750,7 +1277,9 @@ function unique(values, label) {
750
1277
  throw new InventoryError(`${label} must be unique.`);
751
1278
  }
752
1279
  function identifier(value, label) {
753
- if (typeof value !== "string" || !IDENTIFIER.test(value) || sanitizeSecretString(value) !== value)
1280
+ if (typeof value !== "string" ||
1281
+ !IDENTIFIER.test(value) ||
1282
+ sanitizeSecretString(value) !== value)
754
1283
  throw new InventoryError(`${label} must be a safe identifier.`);
755
1284
  return value;
756
1285
  }
@@ -759,18 +1288,26 @@ function identifiers(value, label, max) {
759
1288
  }
760
1289
  function scopes(value, label, max) {
761
1290
  return array(value, label, max).map((entry) => {
762
- if (typeof entry !== "string" || !SCOPE_IDENTIFIER.test(entry) || sanitizeSecretString(entry) !== entry)
1291
+ if (typeof entry !== "string" ||
1292
+ !SCOPE_IDENTIFIER.test(entry) ||
1293
+ sanitizeSecretString(entry) !== entry)
763
1294
  throw new InventoryError(`${label} must be safe scope identifiers.`);
764
1295
  return entry;
765
1296
  });
766
1297
  }
767
1298
  function text(value, label, max) {
768
- if (typeof value !== "string" || value.trim() === "" || value.length > max || /[\u0000-\u001f]/.test(value) || sanitizeSecretString(value) !== value)
1299
+ if (typeof value !== "string" ||
1300
+ value.trim() === "" ||
1301
+ value.length > max ||
1302
+ /[\u0000-\u001f]/.test(value) ||
1303
+ sanitizeSecretString(value) !== value)
769
1304
  throw new InventoryError(`${label} is invalid.`);
770
1305
  return value.trim();
771
1306
  }
772
1307
  function timestamp(value, label) {
773
- if (typeof value !== "string" || !TIMESTAMP.test(value) || !Number.isFinite(Date.parse(value)))
1308
+ if (typeof value !== "string" ||
1309
+ !TIMESTAMP.test(value) ||
1310
+ !Number.isFinite(Date.parse(value)))
774
1311
  throw new InventoryError(`${label} must be an ISO UTC timestamp.`);
775
1312
  return value;
776
1313
  }
@@ -780,7 +1317,10 @@ function hash(value, label) {
780
1317
  return value;
781
1318
  }
782
1319
  function nonNegative(value, label, max) {
783
- if (typeof value !== "number" || !Number.isInteger(value) || value < 0 || value > max)
1320
+ if (typeof value !== "number" ||
1321
+ !Number.isInteger(value) ||
1322
+ value < 0 ||
1323
+ value > max)
784
1324
  throw new InventoryError(`${label} is invalid.`);
785
1325
  return value;
786
1326
  }