@pyric/cli 0.1.0-alpha.11 → 0.1.0-alpha.12

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 (301) hide show
  1. package/dist/conformance/.generated/can-i-use-browser.d.ts.map +1 -1
  2. package/dist/conformance/.generated/can-i-use-browser.js +1 -1
  3. package/dist/conformance/.generated/can-i-use-browser.js.map +1 -1
  4. package/dist/conformance/.generated/can-i-use.d.ts.map +1 -1
  5. package/dist/conformance/.generated/can-i-use.js +2 -2
  6. package/dist/conformance/.generated/can-i-use.js.map +1 -1
  7. package/dist/serve/docs-ui/docs/_rhythm/index.html +1 -1
  8. package/dist/serve/docs-ui/docs/agent/set-up-your-agent/index.html +1 -1
  9. package/dist/serve/docs-ui/docs/agent/watch-and-review/index.html +1 -1
  10. package/dist/serve/docs-ui/docs/agent/work-with-an-agent/index.html +1 -1
  11. package/dist/serve/docs-ui/docs/ai-compat/index.html +1 -1
  12. package/dist/serve/docs-ui/docs/api-reference/index.html +2 -2
  13. package/dist/serve/docs-ui/docs/api-reference.md +1 -1
  14. package/dist/serve/docs-ui/docs/app-compat/index.html +1 -1
  15. package/dist/serve/docs-ui/docs/auth-compat/index.html +1 -1
  16. package/dist/serve/docs-ui/docs/build/ai-logic/index.html +1 -1
  17. package/dist/serve/docs-ui/docs/build/authentication/index.html +1 -1
  18. package/dist/serve/docs-ui/docs/build/cloud-firestore/index.html +1 -1
  19. package/dist/serve/docs-ui/docs/build/cloud-messaging/index.html +1 -1
  20. package/dist/serve/docs-ui/docs/build/cloud-storage/index.html +1 -1
  21. package/dist/serve/docs-ui/docs/build/realtime-database/index.html +1 -1
  22. package/dist/serve/docs-ui/docs/conformance-scores/index.html +1 -1
  23. package/dist/serve/docs-ui/docs/create-pyric-reference-api/index.html +1 -1
  24. package/dist/serve/docs-ui/docs/database-compat/index.html +1 -1
  25. package/dist/serve/docs-ui/docs/firestore-compat/index.html +1 -1
  26. package/dist/serve/docs-ui/docs/functions-rtdb-compat/index.html +1 -1
  27. package/dist/serve/docs-ui/docs/get-started/how-the-swap-works/index.html +1 -1
  28. package/dist/serve/docs-ui/docs/get-started/start-building/index.html +1 -1
  29. package/dist/serve/docs-ui/docs/index.json +200 -25
  30. package/dist/serve/docs-ui/docs/messaging-compat/index.html +1 -1
  31. package/dist/serve/docs-ui/docs/observe/see-whats-happening/index.html +1 -1
  32. package/dist/serve/docs-ui/docs/observe/shape-your-data/index.html +1 -1
  33. package/dist/serve/docs-ui/docs/overview/index.html +1 -1
  34. package/dist/serve/docs-ui/docs/pyric-admin-app-reference-api/index.html +1 -1
  35. package/dist/serve/docs-ui/docs/pyric-admin-auth-reference-api/index.html +1 -1
  36. package/dist/serve/docs-ui/docs/pyric-admin-database-reference-api/index.html +1 -1
  37. package/dist/serve/docs-ui/docs/pyric-admin-firestore-reference-api/index.html +1 -1
  38. package/dist/serve/docs-ui/docs/pyric-admin-messaging-reference-api/index.html +916 -0
  39. package/dist/serve/docs-ui/docs/pyric-admin-messaging-reference-api.md +706 -0
  40. package/dist/serve/docs-ui/docs/pyric-admin-storage-reference-api/index.html +1 -1
  41. package/dist/serve/docs-ui/docs/pyric-ai-reference-api/index.html +1 -1
  42. package/dist/serve/docs-ui/docs/pyric-ai-scripting-reference-api/index.html +1 -1
  43. package/dist/serve/docs-ui/docs/pyric-app-reference-api/index.html +1 -1
  44. package/dist/serve/docs-ui/docs/pyric-auth-reference-api/index.html +1 -1
  45. package/dist/serve/docs-ui/docs/pyric-cli-assurance-browser-reference-api/index.html +1 -1
  46. package/dist/serve/docs-ui/docs/pyric-cli-assurance-reference-api/index.html +1 -1
  47. package/dist/serve/docs-ui/docs/pyric-cli-bridge-client-reference-api/index.html +1 -1
  48. package/dist/serve/docs-ui/docs/pyric-cli-bridge-reference-api/index.html +1 -1
  49. package/dist/serve/docs-ui/docs/pyric-cli-conformance-browser-reference-api/index.html +1 -1
  50. package/dist/serve/docs-ui/docs/pyric-cli-conformance-docs-reference-api/index.html +1 -1
  51. package/dist/serve/docs-ui/docs/pyric-cli-conformance-reference-api/index.html +1 -1
  52. package/dist/serve/docs-ui/docs/pyric-cli-credentials-node-reference-api/index.html +1 -1
  53. package/dist/serve/docs-ui/docs/pyric-cli-discover-reference-api/index.html +1 -1
  54. package/dist/serve/docs-ui/docs/pyric-cli-register-reference-api/index.html +1 -1
  55. package/dist/serve/docs-ui/docs/pyric-cli-remote-reference-api/index.html +1 -1
  56. package/dist/serve/docs-ui/docs/pyric-cli-serve-worker-reference-api/index.html +131 -66
  57. package/dist/serve/docs-ui/docs/pyric-cli-serve-worker-reference-api.md +130 -65
  58. package/dist/serve/docs-ui/docs/pyric-cli-verify-reference-api/index.html +1 -1
  59. package/dist/serve/docs-ui/docs/pyric-cli-vite-reference-api/index.html +1 -1
  60. package/dist/serve/docs-ui/docs/pyric-database-reference-api/index.html +1 -1
  61. package/dist/serve/docs-ui/docs/pyric-firestore-reference-api/index.html +1 -1
  62. package/dist/serve/docs-ui/docs/pyric-messaging-reference-api/index.html +1 -1
  63. package/dist/serve/docs-ui/docs/pyric-messaging-sw-reference-api/index.html +1 -1
  64. package/dist/serve/docs-ui/docs/pyric-rules-reference-api/index.html +1 -1
  65. package/dist/serve/docs-ui/docs/pyric-sandbox-database-reference-api/index.html +1 -1
  66. package/dist/serve/docs-ui/docs/pyric-sandbox-firestore-reference-api/index.html +1 -1
  67. package/dist/serve/docs-ui/docs/pyric-sandbox-reference-api/index.html +37 -14
  68. package/dist/serve/docs-ui/docs/pyric-sandbox-reference-api.md +28 -8
  69. package/dist/serve/docs-ui/docs/pyric-storage-reference-api/index.html +1 -1
  70. package/dist/serve/docs-ui/docs/pyric-ui-agents-reference-api/index.html +1 -1
  71. package/dist/serve/docs-ui/docs/pyric-ui-auth-hooks-reference-api/index.html +1 -1
  72. package/dist/serve/docs-ui/docs/pyric-ui-auth-reference-api/index.html +1 -1
  73. package/dist/serve/docs-ui/docs/pyric-ui-events-hooks-reference-api/index.html +1 -1
  74. package/dist/serve/docs-ui/docs/pyric-ui-events-reference-api/index.html +1 -1
  75. package/dist/serve/docs-ui/docs/pyric-ui-firestore-hooks-reference-api/index.html +1 -1
  76. package/dist/serve/docs-ui/docs/pyric-ui-firestore-reference-api/index.html +1 -1
  77. package/dist/serve/docs-ui/docs/pyric-ui-primitives-reference-api/index.html +1 -1
  78. package/dist/serve/docs-ui/docs/pyric-ui-rtdb-reference-api/index.html +1 -1
  79. package/dist/serve/docs-ui/docs/pyric-ui-rules-hooks-reference-api/index.html +1 -1
  80. package/dist/serve/docs-ui/docs/pyric-ui-rules-reference-api/index.html +1 -1
  81. package/dist/serve/docs-ui/docs/pyric-ui-storage-hooks-reference-api/index.html +1 -1
  82. package/dist/serve/docs-ui/docs/pyric-ui-storage-reference-api/index.html +1 -1
  83. package/dist/serve/docs-ui/docs/pyric-ui-traffic-hooks-reference-api/index.html +1 -1
  84. package/dist/serve/docs-ui/docs/pyric-ui-traffic-reference-api/index.html +1 -1
  85. package/dist/serve/docs-ui/docs/rules-compat/index.html +1 -1
  86. package/dist/serve/docs-ui/docs/secure/audit-your-rules/index.html +1 -1
  87. package/dist/serve/docs-ui/docs/secure/firestore-rules-limits/index.html +1 -1
  88. package/dist/serve/docs-ui/docs/secure/read-a-denial/index.html +1 -1
  89. package/dist/serve/docs-ui/docs/secure/rtdb-rules-in-typescript/index.html +1 -1
  90. package/dist/serve/docs-ui/docs/secure/rules-standard-library/index.html +1 -1
  91. package/dist/serve/docs-ui/docs/secure/secure-it-with-rules/index.html +1 -1
  92. package/dist/serve/docs-ui/docs/secure/simulate-and-lint/index.html +1 -1
  93. package/dist/serve/docs-ui/docs/secure/write-a-rules-test-suite/index.html +1 -1
  94. package/dist/serve/docs-ui/docs/ship/ship-to-production/index.html +1 -1
  95. package/dist/serve/docs-ui/docs/ship/test-in-node/index.html +1 -1
  96. package/dist/serve/docs-ui/docs/storage-compat/index.html +1 -1
  97. package/dist/serve/docs-ui/docs/trust/how-we-know-it-matches-firebase/index.html +1 -1
  98. package/dist/serve/docs-ui/docs/trust/versioning-and-compatibility/index.html +1 -1
  99. package/dist/serve/docs-ui/llms.txt +1 -1
  100. package/dist/serve/worker/host/core.js +1 -1
  101. package/dist/serve/worker/host/core.js.map +1 -1
  102. package/dist/serve/worker/host/firestore-writes.d.ts.map +1 -1
  103. package/dist/serve/worker/host/firestore-writes.js +3 -2
  104. package/dist/serve/worker/host/firestore-writes.js.map +1 -1
  105. package/dist/serve/worker/index.d.ts +2 -1
  106. package/dist/serve/worker/index.d.ts.map +1 -1
  107. package/dist/serve/worker/index.js +2 -1
  108. package/dist/serve/worker/index.js.map +1 -1
  109. package/dist/serve/worker/protocol.js +2 -2
  110. package/dist/serve/worker/protocol.js.map +1 -1
  111. package/package.json +5 -4
  112. package/src/assurance/.generated/conformance-verdicts.ts +1090 -0
  113. package/src/assurance/attachment.ts +211 -0
  114. package/src/assurance/browser.ts +80 -0
  115. package/src/assurance/campaign.ts +496 -0
  116. package/src/assurance/capabilities.ts +446 -0
  117. package/src/assurance/cases.ts +43 -0
  118. package/src/assurance/index.ts +75 -0
  119. package/src/assurance/runner.ts +885 -0
  120. package/src/assurance/tool-names.ts +15 -0
  121. package/src/assurance/tools.ts +830 -0
  122. package/src/assurance/types.ts +352 -0
  123. package/src/assurance/validation.ts +434 -0
  124. package/src/bridge/client/bridge.ts +550 -0
  125. package/src/bridge/client/dispatch.ts +137 -0
  126. package/src/bridge/client.ts +45 -0
  127. package/src/bridge/protocol.ts +351 -0
  128. package/src/bridge/server/audit.ts +53 -0
  129. package/src/bridge/server/bridge.ts +597 -0
  130. package/src/bridge/server/headless.ts +160 -0
  131. package/src/bridge/server/json-schema-to-zod.ts +108 -0
  132. package/src/bridge/server/local-bridge.ts +66 -0
  133. package/src/bridge/server/logger.ts +50 -0
  134. package/src/bridge/server/mcp-contract.ts +93 -0
  135. package/src/bridge/server/mcp.ts +129 -0
  136. package/src/bridge/server/peer.ts +232 -0
  137. package/src/bridge/server/standalone.ts +347 -0
  138. package/src/bridge/server/tool-metadata.ts +99 -0
  139. package/src/bridge/server.ts +28 -0
  140. package/src/cli/can-i-use.ts +50 -0
  141. package/src/cli/cli.test.ts +493 -0
  142. package/src/cli/database-rules.ts +329 -0
  143. package/src/cli/dev-runner.ts +282 -0
  144. package/src/cli/firebase-json.ts +100 -0
  145. package/src/cli/firestore-indexes.ts +70 -0
  146. package/src/cli/index.ts +425 -0
  147. package/src/cli/init.ts +260 -0
  148. package/src/cli/mcp-proxy.ts +196 -0
  149. package/src/cli/parse-args.ts +80 -0
  150. package/src/cli/rules.ts +259 -0
  151. package/src/cli/scope.ts +67 -0
  152. package/src/cli/serve.ts +996 -0
  153. package/src/cli/service-commands.ts +73 -0
  154. package/src/cli/snapshot.ts +164 -0
  155. package/src/cli/storage-rules.ts +172 -0
  156. package/src/cli/verify.ts +416 -0
  157. package/src/conformance/.generated/can-i-use-browser.ts +113 -0
  158. package/src/conformance/.generated/can-i-use.ts +117 -0
  159. package/src/conformance/.generated/conformance-docs.ts +14 -0
  160. package/src/conformance/browser.ts +24 -0
  161. package/src/conformance/can-i-use-tool.ts +51 -0
  162. package/src/conformance/can-i-use.ts +18 -0
  163. package/src/conformance/docs.ts +8 -0
  164. package/src/conformance/index.ts +20 -0
  165. package/src/conformance/tools.ts +16 -0
  166. package/src/credentials/core/memoize-ttl.ts +142 -0
  167. package/src/credentials/core/types.ts +10 -0
  168. package/src/credentials/node/from-adc.ts +96 -0
  169. package/src/credentials/node/from-service-account.ts +133 -0
  170. package/src/credentials/node/index.ts +6 -0
  171. package/src/discover/concurrency.ts +127 -0
  172. package/src/discover/crawler-adapter.ts +142 -0
  173. package/src/discover/crawler.ts +1127 -0
  174. package/src/discover/credential-free.ts +27 -0
  175. package/src/discover/findCollectionGroup.ts +131 -0
  176. package/src/discover/firestore-source.ts +59 -0
  177. package/src/discover/index.ts +5 -0
  178. package/src/discover/merge.ts +523 -0
  179. package/src/discover/session.ts +402 -0
  180. package/src/discover/tools.ts +201 -0
  181. package/src/discover/types.ts +187 -0
  182. package/src/discover/wire.ts +324 -0
  183. package/src/functions-rtdb/child.ts +382 -0
  184. package/src/functions-rtdb/delivery.ts +7 -0
  185. package/src/functions-rtdb/discovery.ts +136 -0
  186. package/src/functions-rtdb/event.ts +64 -0
  187. package/src/functions-rtdb/execution.ts +111 -0
  188. package/src/functions-rtdb/in-memory-delivery.ts +40 -0
  189. package/src/functions-rtdb/project.ts +74 -0
  190. package/src/functions-rtdb/projection.ts +104 -0
  191. package/src/functions-rtdb/reference-pattern.ts +20 -0
  192. package/src/functions-rtdb/remote-delivery.ts +23 -0
  193. package/src/pkg-version.ts +49 -0
  194. package/src/register/esm-exports.ts +63 -0
  195. package/src/register/hooks.ts +30 -0
  196. package/src/register/index.ts +150 -0
  197. package/src/register/mapping.ts +33 -0
  198. package/src/remote/index.ts +1050 -0
  199. package/src/rtdb/crawl-snapshot.ts +118 -0
  200. package/src/rtdb/inspection.ts +137 -0
  201. package/src/rtdb/load-rules-document.ts +56 -0
  202. package/src/rtdb/rules-generation-tool.ts +38 -0
  203. package/src/rtdb/rules-json.ts +29 -0
  204. package/src/serve/activity-guard.ts +29 -0
  205. package/src/serve/activity-route.ts +164 -0
  206. package/src/serve/activity-warning.ts +26 -0
  207. package/src/serve/bridge-mount.ts +200 -0
  208. package/src/serve/bundler.ts +493 -0
  209. package/src/serve/capture-store.ts +65 -0
  210. package/src/serve/discovery.ts +199 -0
  211. package/src/serve/entries/ai.ts +292 -0
  212. package/src/serve/entries/app-backend.ts +4 -0
  213. package/src/serve/entries/app-client.ts +26 -0
  214. package/src/serve/entries/app-session-store.ts +47 -0
  215. package/src/serve/entries/app.ts +11 -0
  216. package/src/serve/entries/auth-helper-core.ts +181 -0
  217. package/src/serve/entries/auth-helper-dom.ts +134 -0
  218. package/src/serve/entries/auth-helper-runtime.ts +20 -0
  219. package/src/serve/entries/auth.ts +255 -0
  220. package/src/serve/entries/bridge-url.ts +31 -0
  221. package/src/serve/entries/database.ts +129 -0
  222. package/src/serve/entries/firestore.ts +232 -0
  223. package/src/serve/entries/init.ts +46 -0
  224. package/src/serve/entries/keepalive.ts +46 -0
  225. package/src/serve/entries/messaging-sw.ts +75 -0
  226. package/src/serve/entries/messaging.ts +96 -0
  227. package/src/serve/entries/runtime.ts +540 -0
  228. package/src/serve/entries/session-store.ts +97 -0
  229. package/src/serve/entries/storage.ts +92 -0
  230. package/src/serve/entries/tab-sync-wiring.ts +274 -0
  231. package/src/serve/entries/worker-runtime.ts +75 -0
  232. package/src/serve/init-payload.ts +42 -0
  233. package/src/serve/namespace.ts +675 -0
  234. package/src/serve/open-browser.ts +68 -0
  235. package/src/serve/rules.ts +272 -0
  236. package/src/serve/sandbox-marker.ts +31 -0
  237. package/src/serve/server.ts +428 -0
  238. package/src/serve/standalone-assets.ts +180 -0
  239. package/src/serve/state-store.ts +166 -0
  240. package/src/serve/studio/disk-project-store.ts +185 -0
  241. package/src/serve/studio/disk-workspace.ts +162 -0
  242. package/src/serve/studio/index.ts +20 -0
  243. package/src/serve/studio/routes.ts +237 -0
  244. package/src/serve/studio/store-types.ts +48 -0
  245. package/src/serve/studio/studio-storage.test.ts +289 -0
  246. package/src/serve/vite-plugin.ts +1102 -0
  247. package/src/serve/worker/activity-bootstrap.ts +24 -0
  248. package/src/serve/worker/client/admin-firestore.ts +38 -0
  249. package/src/serve/worker/client/ai.ts +124 -0
  250. package/src/serve/worker/client/auth.ts +489 -0
  251. package/src/serve/worker/client/connection.ts +202 -0
  252. package/src/serve/worker/client/core.ts +350 -0
  253. package/src/serve/worker/client/disconnect.ts +45 -0
  254. package/src/serve/worker/client/firestore-reads.ts +181 -0
  255. package/src/serve/worker/client/firestore-refs.ts +226 -0
  256. package/src/serve/worker/client/firestore-writes.ts +232 -0
  257. package/src/serve/worker/client/handles.ts +91 -0
  258. package/src/serve/worker/client/messaging.ts +119 -0
  259. package/src/serve/worker/client/presence.ts +174 -0
  260. package/src/serve/worker/client/rtdb.ts +311 -0
  261. package/src/serve/worker/client/rules.ts +63 -0
  262. package/src/serve/worker/client/service-worker-connection.ts +59 -0
  263. package/src/serve/worker/client/snapshots.ts +86 -0
  264. package/src/serve/worker/client/storage.ts +195 -0
  265. package/src/serve/worker/client/studio.ts +88 -0
  266. package/src/serve/worker/client.ts +57 -0
  267. package/src/serve/worker/durable-persistence.ts +137 -0
  268. package/src/serve/worker/entry.ts +179 -0
  269. package/src/serve/worker/host/admin-firestore.ts +88 -0
  270. package/src/serve/worker/host/connection.ts +179 -0
  271. package/src/serve/worker/host/core.ts +387 -0
  272. package/src/serve/worker/host/dispatch.ts +316 -0
  273. package/src/serve/worker/host/firestore-reads.ts +117 -0
  274. package/src/serve/worker/host/firestore-writes.ts +456 -0
  275. package/src/serve/worker/host/presence.ts +312 -0
  276. package/src/serve/worker/host/rtdb.ts +136 -0
  277. package/src/serve/worker/host/rules.ts +128 -0
  278. package/src/serve/worker/host/storage.ts +312 -0
  279. package/src/serve/worker/host/studio.ts +75 -0
  280. package/src/serve/worker/host/subscriptions.ts +238 -0
  281. package/src/serve/worker/host-ai.ts +165 -0
  282. package/src/serve/worker/host-auth.ts +469 -0
  283. package/src/serve/worker/host-context.ts +273 -0
  284. package/src/serve/worker/host-events.ts +94 -0
  285. package/src/serve/worker/host-messaging.ts +240 -0
  286. package/src/serve/worker/host.ts +47 -0
  287. package/src/serve/worker/index.ts +196 -0
  288. package/src/serve/worker/presence-timing.ts +15 -0
  289. package/src/serve/worker/protocol.ts +1112 -0
  290. package/src/serve/worker/serve-init.ts +606 -0
  291. package/src/serve/worker/service-worker-channel.ts +33 -0
  292. package/src/serve/worker/service-worker-relay.ts +86 -0
  293. package/src/serve/writer-lock.ts +53 -0
  294. package/src/verify/cases.ts +233 -0
  295. package/src/verify/fixture.ts +258 -0
  296. package/src/verify/index.ts +519 -0
  297. package/src/verify/tools.ts +108 -0
  298. package/src/version/compat-target.ts +17 -0
  299. package/src/vite.ts +17 -0
  300. package/dist/serve/docs-ui/docs/pyric-firestore-values-reference-api/index.html +0 -31
  301. package/dist/serve/docs-ui/docs/pyric-firestore-values-reference-api.md +0 -29
@@ -0,0 +1,496 @@
1
+ import { runAuthorizationCampaign } from "./runner.js";
2
+ import { runSecurityCases } from "./cases.js";
3
+ import {
4
+ assertActor,
5
+ assertInvariant,
6
+ assertObservation,
7
+ assertProbe,
8
+ assertTarget,
9
+ } from "./validation.js";
10
+ import {
11
+ ASSURANCE_CAMPAIGN_SCHEMA,
12
+ AssuranceInputError,
13
+ type AssuranceActor,
14
+ type AssuranceCampaignContext,
15
+ type AssuranceObservation,
16
+ type AssuranceProbe,
17
+ type AssuranceProbeResult,
18
+ type AssuranceVisualizationSnapshot,
19
+ type AuthorizationCampaignReport,
20
+ type AuthorizationCampaignSpec,
21
+ type CampaignExport,
22
+ type LocalFirebaseTarget,
23
+ type MinimizationResult,
24
+ type ProposalInput,
25
+ type SecurityCase,
26
+ type SecurityInvariant,
27
+ } from "./types.js";
28
+
29
+ export interface CreateAuthorizationCampaignOptions {
30
+ id: string;
31
+ target: LocalFirebaseTarget;
32
+ context?: AssuranceCampaignContext;
33
+ safety?: {
34
+ network?: "forbid";
35
+ maxRuns?: number;
36
+ };
37
+ }
38
+
39
+ export class AuthorizationCampaign {
40
+ readonly id: string;
41
+ readonly target: LocalFirebaseTarget;
42
+ readonly context: AssuranceCampaignContext | undefined;
43
+ readonly maxRuns: number;
44
+
45
+ #actors = new Map<string, AssuranceActor>();
46
+ #invariants = new Map<string, SecurityInvariant>();
47
+ #observations = new Map<string, AssuranceObservation>();
48
+ #probes = new Map<string, AssuranceProbe>();
49
+ #report: AuthorizationCampaignReport | undefined;
50
+ #verifications: AuthorizationCampaignReport[] = [];
51
+ #runs = 0;
52
+
53
+ constructor(options: CreateAuthorizationCampaignOptions) {
54
+ if (!options.id.trim())
55
+ throw new AssuranceInputError("campaign id is required.");
56
+ assertTarget(options.target);
57
+ this.id = options.id;
58
+ this.target = options.target;
59
+ this.context = options.context;
60
+ const maxRuns = options.safety?.maxRuns ?? 200;
61
+ if (!Number.isInteger(maxRuns) || maxRuns <= 0) {
62
+ throw new AssuranceInputError(
63
+ "campaign maxRuns must be a positive integer.",
64
+ );
65
+ }
66
+ this.maxRuns = maxRuns;
67
+ }
68
+
69
+ addActor(actor: AssuranceActor): AssuranceActor {
70
+ assertActor(actor);
71
+ this.#addUnique(this.#actors, actor, "actor");
72
+ return actor;
73
+ }
74
+
75
+ addInvariant(invariant: SecurityInvariant): SecurityInvariant {
76
+ assertInvariant(invariant);
77
+ this.#addUnique(this.#invariants, invariant, "invariant");
78
+ return invariant;
79
+ }
80
+
81
+ addObservation(observation: AssuranceObservation): AssuranceObservation {
82
+ assertObservation(observation);
83
+ if (!this.#actors.has(observation.actorId)) {
84
+ throw new AssuranceInputError(
85
+ `observation '${observation.id}' references unknown actor '${observation.actorId}'.`,
86
+ );
87
+ }
88
+ this.#addUnique(this.#observations, observation, "observation");
89
+ return observation;
90
+ }
91
+
92
+ addProbe(probe: AssuranceProbe): AssuranceProbe {
93
+ assertProbe(probe);
94
+ if (!this.#actors.has(probe.actorId)) {
95
+ throw new AssuranceInputError(
96
+ `probe '${probe.id}' references unknown actor '${probe.actorId}'.`,
97
+ );
98
+ }
99
+ const invariant = this.#invariants.get(probe.invariantId);
100
+ if (!invariant) {
101
+ throw new AssuranceInputError(
102
+ `probe '${probe.id}' references unknown invariant '${probe.invariantId}'.`,
103
+ );
104
+ }
105
+ if (
106
+ invariant.service !== "cross-service" &&
107
+ invariant.service !== probe.control.service
108
+ ) {
109
+ throw new AssuranceInputError(
110
+ `probe '${probe.id}' service does not match invariant '${invariant.id}'.`,
111
+ );
112
+ }
113
+ this.#addUnique(this.#probes, probe, "probe");
114
+ return probe;
115
+ }
116
+
117
+ propose(input: ProposalInput): AssuranceProbe[] {
118
+ if (!input || typeof input !== "object") {
119
+ throw new AssuranceInputError("proposal input must be an object.");
120
+ }
121
+ if (
122
+ typeof input.observationId !== "string" ||
123
+ !input.observationId.trim()
124
+ ) {
125
+ throw new AssuranceInputError("proposal observationId is required.");
126
+ }
127
+ if (typeof input.invariantId !== "string" || !input.invariantId.trim()) {
128
+ throw new AssuranceInputError("proposal invariantId is required.");
129
+ }
130
+ if (!Array.isArray(input.mutations)) {
131
+ throw new AssuranceInputError("proposal mutations must be an array.");
132
+ }
133
+ const observation = this.#observations.get(input.observationId);
134
+ if (!observation) {
135
+ throw new AssuranceInputError(
136
+ `unknown observation '${input.observationId}'.`,
137
+ );
138
+ }
139
+ if (!this.#invariants.has(input.invariantId)) {
140
+ throw new AssuranceInputError(
141
+ `unknown invariant '${input.invariantId}'.`,
142
+ );
143
+ }
144
+ if (input.mutations.length === 0) {
145
+ throw new AssuranceInputError("at least one mutation is required.");
146
+ }
147
+
148
+ return input.mutations.map((mutation, index) => {
149
+ const id =
150
+ mutation.id ??
151
+ `${input.observationId}-${mutation.dimension}-${index + 1}`;
152
+ const probe: AssuranceProbe = {
153
+ id,
154
+ actorId: observation.actorId,
155
+ invariantId: input.invariantId,
156
+ control: observation.operation,
157
+ mutation: {
158
+ dimension: mutation.dimension,
159
+ description: mutation.description,
160
+ operation: mutation.operation,
161
+ },
162
+ };
163
+ return this.addProbe(probe);
164
+ });
165
+ }
166
+
167
+ spec(probeIds?: string[]): AuthorizationCampaignSpec {
168
+ const probes = probeIds
169
+ ? probeIds.map((id) => {
170
+ const probe = this.#probes.get(id);
171
+ if (!probe) throw new AssuranceInputError(`unknown probe '${id}'.`);
172
+ return probe;
173
+ })
174
+ : [...this.#probes.values()];
175
+ return {
176
+ schema: ASSURANCE_CAMPAIGN_SCHEMA,
177
+ id: this.id,
178
+ target: this.target,
179
+ actors: [...this.#actors.values()],
180
+ invariants: [...this.#invariants.values()],
181
+ probes,
182
+ };
183
+ }
184
+
185
+ async run(probeIds?: string[]): Promise<AuthorizationCampaignReport> {
186
+ const count = probeIds?.length ?? this.#probes.size;
187
+ this.#assertRunBudget(count);
188
+ const report = await runAuthorizationCampaign(this.spec(probeIds));
189
+ this.#runs += count;
190
+ if (!probeIds) {
191
+ this.#report = report;
192
+ } else {
193
+ this.#report = mergeReports(this.#report, report, this.#probes.size);
194
+ }
195
+ return report;
196
+ }
197
+
198
+ inspect(probeId: string): AssuranceProbeResult {
199
+ const result = this.#report?.results.find(
200
+ (item) => item.probeId === probeId,
201
+ );
202
+ if (!result) {
203
+ throw new AssuranceInputError(
204
+ `probe '${probeId}' has no completed run to inspect.`,
205
+ );
206
+ }
207
+ return result;
208
+ }
209
+
210
+ async minimize(probeId: string): Promise<MinimizationResult> {
211
+ const original = this.#probes.get(probeId);
212
+ if (!original) throw new AssuranceInputError(`unknown probe '${probeId}'.`);
213
+ const originalResult = this.inspect(probeId);
214
+ if (
215
+ originalResult.classification !== "local-counterexample" &&
216
+ originalResult.classification !== "candidate-signal"
217
+ ) {
218
+ throw new AssuranceInputError(
219
+ `probe '${probeId}' has no counterexample to minimize.`,
220
+ );
221
+ }
222
+
223
+ const operation = original.mutation.operation;
224
+ const data = "data" in operation ? operation.data : undefined;
225
+ if (
226
+ original.mutation.dimension !== "payload" ||
227
+ !data ||
228
+ typeof data !== "object" ||
229
+ Array.isArray(data)
230
+ ) {
231
+ return {
232
+ probeId,
233
+ changed: false,
234
+ removedPayloadFields: [],
235
+ probe: original,
236
+ result: originalResult,
237
+ };
238
+ }
239
+
240
+ let kept = { ...(data as Record<string, unknown>) };
241
+ const controlData =
242
+ "data" in original.control &&
243
+ original.control.data &&
244
+ typeof original.control.data === "object" &&
245
+ !Array.isArray(original.control.data)
246
+ ? (original.control.data as Record<string, unknown>)
247
+ : {};
248
+ const retainsPayloadDelta = (candidate: Record<string, unknown>) =>
249
+ Object.keys(candidate).some(
250
+ (field) =>
251
+ stableValue(candidate[field]) !== stableValue(controlData[field]),
252
+ );
253
+ if (!retainsPayloadDelta(kept)) {
254
+ return {
255
+ probeId,
256
+ changed: false,
257
+ removedPayloadFields: [],
258
+ probe: original,
259
+ result: originalResult,
260
+ };
261
+ }
262
+ this.#assertRunBudget(Object.keys(kept).length);
263
+ const removedPayloadFields: string[] = [];
264
+ let bestProbe = original;
265
+ let bestResult = originalResult;
266
+ for (const field of Object.keys(kept)) {
267
+ if (Object.keys(kept).length === 1) break;
268
+ const candidateData = { ...kept };
269
+ delete candidateData[field];
270
+ if (!retainsPayloadDelta(candidateData)) continue;
271
+ const candidate: AssuranceProbe = {
272
+ ...original,
273
+ id: `${original.id}--min-${field}`,
274
+ mutation: {
275
+ ...original.mutation,
276
+ operation: { ...operation, data: candidateData } as typeof operation,
277
+ },
278
+ };
279
+ const report = await runAuthorizationCampaign({
280
+ ...this.spec([probeId]),
281
+ probes: [candidate],
282
+ });
283
+ this.#runs++;
284
+ const result = report.results[0]!;
285
+ if (result.classification === originalResult.classification) {
286
+ kept = candidateData;
287
+ removedPayloadFields.push(field);
288
+ bestProbe = { ...candidate, id: original.id };
289
+ bestResult = { ...result, probeId: original.id };
290
+ }
291
+ }
292
+ if (removedPayloadFields.length > 0) {
293
+ this.#probes.set(probeId, bestProbe);
294
+ this.#report = replaceResult(this.#report!, bestResult);
295
+ }
296
+ return {
297
+ probeId,
298
+ changed: removedPayloadFields.length > 0,
299
+ removedPayloadFields,
300
+ probe: bestProbe,
301
+ result: bestResult,
302
+ };
303
+ }
304
+
305
+ exportSecurityCases(
306
+ options: { includeCandidates?: boolean } = {},
307
+ ): SecurityCase[] {
308
+ if (!this.#report)
309
+ throw new AssuranceInputError("campaign has not been run.");
310
+ return this.#report.results
311
+ .filter(
312
+ (result) =>
313
+ result.classification === "local-counterexample" ||
314
+ (options.includeCandidates &&
315
+ result.classification === "candidate-signal"),
316
+ )
317
+ .map((result) => ({
318
+ schema: "pyric.assurance.case.v1" as const,
319
+ id: result.probeId,
320
+ campaignId: this.id,
321
+ actorId: result.actorEvidence.actorId,
322
+ invariant: result.invariant,
323
+ control: result.control.operation,
324
+ mutation: result.mutationSpec,
325
+ expect: result.invariant.expected,
326
+ qualification: result.qualification,
327
+ }));
328
+ }
329
+
330
+ async verifyRules(options: {
331
+ rules: LocalFirebaseTarget["rules"];
332
+ includeCandidates?: boolean;
333
+ id?: string;
334
+ }): Promise<AuthorizationCampaignReport> {
335
+ if (!options.rules || Object.keys(options.rules).length === 0) {
336
+ throw new AssuranceInputError(
337
+ "candidate rules must include at least one service.",
338
+ );
339
+ }
340
+ const cases = this.exportSecurityCases({
341
+ includeCandidates: options.includeCandidates,
342
+ });
343
+ if (cases.length === 0) {
344
+ throw new AssuranceInputError(
345
+ "campaign has no qualified regression cases to verify.",
346
+ );
347
+ }
348
+ this.#assertRunBudget(cases.length);
349
+ const report = await runSecurityCases({
350
+ campaignId:
351
+ options.id ??
352
+ `${this.id}-verification-${this.#verifications.length + 1}`,
353
+ target: {
354
+ ...this.target,
355
+ rules: { ...this.target.rules, ...options.rules },
356
+ },
357
+ actors: [...this.#actors.values()],
358
+ cases,
359
+ });
360
+ this.#runs += cases.length;
361
+ this.#verifications.push(report);
362
+ return report;
363
+ }
364
+
365
+ export(): CampaignExport {
366
+ return {
367
+ schema: "pyric.assurance.export.v1",
368
+ campaign: this.spec(),
369
+ ...(this.context ? { context: this.context } : {}),
370
+ observations: [...this.#observations.values()],
371
+ ...(this.#report ? { report: this.#report } : {}),
372
+ cases: this.#report ? this.exportSecurityCases() : [],
373
+ ...(this.#verifications.length > 0
374
+ ? { verifications: [...this.#verifications] }
375
+ : {}),
376
+ };
377
+ }
378
+
379
+ visualization(): AssuranceVisualizationSnapshot {
380
+ return {
381
+ schema: "pyric.assurance.visualization.v1",
382
+ campaignId: this.id,
383
+ ...(this.context ? { context: this.context } : {}),
384
+ observations: [...this.#observations.values()],
385
+ probes: [...this.#probes.values()],
386
+ ...(this.#report ? { report: this.#report } : {}),
387
+ ...(this.#verifications.length > 0
388
+ ? { verifications: [...this.#verifications] }
389
+ : {}),
390
+ };
391
+ }
392
+
393
+ get report(): AuthorizationCampaignReport | undefined {
394
+ return this.#report;
395
+ }
396
+
397
+ get observations(): AssuranceObservation[] {
398
+ return [...this.#observations.values()];
399
+ }
400
+
401
+ #addUnique<T extends { id: string }>(
402
+ map: Map<string, T>,
403
+ value: T,
404
+ label: string,
405
+ ): void {
406
+ if (!value.id.trim())
407
+ throw new AssuranceInputError(`${label} id is required.`);
408
+ if (map.has(value.id))
409
+ throw new AssuranceInputError(`duplicate ${label} id '${value.id}'.`);
410
+ map.set(value.id, value);
411
+ }
412
+
413
+ #assertRunBudget(count: number): void {
414
+ if (this.#runs + count > this.maxRuns) {
415
+ throw new AssuranceInputError(
416
+ `campaign run budget exceeded: ${this.#runs + count}/${this.maxRuns} probes.`,
417
+ );
418
+ }
419
+ }
420
+ }
421
+
422
+ function stableValue(value: unknown): string {
423
+ if (value === undefined) return "undefined";
424
+ if (value === null || typeof value !== "object") return JSON.stringify(value);
425
+ if (Array.isArray(value)) return `[${value.map(stableValue).join(",")}]`;
426
+ const record = value as Record<string, unknown>;
427
+ return `{${Object.keys(record)
428
+ .sort()
429
+ .map((key) => `${JSON.stringify(key)}:${stableValue(record[key])}`)
430
+ .join(",")}}`;
431
+ }
432
+
433
+ function mergeReports(
434
+ existing: AuthorizationCampaignReport | undefined,
435
+ incoming: AuthorizationCampaignReport,
436
+ totalProbes: number,
437
+ ): AuthorizationCampaignReport {
438
+ if (!existing) return incoming;
439
+ const byId = new Map(
440
+ existing.results.map((result) => [result.probeId, result]),
441
+ );
442
+ for (const result of incoming.results) byId.set(result.probeId, result);
443
+ const results = [...byId.values()];
444
+ return {
445
+ ...incoming,
446
+ results,
447
+ summary: summarizeResults(results, totalProbes),
448
+ };
449
+ }
450
+
451
+ function replaceResult(
452
+ report: AuthorizationCampaignReport,
453
+ replacement: AssuranceProbeResult,
454
+ ): AuthorizationCampaignReport {
455
+ const results = report.results.map((result) =>
456
+ result.probeId === replacement.probeId ? replacement : result,
457
+ );
458
+ return {
459
+ ...report,
460
+ results,
461
+ summary: summarizeResults(results, results.length),
462
+ };
463
+ }
464
+
465
+ function summarizeResults(
466
+ results: AssuranceProbeResult[],
467
+ totalProbes: number,
468
+ ): AuthorizationCampaignReport["summary"] {
469
+ return {
470
+ probes: totalProbes,
471
+ controlsPassed: results.filter(
472
+ (result) => result.control.decision === "ALLOW",
473
+ ).length,
474
+ localCounterexamples: results.filter(
475
+ (result) => result.classification === "local-counterexample",
476
+ ).length,
477
+ candidateSignals: results.filter(
478
+ (result) => result.classification === "candidate-signal",
479
+ ).length,
480
+ noCounterexamples: results.filter(
481
+ (result) => result.classification === "no-counterexample",
482
+ ).length,
483
+ engineGaps: results.filter(
484
+ (result) => result.classification === "engine-gap",
485
+ ).length,
486
+ invalidProbes: results.filter(
487
+ (result) => result.classification === "invalid-probe",
488
+ ).length,
489
+ };
490
+ }
491
+
492
+ export function createAuthorizationCampaign(
493
+ options: CreateAuthorizationCampaignOptions,
494
+ ): AuthorizationCampaign {
495
+ return new AuthorizationCampaign(options);
496
+ }