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

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 (424) hide show
  1. package/dist/cli/serve.js +4 -4
  2. package/dist/cli/serve.js.map +1 -1
  3. package/dist/conformance/.generated/can-i-use-browser.d.ts.map +1 -1
  4. package/dist/conformance/.generated/can-i-use-browser.js +1 -1
  5. package/dist/conformance/.generated/can-i-use-browser.js.map +1 -1
  6. package/dist/conformance/.generated/can-i-use.d.ts.map +1 -1
  7. package/dist/conformance/.generated/can-i-use.js +2 -2
  8. package/dist/conformance/.generated/can-i-use.js.map +1 -1
  9. package/dist/serve/bundler.d.ts +13 -8
  10. package/dist/serve/bundler.d.ts.map +1 -1
  11. package/dist/serve/bundler.js +31 -36
  12. package/dist/serve/bundler.js.map +1 -1
  13. package/dist/serve/docs-ui/docs/_rhythm/index.html +1 -1
  14. package/dist/serve/docs-ui/docs/agent/set-up-your-agent/index.html +1 -1
  15. package/dist/serve/docs-ui/docs/agent/watch-and-review/index.html +1 -1
  16. package/dist/serve/docs-ui/docs/agent/work-with-an-agent/index.html +1 -1
  17. package/dist/serve/docs-ui/docs/ai-compat/index.html +1 -1
  18. package/dist/serve/docs-ui/docs/api-reference/index.html +2 -2
  19. package/dist/serve/docs-ui/docs/api-reference.md +1 -1
  20. package/dist/serve/docs-ui/docs/app-compat/index.html +1 -1
  21. package/dist/serve/docs-ui/docs/auth-compat/index.html +1 -1
  22. package/dist/serve/docs-ui/docs/build/ai-logic/index.html +44 -22
  23. package/dist/serve/docs-ui/docs/build/ai-logic.md +69 -21
  24. package/dist/serve/docs-ui/docs/build/authentication/index.html +1 -1
  25. package/dist/serve/docs-ui/docs/build/cloud-firestore/index.html +1 -1
  26. package/dist/serve/docs-ui/docs/build/cloud-messaging/index.html +1 -1
  27. package/dist/serve/docs-ui/docs/build/cloud-storage/index.html +1 -1
  28. package/dist/serve/docs-ui/docs/build/realtime-database/index.html +1 -1
  29. package/dist/serve/docs-ui/docs/conformance-scores/index.html +1 -1
  30. package/dist/serve/docs-ui/docs/create-pyric-reference-api/index.html +1 -1
  31. package/dist/serve/docs-ui/docs/database-compat/index.html +1 -1
  32. package/dist/serve/docs-ui/docs/firestore-compat/index.html +1 -1
  33. package/dist/serve/docs-ui/docs/functions-rtdb-compat/index.html +1 -1
  34. package/dist/serve/docs-ui/docs/get-started/how-the-swap-works/index.html +2 -1
  35. package/dist/serve/docs-ui/docs/get-started/how-the-swap-works.md +2 -0
  36. package/dist/serve/docs-ui/docs/get-started/start-building/index.html +3 -2
  37. package/dist/serve/docs-ui/docs/get-started/start-building.md +3 -1
  38. package/dist/serve/docs-ui/docs/index.json +349 -29
  39. package/dist/serve/docs-ui/docs/messaging-compat/index.html +1 -1
  40. package/dist/serve/docs-ui/docs/observe/resolve-runtime-status/index.html +28 -0
  41. package/dist/serve/docs-ui/docs/observe/resolve-runtime-status.md +47 -0
  42. package/dist/serve/docs-ui/docs/observe/see-whats-happening/index.html +1 -1
  43. package/dist/serve/docs-ui/docs/observe/shape-your-data/index.html +1 -1
  44. package/dist/serve/docs-ui/docs/overview/index.html +1 -1
  45. package/dist/serve/docs-ui/docs/pyric-admin-app-reference-api/index.html +1 -1
  46. package/dist/serve/docs-ui/docs/pyric-admin-auth-reference-api/index.html +1 -1
  47. package/dist/serve/docs-ui/docs/pyric-admin-database-reference-api/index.html +1 -1
  48. package/dist/serve/docs-ui/docs/pyric-admin-firestore-reference-api/index.html +1 -1
  49. package/dist/serve/docs-ui/docs/pyric-admin-messaging-reference-api/index.html +916 -0
  50. package/dist/serve/docs-ui/docs/pyric-admin-messaging-reference-api.md +706 -0
  51. package/dist/serve/docs-ui/docs/pyric-admin-storage-reference-api/index.html +1 -1
  52. package/dist/serve/docs-ui/docs/pyric-ai-reference-api/index.html +1 -1
  53. package/dist/serve/docs-ui/docs/pyric-ai-scripting-reference-api/index.html +1 -1
  54. package/dist/serve/docs-ui/docs/pyric-app-reference-api/index.html +1 -1
  55. package/dist/serve/docs-ui/docs/pyric-auth-reference-api/index.html +1 -1
  56. package/dist/serve/docs-ui/docs/pyric-cli-assurance-browser-reference-api/index.html +1 -1
  57. package/dist/serve/docs-ui/docs/pyric-cli-assurance-reference-api/index.html +1 -1
  58. package/dist/serve/docs-ui/docs/pyric-cli-bridge-client-reference-api/index.html +1 -1
  59. package/dist/serve/docs-ui/docs/pyric-cli-bridge-reference-api/index.html +1 -1
  60. package/dist/serve/docs-ui/docs/pyric-cli-conformance-browser-reference-api/index.html +1 -1
  61. package/dist/serve/docs-ui/docs/pyric-cli-conformance-docs-reference-api/index.html +1 -1
  62. package/dist/serve/docs-ui/docs/pyric-cli-conformance-reference-api/index.html +1 -1
  63. package/dist/serve/docs-ui/docs/pyric-cli-credentials-node-reference-api/index.html +1 -1
  64. package/dist/serve/docs-ui/docs/pyric-cli-discover-reference-api/index.html +1 -1
  65. package/dist/serve/docs-ui/docs/pyric-cli-register-reference-api/index.html +1 -1
  66. package/dist/serve/docs-ui/docs/pyric-cli-remote-reference-api/index.html +1 -1
  67. package/dist/serve/docs-ui/docs/pyric-cli-serve-worker-reference-api/index.html +168 -76
  68. package/dist/serve/docs-ui/docs/pyric-cli-serve-worker-reference-api.md +156 -73
  69. package/dist/serve/docs-ui/docs/pyric-cli-verify-reference-api/index.html +1 -1
  70. package/dist/serve/docs-ui/docs/pyric-cli-vite-reference-api/index.html +11 -8
  71. package/dist/serve/docs-ui/docs/pyric-cli-vite-reference-api.md +17 -4
  72. package/dist/serve/docs-ui/docs/pyric-database-reference-api/index.html +1 -1
  73. package/dist/serve/docs-ui/docs/pyric-firestore-reference-api/index.html +1 -1
  74. package/dist/serve/docs-ui/docs/pyric-messaging-reference-api/index.html +1 -1
  75. package/dist/serve/docs-ui/docs/pyric-messaging-sw-reference-api/index.html +1 -1
  76. package/dist/serve/docs-ui/docs/pyric-rules-reference-api/index.html +1 -1
  77. package/dist/serve/docs-ui/docs/pyric-sandbox-database-reference-api/index.html +1 -1
  78. package/dist/serve/docs-ui/docs/pyric-sandbox-firestore-reference-api/index.html +1 -1
  79. package/dist/serve/docs-ui/docs/pyric-sandbox-reference-api/index.html +37 -14
  80. package/dist/serve/docs-ui/docs/pyric-sandbox-reference-api.md +28 -8
  81. package/dist/serve/docs-ui/docs/pyric-storage-reference-api/index.html +1 -1
  82. package/dist/serve/docs-ui/docs/pyric-ui-agents-reference-api/index.html +1 -1
  83. package/dist/serve/docs-ui/docs/pyric-ui-auth-hooks-reference-api/index.html +1 -1
  84. package/dist/serve/docs-ui/docs/pyric-ui-auth-reference-api/index.html +1 -1
  85. package/dist/serve/docs-ui/docs/pyric-ui-events-hooks-reference-api/index.html +1 -1
  86. package/dist/serve/docs-ui/docs/pyric-ui-events-reference-api/index.html +1 -1
  87. package/dist/serve/docs-ui/docs/pyric-ui-firestore-hooks-reference-api/index.html +1 -1
  88. package/dist/serve/docs-ui/docs/pyric-ui-firestore-reference-api/index.html +1 -1
  89. package/dist/serve/docs-ui/docs/pyric-ui-primitives-reference-api/index.html +1 -1
  90. package/dist/serve/docs-ui/docs/pyric-ui-rtdb-reference-api/index.html +1 -1
  91. package/dist/serve/docs-ui/docs/pyric-ui-rules-hooks-reference-api/index.html +1 -1
  92. package/dist/serve/docs-ui/docs/pyric-ui-rules-reference-api/index.html +1 -1
  93. package/dist/serve/docs-ui/docs/pyric-ui-storage-hooks-reference-api/index.html +1 -1
  94. package/dist/serve/docs-ui/docs/pyric-ui-storage-reference-api/index.html +1 -1
  95. package/dist/serve/docs-ui/docs/pyric-ui-traffic-hooks-reference-api/index.html +1 -1
  96. package/dist/serve/docs-ui/docs/pyric-ui-traffic-reference-api/index.html +1 -1
  97. package/dist/serve/docs-ui/docs/rules-compat/index.html +1 -1
  98. package/dist/serve/docs-ui/docs/secure/audit-your-rules/index.html +1 -1
  99. package/dist/serve/docs-ui/docs/secure/firestore-rules-limits/index.html +1 -1
  100. package/dist/serve/docs-ui/docs/secure/read-a-denial/index.html +1 -1
  101. package/dist/serve/docs-ui/docs/secure/rtdb-rules-in-typescript/index.html +1 -1
  102. package/dist/serve/docs-ui/docs/secure/rules-standard-library/index.html +1 -1
  103. package/dist/serve/docs-ui/docs/secure/secure-it-with-rules/index.html +1 -1
  104. package/dist/serve/docs-ui/docs/secure/simulate-and-lint/index.html +1 -1
  105. package/dist/serve/docs-ui/docs/secure/write-a-rules-test-suite/index.html +1 -1
  106. package/dist/serve/docs-ui/docs/ship/ship-to-production/index.html +1 -1
  107. package/dist/serve/docs-ui/docs/ship/test-in-node/index.html +1 -1
  108. package/dist/serve/docs-ui/docs/storage-compat/index.html +1 -1
  109. package/dist/serve/docs-ui/docs/trust/how-we-know-it-matches-firebase/index.html +1 -1
  110. package/dist/serve/docs-ui/docs/trust/versioning-and-compatibility/index.html +1 -1
  111. package/dist/serve/docs-ui/docs/tutorial/index.html +429 -0
  112. package/dist/serve/docs-ui/docs/tutorial.md +701 -0
  113. package/dist/serve/docs-ui/llms.txt +3 -1
  114. package/dist/serve/entries/init.d.ts.map +1 -1
  115. package/dist/serve/entries/init.js +3 -0
  116. package/dist/serve/entries/init.js.map +1 -1
  117. package/dist/serve/entries/runtime.d.ts.map +1 -1
  118. package/dist/serve/entries/runtime.js +20 -5
  119. package/dist/serve/entries/runtime.js.map +1 -1
  120. package/dist/serve/entries/worker-runtime.d.ts +2 -2
  121. package/dist/serve/entries/worker-runtime.d.ts.map +1 -1
  122. package/dist/serve/entries/worker-runtime.js +45 -10
  123. package/dist/serve/entries/worker-runtime.js.map +1 -1
  124. package/dist/serve/runtime/chip-config.d.ts +19 -0
  125. package/dist/serve/runtime/chip-config.d.ts.map +1 -0
  126. package/dist/serve/runtime/chip-config.js +19 -0
  127. package/dist/serve/runtime/chip-config.js.map +1 -0
  128. package/dist/serve/runtime/chip-install.d.ts +10 -0
  129. package/dist/serve/runtime/chip-install.d.ts.map +1 -0
  130. package/dist/serve/runtime/chip-install.js +15 -0
  131. package/dist/serve/runtime/chip-install.js.map +1 -0
  132. package/dist/serve/runtime/chip.d.ts +17 -0
  133. package/dist/serve/runtime/chip.d.ts.map +1 -0
  134. package/dist/serve/runtime/chip.js +257 -0
  135. package/dist/serve/runtime/chip.js.map +1 -0
  136. package/dist/serve/runtime/manifest.d.ts +22 -0
  137. package/dist/serve/runtime/manifest.d.ts.map +1 -0
  138. package/dist/serve/runtime/manifest.js +20 -0
  139. package/dist/serve/runtime/manifest.js.map +1 -0
  140. package/dist/serve/runtime/status.d.ts +49 -0
  141. package/dist/serve/runtime/status.d.ts.map +1 -0
  142. package/dist/serve/runtime/status.js +164 -0
  143. package/dist/serve/runtime/status.js.map +1 -0
  144. package/dist/serve/runtime/worker-connection.d.ts +4 -0
  145. package/dist/serve/runtime/worker-connection.d.ts.map +1 -0
  146. package/dist/serve/runtime/worker-connection.js +11 -0
  147. package/dist/serve/runtime/worker-connection.js.map +1 -0
  148. package/dist/serve/runtime/worker-generation.d.ts +14 -0
  149. package/dist/serve/runtime/worker-generation.d.ts.map +1 -0
  150. package/dist/serve/runtime/worker-generation.js +56 -0
  151. package/dist/serve/runtime/worker-generation.js.map +1 -0
  152. package/dist/serve/runtime/worker-replacement.d.ts +18 -0
  153. package/dist/serve/runtime/worker-replacement.d.ts.map +1 -0
  154. package/dist/serve/runtime/worker-replacement.js +29 -0
  155. package/dist/serve/runtime/worker-replacement.js.map +1 -0
  156. package/dist/serve/standalone-assets.d.ts +2 -2
  157. package/dist/serve/standalone-assets.d.ts.map +1 -1
  158. package/dist/serve/standalone-assets.js +1 -1
  159. package/dist/serve/standalone-assets.js.map +1 -1
  160. package/dist/serve/studio-ui/assets/{RulesCodeEditor-CxVYcZXw.js → RulesCodeEditor-BDlH-5lp.js} +1 -1
  161. package/dist/serve/studio-ui/assets/{index-cpSfgQ7H.js → index-Bf9SSE8L.js} +5 -5
  162. package/dist/serve/studio-ui/assets/{seed-DeuFKi4J.js → seed-D_UjxfOU.js} +1 -1
  163. package/dist/serve/studio-ui/index.html +1 -1
  164. package/dist/serve/vite-ai-config.d.ts +25 -0
  165. package/dist/serve/vite-ai-config.d.ts.map +1 -0
  166. package/dist/serve/vite-ai-config.js +45 -0
  167. package/dist/serve/vite-ai-config.js.map +1 -0
  168. package/dist/serve/vite-plugin.d.ts +17 -36
  169. package/dist/serve/vite-plugin.d.ts.map +1 -1
  170. package/dist/serve/vite-plugin.js +38 -57
  171. package/dist/serve/vite-plugin.js.map +1 -1
  172. package/dist/serve/vite-rules-source.d.ts +8 -0
  173. package/dist/serve/vite-rules-source.d.ts.map +1 -0
  174. package/dist/serve/vite-rules-source.js +23 -0
  175. package/dist/serve/vite-rules-source.js.map +1 -0
  176. package/dist/serve/vite-worker-runtime.d.ts +20 -0
  177. package/dist/serve/vite-worker-runtime.d.ts.map +1 -0
  178. package/dist/serve/vite-worker-runtime.js +25 -0
  179. package/dist/serve/vite-worker-runtime.js.map +1 -0
  180. package/dist/serve/worker/client/connection.d.ts +7 -2
  181. package/dist/serve/worker/client/connection.d.ts.map +1 -1
  182. package/dist/serve/worker/client/connection.js +13 -16
  183. package/dist/serve/worker/client/connection.js.map +1 -1
  184. package/dist/serve/worker/client/core.d.ts +7 -0
  185. package/dist/serve/worker/client/core.d.ts.map +1 -1
  186. package/dist/serve/worker/client/core.js +28 -0
  187. package/dist/serve/worker/client/core.js.map +1 -1
  188. package/dist/serve/worker/client/runtime-control.d.ts +8 -0
  189. package/dist/serve/worker/client/runtime-control.d.ts.map +1 -0
  190. package/dist/serve/worker/client/runtime-control.js +11 -0
  191. package/dist/serve/worker/client/runtime-control.js.map +1 -0
  192. package/dist/serve/worker/entry.js +44 -2
  193. package/dist/serve/worker/entry.js.map +1 -1
  194. package/dist/serve/worker/host/core.js +1 -1
  195. package/dist/serve/worker/host/core.js.map +1 -1
  196. package/dist/serve/worker/host/firestore-writes.d.ts.map +1 -1
  197. package/dist/serve/worker/host/firestore-writes.js +3 -2
  198. package/dist/serve/worker/host/firestore-writes.js.map +1 -1
  199. package/dist/serve/worker/host/studio.js +1 -1
  200. package/dist/serve/worker/host/studio.js.map +1 -1
  201. package/dist/serve/worker/host-context.d.ts +1 -1
  202. package/dist/serve/worker/host-context.d.ts.map +1 -1
  203. package/dist/serve/worker/index.d.ts +2 -1
  204. package/dist/serve/worker/index.d.ts.map +1 -1
  205. package/dist/serve/worker/index.js +2 -1
  206. package/dist/serve/worker/index.js.map +1 -1
  207. package/dist/serve/worker/protocol.d.ts +15 -1
  208. package/dist/serve/worker/protocol.d.ts.map +1 -1
  209. package/dist/serve/worker/protocol.js +2 -2
  210. package/dist/serve/worker/protocol.js.map +1 -1
  211. package/dist/serve/worker/retirement.d.ts +21 -0
  212. package/dist/serve/worker/retirement.d.ts.map +1 -0
  213. package/dist/serve/worker/retirement.js +97 -0
  214. package/dist/serve/worker/retirement.js.map +1 -0
  215. package/dist/serve/worker/serve-init.d.ts.map +1 -1
  216. package/dist/serve/worker/serve-init.js +5 -6
  217. package/dist/serve/worker/serve-init.js.map +1 -1
  218. package/dist/vite.d.ts +1 -0
  219. package/dist/vite.d.ts.map +1 -1
  220. package/dist/vite.js.map +1 -1
  221. package/package.json +6 -4
  222. package/src/assurance/.generated/conformance-verdicts.ts +1090 -0
  223. package/src/assurance/attachment.ts +211 -0
  224. package/src/assurance/browser.ts +80 -0
  225. package/src/assurance/campaign.ts +496 -0
  226. package/src/assurance/capabilities.ts +446 -0
  227. package/src/assurance/cases.ts +43 -0
  228. package/src/assurance/index.ts +75 -0
  229. package/src/assurance/runner.ts +885 -0
  230. package/src/assurance/tool-names.ts +15 -0
  231. package/src/assurance/tools.ts +830 -0
  232. package/src/assurance/types.ts +352 -0
  233. package/src/assurance/validation.ts +434 -0
  234. package/src/bridge/client/bridge.ts +550 -0
  235. package/src/bridge/client/dispatch.ts +137 -0
  236. package/src/bridge/client.ts +45 -0
  237. package/src/bridge/protocol.ts +351 -0
  238. package/src/bridge/server/audit.ts +53 -0
  239. package/src/bridge/server/bridge.ts +597 -0
  240. package/src/bridge/server/headless.ts +160 -0
  241. package/src/bridge/server/json-schema-to-zod.ts +108 -0
  242. package/src/bridge/server/local-bridge.ts +66 -0
  243. package/src/bridge/server/logger.ts +50 -0
  244. package/src/bridge/server/mcp-contract.ts +93 -0
  245. package/src/bridge/server/mcp.ts +129 -0
  246. package/src/bridge/server/peer.ts +232 -0
  247. package/src/bridge/server/standalone.ts +347 -0
  248. package/src/bridge/server/tool-metadata.ts +99 -0
  249. package/src/bridge/server.ts +28 -0
  250. package/src/cli/can-i-use.ts +50 -0
  251. package/src/cli/cli.test.ts +493 -0
  252. package/src/cli/database-rules.ts +329 -0
  253. package/src/cli/dev-runner.ts +282 -0
  254. package/src/cli/firebase-json.ts +100 -0
  255. package/src/cli/firestore-indexes.ts +70 -0
  256. package/src/cli/index.ts +425 -0
  257. package/src/cli/init.ts +260 -0
  258. package/src/cli/mcp-proxy.ts +196 -0
  259. package/src/cli/parse-args.ts +80 -0
  260. package/src/cli/rules.ts +259 -0
  261. package/src/cli/scope.ts +67 -0
  262. package/src/cli/serve.ts +996 -0
  263. package/src/cli/service-commands.ts +73 -0
  264. package/src/cli/snapshot.ts +164 -0
  265. package/src/cli/storage-rules.ts +172 -0
  266. package/src/cli/verify.ts +416 -0
  267. package/src/conformance/.generated/can-i-use-browser.ts +113 -0
  268. package/src/conformance/.generated/can-i-use.ts +117 -0
  269. package/src/conformance/.generated/conformance-docs.ts +14 -0
  270. package/src/conformance/browser.ts +24 -0
  271. package/src/conformance/can-i-use-tool.ts +51 -0
  272. package/src/conformance/can-i-use.ts +18 -0
  273. package/src/conformance/docs.ts +8 -0
  274. package/src/conformance/index.ts +20 -0
  275. package/src/conformance/tools.ts +16 -0
  276. package/src/credentials/core/memoize-ttl.ts +142 -0
  277. package/src/credentials/core/types.ts +10 -0
  278. package/src/credentials/node/from-adc.ts +96 -0
  279. package/src/credentials/node/from-service-account.ts +133 -0
  280. package/src/credentials/node/index.ts +6 -0
  281. package/src/discover/concurrency.ts +127 -0
  282. package/src/discover/crawler-adapter.ts +142 -0
  283. package/src/discover/crawler.ts +1127 -0
  284. package/src/discover/credential-free.ts +27 -0
  285. package/src/discover/findCollectionGroup.ts +131 -0
  286. package/src/discover/firestore-source.ts +59 -0
  287. package/src/discover/index.ts +5 -0
  288. package/src/discover/merge.ts +523 -0
  289. package/src/discover/session.ts +402 -0
  290. package/src/discover/tools.ts +201 -0
  291. package/src/discover/types.ts +187 -0
  292. package/src/discover/wire.ts +324 -0
  293. package/src/functions-rtdb/child.ts +382 -0
  294. package/src/functions-rtdb/delivery.ts +7 -0
  295. package/src/functions-rtdb/discovery.ts +136 -0
  296. package/src/functions-rtdb/event.ts +64 -0
  297. package/src/functions-rtdb/execution.ts +111 -0
  298. package/src/functions-rtdb/in-memory-delivery.ts +40 -0
  299. package/src/functions-rtdb/project.ts +74 -0
  300. package/src/functions-rtdb/projection.ts +104 -0
  301. package/src/functions-rtdb/reference-pattern.ts +20 -0
  302. package/src/functions-rtdb/remote-delivery.ts +23 -0
  303. package/src/pkg-version.ts +49 -0
  304. package/src/register/esm-exports.ts +63 -0
  305. package/src/register/hooks.ts +30 -0
  306. package/src/register/index.ts +150 -0
  307. package/src/register/mapping.ts +33 -0
  308. package/src/remote/index.ts +1050 -0
  309. package/src/rtdb/crawl-snapshot.ts +118 -0
  310. package/src/rtdb/inspection.ts +137 -0
  311. package/src/rtdb/load-rules-document.ts +56 -0
  312. package/src/rtdb/rules-generation-tool.ts +38 -0
  313. package/src/rtdb/rules-json.ts +29 -0
  314. package/src/serve/activity-guard.ts +29 -0
  315. package/src/serve/activity-route.ts +164 -0
  316. package/src/serve/activity-warning.ts +26 -0
  317. package/src/serve/bridge-mount.ts +200 -0
  318. package/src/serve/bundler.ts +519 -0
  319. package/src/serve/capture-store.ts +65 -0
  320. package/src/serve/discovery.ts +199 -0
  321. package/src/serve/entries/ai.ts +292 -0
  322. package/src/serve/entries/app-backend.ts +4 -0
  323. package/src/serve/entries/app-client.ts +26 -0
  324. package/src/serve/entries/app-session-store.ts +47 -0
  325. package/src/serve/entries/app.ts +11 -0
  326. package/src/serve/entries/auth-helper-core.ts +181 -0
  327. package/src/serve/entries/auth-helper-dom.ts +134 -0
  328. package/src/serve/entries/auth-helper-runtime.ts +20 -0
  329. package/src/serve/entries/auth.ts +255 -0
  330. package/src/serve/entries/bridge-url.ts +31 -0
  331. package/src/serve/entries/database.ts +129 -0
  332. package/src/serve/entries/firestore.ts +232 -0
  333. package/src/serve/entries/init.ts +49 -0
  334. package/src/serve/entries/keepalive.ts +46 -0
  335. package/src/serve/entries/messaging-sw.ts +75 -0
  336. package/src/serve/entries/messaging.ts +96 -0
  337. package/src/serve/entries/runtime.ts +556 -0
  338. package/src/serve/entries/session-store.ts +97 -0
  339. package/src/serve/entries/storage.ts +92 -0
  340. package/src/serve/entries/tab-sync-wiring.ts +274 -0
  341. package/src/serve/entries/worker-runtime.ts +135 -0
  342. package/src/serve/init-payload.ts +42 -0
  343. package/src/serve/namespace.ts +675 -0
  344. package/src/serve/open-browser.ts +68 -0
  345. package/src/serve/rules.ts +272 -0
  346. package/src/serve/runtime/chip-config.ts +30 -0
  347. package/src/serve/runtime/chip-install.ts +27 -0
  348. package/src/serve/runtime/chip.ts +282 -0
  349. package/src/serve/runtime/manifest.ts +37 -0
  350. package/src/serve/runtime/status.ts +216 -0
  351. package/src/serve/runtime/worker-connection.ts +14 -0
  352. package/src/serve/runtime/worker-generation.ts +75 -0
  353. package/src/serve/runtime/worker-replacement.ts +42 -0
  354. package/src/serve/sandbox-marker.ts +31 -0
  355. package/src/serve/server.ts +428 -0
  356. package/src/serve/standalone-assets.ts +180 -0
  357. package/src/serve/state-store.ts +166 -0
  358. package/src/serve/studio/disk-project-store.ts +185 -0
  359. package/src/serve/studio/disk-workspace.ts +162 -0
  360. package/src/serve/studio/index.ts +20 -0
  361. package/src/serve/studio/routes.ts +237 -0
  362. package/src/serve/studio/store-types.ts +48 -0
  363. package/src/serve/studio/studio-storage.test.ts +289 -0
  364. package/src/serve/vite-ai-config.ts +77 -0
  365. package/src/serve/vite-plugin.ts +1075 -0
  366. package/src/serve/vite-rules-source.ts +28 -0
  367. package/src/serve/vite-worker-runtime.ts +51 -0
  368. package/src/serve/worker/activity-bootstrap.ts +24 -0
  369. package/src/serve/worker/client/admin-firestore.ts +38 -0
  370. package/src/serve/worker/client/ai.ts +124 -0
  371. package/src/serve/worker/client/auth.ts +489 -0
  372. package/src/serve/worker/client/connection.ts +227 -0
  373. package/src/serve/worker/client/core.ts +384 -0
  374. package/src/serve/worker/client/disconnect.ts +45 -0
  375. package/src/serve/worker/client/firestore-reads.ts +181 -0
  376. package/src/serve/worker/client/firestore-refs.ts +226 -0
  377. package/src/serve/worker/client/firestore-writes.ts +232 -0
  378. package/src/serve/worker/client/handles.ts +91 -0
  379. package/src/serve/worker/client/messaging.ts +119 -0
  380. package/src/serve/worker/client/presence.ts +174 -0
  381. package/src/serve/worker/client/rtdb.ts +311 -0
  382. package/src/serve/worker/client/rules.ts +63 -0
  383. package/src/serve/worker/client/runtime-control.ts +22 -0
  384. package/src/serve/worker/client/service-worker-connection.ts +59 -0
  385. package/src/serve/worker/client/snapshots.ts +86 -0
  386. package/src/serve/worker/client/storage.ts +195 -0
  387. package/src/serve/worker/client/studio.ts +88 -0
  388. package/src/serve/worker/client.ts +57 -0
  389. package/src/serve/worker/durable-persistence.ts +137 -0
  390. package/src/serve/worker/entry.ts +223 -0
  391. package/src/serve/worker/host/admin-firestore.ts +88 -0
  392. package/src/serve/worker/host/connection.ts +179 -0
  393. package/src/serve/worker/host/core.ts +387 -0
  394. package/src/serve/worker/host/dispatch.ts +316 -0
  395. package/src/serve/worker/host/firestore-reads.ts +117 -0
  396. package/src/serve/worker/host/firestore-writes.ts +456 -0
  397. package/src/serve/worker/host/presence.ts +312 -0
  398. package/src/serve/worker/host/rtdb.ts +136 -0
  399. package/src/serve/worker/host/rules.ts +128 -0
  400. package/src/serve/worker/host/storage.ts +312 -0
  401. package/src/serve/worker/host/studio.ts +75 -0
  402. package/src/serve/worker/host/subscriptions.ts +238 -0
  403. package/src/serve/worker/host-ai.ts +165 -0
  404. package/src/serve/worker/host-auth.ts +469 -0
  405. package/src/serve/worker/host-context.ts +273 -0
  406. package/src/serve/worker/host-events.ts +94 -0
  407. package/src/serve/worker/host-messaging.ts +240 -0
  408. package/src/serve/worker/host.ts +47 -0
  409. package/src/serve/worker/index.ts +196 -0
  410. package/src/serve/worker/presence-timing.ts +15 -0
  411. package/src/serve/worker/protocol.ts +1124 -0
  412. package/src/serve/worker/retirement.ts +125 -0
  413. package/src/serve/worker/serve-init.ts +605 -0
  414. package/src/serve/worker/service-worker-channel.ts +33 -0
  415. package/src/serve/worker/service-worker-relay.ts +86 -0
  416. package/src/serve/writer-lock.ts +53 -0
  417. package/src/verify/cases.ts +233 -0
  418. package/src/verify/fixture.ts +258 -0
  419. package/src/verify/index.ts +519 -0
  420. package/src/verify/tools.ts +108 -0
  421. package/src/version/compat-target.ts +17 -0
  422. package/src/vite.ts +18 -0
  423. package/dist/serve/docs-ui/docs/pyric-firestore-values-reference-api/index.html +0 -31
  424. package/dist/serve/docs-ui/docs/pyric-firestore-values-reference-api.md +0 -29
@@ -0,0 +1,885 @@
1
+ import { getAuth, sandbox as authSandbox, type SeedUser } from "pyric/auth";
2
+ import {
3
+ endAt as endAtRtdb,
4
+ endBefore as endBeforeRtdb,
5
+ equalTo as equalToRtdb,
6
+ getAdminDatabase,
7
+ getDatabase,
8
+ get as getRtdb,
9
+ limitToFirst as limitToFirstRtdb,
10
+ limitToLast as limitToLastRtdb,
11
+ orderByChild as orderByChildRtdb,
12
+ orderByKey as orderByKeyRtdb,
13
+ orderByValue as orderByValueRtdb,
14
+ query as queryRtdb,
15
+ ref as rtdbRef,
16
+ remove as removeRtdb,
17
+ sandbox as rtdbSandbox,
18
+ set as setRtdb,
19
+ startAfter as startAfterRtdb,
20
+ startAt as startAtRtdb,
21
+ update as updateRtdb,
22
+ type Database,
23
+ type QueryConstraint as RtdbQueryConstraint,
24
+ } from "pyric/database";
25
+ import {
26
+ collection,
27
+ deleteDoc,
28
+ doc,
29
+ getDoc,
30
+ getDocs,
31
+ getFirestore,
32
+ limit,
33
+ orderBy,
34
+ query,
35
+ setDoc,
36
+ updateDoc,
37
+ where,
38
+ type Firestore,
39
+ type Query,
40
+ type QueryConstraint,
41
+ } from "pyric/firestore";
42
+ import { seedDocuments, setRules, snapshotDocuments } from "pyric/sandbox/firestore";
43
+ import {
44
+ deleteObject,
45
+ getBlob,
46
+ getMetadata,
47
+ getStorageSandbox,
48
+ listAll,
49
+ ref as storageRef,
50
+ updateMetadata,
51
+ uploadBytes,
52
+ type FirebaseStorage,
53
+ } from "pyric/storage";
54
+ import { getAdminStorageSandbox } from "pyric/storage/internal";
55
+ import {
56
+ initializeSandbox,
57
+ type AuthState,
58
+ type LocalSandbox,
59
+ type SandboxEvent,
60
+ } from "pyric/sandbox";
61
+ import { qualifyProbe } from "./capabilities.js";
62
+ import {
63
+ assertActor,
64
+ assertInvariant,
65
+ assertProbe,
66
+ assertTarget,
67
+ } from "./validation.js";
68
+ import {
69
+ ASSURANCE_CAMPAIGN_SCHEMA,
70
+ ASSURANCE_REPORT_SCHEMA,
71
+ AssuranceInputError,
72
+ type ActorEvidence,
73
+ type AssuranceActor,
74
+ type AssuranceDecision,
75
+ type AssuranceEventEvidence,
76
+ type AssuranceProbe,
77
+ type AssuranceProbeResult,
78
+ type AuthorizationCampaignReport,
79
+ type AuthorizationCampaignSpec,
80
+ type FirebaseOperation,
81
+ type LocalFirebaseTarget,
82
+ type OperationEvidence,
83
+ type ProbeClassification,
84
+ type SecurityInvariant,
85
+ type StateDiff,
86
+ type StorageOperation,
87
+ } from "./types.js";
88
+
89
+ interface ActorRuntime {
90
+ evidence: ActorEvidence;
91
+ auth: AuthState | null;
92
+ }
93
+
94
+ interface ServiceRuntime {
95
+ sandbox: LocalSandbox;
96
+ actor: ActorRuntime;
97
+ firestore?: Firestore;
98
+ rtdb?: Database;
99
+ rtdbAdmin?: Database;
100
+ storage?: FirebaseStorage;
101
+ storageAdmin?: FirebaseStorage;
102
+ }
103
+
104
+ interface ExecutedOperation {
105
+ actorEvidence: ActorEvidence;
106
+ evidence: OperationEvidence;
107
+ stateDiff: StateDiff;
108
+ }
109
+
110
+ let storageRunSequence = 0;
111
+
112
+ function assertCampaign(spec: AuthorizationCampaignSpec): void {
113
+ if (spec.schema !== ASSURANCE_CAMPAIGN_SCHEMA) {
114
+ throw new AssuranceInputError(
115
+ `campaign schema must be '${ASSURANCE_CAMPAIGN_SCHEMA}'.`,
116
+ );
117
+ }
118
+ if (!spec.id.trim())
119
+ throw new AssuranceInputError("campaign id is required.");
120
+ assertTarget(spec.target);
121
+ if (!Array.isArray(spec.actors)) {
122
+ throw new AssuranceInputError("campaign actors must be an array.");
123
+ }
124
+ if (!Array.isArray(spec.invariants)) {
125
+ throw new AssuranceInputError("campaign invariants must be an array.");
126
+ }
127
+ if (!Array.isArray(spec.probes)) {
128
+ throw new AssuranceInputError("campaign probes must be an array.");
129
+ }
130
+ for (const actor of spec.actors) assertActor(actor);
131
+ for (const invariant of spec.invariants) assertInvariant(invariant);
132
+ for (const probe of spec.probes) assertProbe(probe);
133
+ uniqueIds(spec.actors, "actor");
134
+ uniqueIds(spec.invariants, "invariant");
135
+ uniqueIds(spec.probes, "probe");
136
+
137
+ for (const probe of spec.probes) {
138
+ const actor = spec.actors.find((item) => item.id === probe.actorId);
139
+ const invariant = spec.invariants.find(
140
+ (item) => item.id === probe.invariantId,
141
+ );
142
+ if (!actor)
143
+ throw new AssuranceInputError(
144
+ `probe '${probe.id}' references unknown actor '${probe.actorId}'.`,
145
+ );
146
+ if (!invariant) {
147
+ throw new AssuranceInputError(
148
+ `probe '${probe.id}' references unknown invariant '${probe.invariantId}'.`,
149
+ );
150
+ }
151
+ if (
152
+ invariant.service !== "cross-service" &&
153
+ invariant.service !== probe.control.service
154
+ ) {
155
+ throw new AssuranceInputError(
156
+ `probe '${probe.id}' service does not match invariant '${invariant.id}'.`,
157
+ );
158
+ }
159
+ }
160
+ }
161
+
162
+ function uniqueIds(items: ReadonlyArray<{ id: string }>, label: string): void {
163
+ const seen = new Set<string>();
164
+ for (const item of items) {
165
+ if (!item.id.trim())
166
+ throw new AssuranceInputError(`${label} id is required.`);
167
+ if (seen.has(item.id))
168
+ throw new AssuranceInputError(`duplicate ${label} id '${item.id}'.`);
169
+ seen.add(item.id);
170
+ }
171
+ }
172
+
173
+ function stableStringify(value: unknown): string {
174
+ if (value === null || typeof value !== "object") return JSON.stringify(value);
175
+ if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
176
+ const record = value as Record<string, unknown>;
177
+ return `{${Object.keys(record)
178
+ .sort()
179
+ .map((key) => `${JSON.stringify(key)}:${stableStringify(record[key])}`)
180
+ .join(",")}}`;
181
+ }
182
+
183
+ function targetHash(target: LocalFirebaseTarget): string {
184
+ const input = stableStringify(target);
185
+ let hash = 0x811c9dc5;
186
+ for (let index = 0; index < input.length; index++) {
187
+ hash ^= input.charCodeAt(index);
188
+ hash = Math.imul(hash, 0x01000193) >>> 0;
189
+ }
190
+ return `fnv1a32-${hash.toString(16).padStart(8, "0")}`;
191
+ }
192
+
193
+ function authUsers(target: LocalFirebaseTarget): SeedUser[] {
194
+ return (target.state.auth?.users ?? []).map((user) => ({
195
+ uid: user.uid,
196
+ email: user.email ?? `${user.uid}@fixture.pyric.invalid`,
197
+ password: user.password ?? "__pyric_fixture_password__",
198
+ ...(user.customClaims ? { customClaims: user.customClaims } : {}),
199
+ }));
200
+ }
201
+
202
+ function seedAuth(
203
+ sandbox: LocalSandbox,
204
+ target: LocalFirebaseTarget,
205
+ ): ReturnType<typeof getAuth> {
206
+ const auth = getAuth(sandbox);
207
+ authSandbox.seedUsers(auth, authUsers(target));
208
+ for (const user of target.state.auth?.users ?? []) {
209
+ if (user.disabled !== undefined || user.emailVerified !== undefined) {
210
+ authSandbox.updateUser(auth, user.uid, {
211
+ ...(user.disabled !== undefined ? { disabled: user.disabled } : {}),
212
+ ...(user.emailVerified !== undefined
213
+ ? { emailVerified: user.emailVerified }
214
+ : {}),
215
+ });
216
+ }
217
+ }
218
+ return auth;
219
+ }
220
+
221
+ function acquireActor(
222
+ sandbox: LocalSandbox,
223
+ target: LocalFirebaseTarget,
224
+ actor: AssuranceActor,
225
+ ): ActorRuntime {
226
+ const auth = seedAuth(sandbox, target);
227
+ try {
228
+ switch (actor.acquisition.kind) {
229
+ case "anonymous-request":
230
+ return {
231
+ auth: null,
232
+ evidence: {
233
+ actorId: actor.id,
234
+ acquisition: actor.acquisition.kind,
235
+ reachability: "reachable",
236
+ },
237
+ };
238
+ case "anonymous-account": {
239
+ const session = authSandbox.mintSession(auth, { kind: "anonymous" });
240
+ return {
241
+ auth: session.state,
242
+ evidence: {
243
+ actorId: actor.id,
244
+ acquisition: actor.acquisition.kind,
245
+ reachability: "reachable",
246
+ uid: session.user.uid,
247
+ },
248
+ };
249
+ }
250
+ case "password": {
251
+ const session = authSandbox.mintSession(auth, {
252
+ kind: "password",
253
+ email: actor.acquisition.email,
254
+ password: actor.acquisition.password,
255
+ });
256
+ return {
257
+ auth: session.state,
258
+ evidence: {
259
+ actorId: actor.id,
260
+ acquisition: actor.acquisition.kind,
261
+ reachability: "reachable",
262
+ uid: session.user.uid,
263
+ },
264
+ };
265
+ }
266
+ case "fixture-user": {
267
+ const session = authSandbox.mintSession(auth, {
268
+ kind: "uid",
269
+ uid: actor.acquisition.uid,
270
+ });
271
+ return {
272
+ auth: session.state,
273
+ evidence: {
274
+ actorId: actor.id,
275
+ acquisition: actor.acquisition.kind,
276
+ reachability: "reachable",
277
+ uid: session.user.uid,
278
+ },
279
+ };
280
+ }
281
+ case "synthetic":
282
+ return {
283
+ auth: {
284
+ uid: actor.acquisition.uid,
285
+ token: actor.acquisition.token ?? {},
286
+ },
287
+ evidence: {
288
+ actorId: actor.id,
289
+ acquisition: actor.acquisition.kind,
290
+ reachability: "synthetic",
291
+ uid: actor.acquisition.uid,
292
+ },
293
+ };
294
+ }
295
+ } catch (error) {
296
+ return {
297
+ auth: null,
298
+ evidence: {
299
+ actorId: actor.id,
300
+ acquisition: actor.acquisition.kind,
301
+ reachability: "unreachable",
302
+ error: error instanceof Error ? error.message : String(error),
303
+ },
304
+ };
305
+ }
306
+ }
307
+
308
+ async function createRuntime(
309
+ campaignId: string,
310
+ probeId: string,
311
+ phase: "control" | "mutation",
312
+ target: LocalFirebaseTarget,
313
+ actorRuntime: ActorRuntime,
314
+ service: FirebaseOperation["service"],
315
+ ): Promise<ServiceRuntime> {
316
+ const sandbox = initializeSandbox();
317
+ seedAuth(sandbox, target);
318
+ const context = sandbox.withAuth(actorRuntime.auth);
319
+ const runtime: ServiceRuntime = { sandbox, actor: actorRuntime };
320
+
321
+ if (service === "firestore") {
322
+ const firestore = getFirestore(context);
323
+ setRules(sandbox, target.rules.firestore!);
324
+ seedDocuments(sandbox, target.state.firestore ?? {});
325
+ runtime.firestore = firestore;
326
+ } else if (service === "rtdb") {
327
+ const rtdb = getDatabase(context);
328
+ const admin = getAdminDatabase(sandbox);
329
+ rtdbSandbox.setRules(rtdb, target.rules.rtdb!);
330
+ rtdbSandbox.setData(admin, { "/": target.state.rtdb ?? {} });
331
+ runtime.rtdb = rtdb;
332
+ runtime.rtdbAdmin = admin;
333
+ } else {
334
+ const dbName = [
335
+ "pyric-assurance",
336
+ campaignId,
337
+ probeId,
338
+ phase,
339
+ Date.now(),
340
+ storageRunSequence++,
341
+ ]
342
+ .join("-")
343
+ .replace(/[^a-zA-Z0-9_-]/g, "_");
344
+ const admin = getAdminStorageSandbox(sandbox, {
345
+ dbName,
346
+ rules: target.rules.storage!,
347
+ });
348
+ for (const object of target.state.storage ?? []) {
349
+ await uploadBytes(
350
+ storageRef(admin, object.path),
351
+ decodeBase64(object.dataBase64),
352
+ {
353
+ ...(object.contentType ? { contentType: object.contentType } : {}),
354
+ ...(object.customMetadata
355
+ ? { customMetadata: object.customMetadata }
356
+ : {}),
357
+ },
358
+ );
359
+ }
360
+ runtime.storageAdmin = admin;
361
+ runtime.storage = getStorageSandbox(context, { dbName });
362
+ }
363
+ return runtime;
364
+ }
365
+
366
+ function decodeBase64(value: string): Uint8Array {
367
+ const binary = atob(value);
368
+ return Uint8Array.from(binary, (char) => char.charCodeAt(0));
369
+ }
370
+
371
+ function eventEvidence(events: SandboxEvent[]): AssuranceEventEvidence[] {
372
+ return events.map((event) => {
373
+ const raw = event as unknown as Record<string, unknown>;
374
+ return {
375
+ ...(typeof raw.id === "string" ? { id: raw.id } : {}),
376
+ ...(typeof raw.at === "number" ? { at: raw.at } : {}),
377
+ ...(typeof raw.kind === "string" ? { kind: raw.kind } : {}),
378
+ ...(typeof raw.service === "string" ? { service: raw.service } : {}),
379
+ ...(typeof raw.method === "string" ? { method: raw.method } : {}),
380
+ ...(typeof raw.op === "string" ? { op: raw.op } : {}),
381
+ ...(typeof raw.path === "string" ? { path: raw.path } : {}),
382
+ ...(typeof raw.result === "string" ? { result: raw.result } : {}),
383
+ ...(raw.auth !== undefined ? { auth: raw.auth } : {}),
384
+ ...(raw.actor !== undefined ? { actor: raw.actor } : {}),
385
+ ...(raw.authLens !== undefined ? { authLens: raw.authLens } : {}),
386
+ ...(Array.isArray(raw.reasons) &&
387
+ raw.reasons.every((item) => typeof item === "string")
388
+ ? { reasons: raw.reasons as string[] }
389
+ : {}),
390
+ ...(raw.origin !== undefined ? { origin: raw.origin } : {}),
391
+ ...(raw.request !== undefined ? { request: raw.request } : {}),
392
+ ...(raw.resourceBefore !== undefined
393
+ ? { resourceBefore: raw.resourceBefore }
394
+ : {}),
395
+ ...(raw.matchedRule !== undefined
396
+ ? { matchedRule: raw.matchedRule }
397
+ : {}),
398
+ ...(raw.evaluatedRule !== undefined
399
+ ? { evaluatedRule: raw.evaluatedRule }
400
+ : {}),
401
+ ...(raw.rules !== undefined ? { rules: raw.rules } : {}),
402
+ };
403
+ });
404
+ }
405
+
406
+ function errorDecision(error: unknown): {
407
+ decision: AssuranceDecision;
408
+ error: { code?: string; message: string };
409
+ } {
410
+ const code = (error as { code?: unknown } | null)?.code;
411
+ const message = error instanceof Error ? error.message : String(error);
412
+ const codeString = typeof code === "string" ? code : undefined;
413
+ const denied =
414
+ /permission[-_ ]?denied|unauthorized/i.test(message) ||
415
+ (codeString
416
+ ? /permission[-_ ]?denied|unauthorized/i.test(codeString)
417
+ : false);
418
+ return {
419
+ decision: denied ? "DENY" : "ERROR",
420
+ error: { ...(codeString ? { code: codeString } : {}), message },
421
+ };
422
+ }
423
+
424
+ async function executeFirestore(
425
+ operation: FirebaseOperation,
426
+ db: Firestore,
427
+ ): Promise<unknown> {
428
+ if (operation.service !== "firestore")
429
+ throw new Error("Expected a Firestore operation.");
430
+ switch (operation.method) {
431
+ case "get": {
432
+ const snapshot = await getDoc(doc(db, operation.path));
433
+ const exists =
434
+ typeof snapshot.exists === "function"
435
+ ? snapshot.exists()
436
+ : snapshot.exists;
437
+ return exists ? snapshot.data() : null;
438
+ }
439
+ case "list": {
440
+ const constraints: QueryConstraint[] = [];
441
+ for (const item of operation.query?.where ?? []) {
442
+ constraints.push(where(item.field, item.op, item.value));
443
+ }
444
+ for (const item of operation.query?.orderBy ?? []) {
445
+ constraints.push(orderBy(item.field, item.direction));
446
+ }
447
+ if (operation.query?.limit !== undefined)
448
+ constraints.push(limit(operation.query.limit));
449
+ const source = constraints.length
450
+ ? query(collection(db, operation.path), ...constraints)
451
+ : collection(db, operation.path);
452
+ const snapshot = await getDocs(source as Query);
453
+ return snapshot.docs.map((item) => ({
454
+ path: `${operation.path.replace(/\/$/, "")}/${item.id}`,
455
+ data: item.data(),
456
+ }));
457
+ }
458
+ case "create":
459
+ case "set":
460
+ await setDoc(doc(db, operation.path), operation.data ?? {});
461
+ return null;
462
+ case "merge":
463
+ await setDoc(doc(db, operation.path), operation.data ?? {}, {
464
+ merge: true,
465
+ });
466
+ return null;
467
+ case "update":
468
+ await updateDoc(doc(db, operation.path), operation.data ?? {});
469
+ return null;
470
+ case "delete":
471
+ await deleteDoc(doc(db, operation.path));
472
+ return null;
473
+ }
474
+ }
475
+
476
+ async function executeRtdb(
477
+ operation: FirebaseOperation,
478
+ db: Database,
479
+ ): Promise<unknown> {
480
+ if (operation.service !== "rtdb")
481
+ throw new Error("Expected an RTDB operation.");
482
+ const target = rtdbRef(db, operation.path);
483
+ switch (operation.method) {
484
+ case "get": {
485
+ const constraints: RtdbQueryConstraint[] = [];
486
+ const querySpec = operation.query;
487
+ if (querySpec?.orderBy?.kind === "child") {
488
+ constraints.push(orderByChildRtdb(querySpec.orderBy.path));
489
+ } else if (querySpec?.orderBy?.kind === "key") {
490
+ constraints.push(orderByKeyRtdb());
491
+ } else if (querySpec?.orderBy?.kind === "value") {
492
+ constraints.push(orderByValueRtdb());
493
+ }
494
+ if (querySpec?.startAt) {
495
+ constraints.push(
496
+ startAtRtdb(querySpec.startAt.value as never, querySpec.startAt.key),
497
+ );
498
+ }
499
+ if (querySpec?.startAfter) {
500
+ constraints.push(
501
+ startAfterRtdb(
502
+ querySpec.startAfter.value as never,
503
+ querySpec.startAfter.key,
504
+ ),
505
+ );
506
+ }
507
+ if (querySpec?.endAt) {
508
+ constraints.push(
509
+ endAtRtdb(querySpec.endAt.value as never, querySpec.endAt.key),
510
+ );
511
+ }
512
+ if (querySpec?.endBefore) {
513
+ constraints.push(
514
+ endBeforeRtdb(
515
+ querySpec.endBefore.value as never,
516
+ querySpec.endBefore.key,
517
+ ),
518
+ );
519
+ }
520
+ if (querySpec?.equalTo) {
521
+ constraints.push(
522
+ equalToRtdb(querySpec.equalTo.value as never, querySpec.equalTo.key),
523
+ );
524
+ }
525
+ if (querySpec?.limitToFirst !== undefined) {
526
+ constraints.push(limitToFirstRtdb(querySpec.limitToFirst));
527
+ }
528
+ if (querySpec?.limitToLast !== undefined) {
529
+ constraints.push(limitToLastRtdb(querySpec.limitToLast));
530
+ }
531
+ const source =
532
+ constraints.length > 0 ? queryRtdb(target, ...constraints) : target;
533
+ return (await getRtdb(source)).val();
534
+ }
535
+ case "set":
536
+ await setRtdb(target, operation.data);
537
+ return null;
538
+ case "update":
539
+ if (
540
+ !operation.data ||
541
+ typeof operation.data !== "object" ||
542
+ Array.isArray(operation.data)
543
+ ) {
544
+ throw new Error("RTDB update data must be an object.");
545
+ }
546
+ await updateRtdb(target, operation.data as Record<string, unknown>);
547
+ return null;
548
+ case "remove":
549
+ await removeRtdb(target);
550
+ return null;
551
+ }
552
+ }
553
+
554
+ async function executeStorage(
555
+ operation: StorageOperation,
556
+ storage: FirebaseStorage,
557
+ ): Promise<unknown> {
558
+ const target = storageRef(storage, operation.path);
559
+ switch (operation.method) {
560
+ case "get": {
561
+ const [blob, metadata] = await Promise.all([
562
+ getBlob(target),
563
+ getMetadata(target),
564
+ ]);
565
+ return {
566
+ text: await blob.text(),
567
+ contentType: metadata.contentType,
568
+ customMetadata: metadata.customMetadata ?? {},
569
+ };
570
+ }
571
+ case "list": {
572
+ const result = await listAll(target);
573
+ return {
574
+ items: result.items.map((item) => item.fullPath),
575
+ prefixes: result.prefixes.map((item) => item.fullPath),
576
+ };
577
+ }
578
+ case "upload": {
579
+ if (!operation.dataBase64)
580
+ throw new Error("Storage upload requires dataBase64.");
581
+ const result = await uploadBytes(
582
+ target,
583
+ decodeBase64(operation.dataBase64),
584
+ {
585
+ ...(operation.contentType
586
+ ? { contentType: operation.contentType }
587
+ : {}),
588
+ ...(operation.customMetadata
589
+ ? { customMetadata: operation.customMetadata }
590
+ : {}),
591
+ },
592
+ );
593
+ return result.metadata;
594
+ }
595
+ case "updateMetadata":
596
+ return updateMetadata(target, {
597
+ ...(operation.contentType
598
+ ? { contentType: operation.contentType }
599
+ : {}),
600
+ ...(operation.customMetadata
601
+ ? { customMetadata: operation.customMetadata }
602
+ : {}),
603
+ });
604
+ case "delete":
605
+ await deleteObject(target);
606
+ return null;
607
+ }
608
+ }
609
+
610
+ async function readState(
611
+ operation: FirebaseOperation,
612
+ runtime: ServiceRuntime,
613
+ ): Promise<unknown> {
614
+ if (operation.service === "firestore") {
615
+ const state = snapshotDocuments(runtime.sandbox);
616
+ if (operation.method === "list") {
617
+ const prefix = `${operation.path.replace(/\/$/, "")}/`;
618
+ return Object.fromEntries(
619
+ Object.entries(state).filter(([path]) => path.startsWith(prefix)),
620
+ );
621
+ }
622
+ return state[operation.path] ?? null;
623
+ }
624
+ if (operation.service === "rtdb") {
625
+ return (await getRtdb(rtdbRef(runtime.rtdbAdmin!, operation.path))).val();
626
+ }
627
+ return readStorageState(operation, runtime.storageAdmin!);
628
+ }
629
+
630
+ async function readStorageState(
631
+ operation: StorageOperation,
632
+ storage: FirebaseStorage,
633
+ ): Promise<unknown> {
634
+ if (operation.method === "list") {
635
+ const result = await listAll(storageRef(storage, operation.path));
636
+ return {
637
+ items: result.items.map((item) => item.fullPath),
638
+ prefixes: result.prefixes.map((item) => item.fullPath),
639
+ };
640
+ }
641
+ const target = storageRef(storage, operation.path);
642
+ try {
643
+ const [blob, metadata] = await Promise.all([
644
+ getBlob(target),
645
+ getMetadata(target),
646
+ ]);
647
+ return {
648
+ text: await blob.text(),
649
+ contentType: metadata.contentType,
650
+ customMetadata: metadata.customMetadata ?? {},
651
+ };
652
+ } catch (error) {
653
+ const code = (error as { code?: string } | null)?.code;
654
+ if (code === "storage/object-not-found") return null;
655
+ throw error;
656
+ }
657
+ }
658
+
659
+ async function executeOperation(
660
+ campaignId: string,
661
+ probeId: string,
662
+ phase: "control" | "mutation",
663
+ target: LocalFirebaseTarget,
664
+ actorRuntime: ActorRuntime,
665
+ operation: FirebaseOperation,
666
+ ): Promise<ExecutedOperation> {
667
+ const runtime = await createRuntime(
668
+ campaignId,
669
+ probeId,
670
+ phase,
671
+ target,
672
+ actorRuntime,
673
+ operation.service,
674
+ );
675
+ const before = await readState(operation, runtime);
676
+ const eventOffset = runtime.sandbox.history().length;
677
+ let decision: AssuranceDecision = "ALLOW";
678
+ let output: unknown;
679
+ let failure: { code?: string; message: string } | undefined;
680
+ try {
681
+ output =
682
+ operation.service === "firestore"
683
+ ? await executeFirestore(operation, runtime.firestore!)
684
+ : operation.service === "rtdb"
685
+ ? await executeRtdb(operation, runtime.rtdb!)
686
+ : await executeStorage(operation, runtime.storage!);
687
+ } catch (error) {
688
+ const result = errorDecision(error);
689
+ decision = result.decision;
690
+ failure = result.error;
691
+ }
692
+ const events = eventEvidence(runtime.sandbox.history().slice(eventOffset));
693
+ const after = await readState(operation, runtime);
694
+ return {
695
+ actorEvidence: runtime.actor.evidence,
696
+ evidence: {
697
+ operation,
698
+ decision,
699
+ ...(output !== undefined ? { output } : {}),
700
+ ...(failure ? { error: failure } : {}),
701
+ events,
702
+ },
703
+ stateDiff: {
704
+ changed: stableStringify(before) !== stableStringify(after),
705
+ before,
706
+ after,
707
+ },
708
+ };
709
+ }
710
+
711
+ function skippedEvidence(
712
+ operation: FirebaseOperation,
713
+ decision: AssuranceDecision,
714
+ ): OperationEvidence {
715
+ return { operation, decision, events: [] };
716
+ }
717
+
718
+ function classify(
719
+ actor: ActorEvidence,
720
+ invariant: SecurityInvariant,
721
+ control: OperationEvidence,
722
+ mutation: OperationEvidence,
723
+ stateDiff: StateDiff,
724
+ ): ProbeClassification {
725
+ if (actor.reachability === "unreachable") return "invalid-probe";
726
+ if (control.decision !== "ALLOW") return "invalid-probe";
727
+ if (mutation.decision === "ERROR" || mutation.decision === "UNSUPPORTED")
728
+ return "invalid-probe";
729
+ if (mutation.decision === invariant.expected) return "no-counterexample";
730
+ if (invariant.expected === "DENY" && mutation.decision === "ALLOW") {
731
+ const hasImpact =
732
+ stateDiff.changed ||
733
+ (mutation.output !== undefined && mutation.output !== null);
734
+ return actor.reachability === "reachable" &&
735
+ invariant.confidence !== "tentative" &&
736
+ hasImpact
737
+ ? "local-counterexample"
738
+ : "candidate-signal";
739
+ }
740
+ return "candidate-signal";
741
+ }
742
+
743
+ async function runProbe(
744
+ spec: AuthorizationCampaignSpec,
745
+ probe: AssuranceProbe,
746
+ hash: string,
747
+ ): Promise<AssuranceProbeResult> {
748
+ const actor = spec.actors.find((item) => item.id === probe.actorId)!;
749
+ const invariant = spec.invariants.find(
750
+ (item) => item.id === probe.invariantId,
751
+ )!;
752
+ const qualification = qualifyProbe(spec.target, probe);
753
+ if (!qualification.supported) {
754
+ return {
755
+ campaignId: spec.id,
756
+ probeId: probe.id,
757
+ targetHash: hash,
758
+ actorEvidence: {
759
+ actorId: actor.id,
760
+ acquisition: actor.acquisition.kind,
761
+ reachability:
762
+ actor.acquisition.kind === "synthetic" ? "synthetic" : "unreachable",
763
+ },
764
+ invariant,
765
+ mutationSpec: probe.mutation,
766
+ control: skippedEvidence(probe.control, "UNSUPPORTED"),
767
+ mutation: skippedEvidence(probe.mutation.operation, "UNSUPPORTED"),
768
+ qualification,
769
+ classification: qualification.classification ?? "engine-gap",
770
+ };
771
+ }
772
+
773
+ const actorRuntime = acquireActor(initializeSandbox(), spec.target, actor);
774
+ if (actorRuntime.evidence.reachability === "unreachable") {
775
+ const error = {
776
+ message: actorRuntime.evidence.error ?? "Actor acquisition failed.",
777
+ };
778
+ return {
779
+ campaignId: spec.id,
780
+ probeId: probe.id,
781
+ targetHash: hash,
782
+ actorEvidence: actorRuntime.evidence,
783
+ invariant,
784
+ mutationSpec: probe.mutation,
785
+ control: { ...skippedEvidence(probe.control, "ERROR"), error },
786
+ mutation: {
787
+ ...skippedEvidence(probe.mutation.operation, "ERROR"),
788
+ error,
789
+ },
790
+ qualification,
791
+ classification: "invalid-probe",
792
+ };
793
+ }
794
+
795
+ const control = await executeOperation(
796
+ spec.id,
797
+ probe.id,
798
+ "control",
799
+ spec.target,
800
+ actorRuntime,
801
+ probe.control,
802
+ );
803
+ const mutation = await executeOperation(
804
+ spec.id,
805
+ probe.id,
806
+ "mutation",
807
+ spec.target,
808
+ actorRuntime,
809
+ probe.mutation.operation,
810
+ );
811
+ return {
812
+ campaignId: spec.id,
813
+ probeId: probe.id,
814
+ targetHash: hash,
815
+ actorEvidence: control.actorEvidence,
816
+ invariant,
817
+ mutationSpec: probe.mutation,
818
+ control: control.evidence,
819
+ mutation: mutation.evidence,
820
+ stateDiff: mutation.stateDiff,
821
+ qualification,
822
+ classification: classify(
823
+ control.actorEvidence,
824
+ invariant,
825
+ control.evidence,
826
+ mutation.evidence,
827
+ mutation.stateDiff,
828
+ ),
829
+ };
830
+ }
831
+
832
+ function summarize(
833
+ results: AssuranceProbeResult[],
834
+ ): AuthorizationCampaignReport["summary"] {
835
+ return {
836
+ probes: results.length,
837
+ controlsPassed: results.filter(
838
+ (result) => result.control.decision === "ALLOW",
839
+ ).length,
840
+ localCounterexamples: results.filter(
841
+ (result) => result.classification === "local-counterexample",
842
+ ).length,
843
+ candidateSignals: results.filter(
844
+ (result) => result.classification === "candidate-signal",
845
+ ).length,
846
+ noCounterexamples: results.filter(
847
+ (result) => result.classification === "no-counterexample",
848
+ ).length,
849
+ engineGaps: results.filter(
850
+ (result) => result.classification === "engine-gap",
851
+ ).length,
852
+ invalidProbes: results.filter(
853
+ (result) => result.classification === "invalid-probe",
854
+ ).length,
855
+ };
856
+ }
857
+
858
+ export async function runAuthorizationCampaign(
859
+ spec: AuthorizationCampaignSpec,
860
+ ): Promise<AuthorizationCampaignReport> {
861
+ assertCampaign(spec);
862
+ if (
863
+ typeof globalThis.indexedDB === "undefined" &&
864
+ spec.probes.some((probe) => probe.control.service === "storage")
865
+ ) {
866
+ const fake = await import("fake-indexeddb");
867
+ Object.assign(globalThis, {
868
+ indexedDB: fake.indexedDB,
869
+ IDBKeyRange: fake.IDBKeyRange,
870
+ });
871
+ }
872
+ const hash = targetHash(spec.target);
873
+ const results: AssuranceProbeResult[] = [];
874
+ for (const probe of spec.probes) {
875
+ results.push(await runProbe(spec, probe, hash));
876
+ }
877
+ return {
878
+ schema: ASSURANCE_REPORT_SCHEMA,
879
+ campaignId: spec.id,
880
+ targetHash: hash,
881
+ localOnly: { network: "forbid", engine: "pyric-local-sandboxes" },
882
+ results,
883
+ summary: summarize(results),
884
+ };
885
+ }