@yiaany/ghostapi 0.1.7 → 0.1.9

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 (322) hide show
  1. package/CHANGELOG.md +42 -2
  2. package/CONTRIBUTING.md +119 -117
  3. package/LICENSE +21 -21
  4. package/README.md +208 -181
  5. package/SECURITY.md +53 -37
  6. package/dist/actions/gateway.d.ts +179 -0
  7. package/dist/actions/gateway.js +514 -0
  8. package/dist/actions/gateway.js.map +1 -0
  9. package/dist/actions/index.d.ts +2 -0
  10. package/dist/actions/index.js +2 -0
  11. package/dist/actions/index.js.map +1 -0
  12. package/dist/ai/aiClient.js +4 -4
  13. package/dist/ai/aiClient.js.map +1 -1
  14. package/dist/ai/aiGenerator.d.ts +2 -2
  15. package/dist/ai/aiGenerator.js +21 -72
  16. package/dist/ai/aiGenerator.js.map +1 -1
  17. package/dist/ai/prompts.js +51 -51
  18. package/dist/ai/prompts.js.map +1 -1
  19. package/dist/approvals/approvalInbox.d.ts +150 -0
  20. package/dist/approvals/approvalInbox.js +412 -0
  21. package/dist/approvals/approvalInbox.js.map +1 -0
  22. package/dist/approvals/index.d.ts +2 -0
  23. package/dist/approvals/index.js +2 -0
  24. package/dist/approvals/index.js.map +1 -0
  25. package/dist/behavior/behaviorStore.d.ts +2 -0
  26. package/dist/behavior/behaviorStore.js +50 -46
  27. package/dist/behavior/behaviorStore.js.map +1 -1
  28. package/dist/cache/index.d.ts +2 -0
  29. package/dist/cache/index.js +58 -14
  30. package/dist/cache/index.js.map +1 -1
  31. package/dist/cli/index.js +438 -58
  32. package/dist/cli/index.js.map +1 -1
  33. package/dist/cli/openUrl.d.ts +10 -0
  34. package/dist/cli/openUrl.js +31 -0
  35. package/dist/cli/openUrl.js.map +1 -0
  36. package/dist/cli/parser.d.ts +155 -0
  37. package/dist/cli/parser.js +504 -2
  38. package/dist/cli/parser.js.map +1 -1
  39. package/dist/config/dataPaths.d.ts +31 -0
  40. package/dist/config/dataPaths.js +45 -0
  41. package/dist/config/dataPaths.js.map +1 -0
  42. package/dist/config/localConfig.d.ts +1 -2
  43. package/dist/config/localConfig.js +27 -21
  44. package/dist/config/localConfig.js.map +1 -1
  45. package/dist/config/serverConfig.d.ts +2 -0
  46. package/dist/config/serverConfig.js +8 -1
  47. package/dist/config/serverConfig.js.map +1 -1
  48. package/dist/contracts/contract.d.ts +76 -0
  49. package/dist/contracts/contract.js +603 -0
  50. package/dist/contracts/contract.js.map +1 -0
  51. package/dist/contracts/index.d.ts +2 -0
  52. package/dist/contracts/index.js +2 -0
  53. package/dist/contracts/index.js.map +1 -0
  54. package/dist/credentials/credentialBroker.d.ts +207 -0
  55. package/dist/credentials/credentialBroker.js +588 -0
  56. package/dist/credentials/credentialBroker.js.map +1 -0
  57. package/dist/credentials/index.d.ts +2 -0
  58. package/dist/credentials/index.js +2 -0
  59. package/dist/credentials/index.js.map +1 -0
  60. package/dist/dashboard/app.js +685 -615
  61. package/dist/dashboard/index.html +13 -7
  62. package/dist/dashboard/styles.css +349 -349
  63. package/dist/egress/capabilities.d.ts +32 -0
  64. package/dist/egress/capabilities.js +165 -0
  65. package/dist/egress/capabilities.js.map +1 -0
  66. package/dist/egress/linuxBootstrap.d.ts +1 -0
  67. package/dist/egress/linuxBootstrap.js +121 -0
  68. package/dist/egress/linuxBootstrap.js.map +1 -0
  69. package/dist/egress/run.d.ts +18 -0
  70. package/dist/egress/run.js +305 -0
  71. package/dist/egress/run.js.map +1 -0
  72. package/dist/errors/errorEngine.d.ts +2 -2
  73. package/dist/errors/errorEngine.js +3 -40
  74. package/dist/errors/errorEngine.js.map +1 -1
  75. package/dist/evals/evals.d.ts +126 -0
  76. package/dist/evals/evals.js +466 -0
  77. package/dist/evals/evals.js.map +1 -0
  78. package/dist/evals/index.d.ts +2 -0
  79. package/dist/evals/index.js +2 -0
  80. package/dist/evals/index.js.map +1 -0
  81. package/dist/evidence/index.d.ts +2 -0
  82. package/dist/evidence/index.js +2 -0
  83. package/dist/evidence/index.js.map +1 -0
  84. package/dist/evidence/report.d.ts +143 -0
  85. package/dist/evidence/report.js +556 -0
  86. package/dist/evidence/report.js.map +1 -0
  87. package/dist/fault/faultLab.d.ts +5 -4
  88. package/dist/fault/faultLab.js +66 -24
  89. package/dist/fault/faultLab.js.map +1 -1
  90. package/dist/index.d.ts +46 -0
  91. package/dist/index.js +23 -0
  92. package/dist/index.js.map +1 -1
  93. package/dist/inventory/analysis.d.ts +12 -0
  94. package/dist/inventory/analysis.js +457 -0
  95. package/dist/inventory/analysis.js.map +1 -0
  96. package/dist/inventory/index.d.ts +5 -0
  97. package/dist/inventory/index.js +4 -0
  98. package/dist/inventory/index.js.map +1 -0
  99. package/dist/inventory/inventoryController.d.ts +53 -0
  100. package/dist/inventory/inventoryController.js +1044 -0
  101. package/dist/inventory/inventoryController.js.map +1 -0
  102. package/dist/inventory/types.d.ts +579 -0
  103. package/dist/inventory/types.js +787 -0
  104. package/dist/inventory/types.js.map +1 -0
  105. package/dist/landing/assets/index-CxWsopzP.css +1 -0
  106. package/dist/landing/assets/index-DZlpyhqd.js +55 -0
  107. package/dist/landing/assets/index-Dk-Xezs3.js +55 -0
  108. package/dist/landing/index.html +15 -15
  109. package/dist/ledger/actionLedger.d.ts +125 -0
  110. package/dist/ledger/actionLedger.js +594 -0
  111. package/dist/ledger/actionLedger.js.map +1 -0
  112. package/dist/ledger/index.d.ts +2 -0
  113. package/dist/ledger/index.js +2 -0
  114. package/dist/ledger/index.js.map +1 -0
  115. package/dist/mcp/server.js +4 -3
  116. package/dist/mcp/server.js.map +1 -1
  117. package/dist/policy/index.d.ts +3 -0
  118. package/dist/policy/index.js +2 -0
  119. package/dist/policy/index.js.map +1 -0
  120. package/dist/policy/policy.d.ts +14 -0
  121. package/dist/policy/policy.js +282 -0
  122. package/dist/policy/policy.js.map +1 -0
  123. package/dist/policy/types.d.ts +52 -0
  124. package/dist/policy/types.js +2 -0
  125. package/dist/policy/types.js.map +1 -0
  126. package/dist/productTelemetry/index.d.ts +16 -0
  127. package/dist/productTelemetry/index.js +142 -0
  128. package/dist/productTelemetry/index.js.map +1 -0
  129. package/dist/providers/conformance.d.ts +7 -0
  130. package/dist/providers/conformance.js +48 -0
  131. package/dist/providers/conformance.js.map +1 -0
  132. package/dist/providers/index.d.ts +4 -2
  133. package/dist/providers/index.js +3 -1
  134. package/dist/providers/index.js.map +1 -1
  135. package/dist/providers/packs/resendPack.d.ts +2 -0
  136. package/dist/providers/packs/resendPack.js +147 -0
  137. package/dist/providers/packs/resendPack.js.map +1 -0
  138. package/dist/providers/packs/stripePack.d.ts +2 -0
  139. package/dist/providers/packs/stripePack.js +852 -0
  140. package/dist/providers/packs/stripePack.js.map +1 -0
  141. package/dist/providers/registry.d.ts +7 -3
  142. package/dist/providers/registry.js +55 -4
  143. package/dist/providers/registry.js.map +1 -1
  144. package/dist/providers/resend.js +2 -11
  145. package/dist/providers/resend.js.map +1 -1
  146. package/dist/providers/runtime.d.ts +8 -0
  147. package/dist/providers/runtime.js +48 -0
  148. package/dist/providers/runtime.js.map +1 -0
  149. package/dist/providers/stripeWebhook.d.ts +13 -0
  150. package/dist/providers/stripeWebhook.js +50 -0
  151. package/dist/providers/stripeWebhook.js.map +1 -0
  152. package/dist/providers/types.d.ts +153 -0
  153. package/dist/proxy/cacheKey.js +3 -3
  154. package/dist/proxy/cacheKey.js.map +1 -1
  155. package/dist/proxy/providerDetector.d.ts +2 -7
  156. package/dist/proxy/providerDetector.js +4 -4
  157. package/dist/proxy/providerDetector.js.map +1 -1
  158. package/dist/proxy/proxyHandler.js +116 -12
  159. package/dist/proxy/proxyHandler.js.map +1 -1
  160. package/dist/proxy/requestNormalizer.d.ts +0 -1
  161. package/dist/proxy/requestNormalizer.js +2 -5
  162. package/dist/proxy/requestNormalizer.js.map +1 -1
  163. package/dist/reliability/costGovernance.d.ts +175 -0
  164. package/dist/reliability/costGovernance.js +515 -0
  165. package/dist/reliability/costGovernance.js.map +1 -0
  166. package/dist/reliability/index.d.ts +8 -0
  167. package/dist/reliability/index.js +5 -0
  168. package/dist/reliability/index.js.map +1 -0
  169. package/dist/reliability/reconciliation.d.ts +157 -0
  170. package/dist/reliability/reconciliation.js +522 -0
  171. package/dist/reliability/reconciliation.js.map +1 -0
  172. package/dist/reliability/runtimeHealth.d.ts +60 -0
  173. package/dist/reliability/runtimeHealth.js +356 -0
  174. package/dist/reliability/runtimeHealth.js.map +1 -0
  175. package/dist/reliability/slo.d.ts +131 -0
  176. package/dist/reliability/slo.js +375 -0
  177. package/dist/reliability/slo.js.map +1 -0
  178. package/dist/report/safetyReport.js +7 -3
  179. package/dist/report/safetyReport.js.map +1 -1
  180. package/dist/safety/index.d.ts +2 -0
  181. package/dist/safety/index.js +2 -0
  182. package/dist/safety/index.js.map +1 -0
  183. package/dist/safety/safetyController.d.ts +187 -0
  184. package/dist/safety/safetyController.js +477 -0
  185. package/dist/safety/safetyController.js.map +1 -0
  186. package/dist/scenarios/scenarioBundle.d.ts +75 -0
  187. package/dist/scenarios/scenarioBundle.js +750 -0
  188. package/dist/scenarios/scenarioBundle.js.map +1 -0
  189. package/dist/scenarios/scenarioStore.d.ts +1 -0
  190. package/dist/scenarios/scenarioStore.js +47 -32
  191. package/dist/scenarios/scenarioStore.js.map +1 -1
  192. package/dist/security/headerSanitizer.d.ts +2 -0
  193. package/dist/security/headerSanitizer.js +36 -1
  194. package/dist/security/headerSanitizer.js.map +1 -1
  195. package/dist/security/secrets.js +24 -4
  196. package/dist/security/secrets.js.map +1 -1
  197. package/dist/server/accessControl.d.ts +5 -0
  198. package/dist/server/accessControl.js +100 -0
  199. package/dist/server/accessControl.js.map +1 -0
  200. package/dist/server/createServer.d.ts +0 -5
  201. package/dist/server/createServer.js +9 -29
  202. package/dist/server/createServer.js.map +1 -1
  203. package/dist/server/eventsStore.d.ts +6 -1
  204. package/dist/server/eventsStore.js +83 -15
  205. package/dist/server/eventsStore.js.map +1 -1
  206. package/dist/server/routes.js +42 -10
  207. package/dist/server/routes.js.map +1 -1
  208. package/dist/server/sse.d.ts +2 -1
  209. package/dist/server/sse.js +15 -3
  210. package/dist/server/sse.js.map +1 -1
  211. package/dist/setup/setupGenerator.js +53 -6
  212. package/dist/setup/setupGenerator.js.map +1 -1
  213. package/dist/state/stateStore.d.ts +5 -0
  214. package/dist/state/stateStore.js +48 -62
  215. package/dist/state/stateStore.js.map +1 -1
  216. package/dist/storage/fileStore.d.ts +10 -0
  217. package/dist/storage/fileStore.js +131 -0
  218. package/dist/storage/fileStore.js.map +1 -0
  219. package/dist/teamControl/controlPlane.d.ts +255 -0
  220. package/dist/teamControl/controlPlane.js +918 -0
  221. package/dist/teamControl/controlPlane.js.map +1 -0
  222. package/dist/teamControl/deployment.d.ts +33 -0
  223. package/dist/teamControl/deployment.js +67 -0
  224. package/dist/teamControl/deployment.js.map +1 -0
  225. package/dist/teamControl/index.d.ts +4 -0
  226. package/dist/teamControl/index.js +3 -0
  227. package/dist/teamControl/index.js.map +1 -0
  228. package/dist/tests/testGenerator.js +4 -1
  229. package/dist/tests/testGenerator.js.map +1 -1
  230. package/dist/trust/index.d.ts +2 -0
  231. package/dist/trust/index.js +2 -0
  232. package/dist/trust/index.js.map +1 -0
  233. package/dist/trust/trustLadder.d.ts +182 -0
  234. package/dist/trust/trustLadder.js +560 -0
  235. package/dist/trust/trustLadder.js.map +1 -0
  236. package/dist/worlds/index.d.ts +2 -0
  237. package/dist/worlds/index.js +2 -0
  238. package/dist/worlds/index.js.map +1 -0
  239. package/dist/worlds/worlds.d.ts +160 -0
  240. package/dist/worlds/worlds.js +491 -0
  241. package/dist/worlds/worlds.js.map +1 -0
  242. package/docs/assets/dashboard.png +0 -0
  243. package/docs/ci.md +49 -0
  244. package/docs/commercial/README.md +29 -0
  245. package/docs/commercial/data-inventory.md +36 -0
  246. package/docs/commercial/manual-invoicing.md +44 -0
  247. package/docs/commercial/metrics.md +51 -0
  248. package/docs/commercial/pricing.md +103 -0
  249. package/docs/commercial/privacy-policy-draft.md +41 -0
  250. package/docs/commercial/terms-topics-for-counsel.md +51 -0
  251. package/docs/design-partners/README.md +52 -0
  252. package/docs/design-partners/demo-narratives.md +33 -0
  253. package/docs/design-partners/design-partner-offer.md +39 -0
  254. package/docs/design-partners/discovery-questions.md +28 -0
  255. package/docs/design-partners/feedback-capture-template.md +48 -0
  256. package/docs/design-partners/icp-one-pager.md +43 -0
  257. package/docs/design-partners/onboarding-checklist.md +33 -0
  258. package/docs/design-partners/pilot-success-criteria.md +27 -0
  259. package/docs/design-partners/pricing-interview-script.md +18 -0
  260. package/docs/design-partners/telemetry-plan.md +48 -0
  261. package/docs/development/baseline.md +114 -0
  262. package/docs/development/onboarding-smoke.md +37 -0
  263. package/docs/development/verification-0.1.8.md +25 -0
  264. package/docs/enterprise-product-roadmap-ru.md +1879 -0
  265. package/docs/fundraising/README.md +87 -0
  266. package/docs/fundraising/data-room-checklist.md +72 -0
  267. package/docs/fundraising/demo-script.md +52 -0
  268. package/docs/fundraising/design-partners-50.md +74 -0
  269. package/docs/fundraising/launch-posts.md +37 -0
  270. package/docs/fundraising/metrics-and-evidence.md +30 -0
  271. package/docs/fundraising/roadmap-12-month.md +25 -0
  272. package/docs/fundraising/technical-due-diligence-index.md +37 -0
  273. package/docs/fundraising/yc-application.md +25 -0
  274. package/docs/github-actions.md +84 -0
  275. package/docs/hosted-pilot.md +111 -0
  276. package/docs/mcp.md +52 -50
  277. package/docs/operations/disaster-recovery-runbook.md +81 -0
  278. package/docs/operations/kill-switch-runbook.md +32 -0
  279. package/docs/policy.md +61 -0
  280. package/docs/providers/authoring-packs.md +175 -0
  281. package/docs/providers/stripe-core-pack.md +82 -0
  282. package/docs/release-checklist.md +83 -71
  283. package/docs/release-migration-and-rollback.md +63 -0
  284. package/docs/release-readiness.md +39 -0
  285. package/docs/security/action-gateway-threat-model.md +34 -0
  286. package/docs/security/action-ledger-incident-replay-threat-model.md +42 -0
  287. package/docs/security/approval-inbox-threat-model.md +35 -0
  288. package/docs/security/credential-broker-threat-model.md +52 -0
  289. package/docs/security/egress-threat-model.md +126 -0
  290. package/docs/security/inventory-threat-model.md +104 -0
  291. package/docs/security/kill-switch-budgets-threat-model.md +37 -0
  292. package/docs/security/reliability-threat-model.md +111 -0
  293. package/docs/security/trust-ladder-threat-model.md +33 -0
  294. package/docs/team-control-plane.md +72 -0
  295. package/docs/usage.md +571 -57
  296. package/examples/README.md +10 -0
  297. package/examples/agent-instructions/README.md +39 -39
  298. package/examples/ci-smoke/README.md +21 -0
  299. package/examples/ci-smoke/ghostapi.policy.yaml +22 -0
  300. package/examples/ci-smoke/package.json +9 -0
  301. package/examples/ci-smoke/production-egress.mjs +2 -0
  302. package/examples/ci-smoke/safe.mjs +17 -0
  303. package/examples/evals/README.md +24 -0
  304. package/examples/evals/retry-after.eval.json +52 -0
  305. package/examples/generic-rest/README.md +27 -27
  306. package/examples/github-fetch/README.md +35 -35
  307. package/examples/openai-streaming/README.md +16 -0
  308. package/examples/openai-streaming/streaming-tool-call.mjs +31 -0
  309. package/examples/policy/ghostapi.policy.yaml +27 -0
  310. package/examples/record-replay/README.md +19 -0
  311. package/examples/record-replay/replay-requests.json +22 -0
  312. package/examples/record-replay/stripe-checkout.har.json +30 -0
  313. package/examples/resend-node/README.md +22 -22
  314. package/examples/stripe-node/README.md +35 -29
  315. package/examples/stripe-node/checkout-flow.mjs +61 -0
  316. package/examples/twilio-node/README.md +21 -21
  317. package/examples/worlds/README.md +19 -0
  318. package/examples/worlds/subscription-recovery.mjs +12 -0
  319. package/package.json +27 -12
  320. package/dist/providers/stripe.d.ts +0 -2
  321. package/dist/providers/stripe.js +0 -15
  322. package/dist/providers/stripe.js.map +0 -1
@@ -0,0 +1,104 @@
1
+ # Agent Inventory And Attack-Path Graph Threat Model
2
+
3
+ Applies to the local agent inventory layer: the persisted inventory store, attack-path graph, blast-radius analysis, detections, remediation workflow, open export, removal analysis, and ROI report.
4
+
5
+ ## Scope
6
+
7
+ This threat model covers the inventory module added in the "agent inventory, attack-path graph, and indispensability" session:
8
+
9
+ - `inventory.json` local persisted store under `.ghostapi/`, created by `getDataPaths().inventoryStore`.
10
+ - Import of agents, tools, identities, providers, resources, side effects, credentials, and policies from `config`, `ci`, `gateway`, and `cloud` sources, each record carrying provenance (source, import time, importer) and freshness.
11
+ - The attack-path graph (`agent -> identity -> tool -> provider -> resource -> side effect`) with persisted, provenance- and freshness-carrying edges.
12
+ - Blast-radius analysis, detections, remediation proposals/applications, open export, removal analysis, and the ROI report.
13
+
14
+ The inventory layer adds no provider, credential, network egress, or hosted control-plane surface. All of it is local-first and synthetic. ROI and removal numbers are derived from imported counters and local records only; nothing is invented, and the pilot entry gate (a real team/enterprise pilot) is not yet met.
15
+
16
+ ## Trust Boundaries
17
+
18
+ | Boundary | In scope | Out of scope |
19
+ | --- | --- | --- |
20
+ | Operator identity | Every inventory action authenticates through an injectable authorizer and requires an explicit permission (`inventory.import` / `inventory.inspect` / `inventory.analyze` / `inventory.remediate` / `inventory.export`); the test authorizer binds issued identities by reference | Real authn/authz infrastructure |
21
+ | Import sources | Imports declare a source (`config` / `ci` / `gateway` / `cloud`) that is recorded as provenance on every record and edge | Live provider or CI credentials; the layer never reaches out to a source |
22
+ | Tenant isolation | All reads and writes are filtered by `tenantId`; graph, findings, export, attack paths, removal, and ROI are tenant-scoped | Multi-tenant federation |
23
+ | Store | Regular non-symlink JSON file, owner-only on POSIX, bounded bytes and record counts | Remote/distributed storage |
24
+
25
+ ## Assets
26
+
27
+ - Agent inventory records and graph edges (`inventory.json`).
28
+ - Provenance and freshness metadata attached to every record and edge.
29
+ - Findings and remediation state (proposals, applications, rejections).
30
+ - The open export artifact (inventory, policies, eval scenario references, evidence metadata, removal analysis, ROI).
31
+ - Import-run and import-source ledger.
32
+
33
+ ## Threats And Mitigations
34
+
35
+ ### T1. Unauthenticated or unauthorized inventory writes
36
+
37
+ Every mutating and reading method calls `authorize` first: `validateOperator` on the authorizer result and an explicit permission check. The default authorizer (`createDisabledInventoryOperatorAuthorizer`) throws "not configured", so a controller with no authorizer cannot be used. Test identities are reference-bound; a plain `{}` is rejected as "not authenticated".
38
+
39
+ ### T2. A poisoned import poisons the graph
40
+
41
+ `validateImportPayload` enforces strict schema, allowed source types, identifier/scope/hash/timestamp formats, bounded array sizes, and per-kind id uniqueness before anything is stored. Cross-references (e.g. an agent referencing an unknown identity) are validated against both the current import and the persisted state, and a reference to a record that does not exist rejects the whole import. Import payloads are canonicalized to a digest recorded on the import run and source.
42
+
43
+ ### T3. Tenant data leaks across the graph or export
44
+
45
+ Every graph edge, record, finding, remediation, import run, and source is filtered by `operator.tenantId` in `snapshotFor`, `graphEdges`, `findAttackPaths`, `computeBlastRadius`, `computeFindings`, `computeCoverage`, `computeRemovalAnalysis`, `computeRoiReport`, and `export`. An edge can only be created from a record that belongs to the importing tenant. Cross-tenant leakage is covered by tests.
46
+
47
+ ### T4. Provenance or freshness is missing or forged
48
+
49
+ Every record and edge carries `provenance` (`sourceId`, `sourceType`, `sourceName`, `importedAt`, `importedBy`) and `freshness` (`firstSeenAt`, `lastSeenAt`) built from the import payload source and the authenticated operator's principal id — never from client-supplied strings. `validateState` re-validates provenance/freshness on every read and write. Edge ids are derived from source/target/relation so re-imports refresh `lastSeenAt` instead of duplicating edges.
50
+
51
+ Edges have a freshness lifecycle. Attack-path and blast-radius analysis only use edges whose `lastSeenAt` is within the configured `edgeStaleDays` window, so a long-unseen relationship cannot be cited as current reachability evidence. On every import the importing tenant's stale edges are garbage-collected from the store, keeping the graph from accumulating frozen relationships that were never refreshed.
52
+
53
+ ### T5. A remediation expands permissions instead of reducing them
54
+
55
+ `reduce_scope` proposals are rejected at proposal time unless the reduced scope list is a strict subset of the credential's current grant scopes and removes at least one scope; the same invariant is enforced again at apply time. `revoke` only ever sets a credential to revoked. `assign_owner` only sets an owner id. `onboard_through_gateway` only sets the gateway-managed flag. `create_eval` only records an eval scenario reference — and, when an `evalScenarioExists` resolver is configured, the referenced scenario must actually exist before the proposal is accepted, so a remediation cannot paper over a finding by pointing at a deleted scenario. No remediation path adds scopes or relaxes a control.
56
+
57
+ Applied remediations are re-verified on every analysis pass: a finding stays resolved only while the remediation remains effective. If a later import re-expands a reduced scope, revokes nothing, clears an assigned owner, un-gates an agent, or deletes the referenced eval scenario, the finding is re-opened instead of staying permanently marked resolved.
58
+
59
+ Remediation targets are validated against the tenant's live data before a proposal is accepted: an `environment` target must actually be referenced by at least one agent, identity, provider, resource, credential, or policy of the tenant, so a remediation cannot be proposed against a stale or invented environment.
60
+
61
+ ### T6. The store is poisoned or exploded
62
+
63
+ The store is validated on every read (`validateState`) and write (`atomicWriteJson(this.path, validateState(state))`). Store bytes are capped at 8 MiB; every collection is bounded (`maxAgents`, `maxEdges` 6,000, `maxFindings` 3,000, etc.), and `assertStoreBounds` rejects edges and findings over their caps on every mutation. Import runs are rotated per tenant at `maxImports` (256), keeping the oldest runs trimmed rather than failing the write. The store must be a regular non-symlink file. Oversized or non-JSON stores are rejected, not partially loaded.
64
+
65
+ ### T7. Secret-shaped or control-character data enters the inventory
66
+
67
+ Identifiers, scopes, and text are validated by regexes and by `sanitizeSecretString`: any value that contains a recognizable secret shape (e.g. `sk_live_*`) or control characters is rejected. Evidence metadata and reasons are bounded text.
68
+
69
+ ### T8. ROI or removal analysis invents savings
70
+
71
+ The ROI report is built only from imported counters (latest per source) and from actually applied remediations in the store; unmeasured counters are reported as `null` and listed in `notMeasured`, and `basis` is `local_inventory_data_only`. The removal analysis reports only numbers computed from the local store and states plainly when nothing verifiable depends on GhostAPI. The heuristic blast-radius classification is explicitly labeled advisory, not proof of exploitability.
72
+
73
+ ### T9. Attack-path graph is unbounded or cross-tenant
74
+
75
+ Edges are created only from the importing tenant's records, are keyed by a derived edge id, and are bounded (`maxEdges` 6,000). Path finding is always scoped to the tenant and to a single agent id. Incomplete paths (a record reference missing from the store) are counted separately and reported as incomplete, not silently dropped.
76
+
77
+ ## Permissions And Capabilities Summary
78
+
79
+ | Action | Check |
80
+ | --- | --- |
81
+ | Import | operator authorizer + `inventory.import` |
82
+ | Inspect / graph / attack paths / blast radius / list remediations | operator authorizer + `inventory.inspect` |
83
+ | Analyze / removal analysis / ROI | operator authorizer + `inventory.analyze` |
84
+ | Propose / apply / reject remediations | operator authorizer + `inventory.remediate` |
85
+ | Export | operator authorizer + `inventory.export` |
86
+
87
+ ## Limits
88
+
89
+ | Resource | Limit |
90
+ | --- | --- |
91
+ | Store bytes | 8 MiB |
92
+ | Import sources / import runs | 64 / 256 |
93
+ | Agents / tools / identities | 500 / 500 / 500 |
94
+ | Providers / resources / side effects | 200 / 1,000 / 2,000 |
95
+ | Credentials / policies | 500 / 100 |
96
+ | Graph edges | 6,000 |
97
+ | Findings / remediations | 3,000 / 1,000 |
98
+ | References / scopes per record | 64 / 32 |
99
+
100
+ ## Out Of Scope
101
+
102
+ - Live provider, CI, or gateway ingestion (the layer consumes locally-supplied payloads only).
103
+ - Hosted multi-tenant control plane and federated sources.
104
+ - Automated kill-switch or revocation execution against real providers (remediations mutate the local inventory state only).
@@ -0,0 +1,37 @@
1
+ # Local Kill Switch, Budget, And Blast-Radius Threat Model
2
+
3
+ ## Scope
4
+
5
+ The Session 27 safety controller is a local, persisted coordination layer for the existing `ghostapi-synthetic` action path. It implements local kill switches, budgets, circuit breakers, bounded queue/dead-letter state, authenticated emergency API calls, audit records, and a scheduled synthetic game-day test. It does not introduce a provider, provider SDK/client, provider account, vault, credential, external HTTP, webhook/email/Slack transport, hosted control plane, or production side effect.
6
+
7
+ ## Emergency Authority
8
+
9
+ `stop()`, `reenable()`, `configureBudget()`, and `configureCircuit()` require an injected `SafetyEmergencyAuthorizer`. The API accepts only verifier-issued operator objects with a stable principal and explicit permission. `safety.stop`, `safety.reenable`, and `safety.configure` are separate permissions. Empty, secret-shaped, or oversized reasons fail closed and every emergency decision is chained into the bounded local audit log.
10
+
11
+ There is intentionally no unauthenticated HTTP emergency endpoint, no CLI token parser, and no notification transport. A future deployment needs a separately reviewed identity authority and authenticated transport before exposing this emergency API remotely.
12
+
13
+ ## Enforcement Semantics
14
+
15
+ - Switch scopes are `global`, `organization`, `project`, `environment`, `agent`, `workload`, `provider`, `operation`, and `risk_class`.
16
+ - Admission serializes against the persisted controller store. Budgets reserve action costs before execution, so parallel callers cannot oversubscribe money, requests, messages, mutations, deletes, token cost, concurrency, or velocity limits.
17
+ - A replay with the same idempotency key and action hash returns a replay lease without a second reservation. Reusing the key with a different action hash is denied.
18
+ - The action gateway calls the controller after plan/simulate and before its attempt record. The synthetic world calls the controller's `commit()` while it holds its own world lock. `commit()` is two-phase: a first mutate re-checks the lease, expiry, and kill-switch/circuit state; only then is the operation executed; a second mutate re-checks the lease and persists the `action.final_check` audit record ("remained active through commit") before returning. A kill switch that wins before the first phase blocks the local mutation. If the lease expires between the phases, `commit()` throws even though the operation already ran — the caller must not treat the action as successfully admitted.
19
+ - Leases are time-bounded: a reserved lease expires after `DEFAULT_LEASE_TTL_MS` (60 s by default, overridable via `leaseTtlMs`). Expired leases are pruned on the next admission, and both `assertActive()` and the `commit()` phases reject an expired lease. `complete()` is tolerant: it still records the outcome for a lease that expired after the operation, so the audit chain reflects what actually happened instead of dropping the terminal record.
20
+ - Kill-switch denials carry the stable error code `SAFETY_KILL_SWITCH` (and admit/commit errors are `SafetyControllerError`), so callers — including the scheduled game day — detect a kill-switch block structurally instead of by parsing message text.
21
+ - Queued actions have a fixed maximum of 100. A full queue returns backpressure. Stopping a matching scope moves queued records to the bounded dead-letter queue; no queued action is auto-retried. The dead-letter queue is also bounded, and when full the oldest entry is evicted so the store never grows without limit.
22
+ - Already approved is not already executable: approval artifacts still pass current policy, identity, idempotency, safety admission, and final-commit checks. An in-flight action that has crossed the final commit cannot be undone; GhostAPI does not claim rollback. A real provider would require idempotency and reconciliation semantics before an equivalent boundary is enabled.
23
+ - Failures, policy violations, latency threshold breaches, and reconciliation mismatches can open persisted circuit breakers. Open breakers deny new admission; no retry loop is created by the controller.
24
+
25
+ ## Persistence And Limits
26
+
27
+ Critical state lives in `.ghostapi/safety-controller.json`, not process memory. It is strict-schema validated, bounded to 1 MiB, guarded as a regular non-symlink file, serialized with the existing file lock, atomically replaced, and covered by a bounded SHA-256 audit chain. This is local coordination and tamper evidence on one filesystem, not a distributed lock, immutable audit ledger, or defense against a malicious same-user actor who can modify both state and runtime.
28
+
29
+ ## Game Day And Runbook
30
+
31
+ The scheduled GitHub workflow runs only `test/safetyController.test.ts`, which performs a local synthetic stop/re-enable drill and never creates a provider side effect. The operational procedure is in [`docs/operations/kill-switch-runbook.md`](../operations/kill-switch-runbook.md).
32
+
33
+ ## Remaining Limits
34
+
35
+ - The controller does not authorize production execution, reconcile a provider outcome, revoke a provider credential, or prove a provider-side stop.
36
+ - Budget units are caller-supplied typed action metadata for the one local synthetic action; a future real action needs independently derived and provider-verified amounts/costs.
37
+ - Local timestamps use the process clock. A real deployment needs a trusted clock, durable audit sink, shared atomic store, reconciliation, and provider-specific final-side-effect guards.
@@ -0,0 +1,111 @@
1
+ # Reliability Surface Threat Model
2
+
3
+ Applies to the local reliability layer: SLO controller, reconciliation service, cost governance, runtime health, and backup/restore.
4
+
5
+ ## Scope
6
+
7
+ This threat model covers the reliability module added in the "reliability" session:
8
+
9
+ - `slo.json`, `reconciliation.json`, `costs.json` local persisted stores under `.ghostapi/reliability/`.
10
+ - Backup and restore of the whole local data directory, including the reliability stores.
11
+ - `/health` and `/health/readiness` HTTP endpoints backed by `checkRuntimeHealth`.
12
+ - Reconciliation reading the action ledger and the synthetic worlds directory.
13
+
14
+ `checkRuntimeHealth` treats every canonical store as a first-class dependency, including `inventory.json` (`inventoryStore`), so a corrupt or missing inventory store is reported as `degraded` and flips readiness, not just the reliability stores.
15
+
16
+ The reliability layer adds no provider, credential, network egress, or hosted control-plane surface. All of it is local-first and synthetic.
17
+
18
+ ## Trust Boundaries
19
+
20
+ | Boundary | In scope | Out of scope |
21
+ | --- | --- | --- |
22
+ | Operator identity | SLO, reconciliation, and cost operators authenticate through injectable authorizers; the test authorizer binds issued identities by reference | Real authn/authz infrastructure |
23
+ | Ledger access | Reconciliation uses a verified ledger capability (reference-bound, tenant-scoped, permission-checked) | Key management for live providers |
24
+ | Provider state | Reconciliation inspects synthetic worlds through `createWorldStateReconciliationProvider` | Live provider APIs |
25
+ | HTTP surface | `/health`, `/health/readiness` are public but return only structural readiness | Authenticated control APIs |
26
+
27
+ ## Assets
28
+
29
+ - SLO samples and configured targets (`slo.json`).
30
+ - Reconciliation findings, resolutions, and last-run state (`reconciliation.json`).
31
+ - Cost records, budgets, alerts (`costs.json`).
32
+ - Backup manifest and copies (`reliability/backups/`).
33
+ - The runtime health report (derived, not persisted).
34
+
35
+ ## Threats And Mitigations
36
+
37
+ ### T1. Unauthorized SLO recording poisons availability targets
38
+
39
+ `recordSample`/`recordSamples` accept a capability object instead of an operator identity. The capability must be a member of the record-capability set created by `createSloRecordIdentity`; any other object is rejected with "record capability". This is reference-bound, so ambient objects cannot be forged.
40
+
41
+ Samples are bounded: `MAX_SAMPLES_PER_METRIC` (5,000), `MAX_SAMPLES` (10,000) globally, enforced on every mutation, and `recordSamples` caps each call at `MAX_RECORD_BATCH` (1,000) so a single oversized call cannot blow past the store bound. SLO store bytes are capped at 4 MiB.
42
+
43
+ Latency evaluation only counts samples whose `ok` flag is true; a latency breach requires both `sample.ok === true` and `durationMs` above the target. Failed samples can no longer be counted as "within SLO" by reporting a low duration.
44
+
45
+ ### T2. Operator impersonation on configure/evaluate/inspect
46
+
47
+ Every SLO operator action authenticates through the configured authorizer and validates permissions against `slo.configure`/`slo.inspect` only. Identities are reference-bound objects issued by the authorizer; a plain `{}` or any other object is rejected as "not authenticated".
48
+
49
+ ### T3. Reconciliation reads a tampered ledger
50
+
51
+ `runReconciliation` requires the operator to hold the `reconciliation.manage` permission and takes an explicit `{ identity }` input; a read-only operator is rejected. It calls `exportTenant`; if ledger integrity verification fails, reconciliation is blocked with a clear error and no findings or SLO samples are written. A tampered ledger cannot silently produce a "valid" report. Provider read failures are detected structurally (`SyntheticWorldError` with code `WORLD_NOT_FOUND`) rather than by matching error text, so a missing world is reported deterministically.
52
+
53
+ ### T4. Reconciliation leaks or records sensitive provider evidence
54
+
55
+ Provider receipts are reduced to `{ actionId }` for matching, and worlds are inspected through the same receipts shape. Findings carry only `actionHash` and bounded non-secret `detail`/`evidenceRef` strings (validated by `identifier`/`hash`/`evidence`/`text`, which reject control characters and secret-shaped values via `sanitizeSecretString`).
56
+
57
+ ### T5. Cost store is poisoned or exploded
58
+
59
+ Cost records are capped (`MAX_RECORDS` 10,000), budgets capped (`MAX_BUDGETS` 32), alerts capped (`MAX_ALERTS` 100), window capped (`MAX_WINDOW_MS` 90 days), store bytes capped at 4 MiB. Amounts are non-negative integers. `recordCost` is idempotent per tenant: a duplicate `actionId` for the same tenant is rejected, so a replay of a cost record cannot double-charge a budget. `report()` is a pure read — it never writes alert state; only `listAlerts()` persists derived alerts. Reports and alerts are scoped to the operator's tenant via the optional `CostOperator.tenantId`, and alerts honor each budget's `alertOnExceed` flag, so an explicit opt-out cannot be overridden by a global alert pass. Forecast output is explicitly labeled as a linear extrapolation approximation, not a provider invoice.
60
+
61
+ ### T6. Backup/restore path traversal
62
+
63
+ Restore validates every manifest entry path with `normalizeEntryPath` (rejects `..`, absolute paths, drive-letter prefixes, NUL bytes) and `assertContained` bounds every source and target path within its root. A crafted manifest that escapes the backup root is rejected before any file is written.
64
+
65
+ ### T7. Tampered backup restores bad data
66
+
67
+ Restore requires `manifest.verified === true` and re-verifies sha256 and size of every entry before and after copy, plus JSON structure for `.json` entries. Tampering with any backed-up file fails "integrity verification" and aborts the restore.
68
+
69
+ ### T8. Backup exfiltrates or follows unsafe links
70
+
71
+ `collectBackupFiles` refuses symbolic links, non-regular files, `.lock`/`.tmp` files, and refuses the whole backup if any is encountered. Exclusions are path-based, not name-based: only the canonical `cache` and `backups` directories, the current destination, and a top-level `runs` directory are skipped — a nested directory merely named `cache` or `backups` inside a world/contract is still backed up. Because the canonical backups location is excluded, backups do not recursively include earlier backups.
72
+
73
+ ### T9. Runtime health check is a denial vector
74
+
75
+ Store files are read with `MAX_CHECK_FILE_BYTES` (4 MiB) cap; oversized or non-JSON stores are reported as `degraded`, never loaded fully. The `/health` endpoint returns only `{ ok, ready }`; `/health/readiness` returns the structural report and 503 when not ready.
76
+
77
+ ### T10. Backup/restore destroys the source
78
+
79
+ Backup only reads the source; restore refuses a target that is the source or inside it, and refuses to restore into a non-empty target directory, so an existing data directory cannot be silently overwritten. Copy is byte-verified before the atomic write to the destination, and failed copies clean up the partial destination.
80
+
81
+ ## Permissions And Capabilities Summary
82
+
83
+ | Action | Check |
84
+ | --- | --- |
85
+ | SLO record | record capability in `sloRecordCapabilities` |
86
+ | SLO configure/inspect/evaluate | operator authorizer + `slo.configure`/`slo.inspect` |
87
+ | Reconciliation run | operator authorizer + `reconciliation.manage` + ledger capability (tenant-scoped, `export`) |
88
+ | Reconciliation findings/resolve | operator authorizer + `reconciliation.inspect`/`reconciliation.manage` |
89
+ | Cost record/configure/report/alerts | operator authorizer + `cost.*` permissions |
90
+ | Health/backup/restore | local-only; backup/restore are not exposed over HTTP |
91
+
92
+ ## Limits
93
+
94
+ | Resource | Limit |
95
+ | --- | --- |
96
+ | SLO samples per metric | 5,000 |
97
+ | SLO samples total | 10,000 |
98
+ | SLO targets | 32 |
99
+ | SLO window | 1 hour .. 30 days |
100
+ | Reconciliation findings | 1,000 |
101
+ | Cost records / budgets / alerts | 10,000 / 32 / 100 |
102
+ | Cost window | up to 90 days |
103
+ | Store bytes | 4 MiB each |
104
+ | Backup total bytes | 64 MiB |
105
+ | Health check file bytes | 4 MiB |
106
+
107
+ ## Out Of Scope
108
+
109
+ - Live SLO alerting to external systems (no egress was added).
110
+ - Multi-node reconciliation consensus (local single-process semantics).
111
+ - Backup to remote/object storage (local directory only).
@@ -0,0 +1,33 @@
1
+ # Local Synthetic Trust Ladder Threat Model
2
+
3
+ ## Scope
4
+
5
+ The Session 26 trust ladder is a local, synthetic, data-only preparation layer. It models `simulate`, `shadow`, `dry-run`, `approve`, `bounded-auto`, and `trusted` capability contracts, promotion evidence, canary eligibility, comparisons, rollback reasons, and audit records. It does not execute an action and does not integrate with the action gateway, approval inbox, credential broker, vault, provider SDK/client, external HTTP, provider account, test account, hosted control plane, or notification transport.
6
+
7
+ The local synthetic runtime is not production authorization. `trusted` is intentionally unsupported. `dry-run` is unsupported because no provider-official safe dry-run semantic exists for `ghostapi-synthetic`; it is never emulated by ordinary execution.
8
+
9
+ ## Trust Boundaries
10
+
11
+ - Target identities are strict synthetic-only values. Production and test-account identities are rejected rather than mixed into local state.
12
+ - Owner decisions arrive only through an injected verifier and compare the verifier-issued stable `principalId` to policy. Caller-shaped owner objects fail closed.
13
+ - Promotion evidence is bounded metadata: run count, violation/error count, named eval status, and timestamps. LLM confidence is not a signal.
14
+ - Shadow comparisons accept only SHA-256 action/context metadata and return matching evidence. They do not read raw provider inputs or invoke an adapter.
15
+ - Bounded outcome comparisons accept only action/outcome/receipt hashes. They do not perform the bounded execution being assessed.
16
+ - State uses private local storage, regular-file/symlink checks, byte limits, a file lock, atomic replacement, and a bounded SHA-256 audit chain. This is coordination/tamper evidence under the existing local filesystem model, not an immutable audit sink.
17
+
18
+ ## Invariants
19
+
20
+ - Every local capability declares `externalSideEffects: false`.
21
+ - `dry-run` and `trusted` are explicitly unsupported; unsupported levels cannot be promoted into.
22
+ - Promotion has no automatic path. It requires the configured verified owner, fresh evidence, minimum runs, every required passing eval, and violation/error rates at or below policy thresholds.
23
+ - Promotion advances only to the next supported level, so an operator cannot skip review stages.
24
+ - Canary assignment is deterministic from a SHA-256 policy/target bucket, checks tenant and resource scope first, and uses a 0-10,000 basis-point percentage.
25
+ - A canary violation can immediately demote the target to `approve` or open the circuit breaker based on policy. Stop conditions open the breaker. Once open, no further assignment, comparison, or canary outcome is accepted.
26
+ - `rollbackToApproval()` requires the verified policy owner and appends an audit record with the supplied bounded reason.
27
+ - The implementation cannot mutate a synthetic world or a provider because it has no execution dependency or capability.
28
+
29
+ ## Remaining Limits
30
+
31
+ - Shadow and outcome evidence compare supplied hash metadata. They cannot prove a real provider request or external outcome because the implementation deliberately has no provider transport.
32
+ - This is not an approval authority, action-level policy engine, kill switch for a real provider, budget/velocity enforcement gateway, reconciliation service, test-account boundary, or production audit system.
33
+ - A future real provider integration requires a separately reviewed provider-specific identity model, official dry-run proof where claimed, action authorization, inbox/credential execution boundary, idempotency and reconciliation semantics, kill switch, budgets, durable audit storage, and explicit owner/test-account gates before any side effect is enabled.
@@ -0,0 +1,72 @@
1
+ # Team Control-Plane Prototype
2
+
3
+ ## Scope
4
+
5
+ As of August 8, 2026, the cloud/design-partner gate remains unmet. This is a local architecture prototype stored in a private JSON file. It has no hosted or public endpoint, network listener, deployment, SSO/OIDC/SAML integration, cloud synchronization, telemetry, or external dependency. Local GhostAPI workflows do not require an account or team token.
6
+
7
+ The store is `.ghostapi/team-control-plane.json` (or the configured `GHOSTAPI_DATA_DIR`). Existing same-directory atomic writes and cooperative cross-process locking protect updates. The store rejects symlinks and data over 1 MiB. This is not a compliance product, immutable storage system, credential broker, or distributed security boundary.
8
+
9
+ ## Data Flow
10
+
11
+ ```mermaid
12
+ flowchart LR
13
+ Caller[Local typed-library caller] --> Auth[Token authentication or local actor]
14
+ Auth --> Permit[Central permission and scope evaluator]
15
+ Permit --> Plane[Local control-plane methods]
16
+ Plane --> Lock[Locked atomic JSON store]
17
+ Plane --> Ledger[Per-organization SHA-256 audit chain]
18
+ Future[Future HTTP transport] -. must apply .-> Headers[CSP and security headers]
19
+ Future -. must apply .-> Limit[Fixed-window fail-closed limiter]
20
+ Disabled[Disabled identity provider] -. always denies; no I/O .-> Future
21
+ ```
22
+
23
+ No transport is supplied. A future HTTP layer must authenticate before dispatch, apply `createTeamControlPlaneSecurityHeaders()` (or the immutable `TEAM_CONTROL_PLANE_SECURITY_HEADERS`), use `TeamControlPlaneRateLimiter` before processing with a non-secret opaque principal key, avoid logging raw tokens, and fail closed on invalid keys, invalid limits, invalid clocks, or limiter capacity exhaustion.
24
+
25
+ ## Tenant And Authorization Model
26
+
27
+ Organizations are tenant boundaries. Projects, environments, scenarios, evidence, policies, service accounts, tokens, and audit records all belong to one organization. Parent references are validated on every read and write. Missing scoped parents return `Resource not found`, preventing cross-tenant identifier disclosure.
28
+
29
+ All authorization uses the centralized `TEAM_PERMISSION_MATRIX`. The least-privilege matrix is:
30
+
31
+ | Role | Permissions |
32
+ | --- | --- |
33
+ | `owner` | `member.manage`, `project.manage`, `environment.manage`, `policy.manage`, `token.manage`, `service_account.manage`, `audit.read`, `audit.export`, `data.delete`, `retention.manage`, all scoped reads, `scenario.publish`, `evidence.upload` |
34
+ | `admin` | Everything listed for owner except `member.manage` |
35
+ | `developer` | Scoped reads, `scenario.publish`, `evidence.upload` |
36
+ | `viewer` | Scoped reads only |
37
+ | `service_account` | Token-scoped project/environment reads, `scenario.publish`, and `evidence.upload` only; no organization-wide policy or audit access |
38
+
39
+ `admin` cannot grant members or issue, rotate, or revoke a token for an owner. `owner` can manage every human-member token. Service-account actors have a distinct `service:<id>` audit identity and cannot impersonate a human member.
40
+
41
+ ## Tokens And Service Accounts
42
+
43
+ Owners and admins create managed service accounts using an identifier and display name. A service account has no plaintext secret and is not a human member. Tokens are typed `user` or `service`, have SHA-256 digests only in persisted state, are returned once at issuance, can be revoked, and must expire within 90 days.
44
+
45
+ `issueToken` remains the human-token API. `issueServiceToken` requires a live service account and a nonempty, bounded list of scope entries. Every entry names an existing `{ projectId, environmentId }` and a nonempty subset of service-account scoped permissions. Authentication validates token digest, expiry, revocation, account state, and scope. A service actor is an in-memory authenticated capability returned only by `authenticateToken`; constructing an object with a token id is rejected. Each scoped method resolves that token's persisted scope, expiry, revocation, and account state again before acting. Rotation revokes the prior token and creates the replacement in one locked mutation; `disableServiceAccount` immediately revokes all of its live tokens. No reusable plaintext token is stored.
46
+
47
+ ## Audit Integrity And Export
48
+
49
+ Schema v3 uses a per-organization append-only SHA-256 chain. Each record includes `sequence`, `previousHash`, and `recordHash`; the organization anchor supplies the sequence and hash before the retained window. `exportAudit` returns the anchor, records, and an integrity result. `verifyAuditExport` is public and detects changes to a record, record order, sequence, or chain link. Exports contain audit metadata only and never include token plaintext or other secrets.
50
+
51
+ Schema v1 migrates through v2 to v3. Legacy `member` becomes `developer`; typed user tokens replace legacy `memberId` token bindings; legacy audit rows are replayed into a validated v3 SHA-256 chain. Unknown fields, malformed references, invalid token scopes, invalid service-account references, and invalid chains fail closed.
52
+
53
+ Audit retention is bounded to 90 days and 1,000 records per organization. Pruning does not silently sever the chain: the last pruned record becomes the retained audit anchor. Sanitized evidence is retained for 30 days and capped at 100 records per organization. Owners and admins may explicitly delete evidence; deletion is tenant- and project-bound and audited. `deleteProject` removes that tenant project's environments, scenarios, evidence, and scoped service tokens in the same locked mutation. Audit export is restricted to `audit.export`; data deletion is restricted to `data.delete`.
54
+
55
+ ## Identity And Incident Response
56
+
57
+ `TeamIdentityProvider` is a future integration boundary only. The single provided implementation, `createDisabledIdentityProvider`, performs no I/O and always denies authentication. No OIDC, SAML, SSO, SCIM, or remote identity protocol is implemented.
58
+
59
+ If a token is exposed, an owner or authorized admin must revoke it, remove it from local logs or CI output, rotate or issue a short-lived replacement, and inspect `exportAudit` with `verifyAuditExport`. If the integrity result is false or the store fails validation, stop using the store, preserve the file for local investigation, restore only a reviewed local backup, and do not manually repair records or bypass validation. If evidence is incorrectly retained, use explicit evidence deletion or `pruneRetention`; both actions are audited.
60
+
61
+ ## Verification
62
+
63
+ ```bash
64
+ npm run typecheck
65
+ npm test -- --run test/teamControl.test.ts
66
+ ```
67
+
68
+ ## Explicit Non-Goals
69
+
70
+ - No hosted API, public endpoint, deployment, dashboard, cloud sync, billing, SSO, SCIM, or generic project management.
71
+ - No raw traffic, source code, secrets, or automatic artifact upload.
72
+ - No compliance, legal-hold, immutable-storage, or distributed-isolation claim.