pyric-tools 0.1.0-alpha.7

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 (1052) hide show
  1. package/README.md +125 -0
  2. package/dist/auth/domains/handler.d.ts +6 -0
  3. package/dist/auth/domains/handler.d.ts.map +1 -0
  4. package/dist/auth/domains/handler.js +75 -0
  5. package/dist/auth/domains/handler.js.map +1 -0
  6. package/dist/auth/domains/spec.d.ts +25 -0
  7. package/dist/auth/domains/spec.d.ts.map +1 -0
  8. package/dist/auth/domains/spec.js +6 -0
  9. package/dist/auth/domains/spec.js.map +1 -0
  10. package/dist/auth/index.d.ts +34 -0
  11. package/dist/auth/index.d.ts.map +1 -0
  12. package/dist/auth/index.js +30 -0
  13. package/dist/auth/index.js.map +1 -0
  14. package/dist/auth/mapper.d.ts +5 -0
  15. package/dist/auth/mapper.d.ts.map +1 -0
  16. package/dist/auth/mapper.js +49 -0
  17. package/dist/auth/mapper.js.map +1 -0
  18. package/dist/auth/provider/handler.d.ts +9 -0
  19. package/dist/auth/provider/handler.d.ts.map +1 -0
  20. package/dist/auth/provider/handler.js +128 -0
  21. package/dist/auth/provider/handler.js.map +1 -0
  22. package/dist/auth/provider/spec.d.ts +29 -0
  23. package/dist/auth/provider/spec.d.ts.map +1 -0
  24. package/dist/auth/provider/spec.js +12 -0
  25. package/dist/auth/provider/spec.js.map +1 -0
  26. package/dist/auth/resolver.d.ts +16 -0
  27. package/dist/auth/resolver.d.ts.map +1 -0
  28. package/dist/auth/resolver.js +32 -0
  29. package/dist/auth/resolver.js.map +1 -0
  30. package/dist/auth/tools.d.ts +23 -0
  31. package/dist/auth/tools.d.ts.map +1 -0
  32. package/dist/auth/tools.js +71 -0
  33. package/dist/auth/tools.js.map +1 -0
  34. package/dist/auth/types.d.ts +37 -0
  35. package/dist/auth/types.d.ts.map +1 -0
  36. package/dist/auth/types.js +17 -0
  37. package/dist/auth/types.js.map +1 -0
  38. package/dist/bridge/client/bridge.d.ts +133 -0
  39. package/dist/bridge/client/bridge.d.ts.map +1 -0
  40. package/dist/bridge/client/bridge.js +430 -0
  41. package/dist/bridge/client/bridge.js.map +1 -0
  42. package/dist/bridge/client/dispatch.d.ts +49 -0
  43. package/dist/bridge/client/dispatch.d.ts.map +1 -0
  44. package/dist/bridge/client/dispatch.js +103 -0
  45. package/dist/bridge/client/dispatch.js.map +1 -0
  46. package/dist/bridge/client.d.ts +24 -0
  47. package/dist/bridge/client.d.ts.map +1 -0
  48. package/dist/bridge/client.js +26 -0
  49. package/dist/bridge/client.js.map +1 -0
  50. package/dist/bridge/protocol.d.ts +228 -0
  51. package/dist/bridge/protocol.d.ts.map +1 -0
  52. package/dist/bridge/protocol.js +131 -0
  53. package/dist/bridge/protocol.js.map +1 -0
  54. package/dist/bridge/server/audit.d.ts +19 -0
  55. package/dist/bridge/server/audit.d.ts.map +1 -0
  56. package/dist/bridge/server/audit.js +46 -0
  57. package/dist/bridge/server/audit.js.map +1 -0
  58. package/dist/bridge/server/bridge.d.ts +165 -0
  59. package/dist/bridge/server/bridge.d.ts.map +1 -0
  60. package/dist/bridge/server/bridge.js +410 -0
  61. package/dist/bridge/server/bridge.js.map +1 -0
  62. package/dist/bridge/server/confirm-policy.d.ts +56 -0
  63. package/dist/bridge/server/confirm-policy.d.ts.map +1 -0
  64. package/dist/bridge/server/confirm-policy.js +107 -0
  65. package/dist/bridge/server/confirm-policy.js.map +1 -0
  66. package/dist/bridge/server/confirm-prompt.d.ts +73 -0
  67. package/dist/bridge/server/confirm-prompt.d.ts.map +1 -0
  68. package/dist/bridge/server/confirm-prompt.js +199 -0
  69. package/dist/bridge/server/confirm-prompt.js.map +1 -0
  70. package/dist/bridge/server/confirm.d.ts +92 -0
  71. package/dist/bridge/server/confirm.d.ts.map +1 -0
  72. package/dist/bridge/server/confirm.js +210 -0
  73. package/dist/bridge/server/confirm.js.map +1 -0
  74. package/dist/bridge/server/headless.d.ts +36 -0
  75. package/dist/bridge/server/headless.d.ts.map +1 -0
  76. package/dist/bridge/server/headless.js +132 -0
  77. package/dist/bridge/server/headless.js.map +1 -0
  78. package/dist/bridge/server/json-schema-to-zod.d.ts +37 -0
  79. package/dist/bridge/server/json-schema-to-zod.d.ts.map +1 -0
  80. package/dist/bridge/server/json-schema-to-zod.js +93 -0
  81. package/dist/bridge/server/json-schema-to-zod.js.map +1 -0
  82. package/dist/bridge/server/local-bridge.d.ts +33 -0
  83. package/dist/bridge/server/local-bridge.d.ts.map +1 -0
  84. package/dist/bridge/server/local-bridge.js +42 -0
  85. package/dist/bridge/server/local-bridge.js.map +1 -0
  86. package/dist/bridge/server/logger.d.ts +28 -0
  87. package/dist/bridge/server/logger.d.ts.map +1 -0
  88. package/dist/bridge/server/logger.js +42 -0
  89. package/dist/bridge/server/logger.js.map +1 -0
  90. package/dist/bridge/server/mcp.d.ts +33 -0
  91. package/dist/bridge/server/mcp.d.ts.map +1 -0
  92. package/dist/bridge/server/mcp.js +159 -0
  93. package/dist/bridge/server/mcp.js.map +1 -0
  94. package/dist/bridge/server/peer.d.ts +39 -0
  95. package/dist/bridge/server/peer.d.ts.map +1 -0
  96. package/dist/bridge/server/peer.js +189 -0
  97. package/dist/bridge/server/peer.js.map +1 -0
  98. package/dist/bridge/server/standalone.d.ts +102 -0
  99. package/dist/bridge/server/standalone.d.ts.map +1 -0
  100. package/dist/bridge/server/standalone.js +352 -0
  101. package/dist/bridge/server/standalone.js.map +1 -0
  102. package/dist/bridge/server/tool-metadata.d.ts +38 -0
  103. package/dist/bridge/server/tool-metadata.d.ts.map +1 -0
  104. package/dist/bridge/server/tool-metadata.js +79 -0
  105. package/dist/bridge/server/tool-metadata.js.map +1 -0
  106. package/dist/bridge/server.d.ts +25 -0
  107. package/dist/bridge/server.d.ts.map +1 -0
  108. package/dist/bridge/server.js +23 -0
  109. package/dist/bridge/server.js.map +1 -0
  110. package/dist/cli/auth.d.ts +37 -0
  111. package/dist/cli/auth.d.ts.map +1 -0
  112. package/dist/cli/auth.js +119 -0
  113. package/dist/cli/auth.js.map +1 -0
  114. package/dist/cli/database-rules.d.ts +25 -0
  115. package/dist/cli/database-rules.d.ts.map +1 -0
  116. package/dist/cli/database-rules.js +215 -0
  117. package/dist/cli/database-rules.js.map +1 -0
  118. package/dist/cli/deploy-progress.d.ts +35 -0
  119. package/dist/cli/deploy-progress.d.ts.map +1 -0
  120. package/dist/cli/deploy-progress.js +69 -0
  121. package/dist/cli/deploy-progress.js.map +1 -0
  122. package/dist/cli/deploy.d.ts +81 -0
  123. package/dist/cli/deploy.d.ts.map +1 -0
  124. package/dist/cli/deploy.js +413 -0
  125. package/dist/cli/deploy.js.map +1 -0
  126. package/dist/cli/dev-runner.d.ts +112 -0
  127. package/dist/cli/dev-runner.d.ts.map +1 -0
  128. package/dist/cli/dev-runner.js +227 -0
  129. package/dist/cli/dev-runner.js.map +1 -0
  130. package/dist/cli/discover.d.ts +47 -0
  131. package/dist/cli/discover.d.ts.map +1 -0
  132. package/dist/cli/discover.js +77 -0
  133. package/dist/cli/discover.js.map +1 -0
  134. package/dist/cli/firebase-json.d.ts +66 -0
  135. package/dist/cli/firebase-json.d.ts.map +1 -0
  136. package/dist/cli/firebase-json.js +60 -0
  137. package/dist/cli/firebase-json.js.map +1 -0
  138. package/dist/cli/index.d.ts +55 -0
  139. package/dist/cli/index.d.ts.map +1 -0
  140. package/dist/cli/index.js +500 -0
  141. package/dist/cli/index.js.map +1 -0
  142. package/dist/cli/init-templates.d.ts +37 -0
  143. package/dist/cli/init-templates.d.ts.map +1 -0
  144. package/dist/cli/init-templates.js +638 -0
  145. package/dist/cli/init-templates.js.map +1 -0
  146. package/dist/cli/init.d.ts +124 -0
  147. package/dist/cli/init.d.ts.map +1 -0
  148. package/dist/cli/init.js +423 -0
  149. package/dist/cli/init.js.map +1 -0
  150. package/dist/cli/login.d.ts +18 -0
  151. package/dist/cli/login.d.ts.map +1 -0
  152. package/dist/cli/login.js +59 -0
  153. package/dist/cli/login.js.map +1 -0
  154. package/dist/cli/mcp-proxy.d.ts +10 -0
  155. package/dist/cli/mcp-proxy.d.ts.map +1 -0
  156. package/dist/cli/mcp-proxy.js +126 -0
  157. package/dist/cli/mcp-proxy.js.map +1 -0
  158. package/dist/cli/parse-args.d.ts +20 -0
  159. package/dist/cli/parse-args.d.ts.map +1 -0
  160. package/dist/cli/parse-args.js +69 -0
  161. package/dist/cli/parse-args.js.map +1 -0
  162. package/dist/cli/rules.d.ts +40 -0
  163. package/dist/cli/rules.d.ts.map +1 -0
  164. package/dist/cli/rules.js +162 -0
  165. package/dist/cli/rules.js.map +1 -0
  166. package/dist/cli/scope.d.ts +38 -0
  167. package/dist/cli/scope.d.ts.map +1 -0
  168. package/dist/cli/scope.js +63 -0
  169. package/dist/cli/scope.js.map +1 -0
  170. package/dist/cli/serve.d.ts +122 -0
  171. package/dist/cli/serve.d.ts.map +1 -0
  172. package/dist/cli/serve.js +685 -0
  173. package/dist/cli/serve.js.map +1 -0
  174. package/dist/cli/snapshot.d.ts +22 -0
  175. package/dist/cli/snapshot.d.ts.map +1 -0
  176. package/dist/cli/snapshot.js +139 -0
  177. package/dist/cli/snapshot.js.map +1 -0
  178. package/dist/cli/verify.d.ts +23 -0
  179. package/dist/cli/verify.d.ts.map +1 -0
  180. package/dist/cli/verify.js +349 -0
  181. package/dist/cli/verify.js.map +1 -0
  182. package/dist/credentials/core/authorize.d.ts +22 -0
  183. package/dist/credentials/core/authorize.d.ts.map +1 -0
  184. package/dist/credentials/core/authorize.js +39 -0
  185. package/dist/credentials/core/authorize.js.map +1 -0
  186. package/dist/credentials/core/client.d.ts +14 -0
  187. package/dist/credentials/core/client.d.ts.map +1 -0
  188. package/dist/credentials/core/client.js +13 -0
  189. package/dist/credentials/core/client.js.map +1 -0
  190. package/dist/credentials/core/exchange.d.ts +24 -0
  191. package/dist/credentials/core/exchange.d.ts.map +1 -0
  192. package/dist/credentials/core/exchange.js +46 -0
  193. package/dist/credentials/core/exchange.js.map +1 -0
  194. package/dist/credentials/core/flow.d.ts +15 -0
  195. package/dist/credentials/core/flow.d.ts.map +1 -0
  196. package/dist/credentials/core/flow.js +40 -0
  197. package/dist/credentials/core/flow.js.map +1 -0
  198. package/dist/credentials/core/from-user-credential.d.ts +3 -0
  199. package/dist/credentials/core/from-user-credential.d.ts.map +1 -0
  200. package/dist/credentials/core/from-user-credential.js +19 -0
  201. package/dist/credentials/core/from-user-credential.js.map +1 -0
  202. package/dist/credentials/core/scopes.d.ts +26 -0
  203. package/dist/credentials/core/scopes.d.ts.map +1 -0
  204. package/dist/credentials/core/scopes.js +32 -0
  205. package/dist/credentials/core/scopes.js.map +1 -0
  206. package/dist/credentials/core/types.d.ts +72 -0
  207. package/dist/credentials/core/types.d.ts.map +1 -0
  208. package/dist/credentials/core/types.js +2 -0
  209. package/dist/credentials/core/types.js.map +1 -0
  210. package/dist/credentials/index.d.ts +14 -0
  211. package/dist/credentials/index.d.ts.map +1 -0
  212. package/dist/credentials/index.js +13 -0
  213. package/dist/credentials/index.js.map +1 -0
  214. package/dist/credentials/node/ensure-scope.d.ts +29 -0
  215. package/dist/credentials/node/ensure-scope.d.ts.map +1 -0
  216. package/dist/credentials/node/ensure-scope.js +42 -0
  217. package/dist/credentials/node/ensure-scope.js.map +1 -0
  218. package/dist/credentials/node/file-store.d.ts +4 -0
  219. package/dist/credentials/node/file-store.d.ts.map +1 -0
  220. package/dist/credentials/node/file-store.js +52 -0
  221. package/dist/credentials/node/file-store.js.map +1 -0
  222. package/dist/credentials/node/from-adc.d.ts +10 -0
  223. package/dist/credentials/node/from-adc.d.ts.map +1 -0
  224. package/dist/credentials/node/from-adc.js +60 -0
  225. package/dist/credentials/node/from-adc.js.map +1 -0
  226. package/dist/credentials/node/index.d.ts +10 -0
  227. package/dist/credentials/node/index.d.ts.map +1 -0
  228. package/dist/credentials/node/index.js +10 -0
  229. package/dist/credentials/node/index.js.map +1 -0
  230. package/dist/credentials/node/loopback-authorizer.d.ts +12 -0
  231. package/dist/credentials/node/loopback-authorizer.d.ts.map +1 -0
  232. package/dist/credentials/node/loopback-authorizer.js +61 -0
  233. package/dist/credentials/node/loopback-authorizer.js.map +1 -0
  234. package/dist/credentials/node/resolve-local-token.d.ts +20 -0
  235. package/dist/credentials/node/resolve-local-token.d.ts.map +1 -0
  236. package/dist/credentials/node/resolve-local-token.js +97 -0
  237. package/dist/credentials/node/resolve-local-token.js.map +1 -0
  238. package/dist/credentials/server/bff.d.ts +44 -0
  239. package/dist/credentials/server/bff.d.ts.map +1 -0
  240. package/dist/credentials/server/bff.js +43 -0
  241. package/dist/credentials/server/bff.js.map +1 -0
  242. package/dist/deploy/api-enablement.d.ts +45 -0
  243. package/dist/deploy/api-enablement.d.ts.map +1 -0
  244. package/dist/deploy/api-enablement.js +125 -0
  245. package/dist/deploy/api-enablement.js.map +1 -0
  246. package/dist/deploy/firestore/databases.d.ts +41 -0
  247. package/dist/deploy/firestore/databases.d.ts.map +1 -0
  248. package/dist/deploy/firestore/databases.js +63 -0
  249. package/dist/deploy/firestore/databases.js.map +1 -0
  250. package/dist/deploy/firestore/indexes.d.ts +120 -0
  251. package/dist/deploy/firestore/indexes.d.ts.map +1 -0
  252. package/dist/deploy/firestore/indexes.js +243 -0
  253. package/dist/deploy/firestore/indexes.js.map +1 -0
  254. package/dist/deploy/firestore/recipes.d.ts +21 -0
  255. package/dist/deploy/firestore/recipes.d.ts.map +1 -0
  256. package/dist/deploy/firestore/recipes.js +34 -0
  257. package/dist/deploy/firestore/recipes.js.map +1 -0
  258. package/dist/deploy/firestore/rules.d.ts +76 -0
  259. package/dist/deploy/firestore/rules.d.ts.map +1 -0
  260. package/dist/deploy/firestore/rules.js +180 -0
  261. package/dist/deploy/firestore/rules.js.map +1 -0
  262. package/dist/deploy/from-admin-app.d.ts +20 -0
  263. package/dist/deploy/from-admin-app.d.ts.map +1 -0
  264. package/dist/deploy/from-admin-app.js +18 -0
  265. package/dist/deploy/from-admin-app.js.map +1 -0
  266. package/dist/deploy/from-service-account.d.ts +29 -0
  267. package/dist/deploy/from-service-account.d.ts.map +1 -0
  268. package/dist/deploy/from-service-account.js +115 -0
  269. package/dist/deploy/from-service-account.js.map +1 -0
  270. package/dist/deploy/functions/bundle.d.ts +37 -0
  271. package/dist/deploy/functions/bundle.d.ts.map +1 -0
  272. package/dist/deploy/functions/bundle.js +181 -0
  273. package/dist/deploy/functions/bundle.js.map +1 -0
  274. package/dist/deploy/functions/core.d.ts +18 -0
  275. package/dist/deploy/functions/core.d.ts.map +1 -0
  276. package/dist/deploy/functions/core.js +211 -0
  277. package/dist/deploy/functions/core.js.map +1 -0
  278. package/dist/deploy/functions/iam.d.ts +17 -0
  279. package/dist/deploy/functions/iam.d.ts.map +1 -0
  280. package/dist/deploy/functions/iam.js +40 -0
  281. package/dist/deploy/functions/iam.js.map +1 -0
  282. package/dist/deploy/functions/operation.d.ts +39 -0
  283. package/dist/deploy/functions/operation.d.ts.map +1 -0
  284. package/dist/deploy/functions/operation.js +37 -0
  285. package/dist/deploy/functions/operation.js.map +1 -0
  286. package/dist/deploy/functions/spec.d.ts +69 -0
  287. package/dist/deploy/functions/spec.d.ts.map +1 -0
  288. package/dist/deploy/functions/spec.js +7 -0
  289. package/dist/deploy/functions/spec.js.map +1 -0
  290. package/dist/deploy/functions/types.d.ts +50 -0
  291. package/dist/deploy/functions/types.d.ts.map +1 -0
  292. package/dist/deploy/functions/types.js +3 -0
  293. package/dist/deploy/functions/types.js.map +1 -0
  294. package/dist/deploy/hosting/channels.d.ts +93 -0
  295. package/dist/deploy/hosting/channels.d.ts.map +1 -0
  296. package/dist/deploy/hosting/channels.js +134 -0
  297. package/dist/deploy/hosting/channels.js.map +1 -0
  298. package/dist/deploy/hosting/config.d.ts +28 -0
  299. package/dist/deploy/hosting/config.d.ts.map +1 -0
  300. package/dist/deploy/hosting/config.js +261 -0
  301. package/dist/deploy/hosting/config.js.map +1 -0
  302. package/dist/deploy/hosting/core.d.ts +38 -0
  303. package/dist/deploy/hosting/core.d.ts.map +1 -0
  304. package/dist/deploy/hosting/core.js +311 -0
  305. package/dist/deploy/hosting/core.js.map +1 -0
  306. package/dist/deploy/hosting/gzip.d.ts +11 -0
  307. package/dist/deploy/hosting/gzip.d.ts.map +1 -0
  308. package/dist/deploy/hosting/gzip.js +20 -0
  309. package/dist/deploy/hosting/gzip.js.map +1 -0
  310. package/dist/deploy/hosting/hash.d.ts +6 -0
  311. package/dist/deploy/hosting/hash.d.ts.map +1 -0
  312. package/dist/deploy/hosting/hash.js +17 -0
  313. package/dist/deploy/hosting/hash.js.map +1 -0
  314. package/dist/deploy/hosting/sites.d.ts +77 -0
  315. package/dist/deploy/hosting/sites.d.ts.map +1 -0
  316. package/dist/deploy/hosting/sites.js +73 -0
  317. package/dist/deploy/hosting/sites.js.map +1 -0
  318. package/dist/deploy/hosting/spec.d.ts +140 -0
  319. package/dist/deploy/hosting/spec.d.ts.map +1 -0
  320. package/dist/deploy/hosting/spec.js +8 -0
  321. package/dist/deploy/hosting/spec.js.map +1 -0
  322. package/dist/deploy/hosting/types.d.ts +71 -0
  323. package/dist/deploy/hosting/types.d.ts.map +1 -0
  324. package/dist/deploy/hosting/types.js +3 -0
  325. package/dist/deploy/hosting/types.js.map +1 -0
  326. package/dist/deploy/hosting/walk.d.ts +31 -0
  327. package/dist/deploy/hosting/walk.d.ts.map +1 -0
  328. package/dist/deploy/hosting/walk.js +167 -0
  329. package/dist/deploy/hosting/walk.js.map +1 -0
  330. package/dist/deploy/index.d.ts +51 -0
  331. package/dist/deploy/index.d.ts.map +1 -0
  332. package/dist/deploy/index.js +50 -0
  333. package/dist/deploy/index.js.map +1 -0
  334. package/dist/deploy/memoize-ttl.d.ts +58 -0
  335. package/dist/deploy/memoize-ttl.d.ts.map +1 -0
  336. package/dist/deploy/memoize-ttl.js +88 -0
  337. package/dist/deploy/memoize-ttl.js.map +1 -0
  338. package/dist/deploy/namespaces.d.ts +119 -0
  339. package/dist/deploy/namespaces.d.ts.map +1 -0
  340. package/dist/deploy/namespaces.js +192 -0
  341. package/dist/deploy/namespaces.js.map +1 -0
  342. package/dist/deploy/preflight.d.ts +119 -0
  343. package/dist/deploy/preflight.d.ts.map +1 -0
  344. package/dist/deploy/preflight.js +440 -0
  345. package/dist/deploy/preflight.js.map +1 -0
  346. package/dist/deploy/provider.d.ts +130 -0
  347. package/dist/deploy/provider.d.ts.map +1 -0
  348. package/dist/deploy/provider.js +21 -0
  349. package/dist/deploy/provider.js.map +1 -0
  350. package/dist/deploy/providers/database.d.ts +8 -0
  351. package/dist/deploy/providers/database.d.ts.map +1 -0
  352. package/dist/deploy/providers/database.js +37 -0
  353. package/dist/deploy/providers/database.js.map +1 -0
  354. package/dist/deploy/providers/firestore.d.ts +11 -0
  355. package/dist/deploy/providers/firestore.d.ts.map +1 -0
  356. package/dist/deploy/providers/firestore.js +43 -0
  357. package/dist/deploy/providers/firestore.js.map +1 -0
  358. package/dist/deploy/providers/functions.d.ts +9 -0
  359. package/dist/deploy/providers/functions.d.ts.map +1 -0
  360. package/dist/deploy/providers/functions.js +49 -0
  361. package/dist/deploy/providers/functions.js.map +1 -0
  362. package/dist/deploy/providers/hosting.d.ts +13 -0
  363. package/dist/deploy/providers/hosting.d.ts.map +1 -0
  364. package/dist/deploy/providers/hosting.js +149 -0
  365. package/dist/deploy/providers/hosting.js.map +1 -0
  366. package/dist/deploy/providers/storage.d.ts +9 -0
  367. package/dist/deploy/providers/storage.d.ts.map +1 -0
  368. package/dist/deploy/providers/storage.js +46 -0
  369. package/dist/deploy/providers/storage.js.map +1 -0
  370. package/dist/deploy/registry.d.ts +17 -0
  371. package/dist/deploy/registry.d.ts.map +1 -0
  372. package/dist/deploy/registry.js +15 -0
  373. package/dist/deploy/registry.js.map +1 -0
  374. package/dist/deploy/rtdb/rules.d.ts +21 -0
  375. package/dist/deploy/rtdb/rules.d.ts.map +1 -0
  376. package/dist/deploy/rtdb/rules.js +104 -0
  377. package/dist/deploy/rtdb/rules.js.map +1 -0
  378. package/dist/deploy/scope.d.ts +81 -0
  379. package/dist/deploy/scope.d.ts.map +1 -0
  380. package/dist/deploy/scope.js +44 -0
  381. package/dist/deploy/scope.js.map +1 -0
  382. package/dist/deploy/tools.d.ts +55 -0
  383. package/dist/deploy/tools.d.ts.map +1 -0
  384. package/dist/deploy/tools.js +329 -0
  385. package/dist/deploy/tools.js.map +1 -0
  386. package/dist/deploy/with-resolved-scope.d.ts +31 -0
  387. package/dist/deploy/with-resolved-scope.d.ts.map +1 -0
  388. package/dist/deploy/with-resolved-scope.js +73 -0
  389. package/dist/deploy/with-resolved-scope.js.map +1 -0
  390. package/dist/discover/concurrency.d.ts +42 -0
  391. package/dist/discover/concurrency.d.ts.map +1 -0
  392. package/dist/discover/concurrency.js +113 -0
  393. package/dist/discover/concurrency.js.map +1 -0
  394. package/dist/discover/crawler-adapter.d.ts +49 -0
  395. package/dist/discover/crawler-adapter.d.ts.map +1 -0
  396. package/dist/discover/crawler-adapter.js +92 -0
  397. package/dist/discover/crawler-adapter.js.map +1 -0
  398. package/dist/discover/crawler.d.ts +333 -0
  399. package/dist/discover/crawler.d.ts.map +1 -0
  400. package/dist/discover/crawler.js +764 -0
  401. package/dist/discover/crawler.js.map +1 -0
  402. package/dist/discover/findCollectionGroup.d.ts +80 -0
  403. package/dist/discover/findCollectionGroup.d.ts.map +1 -0
  404. package/dist/discover/findCollectionGroup.js +81 -0
  405. package/dist/discover/findCollectionGroup.js.map +1 -0
  406. package/dist/discover/index.d.ts +15 -0
  407. package/dist/discover/index.d.ts.map +1 -0
  408. package/dist/discover/index.js +24 -0
  409. package/dist/discover/index.js.map +1 -0
  410. package/dist/discover/merge.d.ts +108 -0
  411. package/dist/discover/merge.d.ts.map +1 -0
  412. package/dist/discover/merge.js +394 -0
  413. package/dist/discover/merge.js.map +1 -0
  414. package/dist/discover/rest-crawler-firestore.d.ts +51 -0
  415. package/dist/discover/rest-crawler-firestore.d.ts.map +1 -0
  416. package/dist/discover/rest-crawler-firestore.js +154 -0
  417. package/dist/discover/rest-crawler-firestore.js.map +1 -0
  418. package/dist/discover/session.d.ts +116 -0
  419. package/dist/discover/session.d.ts.map +1 -0
  420. package/dist/discover/session.js +331 -0
  421. package/dist/discover/session.js.map +1 -0
  422. package/dist/discover/tools.d.ts +59 -0
  423. package/dist/discover/tools.d.ts.map +1 -0
  424. package/dist/discover/tools.js +115 -0
  425. package/dist/discover/tools.js.map +1 -0
  426. package/dist/discover/types.d.ts +191 -0
  427. package/dist/discover/types.d.ts.map +1 -0
  428. package/dist/discover/types.js +12 -0
  429. package/dist/discover/types.js.map +1 -0
  430. package/dist/discover/wire.d.ts +81 -0
  431. package/dist/discover/wire.d.ts.map +1 -0
  432. package/dist/discover/wire.js +308 -0
  433. package/dist/discover/wire.js.map +1 -0
  434. package/dist/pkg-version.d.ts +2 -0
  435. package/dist/pkg-version.d.ts.map +1 -0
  436. package/dist/pkg-version.js +47 -0
  437. package/dist/pkg-version.js.map +1 -0
  438. package/dist/register/esm-exports.d.ts +26 -0
  439. package/dist/register/esm-exports.d.ts.map +1 -0
  440. package/dist/register/esm-exports.js +61 -0
  441. package/dist/register/esm-exports.js.map +1 -0
  442. package/dist/register/exempt.d.ts +11 -0
  443. package/dist/register/exempt.d.ts.map +1 -0
  444. package/dist/register/exempt.js +74 -0
  445. package/dist/register/exempt.js.map +1 -0
  446. package/dist/register/hooks.d.ts +13 -0
  447. package/dist/register/hooks.d.ts.map +1 -0
  448. package/dist/register/hooks.js +15 -0
  449. package/dist/register/hooks.js.map +1 -0
  450. package/dist/register/index.d.ts +5 -0
  451. package/dist/register/index.d.ts.map +1 -0
  452. package/dist/register/index.js +126 -0
  453. package/dist/register/index.js.map +1 -0
  454. package/dist/register/mapping.d.ts +17 -0
  455. package/dist/register/mapping.d.ts.map +1 -0
  456. package/dist/register/mapping.js +31 -0
  457. package/dist/register/mapping.js.map +1 -0
  458. package/dist/registry/admin-deps.d.ts +17 -0
  459. package/dist/registry/admin-deps.d.ts.map +1 -0
  460. package/dist/registry/admin-deps.js +68 -0
  461. package/dist/registry/admin-deps.js.map +1 -0
  462. package/dist/registry/compose.d.ts +68 -0
  463. package/dist/registry/compose.d.ts.map +1 -0
  464. package/dist/registry/compose.js +113 -0
  465. package/dist/registry/compose.js.map +1 -0
  466. package/dist/registry/index.d.ts +11 -0
  467. package/dist/registry/index.d.ts.map +1 -0
  468. package/dist/registry/index.js +11 -0
  469. package/dist/registry/index.js.map +1 -0
  470. package/dist/remote/index.d.ts +232 -0
  471. package/dist/remote/index.d.ts.map +1 -0
  472. package/dist/remote/index.js +719 -0
  473. package/dist/remote/index.js.map +1 -0
  474. package/dist/rtdb/rules-json.d.ts +8 -0
  475. package/dist/rtdb/rules-json.d.ts.map +1 -0
  476. package/dist/rtdb/rules-json.js +17 -0
  477. package/dist/rtdb/rules-json.js.map +1 -0
  478. package/dist/serve/bridge-mount.d.ts +38 -0
  479. package/dist/serve/bridge-mount.d.ts.map +1 -0
  480. package/dist/serve/bridge-mount.js +162 -0
  481. package/dist/serve/bridge-mount.js.map +1 -0
  482. package/dist/serve/bundler.d.ts +134 -0
  483. package/dist/serve/bundler.d.ts.map +1 -0
  484. package/dist/serve/bundler.js +452 -0
  485. package/dist/serve/bundler.js.map +1 -0
  486. package/dist/serve/capture-store.d.ts +27 -0
  487. package/dist/serve/capture-store.d.ts.map +1 -0
  488. package/dist/serve/capture-store.js +47 -0
  489. package/dist/serve/capture-store.js.map +1 -0
  490. package/dist/serve/discovery.d.ts +71 -0
  491. package/dist/serve/discovery.d.ts.map +1 -0
  492. package/dist/serve/discovery.js +158 -0
  493. package/dist/serve/discovery.js.map +1 -0
  494. package/dist/serve/docs-ui/_astro/_slug_.BsN3bPok.css +1 -0
  495. package/dist/serve/docs-ui/docs/_rhythm/index.html +99 -0
  496. package/dist/serve/docs-ui/docs/_rhythm.md +101 -0
  497. package/dist/serve/docs-ui/docs/index.html +1 -0
  498. package/dist/serve/docs-ui/docs/index.json +9914 -0
  499. package/dist/serve/docs-ui/docs/pyric-admin-firestore/index.html +38 -0
  500. package/dist/serve/docs-ui/docs/pyric-admin-firestore-explanation-error-translation/index.html +57 -0
  501. package/dist/serve/docs-ui/docs/pyric-admin-firestore-explanation-error-translation.md +87 -0
  502. package/dist/serve/docs-ui/docs/pyric-admin-firestore-explanation-per-call-delegate/index.html +58 -0
  503. package/dist/serve/docs-ui/docs/pyric-admin-firestore-explanation-per-call-delegate.md +86 -0
  504. package/dist/serve/docs-ui/docs/pyric-admin-firestore-explanation-why-mirror-admin-shape/index.html +59 -0
  505. package/dist/serve/docs-ui/docs/pyric-admin-firestore-explanation-why-mirror-admin-shape.md +82 -0
  506. package/dist/serve/docs-ui/docs/pyric-admin-firestore-how-to-run-a-transaction/index.html +53 -0
  507. package/dist/serve/docs-ui/docs/pyric-admin-firestore-how-to-run-a-transaction.md +70 -0
  508. package/dist/serve/docs-ui/docs/pyric-admin-firestore-how-to-seed-and-set-rules/index.html +62 -0
  509. package/dist/serve/docs-ui/docs/pyric-admin-firestore-how-to-seed-and-set-rules.md +86 -0
  510. package/dist/serve/docs-ui/docs/pyric-admin-firestore-how-to-translate-denials/index.html +80 -0
  511. package/dist/serve/docs-ui/docs/pyric-admin-firestore-how-to-translate-denials.md +92 -0
  512. package/dist/serve/docs-ui/docs/pyric-admin-firestore-how-to-use-onsnapshot/index.html +57 -0
  513. package/dist/serve/docs-ui/docs/pyric-admin-firestore-how-to-use-onsnapshot.md +78 -0
  514. package/dist/serve/docs-ui/docs/pyric-admin-firestore-how-to-write-a-batch/index.html +50 -0
  515. package/dist/serve/docs-ui/docs/pyric-admin-firestore-how-to-write-a-batch.md +67 -0
  516. package/dist/serve/docs-ui/docs/pyric-admin-firestore-reference-api/index.html +53 -0
  517. package/dist/serve/docs-ui/docs/pyric-admin-firestore-reference-api.md +73 -0
  518. package/dist/serve/docs-ui/docs/pyric-admin-firestore-reference-onsnapshot/index.html +68 -0
  519. package/dist/serve/docs-ui/docs/pyric-admin-firestore-reference-onsnapshot.md +75 -0
  520. package/dist/serve/docs-ui/docs/pyric-admin-firestore-reference-re-exported-types/index.html +76 -0
  521. package/dist/serve/docs-ui/docs/pyric-admin-firestore-reference-re-exported-types.md +95 -0
  522. package/dist/serve/docs-ui/docs/pyric-admin-firestore-reference-sandbox-firestore/index.html +40 -0
  523. package/dist/serve/docs-ui/docs/pyric-admin-firestore-reference-sandbox-firestore.md +73 -0
  524. package/dist/serve/docs-ui/docs/pyric-admin-firestore-tutorials-01-first-admin-session/index.html +118 -0
  525. package/dist/serve/docs-ui/docs/pyric-admin-firestore-tutorials-01-first-admin-session.md +146 -0
  526. package/dist/serve/docs-ui/docs/pyric-admin-firestore.md +49 -0
  527. package/dist/serve/docs-ui/docs/pyric-auth/index.html +64 -0
  528. package/dist/serve/docs-ui/docs/pyric-auth-compat/index.html +874 -0
  529. package/dist/serve/docs-ui/docs/pyric-auth-compat.md +265 -0
  530. package/dist/serve/docs-ui/docs/pyric-auth-reference-api/index.html +112 -0
  531. package/dist/serve/docs-ui/docs/pyric-auth-reference-api.md +129 -0
  532. package/dist/serve/docs-ui/docs/pyric-auth-reference-feature-matrix/index.html +781 -0
  533. package/dist/serve/docs-ui/docs/pyric-auth-reference-feature-matrix.md +198 -0
  534. package/dist/serve/docs-ui/docs/pyric-auth-reference-sandbox-test-driver/index.html +80 -0
  535. package/dist/serve/docs-ui/docs/pyric-auth-reference-sandbox-test-driver.md +107 -0
  536. package/dist/serve/docs-ui/docs/pyric-auth.md +84 -0
  537. package/dist/serve/docs-ui/docs/pyric-database/index.html +36 -0
  538. package/dist/serve/docs-ui/docs/pyric-database-compat/index.html +2428 -0
  539. package/dist/serve/docs-ui/docs/pyric-database-compat.md +850 -0
  540. package/dist/serve/docs-ui/docs/pyric-database-explanation-rules-authoring-and-deploy-are-separate/index.html +23 -0
  541. package/dist/serve/docs-ui/docs/pyric-database-explanation-rules-authoring-and-deploy-are-separate.md +29 -0
  542. package/dist/serve/docs-ui/docs/pyric-database-reference-rules-tooling/index.html +263 -0
  543. package/dist/serve/docs-ui/docs/pyric-database-reference-rules-tooling.md +288 -0
  544. package/dist/serve/docs-ui/docs/pyric-database-tutorials-01-author-rtdb-rules-with-constraints/index.html +116 -0
  545. package/dist/serve/docs-ui/docs/pyric-database-tutorials-01-author-rtdb-rules-with-constraints.md +142 -0
  546. package/dist/serve/docs-ui/docs/pyric-database.md +18 -0
  547. package/dist/serve/docs-ui/docs/pyric-firestore/index.html +54 -0
  548. package/dist/serve/docs-ui/docs/pyric-firestore-compat/index.html +1345 -0
  549. package/dist/serve/docs-ui/docs/pyric-firestore-compat.md +366 -0
  550. package/dist/serve/docs-ui/docs/pyric-firestore-explanation-rules-tooling-is-separate/index.html +79 -0
  551. package/dist/serve/docs-ui/docs/pyric-firestore-explanation-rules-tooling-is-separate.md +62 -0
  552. package/dist/serve/docs-ui/docs/pyric-firestore-explanation-target-symbol-opacity/index.html +59 -0
  553. package/dist/serve/docs-ui/docs/pyric-firestore-explanation-target-symbol-opacity.md +85 -0
  554. package/dist/serve/docs-ui/docs/pyric-firestore-explanation-two-backends-one-surface/index.html +61 -0
  555. package/dist/serve/docs-ui/docs/pyric-firestore-explanation-two-backends-one-surface.md +87 -0
  556. package/dist/serve/docs-ui/docs/pyric-firestore-how-to-build-queries/index.html +105 -0
  557. package/dist/serve/docs-ui/docs/pyric-firestore-how-to-build-queries.md +138 -0
  558. package/dist/serve/docs-ui/docs/pyric-firestore-how-to-migrate-from-firebase-firestore/index.html +65 -0
  559. package/dist/serve/docs-ui/docs/pyric-firestore-how-to-migrate-from-firebase-firestore.md +93 -0
  560. package/dist/serve/docs-ui/docs/pyric-firestore-how-to-pick-a-backend/index.html +112 -0
  561. package/dist/serve/docs-ui/docs/pyric-firestore-how-to-pick-a-backend.md +100 -0
  562. package/dist/serve/docs-ui/docs/pyric-firestore-how-to-run-a-transaction/index.html +42 -0
  563. package/dist/serve/docs-ui/docs/pyric-firestore-how-to-run-a-transaction.md +55 -0
  564. package/dist/serve/docs-ui/docs/pyric-firestore-how-to-use-onsnapshot/index.html +61 -0
  565. package/dist/serve/docs-ui/docs/pyric-firestore-how-to-use-onsnapshot.md +83 -0
  566. package/dist/serve/docs-ui/docs/pyric-firestore-how-to-use-sandbox-ops/index.html +52 -0
  567. package/dist/serve/docs-ui/docs/pyric-firestore-how-to-use-sandbox-ops.md +73 -0
  568. package/dist/serve/docs-ui/docs/pyric-firestore-reference-api/index.html +132 -0
  569. package/dist/serve/docs-ui/docs/pyric-firestore-reference-api.md +160 -0
  570. package/dist/serve/docs-ui/docs/pyric-firestore-reference-feature-matrix/index.html +793 -0
  571. package/dist/serve/docs-ui/docs/pyric-firestore-reference-feature-matrix.md +239 -0
  572. package/dist/serve/docs-ui/docs/pyric-firestore-reference-getfirestore/index.html +55 -0
  573. package/dist/serve/docs-ui/docs/pyric-firestore-reference-getfirestore.md +76 -0
  574. package/dist/serve/docs-ui/docs/pyric-firestore-reference-query-constraints/index.html +78 -0
  575. package/dist/serve/docs-ui/docs/pyric-firestore-reference-query-constraints.md +115 -0
  576. package/dist/serve/docs-ui/docs/pyric-firestore-reference-sandbox-ops/index.html +53 -0
  577. package/dist/serve/docs-ui/docs/pyric-firestore-reference-sandbox-ops.md +76 -0
  578. package/dist/serve/docs-ui/docs/pyric-firestore-reference-tool-factories/index.html +51 -0
  579. package/dist/serve/docs-ui/docs/pyric-firestore-reference-tool-factories.md +65 -0
  580. package/dist/serve/docs-ui/docs/pyric-firestore-tutorials-01-write-a-sandbox-demo/index.html +127 -0
  581. package/dist/serve/docs-ui/docs/pyric-firestore-tutorials-01-write-a-sandbox-demo.md +157 -0
  582. package/dist/serve/docs-ui/docs/pyric-firestore-tutorials-02-swap-to-prod-backend/index.html +85 -0
  583. package/dist/serve/docs-ui/docs/pyric-firestore-tutorials-02-swap-to-prod-backend.md +122 -0
  584. package/dist/serve/docs-ui/docs/pyric-firestore.md +69 -0
  585. package/dist/serve/docs-ui/docs/pyric-rules/index.html +93 -0
  586. package/dist/serve/docs-ui/docs/pyric-rules-explanation-agent-failure-modes/index.html +50 -0
  587. package/dist/serve/docs-ui/docs/pyric-rules-explanation-agent-failure-modes.md +74 -0
  588. package/dist/serve/docs-ui/docs/pyric-rules-explanation-lint-vs-validate-vs-simulate-vs-test/index.html +79 -0
  589. package/dist/serve/docs-ui/docs/pyric-rules-explanation-lint-vs-validate-vs-simulate-vs-test.md +69 -0
  590. package/dist/serve/docs-ui/docs/pyric-rules-explanation-runtime-budget-and-shared-gates/index.html +67 -0
  591. package/dist/serve/docs-ui/docs/pyric-rules-explanation-runtime-budget-and-shared-gates.md +71 -0
  592. package/dist/serve/docs-ui/docs/pyric-rules-explanation-sentinel-expression-engine/index.html +49 -0
  593. package/dist/serve/docs-ui/docs/pyric-rules-explanation-sentinel-expression-engine.md +70 -0
  594. package/dist/serve/docs-ui/docs/pyric-rules-explanation-simulator-vs-rules-test-api/index.html +96 -0
  595. package/dist/serve/docs-ui/docs/pyric-rules-explanation-simulator-vs-rules-test-api.md +76 -0
  596. package/dist/serve/docs-ui/docs/pyric-rules-explanation-the-2-plus-modules-extension/index.html +58 -0
  597. package/dist/serve/docs-ui/docs/pyric-rules-explanation-the-2-plus-modules-extension.md +75 -0
  598. package/dist/serve/docs-ui/docs/pyric-rules-explanation-value-wrappers-design/index.html +62 -0
  599. package/dist/serve/docs-ui/docs/pyric-rules-explanation-value-wrappers-design.md +80 -0
  600. package/dist/serve/docs-ui/docs/pyric-rules-explanation-why-this-package-exists/index.html +34 -0
  601. package/dist/serve/docs-ui/docs/pyric-rules-explanation-why-this-package-exists.md +43 -0
  602. package/dist/serve/docs-ui/docs/pyric-rules-how-to-compare-rulesets-for-weakening/index.html +54 -0
  603. package/dist/serve/docs-ui/docs/pyric-rules-how-to-compare-rulesets-for-weakening.md +69 -0
  604. package/dist/serve/docs-ui/docs/pyric-rules-how-to-inspect-rules-via-the-ast/index.html +89 -0
  605. package/dist/serve/docs-ui/docs/pyric-rules-how-to-inspect-rules-via-the-ast.md +113 -0
  606. package/dist/serve/docs-ui/docs/pyric-rules-how-to-lint-rules-source/index.html +50 -0
  607. package/dist/serve/docs-ui/docs/pyric-rules-how-to-lint-rules-source.md +73 -0
  608. package/dist/serve/docs-ui/docs/pyric-rules-how-to-pin-request-time/index.html +47 -0
  609. package/dist/serve/docs-ui/docs/pyric-rules-how-to-pin-request-time.md +58 -0
  610. package/dist/serve/docs-ui/docs/pyric-rules-how-to-register-tools-with-an-agent/index.html +61 -0
  611. package/dist/serve/docs-ui/docs/pyric-rules-how-to-register-tools-with-an-agent.md +78 -0
  612. package/dist/serve/docs-ui/docs/pyric-rules-how-to-resolve-module-imports/index.html +92 -0
  613. package/dist/serve/docs-ui/docs/pyric-rules-how-to-resolve-module-imports.md +93 -0
  614. package/dist/serve/docs-ui/docs/pyric-rules-how-to-simulate-rules-locally/index.html +89 -0
  615. package/dist/serve/docs-ui/docs/pyric-rules-how-to-simulate-rules-locally.md +117 -0
  616. package/dist/serve/docs-ui/docs/pyric-rules-how-to-test-rules-against-firebase/index.html +70 -0
  617. package/dist/serve/docs-ui/docs/pyric-rules-how-to-test-rules-against-firebase.md +93 -0
  618. package/dist/serve/docs-ui/docs/pyric-rules-reference-api/index.html +129 -0
  619. package/dist/serve/docs-ui/docs/pyric-rules-reference-api.md +176 -0
  620. package/dist/serve/docs-ui/docs/pyric-rules-reference-ast/index.html +85 -0
  621. package/dist/serve/docs-ui/docs/pyric-rules-reference-ast.md +105 -0
  622. package/dist/serve/docs-ui/docs/pyric-rules-reference-errors/index.html +63 -0
  623. package/dist/serve/docs-ui/docs/pyric-rules-reference-errors.md +89 -0
  624. package/dist/serve/docs-ui/docs/pyric-rules-reference-lint-rules/index.html +148 -0
  625. package/dist/serve/docs-ui/docs/pyric-rules-reference-lint-rules.md +159 -0
  626. package/dist/serve/docs-ui/docs/pyric-rules-reference-simulator-context/index.html +66 -0
  627. package/dist/serve/docs-ui/docs/pyric-rules-reference-simulator-context.md +98 -0
  628. package/dist/serve/docs-ui/docs/pyric-rules-reference-stdlib-modules/index.html +239 -0
  629. package/dist/serve/docs-ui/docs/pyric-rules-reference-stdlib-modules.md +135 -0
  630. package/dist/serve/docs-ui/docs/pyric-rules-reference-test-case-schema/index.html +93 -0
  631. package/dist/serve/docs-ui/docs/pyric-rules-reference-test-case-schema.md +109 -0
  632. package/dist/serve/docs-ui/docs/pyric-rules-reference-validator-findings/index.html +86 -0
  633. package/dist/serve/docs-ui/docs/pyric-rules-reference-validator-findings.md +113 -0
  634. package/dist/serve/docs-ui/docs/pyric-rules-reference-value-wrappers/index.html +101 -0
  635. package/dist/serve/docs-ui/docs/pyric-rules-reference-value-wrappers.md +134 -0
  636. package/dist/serve/docs-ui/docs/pyric-rules-tutorials-01-lint-your-first-rules-file/index.html +107 -0
  637. package/dist/serve/docs-ui/docs/pyric-rules-tutorials-01-lint-your-first-rules-file.md +142 -0
  638. package/dist/serve/docs-ui/docs/pyric-rules-tutorials-02-write-a-test-suite-for-your-rules/index.html +151 -0
  639. package/dist/serve/docs-ui/docs/pyric-rules-tutorials-02-write-a-test-suite-for-your-rules.md +194 -0
  640. package/dist/serve/docs-ui/docs/pyric-rules.md +88 -0
  641. package/dist/serve/docs-ui/docs/pyric-sandbox/index.html +67 -0
  642. package/dist/serve/docs-ui/docs/pyric-sandbox-explanation-every-op-is-a-request/index.html +141 -0
  643. package/dist/serve/docs-ui/docs/pyric-sandbox-explanation-every-op-is-a-request.md +157 -0
  644. package/dist/serve/docs-ui/docs/pyric-sandbox-explanation-identity-is-a-context/index.html +66 -0
  645. package/dist/serve/docs-ui/docs/pyric-sandbox-explanation-identity-is-a-context.md +98 -0
  646. package/dist/serve/docs-ui/docs/pyric-sandbox-explanation-internal-adapter-protocol/index.html +71 -0
  647. package/dist/serve/docs-ui/docs/pyric-sandbox-explanation-internal-adapter-protocol.md +105 -0
  648. package/dist/serve/docs-ui/docs/pyric-sandbox-explanation-listener-re-evaluation/index.html +56 -0
  649. package/dist/serve/docs-ui/docs/pyric-sandbox-explanation-listener-re-evaluation.md +80 -0
  650. package/dist/serve/docs-ui/docs/pyric-sandbox-explanation-local-backend-vs-firestore-offline/index.html +64 -0
  651. package/dist/serve/docs-ui/docs/pyric-sandbox-explanation-local-backend-vs-firestore-offline.md +56 -0
  652. package/dist/serve/docs-ui/docs/pyric-sandbox-explanation-why-adapters-are-siblings/index.html +88 -0
  653. package/dist/serve/docs-ui/docs/pyric-sandbox-explanation-why-adapters-are-siblings.md +87 -0
  654. package/dist/serve/docs-ui/docs/pyric-sandbox-explanation-why-this-package-exists/index.html +69 -0
  655. package/dist/serve/docs-ui/docs/pyric-sandbox-explanation-why-this-package-exists.md +57 -0
  656. package/dist/serve/docs-ui/docs/pyric-sandbox-how-to-multiple-isolated-sandboxes/index.html +63 -0
  657. package/dist/serve/docs-ui/docs/pyric-sandbox-how-to-multiple-isolated-sandboxes.md +83 -0
  658. package/dist/serve/docs-ui/docs/pyric-sandbox-how-to-observe-events/index.html +159 -0
  659. package/dist/serve/docs-ui/docs/pyric-sandbox-how-to-observe-events.md +154 -0
  660. package/dist/serve/docs-ui/docs/pyric-sandbox-how-to-pick-an-adapter/index.html +79 -0
  661. package/dist/serve/docs-ui/docs/pyric-sandbox-how-to-pick-an-adapter.md +69 -0
  662. package/dist/serve/docs-ui/docs/pyric-sandbox-how-to-replay-events/index.html +89 -0
  663. package/dist/serve/docs-ui/docs/pyric-sandbox-how-to-replay-events.md +87 -0
  664. package/dist/serve/docs-ui/docs/pyric-sandbox-how-to-reset-between-tests/index.html +68 -0
  665. package/dist/serve/docs-ui/docs/pyric-sandbox-how-to-reset-between-tests.md +86 -0
  666. package/dist/serve/docs-ui/docs/pyric-sandbox-how-to-seed-data-and-rules/index.html +91 -0
  667. package/dist/serve/docs-ui/docs/pyric-sandbox-how-to-seed-data-and-rules.md +112 -0
  668. package/dist/serve/docs-ui/docs/pyric-sandbox-how-to-switch-users/index.html +50 -0
  669. package/dist/serve/docs-ui/docs/pyric-sandbox-how-to-switch-users.md +70 -0
  670. package/dist/serve/docs-ui/docs/pyric-sandbox-how-to-use-admin-reads/index.html +54 -0
  671. package/dist/serve/docs-ui/docs/pyric-sandbox-how-to-use-admin-reads.md +73 -0
  672. package/dist/serve/docs-ui/docs/pyric-sandbox-reference-api/index.html +77 -0
  673. package/dist/serve/docs-ui/docs/pyric-sandbox-reference-api.md +117 -0
  674. package/dist/serve/docs-ui/docs/pyric-sandbox-reference-divergences/index.html +125 -0
  675. package/dist/serve/docs-ui/docs/pyric-sandbox-reference-divergences.md +137 -0
  676. package/dist/serve/docs-ui/docs/pyric-sandbox-reference-error-codes/index.html +110 -0
  677. package/dist/serve/docs-ui/docs/pyric-sandbox-reference-error-codes.md +114 -0
  678. package/dist/serve/docs-ui/docs/pyric-sandbox-reference-internal-protocol/index.html +72 -0
  679. package/dist/serve/docs-ui/docs/pyric-sandbox-reference-internal-protocol.md +99 -0
  680. package/dist/serve/docs-ui/docs/pyric-sandbox-reference-sandbox-and-context/index.html +90 -0
  681. package/dist/serve/docs-ui/docs/pyric-sandbox-reference-sandbox-and-context.md +128 -0
  682. package/dist/serve/docs-ui/docs/pyric-sandbox-reference-sandbox-event/index.html +146 -0
  683. package/dist/serve/docs-ui/docs/pyric-sandbox-reference-sandbox-event.md +188 -0
  684. package/dist/serve/docs-ui/docs/pyric-sandbox-reference-snapshot-and-admin/index.html +53 -0
  685. package/dist/serve/docs-ui/docs/pyric-sandbox-reference-snapshot-and-admin.md +77 -0
  686. package/dist/serve/docs-ui/docs/pyric-sandbox-tutorials-01-your-first-sandbox-session/index.html +115 -0
  687. package/dist/serve/docs-ui/docs/pyric-sandbox-tutorials-01-your-first-sandbox-session.md +154 -0
  688. package/dist/serve/docs-ui/docs/pyric-sandbox-tutorials-02-use-the-sandbox-in-a-test-harness/index.html +191 -0
  689. package/dist/serve/docs-ui/docs/pyric-sandbox-tutorials-02-use-the-sandbox-in-a-test-harness.md +222 -0
  690. package/dist/serve/docs-ui/docs/pyric-sandbox-tutorials-03-build-a-traffic-monitor/index.html +163 -0
  691. package/dist/serve/docs-ui/docs/pyric-sandbox-tutorials-03-build-a-traffic-monitor.md +192 -0
  692. package/dist/serve/docs-ui/docs/pyric-sandbox.md +63 -0
  693. package/dist/serve/docs-ui/docs/pyric-storage/index.html +65 -0
  694. package/dist/serve/docs-ui/docs/pyric-storage-compat/index.html +1076 -0
  695. package/dist/serve/docs-ui/docs/pyric-storage-compat.md +337 -0
  696. package/dist/serve/docs-ui/docs/pyric-storage-explanation-implementation-scope/index.html +63 -0
  697. package/dist/serve/docs-ui/docs/pyric-storage-explanation-implementation-scope.md +104 -0
  698. package/dist/serve/docs-ui/docs/pyric-storage-explanation-session-archive-use-case/index.html +58 -0
  699. package/dist/serve/docs-ui/docs/pyric-storage-explanation-session-archive-use-case.md +80 -0
  700. package/dist/serve/docs-ui/docs/pyric-storage-explanation-why-indexeddb/index.html +50 -0
  701. package/dist/serve/docs-ui/docs/pyric-storage-explanation-why-indexeddb.md +82 -0
  702. package/dist/serve/docs-ui/docs/pyric-storage-how-to-enforce-rules/index.html +104 -0
  703. package/dist/serve/docs-ui/docs/pyric-storage-how-to-enforce-rules.md +111 -0
  704. package/dist/serve/docs-ui/docs/pyric-storage-how-to-list-and-delete/index.html +53 -0
  705. package/dist/serve/docs-ui/docs/pyric-storage-how-to-list-and-delete.md +76 -0
  706. package/dist/serve/docs-ui/docs/pyric-storage-how-to-round-trip-metadata/index.html +77 -0
  707. package/dist/serve/docs-ui/docs/pyric-storage-how-to-round-trip-metadata.md +100 -0
  708. package/dist/serve/docs-ui/docs/pyric-storage-how-to-switch-backends/index.html +65 -0
  709. package/dist/serve/docs-ui/docs/pyric-storage-how-to-switch-backends.md +82 -0
  710. package/dist/serve/docs-ui/docs/pyric-storage-how-to-test-rule-expressions/index.html +70 -0
  711. package/dist/serve/docs-ui/docs/pyric-storage-how-to-test-rule-expressions.md +82 -0
  712. package/dist/serve/docs-ui/docs/pyric-storage-reference-api/index.html +92 -0
  713. package/dist/serve/docs-ui/docs/pyric-storage-reference-api.md +152 -0
  714. package/dist/serve/docs-ui/docs/pyric-storage-reference-error-codes/index.html +46 -0
  715. package/dist/serve/docs-ui/docs/pyric-storage-reference-error-codes.md +66 -0
  716. package/dist/serve/docs-ui/docs/pyric-storage-reference-rules-subset/index.html +73 -0
  717. package/dist/serve/docs-ui/docs/pyric-storage-reference-rules-subset.md +90 -0
  718. package/dist/serve/docs-ui/docs/pyric-storage-reference-storage-options/index.html +45 -0
  719. package/dist/serve/docs-ui/docs/pyric-storage-reference-storage-options.md +65 -0
  720. package/dist/serve/docs-ui/docs/pyric-storage-tutorials-01-upload-and-download/index.html +114 -0
  721. package/dist/serve/docs-ui/docs/pyric-storage-tutorials-01-upload-and-download.md +141 -0
  722. package/dist/serve/docs-ui/docs/pyric-storage.md +63 -0
  723. package/dist/serve/docs-ui/docs/pyric-tools/index.html +45 -0
  724. package/dist/serve/docs-ui/docs/pyric-tools-bridge/index.html +71 -0
  725. package/dist/serve/docs-ui/docs/pyric-tools-bridge.md +90 -0
  726. package/dist/serve/docs-ui/docs/pyric-tools-deploy/index.html +59 -0
  727. package/dist/serve/docs-ui/docs/pyric-tools-deploy-explanation-primitives-vs-orchestrators/index.html +63 -0
  728. package/dist/serve/docs-ui/docs/pyric-tools-deploy-explanation-primitives-vs-orchestrators.md +83 -0
  729. package/dist/serve/docs-ui/docs/pyric-tools-deploy-explanation-token-caching/index.html +47 -0
  730. package/dist/serve/docs-ui/docs/pyric-tools-deploy-explanation-token-caching.md +70 -0
  731. package/dist/serve/docs-ui/docs/pyric-tools-deploy-explanation-why-no-firebase-cli/index.html +36 -0
  732. package/dist/serve/docs-ui/docs/pyric-tools-deploy-explanation-why-no-firebase-cli.md +50 -0
  733. package/dist/serve/docs-ui/docs/pyric-tools-deploy-explanation-why-this-package-exists/index.html +47 -0
  734. package/dist/serve/docs-ui/docs/pyric-tools-deploy-explanation-why-this-package-exists.md +60 -0
  735. package/dist/serve/docs-ui/docs/pyric-tools-deploy-how-to-build-projectscope-from-firebase-auth/index.html +55 -0
  736. package/dist/serve/docs-ui/docs/pyric-tools-deploy-how-to-build-projectscope-from-firebase-auth.md +68 -0
  737. package/dist/serve/docs-ui/docs/pyric-tools-deploy-how-to-build-projectscope-from-service-account/index.html +35 -0
  738. package/dist/serve/docs-ui/docs/pyric-tools-deploy-how-to-build-projectscope-from-service-account.md +52 -0
  739. package/dist/serve/docs-ui/docs/pyric-tools-deploy-how-to-bundle-and-deploy-a-function/index.html +81 -0
  740. package/dist/serve/docs-ui/docs/pyric-tools-deploy-how-to-bundle-and-deploy-a-function.md +98 -0
  741. package/dist/serve/docs-ui/docs/pyric-tools-deploy-how-to-deploy-firestore-indexes/index.html +71 -0
  742. package/dist/serve/docs-ui/docs/pyric-tools-deploy-how-to-deploy-firestore-indexes.md +91 -0
  743. package/dist/serve/docs-ui/docs/pyric-tools-deploy-how-to-deploy-firestore-rules/index.html +100 -0
  744. package/dist/serve/docs-ui/docs/pyric-tools-deploy-how-to-deploy-firestore-rules.md +105 -0
  745. package/dist/serve/docs-ui/docs/pyric-tools-deploy-how-to-deploy-hosting-rewrites/index.html +86 -0
  746. package/dist/serve/docs-ui/docs/pyric-tools-deploy-how-to-deploy-hosting-rewrites.md +92 -0
  747. package/dist/serve/docs-ui/docs/pyric-tools-deploy-how-to-deploy-realtime-database-rules/index.html +132 -0
  748. package/dist/serve/docs-ui/docs/pyric-tools-deploy-how-to-deploy-realtime-database-rules.md +165 -0
  749. package/dist/serve/docs-ui/docs/pyric-tools-deploy-how-to-deploy-to-a-preview-channel/index.html +46 -0
  750. package/dist/serve/docs-ui/docs/pyric-tools-deploy-how-to-deploy-to-a-preview-channel.md +62 -0
  751. package/dist/serve/docs-ui/docs/pyric-tools-deploy-how-to-handle-errors-and-outcomes/index.html +75 -0
  752. package/dist/serve/docs-ui/docs/pyric-tools-deploy-how-to-handle-errors-and-outcomes.md +94 -0
  753. package/dist/serve/docs-ui/docs/pyric-tools-deploy-how-to-provision-a-firestore-database/index.html +48 -0
  754. package/dist/serve/docs-ui/docs/pyric-tools-deploy-how-to-provision-a-firestore-database.md +65 -0
  755. package/dist/serve/docs-ui/docs/pyric-tools-deploy-how-to-register-tools-with-an-agent/index.html +65 -0
  756. package/dist/serve/docs-ui/docs/pyric-tools-deploy-how-to-register-tools-with-an-agent.md +82 -0
  757. package/dist/serve/docs-ui/docs/pyric-tools-deploy-reference-api/index.html +155 -0
  758. package/dist/serve/docs-ui/docs/pyric-tools-deploy-reference-api.md +190 -0
  759. package/dist/serve/docs-ui/docs/pyric-tools-deploy-reference-cli-agent-io/index.html +84 -0
  760. package/dist/serve/docs-ui/docs/pyric-tools-deploy-reference-cli-agent-io.md +66 -0
  761. package/dist/serve/docs-ui/docs/pyric-tools-deploy-reference-error-codes/index.html +71 -0
  762. package/dist/serve/docs-ui/docs/pyric-tools-deploy-reference-error-codes.md +102 -0
  763. package/dist/serve/docs-ui/docs/pyric-tools-deploy-reference-firestore-namespace/index.html +138 -0
  764. package/dist/serve/docs-ui/docs/pyric-tools-deploy-reference-firestore-namespace.md +175 -0
  765. package/dist/serve/docs-ui/docs/pyric-tools-deploy-reference-functions-namespace/index.html +105 -0
  766. package/dist/serve/docs-ui/docs/pyric-tools-deploy-reference-functions-namespace.md +133 -0
  767. package/dist/serve/docs-ui/docs/pyric-tools-deploy-reference-hosting-config/index.html +194 -0
  768. package/dist/serve/docs-ui/docs/pyric-tools-deploy-reference-hosting-config.md +95 -0
  769. package/dist/serve/docs-ui/docs/pyric-tools-deploy-reference-hosting-namespace/index.html +99 -0
  770. package/dist/serve/docs-ui/docs/pyric-tools-deploy-reference-hosting-namespace.md +126 -0
  771. package/dist/serve/docs-ui/docs/pyric-tools-deploy-reference-rtdb-namespace/index.html +110 -0
  772. package/dist/serve/docs-ui/docs/pyric-tools-deploy-reference-rtdb-namespace.md +133 -0
  773. package/dist/serve/docs-ui/docs/pyric-tools-deploy-reference-scope-and-outcome/index.html +68 -0
  774. package/dist/serve/docs-ui/docs/pyric-tools-deploy-reference-scope-and-outcome.md +88 -0
  775. package/dist/serve/docs-ui/docs/pyric-tools-deploy-reference-tool-factories/index.html +160 -0
  776. package/dist/serve/docs-ui/docs/pyric-tools-deploy-reference-tool-factories.md +104 -0
  777. package/dist/serve/docs-ui/docs/pyric-tools-deploy-tutorials-01-deploy-a-cloud-function/index.html +88 -0
  778. package/dist/serve/docs-ui/docs/pyric-tools-deploy-tutorials-01-deploy-a-cloud-function.md +119 -0
  779. package/dist/serve/docs-ui/docs/pyric-tools-deploy.md +52 -0
  780. package/dist/serve/docs-ui/docs/pyric-tools-how-to-build-a-standalone-binary/index.html +92 -0
  781. package/dist/serve/docs-ui/docs/pyric-tools-how-to-build-a-standalone-binary.md +95 -0
  782. package/dist/serve/docs-ui/docs/pyric-tools-how-to-configure-auth-providers-and-domains/index.html +101 -0
  783. package/dist/serve/docs-ui/docs/pyric-tools-how-to-configure-auth-providers-and-domains.md +126 -0
  784. package/dist/serve/docs-ui/docs/pyric-tools-how-to-discover-a-schema-from-firestore/index.html +95 -0
  785. package/dist/serve/docs-ui/docs/pyric-tools-how-to-discover-a-schema-from-firestore.md +119 -0
  786. package/dist/serve/docs-ui/docs/pyric-tools-how-to-promote-sandbox-state-to-a-fixture/index.html +52 -0
  787. package/dist/serve/docs-ui/docs/pyric-tools-how-to-promote-sandbox-state-to-a-fixture.md +83 -0
  788. package/dist/serve/docs-ui/docs/pyric-tools-how-to-serve-persistence-and-multi-tab/index.html +232 -0
  789. package/dist/serve/docs-ui/docs/pyric-tools-how-to-serve-persistence-and-multi-tab.md +178 -0
  790. package/dist/serve/docs-ui/docs/pyric-tools-how-to-use-the-vite-plugin/index.html +340 -0
  791. package/dist/serve/docs-ui/docs/pyric-tools-how-to-use-the-vite-plugin.md +288 -0
  792. package/dist/serve/docs-ui/docs/pyric-tools-how-to-verify-against-a-captured-session/index.html +128 -0
  793. package/dist/serve/docs-ui/docs/pyric-tools-how-to-verify-against-a-captured-session.md +173 -0
  794. package/dist/serve/docs-ui/docs/pyric-tools-reference-cli/index.html +433 -0
  795. package/dist/serve/docs-ui/docs/pyric-tools-reference-cli.md +252 -0
  796. package/dist/serve/docs-ui/docs/pyric-tools-reference-verify/index.html +81 -0
  797. package/dist/serve/docs-ui/docs/pyric-tools-reference-verify.md +98 -0
  798. package/dist/serve/docs-ui/docs/pyric-tools-tutorials-getting-started/index.html +92 -0
  799. package/dist/serve/docs-ui/docs/pyric-tools-tutorials-getting-started.md +117 -0
  800. package/dist/serve/docs-ui/docs/pyric-tools-tutorials-server-adoption/index.html +221 -0
  801. package/dist/serve/docs-ui/docs/pyric-tools-tutorials-server-adoption.md +239 -0
  802. package/dist/serve/docs-ui/docs/pyric-tools-tutorials-wire-claude-code/index.html +205 -0
  803. package/dist/serve/docs-ui/docs/pyric-tools-tutorials-wire-claude-code.md +250 -0
  804. package/dist/serve/docs-ui/docs/pyric-tools.md +47 -0
  805. package/dist/serve/docs-ui/docs/ui/index.html +76 -0
  806. package/dist/serve/docs-ui/docs/ui-auth-authsigninhelper/index.html +194 -0
  807. package/dist/serve/docs-ui/docs/ui-auth-authsigninhelper.md +108 -0
  808. package/dist/serve/docs-ui/docs/ui-auth-authusers/index.html +258 -0
  809. package/dist/serve/docs-ui/docs/ui-auth-authusers.md +174 -0
  810. package/dist/serve/docs-ui/docs/ui-firestore-collectionlist/index.html +92 -0
  811. package/dist/serve/docs-ui/docs/ui-firestore-collectionlist.md +71 -0
  812. package/dist/serve/docs-ui/docs/ui-firestore-deletewithconfirm/index.html +128 -0
  813. package/dist/serve/docs-ui/docs/ui-firestore-deletewithconfirm.md +94 -0
  814. package/dist/serve/docs-ui/docs/ui-firestore-documenteditor/index.html +154 -0
  815. package/dist/serve/docs-ui/docs/ui-firestore-documenteditor.md +107 -0
  816. package/dist/serve/docs-ui/docs/ui-firestore-documentlist/index.html +132 -0
  817. package/dist/serve/docs-ui/docs/ui-firestore-documentlist.md +86 -0
  818. package/dist/serve/docs-ui/docs/ui-firestore-documentpreview/index.html +159 -0
  819. package/dist/serve/docs-ui/docs/ui-firestore-documentpreview.md +78 -0
  820. package/dist/serve/docs-ui/docs/ui-firestore-querybuilder/index.html +104 -0
  821. package/dist/serve/docs-ui/docs/ui-firestore-querybuilder.md +95 -0
  822. package/dist/serve/docs-ui/docs/ui-firestore-referencepicker/index.html +113 -0
  823. package/dist/serve/docs-ui/docs/ui-firestore-referencepicker.md +85 -0
  824. package/dist/serve/docs-ui/docs/ui-primitives-badge/index.html +56 -0
  825. package/dist/serve/docs-ui/docs/ui-primitives-badge.md +34 -0
  826. package/dist/serve/docs-ui/docs/ui-primitives-confirmdialog/index.html +140 -0
  827. package/dist/serve/docs-ui/docs/ui-primitives-confirmdialog.md +98 -0
  828. package/dist/serve/docs-ui/docs/ui-primitives-copybutton/index.html +58 -0
  829. package/dist/serve/docs-ui/docs/ui-primitives-copybutton.md +31 -0
  830. package/dist/serve/docs-ui/docs/ui-primitives-jsonview/index.html +58 -0
  831. package/dist/serve/docs-ui/docs/ui-primitives-jsonview.md +41 -0
  832. package/dist/serve/docs-ui/docs/ui-primitives-segmentedcontrol/index.html +70 -0
  833. package/dist/serve/docs-ui/docs/ui-primitives-segmentedcontrol.md +44 -0
  834. package/dist/serve/docs-ui/docs/ui-primitives-toast/index.html +87 -0
  835. package/dist/serve/docs-ui/docs/ui-primitives-toast.md +74 -0
  836. package/dist/serve/docs-ui/docs/ui-primitives-virtuallist/index.html +85 -0
  837. package/dist/serve/docs-ui/docs/ui-primitives-virtuallist.md +48 -0
  838. package/dist/serve/docs-ui/docs/ui-storage/index.html +154 -0
  839. package/dist/serve/docs-ui/docs/ui-storage-deleteselectionwithconfirm/index.html +127 -0
  840. package/dist/serve/docs-ui/docs/ui-storage-deleteselectionwithconfirm.md +83 -0
  841. package/dist/serve/docs-ui/docs/ui-storage-objectbrowser/index.html +137 -0
  842. package/dist/serve/docs-ui/docs/ui-storage-objectbrowser.md +84 -0
  843. package/dist/serve/docs-ui/docs/ui-storage-objectinspector/index.html +139 -0
  844. package/dist/serve/docs-ui/docs/ui-storage-objectinspector.md +87 -0
  845. package/dist/serve/docs-ui/docs/ui-storage-pathbreadcrumb/index.html +72 -0
  846. package/dist/serve/docs-ui/docs/ui-storage-pathbreadcrumb.md +51 -0
  847. package/dist/serve/docs-ui/docs/ui-storage-rules-aware-affordances/index.html +65 -0
  848. package/dist/serve/docs-ui/docs/ui-storage-rules-aware-affordances.md +53 -0
  849. package/dist/serve/docs-ui/docs/ui-storage-uploaddropzone/index.html +93 -0
  850. package/dist/serve/docs-ui/docs/ui-storage-uploaddropzone.md +72 -0
  851. package/dist/serve/docs-ui/docs/ui-storage-usestoragerulesgate/index.html +153 -0
  852. package/dist/serve/docs-ui/docs/ui-storage-usestoragerulesgate.md +105 -0
  853. package/dist/serve/docs-ui/docs/ui-storage.md +165 -0
  854. package/dist/serve/docs-ui/docs/ui-traffic/index.html +88 -0
  855. package/dist/serve/docs-ui/docs/ui-traffic-ruleheatmap/index.html +79 -0
  856. package/dist/serve/docs-ui/docs/ui-traffic-ruleheatmap.md +54 -0
  857. package/dist/serve/docs-ui/docs/ui-traffic-trafficdetail/index.html +78 -0
  858. package/dist/serve/docs-ui/docs/ui-traffic-trafficdetail.md +53 -0
  859. package/dist/serve/docs-ui/docs/ui-traffic-trafficlog/index.html +118 -0
  860. package/dist/serve/docs-ui/docs/ui-traffic-trafficlog.md +63 -0
  861. package/dist/serve/docs-ui/docs/ui-traffic-trafficstats/index.html +51 -0
  862. package/dist/serve/docs-ui/docs/ui-traffic-trafficstats.md +40 -0
  863. package/dist/serve/docs-ui/docs/ui-traffic.md +96 -0
  864. package/dist/serve/docs-ui/docs/ui.md +88 -0
  865. package/dist/serve/docs-ui/index.html +1 -0
  866. package/dist/serve/docs-ui/llms.txt +221 -0
  867. package/dist/serve/entries/app.d.ts +26 -0
  868. package/dist/serve/entries/app.d.ts.map +1 -0
  869. package/dist/serve/entries/app.js +51 -0
  870. package/dist/serve/entries/app.js.map +1 -0
  871. package/dist/serve/entries/auth-helper-core.d.ts +47 -0
  872. package/dist/serve/entries/auth-helper-core.d.ts.map +1 -0
  873. package/dist/serve/entries/auth-helper-core.js +121 -0
  874. package/dist/serve/entries/auth-helper-core.js.map +1 -0
  875. package/dist/serve/entries/auth-helper-dom.d.ts +14 -0
  876. package/dist/serve/entries/auth-helper-dom.d.ts.map +1 -0
  877. package/dist/serve/entries/auth-helper-dom.js +116 -0
  878. package/dist/serve/entries/auth-helper-dom.js.map +1 -0
  879. package/dist/serve/entries/auth.d.ts +62 -0
  880. package/dist/serve/entries/auth.d.ts.map +1 -0
  881. package/dist/serve/entries/auth.js +128 -0
  882. package/dist/serve/entries/auth.js.map +1 -0
  883. package/dist/serve/entries/bridge-url.d.ts +25 -0
  884. package/dist/serve/entries/bridge-url.d.ts.map +1 -0
  885. package/dist/serve/entries/bridge-url.js +30 -0
  886. package/dist/serve/entries/bridge-url.js.map +1 -0
  887. package/dist/serve/entries/database.d.ts +34 -0
  888. package/dist/serve/entries/database.d.ts.map +1 -0
  889. package/dist/serve/entries/database.js +42 -0
  890. package/dist/serve/entries/database.js.map +1 -0
  891. package/dist/serve/entries/firestore.d.ts +68 -0
  892. package/dist/serve/entries/firestore.d.ts.map +1 -0
  893. package/dist/serve/entries/firestore.js +115 -0
  894. package/dist/serve/entries/firestore.js.map +1 -0
  895. package/dist/serve/entries/init.d.ts +2 -0
  896. package/dist/serve/entries/init.d.ts.map +1 -0
  897. package/dist/serve/entries/init.js +14 -0
  898. package/dist/serve/entries/init.js.map +1 -0
  899. package/dist/serve/entries/keepalive.d.ts +20 -0
  900. package/dist/serve/entries/keepalive.d.ts.map +1 -0
  901. package/dist/serve/entries/keepalive.js +38 -0
  902. package/dist/serve/entries/keepalive.js.map +1 -0
  903. package/dist/serve/entries/runtime.d.ts +60 -0
  904. package/dist/serve/entries/runtime.d.ts.map +1 -0
  905. package/dist/serve/entries/runtime.js +555 -0
  906. package/dist/serve/entries/runtime.js.map +1 -0
  907. package/dist/serve/entries/session-store.d.ts +45 -0
  908. package/dist/serve/entries/session-store.d.ts.map +1 -0
  909. package/dist/serve/entries/session-store.js +71 -0
  910. package/dist/serve/entries/session-store.js.map +1 -0
  911. package/dist/serve/entries/storage.d.ts +23 -0
  912. package/dist/serve/entries/storage.d.ts.map +1 -0
  913. package/dist/serve/entries/storage.js +49 -0
  914. package/dist/serve/entries/storage.js.map +1 -0
  915. package/dist/serve/entries/tab-sync-wiring.d.ts +88 -0
  916. package/dist/serve/entries/tab-sync-wiring.d.ts.map +1 -0
  917. package/dist/serve/entries/tab-sync-wiring.js +217 -0
  918. package/dist/serve/entries/tab-sync-wiring.js.map +1 -0
  919. package/dist/serve/namespace.d.ts +93 -0
  920. package/dist/serve/namespace.d.ts.map +1 -0
  921. package/dist/serve/namespace.js +367 -0
  922. package/dist/serve/namespace.js.map +1 -0
  923. package/dist/serve/open-browser.d.ts +23 -0
  924. package/dist/serve/open-browser.d.ts.map +1 -0
  925. package/dist/serve/open-browser.js +58 -0
  926. package/dist/serve/open-browser.js.map +1 -0
  927. package/dist/serve/playground-ui/_astro/HomePage.BFOjZGZp.js +2 -0
  928. package/dist/serve/playground-ui/_astro/PlaygroundPage.3jki8il0.js +3300 -0
  929. package/dist/serve/playground-ui/_astro/PlaygroundPage.MhP_vFvK.css +32 -0
  930. package/dist/serve/playground-ui/_astro/TopBar.ChifCjLD.js +5025 -0
  931. package/dist/serve/playground-ui/_astro/client.D61DpmEZ.js +274 -0
  932. package/dist/serve/playground-ui/_astro/client.GCK88uRs.js +1 -0
  933. package/dist/serve/playground-ui/_astro/esbuild.BHljloGq.wasm +0 -0
  934. package/dist/serve/playground-ui/_astro/index.B9aLOeXL.js +29 -0
  935. package/dist/serve/playground-ui/_astro/index.BTKE7uRF.js +1227 -0
  936. package/dist/serve/playground-ui/_astro/index.BfCthstx.css +1 -0
  937. package/dist/serve/playground-ui/_astro/index.Mr0Dtjih.js +342 -0
  938. package/dist/serve/playground-ui/_astro/ort-wasm-simd-threaded.asyncify.DMmc6YqF.wasm +0 -0
  939. package/dist/serve/playground-ui/favicon.svg +4 -0
  940. package/dist/serve/playground-ui/index.html +2 -0
  941. package/dist/serve/playground-ui/playground/index.html +2 -0
  942. package/dist/serve/rules.d.ts +45 -0
  943. package/dist/serve/rules.d.ts.map +1 -0
  944. package/dist/serve/rules.js +139 -0
  945. package/dist/serve/rules.js.map +1 -0
  946. package/dist/serve/sandbox-marker.d.ts +9 -0
  947. package/dist/serve/sandbox-marker.d.ts.map +1 -0
  948. package/dist/serve/sandbox-marker.js +32 -0
  949. package/dist/serve/sandbox-marker.js.map +1 -0
  950. package/dist/serve/server.d.ts +135 -0
  951. package/dist/serve/server.d.ts.map +1 -0
  952. package/dist/serve/server.js +328 -0
  953. package/dist/serve/server.js.map +1 -0
  954. package/dist/serve/standalone-assets.d.ts +66 -0
  955. package/dist/serve/standalone-assets.d.ts.map +1 -0
  956. package/dist/serve/standalone-assets.js +144 -0
  957. package/dist/serve/standalone-assets.js.map +1 -0
  958. package/dist/serve/state-store.d.ts +45 -0
  959. package/dist/serve/state-store.d.ts.map +1 -0
  960. package/dist/serve/state-store.js +120 -0
  961. package/dist/serve/state-store.js.map +1 -0
  962. package/dist/serve/studio/disk-project-store.d.ts +9 -0
  963. package/dist/serve/studio/disk-project-store.d.ts.map +1 -0
  964. package/dist/serve/studio/disk-project-store.js +173 -0
  965. package/dist/serve/studio/disk-project-store.js.map +1 -0
  966. package/dist/serve/studio/disk-workspace.d.ts +14 -0
  967. package/dist/serve/studio/disk-workspace.d.ts.map +1 -0
  968. package/dist/serve/studio/disk-workspace.js +152 -0
  969. package/dist/serve/studio/disk-workspace.js.map +1 -0
  970. package/dist/serve/studio/index.d.ts +10 -0
  971. package/dist/serve/studio/index.d.ts.map +1 -0
  972. package/dist/serve/studio/index.js +9 -0
  973. package/dist/serve/studio/index.js.map +1 -0
  974. package/dist/serve/studio/routes.d.ts +40 -0
  975. package/dist/serve/studio/routes.d.ts.map +1 -0
  976. package/dist/serve/studio/routes.js +182 -0
  977. package/dist/serve/studio/routes.js.map +1 -0
  978. package/dist/serve/studio/store-types.d.ts +45 -0
  979. package/dist/serve/studio/store-types.d.ts.map +1 -0
  980. package/dist/serve/studio/store-types.js +11 -0
  981. package/dist/serve/studio/store-types.js.map +1 -0
  982. package/dist/serve/studio-ui/assets/RulesCodeEditor-Bncteb8z.js +23 -0
  983. package/dist/serve/studio-ui/assets/index-CmkJxFcG.js +4577 -0
  984. package/dist/serve/studio-ui/assets/index-DNK39P6C.css +1 -0
  985. package/dist/serve/studio-ui/assets/seed-Bab4ArXQ.js +2 -0
  986. package/dist/serve/studio-ui/index.html +32 -0
  987. package/dist/serve/studio-ui/pyric-logo.svg +76 -0
  988. package/dist/serve/vite-plugin.d.ts +57 -0
  989. package/dist/serve/vite-plugin.d.ts.map +1 -0
  990. package/dist/serve/vite-plugin.js +687 -0
  991. package/dist/serve/vite-plugin.js.map +1 -0
  992. package/dist/serve/worker/client.d.ts +668 -0
  993. package/dist/serve/worker/client.d.ts.map +1 -0
  994. package/dist/serve/worker/client.js +1635 -0
  995. package/dist/serve/worker/client.js.map +1 -0
  996. package/dist/serve/worker/entry.d.ts +40 -0
  997. package/dist/serve/worker/entry.d.ts.map +1 -0
  998. package/dist/serve/worker/entry.js +122 -0
  999. package/dist/serve/worker/entry.js.map +1 -0
  1000. package/dist/serve/worker/host-auth.d.ts +47 -0
  1001. package/dist/serve/worker/host-auth.d.ts.map +1 -0
  1002. package/dist/serve/worker/host-auth.js +434 -0
  1003. package/dist/serve/worker/host-auth.js.map +1 -0
  1004. package/dist/serve/worker/host-context.d.ts +180 -0
  1005. package/dist/serve/worker/host-context.d.ts.map +1 -0
  1006. package/dist/serve/worker/host-context.js +45 -0
  1007. package/dist/serve/worker/host-context.js.map +1 -0
  1008. package/dist/serve/worker/host-events.d.ts +21 -0
  1009. package/dist/serve/worker/host-events.d.ts.map +1 -0
  1010. package/dist/serve/worker/host-events.js +87 -0
  1011. package/dist/serve/worker/host-events.js.map +1 -0
  1012. package/dist/serve/worker/host.d.ts +81 -0
  1013. package/dist/serve/worker/host.d.ts.map +1 -0
  1014. package/dist/serve/worker/host.js +1605 -0
  1015. package/dist/serve/worker/host.js.map +1 -0
  1016. package/dist/serve/worker/index.d.ts +22 -0
  1017. package/dist/serve/worker/index.d.ts.map +1 -0
  1018. package/dist/serve/worker/index.js +61 -0
  1019. package/dist/serve/worker/index.js.map +1 -0
  1020. package/dist/serve/worker/protocol.d.ts +955 -0
  1021. package/dist/serve/worker/protocol.d.ts.map +1 -0
  1022. package/dist/serve/worker/protocol.js +197 -0
  1023. package/dist/serve/worker/protocol.js.map +1 -0
  1024. package/dist/serve/worker/serve-init.d.ts +192 -0
  1025. package/dist/serve/worker/serve-init.d.ts.map +1 -0
  1026. package/dist/serve/worker/serve-init.js +555 -0
  1027. package/dist/serve/worker/serve-init.js.map +1 -0
  1028. package/dist/serve/writer-lock.d.ts +32 -0
  1029. package/dist/serve/writer-lock.d.ts.map +1 -0
  1030. package/dist/serve/writer-lock.js +43 -0
  1031. package/dist/serve/writer-lock.js.map +1 -0
  1032. package/dist/verify/cases.d.ts +37 -0
  1033. package/dist/verify/cases.d.ts.map +1 -0
  1034. package/dist/verify/cases.js +173 -0
  1035. package/dist/verify/cases.js.map +1 -0
  1036. package/dist/verify/fixture.d.ts +79 -0
  1037. package/dist/verify/fixture.d.ts.map +1 -0
  1038. package/dist/verify/fixture.js +114 -0
  1039. package/dist/verify/fixture.js.map +1 -0
  1040. package/dist/verify/index.d.ts +102 -0
  1041. package/dist/verify/index.d.ts.map +1 -0
  1042. package/dist/verify/index.js +318 -0
  1043. package/dist/verify/index.js.map +1 -0
  1044. package/dist/verify/tools.d.ts +7 -0
  1045. package/dist/verify/tools.d.ts.map +1 -0
  1046. package/dist/verify/tools.js +83 -0
  1047. package/dist/verify/tools.js.map +1 -0
  1048. package/dist/vite.d.ts +12 -0
  1049. package/dist/vite.d.ts.map +1 -0
  1050. package/dist/vite.js +16 -0
  1051. package/dist/vite.js.map +1 -0
  1052. package/package.json +127 -0
@@ -0,0 +1,850 @@
1
+ <!-- Generated from scripts/compat/registry/*.ts. Do not edit by hand; run bun run compat:generate. -->
2
+
3
+ # `@pyric/rtdb` compatibility matrix
4
+
5
+ > ⚠ **EXPERIMENTAL — not v1-supported.** `@pyric/rtdb` is functional but
6
+ > work-in-progress. The v1-supported, conformance-held surface is **auth +
7
+ > firestore + rules**. The modular `firebase/database` shim rows below are
8
+ > verified **sandbox-side** by unit probes — they are NOT wrong — but most are
9
+ > **not yet captured against a live prod project** (no `oracle:` citation), so
10
+ > conformance is best-effort, not guaranteed. Don't depend on RTDB parity for a
11
+ > production swap yet.
12
+
13
+ The single readable contract for "what this package guarantees vs the
14
+ production Firebase Realtime Database surface."
15
+
16
+ `@pyric/rtdb` covers **two surfaces** that share the package:
17
+
18
+ 1. **Agent-tool surface (shipped today).** The factory +
19
+ rule-authoring DSL — the 11 agent tools returned by
20
+ `createRtdbAdminTools({ host })` (`rtdb_get`, `rtdb_set`,
21
+ `rtdb_update`, `rtdb_push`, `rtdb_delete`, `rtdb_get_rules`,
22
+ `rtdb_deploy_rules`, `rtdb_crawl_structure`,
23
+ `rtdb_simulate_access`, `rtdb_validated_write`,
24
+ `rtdb_build_expression`); the lower-level `getRtdbTools(host)`
25
+ programmatic API; the `RtdbHost` contract and `fetchDatabase`
26
+ REST helper; the constraint authoring surface (`atoms`,
27
+ `policies`, `compose`, `ruleset`); the IR / simulator / mapper
28
+ internals.
29
+ 2. **Modular SDK surface.** A `firebase/database`
30
+ modular shim — `getDatabase`, `ref`, `get`, `set`, `update`,
31
+ `remove`, `push`, `onValue`, `runTransaction`, `query` constraints,
32
+ sentinels, etc. — mirroring how `pyric/auth`, `pyric/firestore`,
33
+ `pyric/storage` ship both an agent-tool surface AND a modular
34
+ SDK surface in one package. The sandbox implementation lives in
35
+ `packages/pyric/src/database/modular.ts` + `packages/pyric/src/database/sandbox/`;
36
+ the matrix below tracks which rows the sandbox locks (✓) vs. those
37
+ still pending implementation (—). Oracle probes capture prod
38
+ behavior so the sandbox is correct by construction.
39
+
40
+ The package wraps two real services:
41
+
42
+ - The **RTDB REST API** for admin-mode data ops and rules deploy
43
+ (`fetchDatabase` → `${databaseUrl}<path>.json?…`).
44
+ - The **`firebase/database` modular SDK** for user-mode data ops
45
+ (via `host.getClientForUser(auth)`).
46
+ - The **`firebase-admin/database` SDK** for admin-mode data ops in
47
+ the data handler.
48
+
49
+ The conformance oracle for this package therefore observes a
50
+ mixture: REST-shape claims (status codes, response bodies) and
51
+ `firebase/database` SDK-shape claims (ref/get/set/onValue/push
52
+ semantics). The sandbox-vs-prod axis is much smaller here than in
53
+ `pyric/auth` or `pyric/firestore` because there is no in-process
54
+ RTDB sandbox — the package goes directly to the real service. The
55
+ relevant divergence axis is **simulator-vs-prod-rules-engine** for
56
+ the rule-evaluation surface.
57
+
58
+ See the design rationale for the methodology
59
+ (vocabulary of conformance / oracle / matrix; how to add rows;
60
+ how the runner attributes failures).
61
+
62
+ ## Status legend
63
+
64
+ | Status | Meaning |
65
+ |---|---|
66
+ | ✓ | **Conforming** — observable behavior matches the wrapped service, locked by a passing probe |
67
+ | ⚠ | **Diverged (documented)** — intentional difference with a written reason |
68
+ | ✗ | **Bug** — should match the wrapped service but doesn't; failing probe pins it |
69
+ | — | **Unsupported** — not implemented yet (deliberately or pending) |
70
+ | ? | **Unverified** — claim from docs / source that we haven't yet observed against the live service |
71
+
72
+ Probe references: `unit:<file>` means a Bun test in
73
+ `packages/pyric/test/database/<file>`. `oracle:<obs>` means an observation
74
+ under `scripts/oracle/observations/<obs>.json`.
75
+
76
+ Targets:
77
+ - **admin** — operations run via `firebase-admin/database` or the
78
+ REST `access_token=…` path. Rules bypassed.
79
+ - **user** — operations run via `host.getClientForUser(auth)`
80
+ (`firebase/database` modular SDK) or REST `auth=…`. Rules
81
+ enforced.
82
+ - **simulator** — the in-process rule evaluator
83
+ (`SimulateHandler` + the Ohm-based RTDB expression grammar).
84
+ Acts as the oracle for "would prod allow this?" questions
85
+ without round-tripping.
86
+
87
+ ---
88
+
89
+ ## `RtdbHost` contract + `fetchDatabase`
90
+
91
+ | # | Behavior | Status | Probe |
92
+ |---|---|---|---|
93
+ | 1 | `fetchDatabase(host, path)` (no userToken) calls the URL `<databaseUrl><path>?access_token=<adminToken>` | ✓ | `unit:host.test.ts` |
94
+ | 2 | `fetchDatabase(host, path, params, userToken)` uses `auth=<userToken>` (NOT `access_token`) and does NOT call `resolveAdminToken()` | ✓ | `unit:host.test.ts` |
95
+ | 3 | Extra `params` are merged into the URL query string alongside the auth param | ✓ | `unit:host.test.ts` |
96
+ | 4 | `host.databaseUrl` is concatenated as a prefix to the path | ✓ | `unit:host.test.ts` |
97
+ | 5 | REST endpoints respond on `<databaseUrl><path>.json` — `.json` suffix is the RTDB REST contract | ✓ | oracle: `scripts/oracle/observations/rtdb-rest-json-suffix-contract.json` — `<path>.json` returned `application/json; charset=utf-8` and round-tripped the seeded payload; the same URL WITHOUT the `.json` suffix returned `text/html; charset=utf-8` (the Google sign-in redirector page). Locks the `.json`-suffix contract every handler that calls `fetchDatabase` depends on. |
98
+
99
+ ## `getRtdbTools(host)` — programmatic surface
100
+
101
+ | # | Behavior | Status | Probe |
102
+ |---|---|---|---|
103
+ | 6 | Returns an object with the 11 methods listed in the `RtdbTools` interface (`generateIR`, `simulate`, `writeRules`, `crawlStructure`, `readData`, `setData`, `updateData`, `pushData`, `removeData`, `validatedWrite`) | ✓ | `unit:resolver.test.ts` |
104
+ | 7 | `simulate()` returns `IR_NOT_GENERATED` until `generateIR()` has been called and cached the IR | ✓ | `unit:simulation/handler.test.ts` |
105
+ | 8 | After a successful `generateIR()`, the resolver caches the IR for subsequent `simulate()` calls | ✓ | `unit:resolver.test.ts` |
106
+ | 9 | `crawlStructure({ auth })` resolves the user token via `host.resolveUserToken` before crawling; a token-resolution failure surfaces as `PERMISSION_DENIED` | ✓ | `unit:resolver.test.ts` |
107
+
108
+ ## `rtdb_get` / `readData(path)` — single-path read
109
+
110
+ | # | Behavior | Status | Probe |
111
+ |---|---|---|---|
112
+ | 10 | Admin mode returns `{ success: true, data: <value or null> }`; uses `firebase-admin/database` `ref(path).get().val()` | ✓ | `unit:data/handler.test.ts`; oracle: `scripts/oracle/observations/rtdb-handler-admin-vs-user-returnshape.json` — admin-SDK `ref(path).get().val()` returned the exact seeded payload, wrapped as `{ success: true, data: <value> }`. |
113
+ | 11 | User mode returns `{ success: true, data: <value or null> }`; uses `firebase/database` modular `get(ref(db, path)).val()` via `host.getClientForUser(auth)` | ✓ | `unit:data/handler.test.ts`; oracle: `scripts/oracle/observations/rtdb-handler-admin-vs-user-returnshape.json` — `shapesAgree: true` between admin and modular paths against blockingfun (same `data` value, same `success: true` shape). |
114
+ | 12 | `null` at the path (empty / missing) round-trips as `data: null` (NOT a not-found error) — matches `DataSnapshot.val()` returning `null` for absent paths | ✓ | `unit:data/handler.test.ts` ("admin GET returns null for empty path") |
115
+ | 13 | Any thrown error in admin mode is wrapped as `{ success: false, error: { code: 'READ_FAILED', recoverable: false } }` | ✓ | `unit:data/handler.test.ts` |
116
+ | 14 | Rules-denied user-mode `get`/`set`/`update`/`push`/`remove` surface as `{ success: false, error: { code: 'PERMISSION_DENIED', recoverable: false } }` — the handler inspects the caught error before the generic `READ_FAILED` / `WRITE_FAILED` wrap and preserves the `PERMISSION_DENIED` signal. The inspection matches both `(err.code === 'PERMISSION_DENIED')` and `(err.message.toLowerCase().includes('permission_denied'))` so it covers the uppercase `set/get/remove` shape AND the lowercase `runTransaction` shape from oracle row #15 / M37e. Non-rules errors (network, token mint, etc.) still surface as `READ_FAILED` / `WRITE_FAILED`. | ✓ | `unit:data/handler.test.ts` ("rules-denied GET/SET/REMOVE surfaces as PERMISSION_DENIED", "non-rules error for GET/SET still surfaces as READ_FAILED/WRITE_FAILED", "transaction-shaped rules-denied (lowercase message, no .code) surfaces as PERMISSION_DENIED"); oracles cited: `scripts/oracle/observations/rtdb-rules-denied-error-code.json` + `scripts/oracle/observations/rtdb-modular-runtransaction-on-rules-denied-path.json` |
117
+ | 15 | Rules-denied read against the real RTDB throws on the modular SDK side with `code: 'PERMISSION_DENIED'` and message `PERMISSION_DENIED: Permission denied`. The thrown value is a **plain `Error`** (not a `FirebaseError`) — `.name === 'Error'`, `.constructor.name === 'Error'` — diverging from Firestore/Auth which throw `FirebaseError`. | ✓ | oracle: `scripts/oracle/observations/rtdb-rules-denied-error-code.json` (`code: 'PERMISSION_DENIED'`, `errorName: 'Error'`, `constructorName: 'Error'`, `isErrorInstance: true` against blockingfun, fb-js-sdk 12.13.0; observed on the `set` path — the `get`/`set` paths share the same error-emit code in firebase/database) |
118
+
119
+ ## `rtdb_set` / `setData(path, data)` — full overwrite
120
+
121
+ | # | Behavior | Status | Probe |
122
+ |---|---|---|---|
123
+ | 16 | Admin mode replaces the value at the path entirely; resolves `{ success: true, data: null }` | ✓ | `unit:data/handler.test.ts` |
124
+ | 17 | User mode replaces via the modular SDK's `set(ref, data)` | ✓ | `unit:data/handler.test.ts` |
125
+ | 18 | Setting `null` at a path is equivalent to removing it (matches RTDB's documented behavior) | ✓ | oracle: `scripts/oracle/observations/rtdb-remove-vs-set-null.json` — observed `afterRemove: null === afterSetNull: null` against blockingfun, fb-js-sdk 12.13.0; sandbox-aligned: `unit:modular/sandbox-target.test.ts` ("remove and set(null) produce identical end-state") |
126
+ | 19 | Errors on either path wrap as `{ success: false, error: { code: 'WRITE_FAILED', recoverable: false } }` | ✓ | `unit:data/handler.test.ts` |
127
+ | 20 | Rules-denied write against the real RTDB throws with `code: 'PERMISSION_DENIED'` (uppercase, snake-case — distinct from Firestore's lowercase-kebab `'permission-denied'`) and message `PERMISSION_DENIED: Permission denied`. The thrown value is a **plain `Error`**, not a `FirebaseError`. | ✓ | oracle: `scripts/oracle/observations/rtdb-rules-denied-error-code.json` (against blockingfun, fb-js-sdk 12.13.0) |
128
+
129
+ ## `rtdb_update` / `updateData(path, data)` — partial / multi-location update
130
+
131
+ | # | Behavior | Status | Probe |
132
+ |---|---|---|---|
133
+ | 21 | Admin mode merges top-level keys at the path; resolves `{ success: true, data: null }` | ✓ | `unit:data/handler.test.ts` |
134
+ | 22 | User mode merges via the modular SDK's `update(ref, data)` | ✓ | `unit:data/handler.test.ts` |
135
+ | 23 | When `path === '/'` and `data` keys are root-relative paths (e.g. `{ '/users/alice/name': 'A', '/posts/p1/author': 'alice' }`), the underlying SDK performs an atomic fan-out write at every listed path | ✓ | oracle: `scripts/oracle/observations/rtdb-modular-update-multipath-atomic.json` — `update(parentRef, { 'a/x': 1, 'b/y': 2 })` landed both writes; see also `rtdb-modular-update-multipath-rules-denial.json` for the atomic rollback when one path is denied. Sandbox-aligned: `unit:modular/sandbox-target.test.ts` ("writes every listed path atomically" + "rejects the entire update if rules deny any one path") |
136
+ | 24 | Update operations are validated for syntax (overlapping paths, invalid characters) by the underlying SDK; surface as `WRITE_FAILED` | ✓ | Sandbox aligned: locked by unit test `packages/pyric/test/database/modular/sandbox-target.test.ts` ("rejects overlapping paths") — descendant-path overlap throws before any path is written |
137
+
138
+ ## `rtdb_push` / `pushData(path, data)` — auto-id append
139
+
140
+ | # | Behavior | Status | Probe |
141
+ |---|---|---|---|
142
+ | 25 | Admin mode returns `{ success: true, data: { key: <auto-id> } }` | ✓ | `unit:data/handler.test.ts` |
143
+ | 26 | User mode returns `{ success: true, data: { key: <auto-id> } }` via `push(ref, data).key` | ✓ | `unit:data/handler.test.ts` |
144
+ | 27 | Auto-id format is RTDB's "push ID": 20 characters, starts with `-`, lexicographically sortable, timestamp-prefixed (encodes the millisecond timestamp at the time of generation) | ✓ | oracle: `scripts/oracle/observations/rtdb-push-autoid-format.json` (against blockingfun, fb-js-sdk 12.13.0: 3 sequential `push()` calls returned 20-char keys (`-OsshG1AxGukSGUYn_De`, `-OsshG1GZ2pAt7bveAWv`, `-OsshG1NmrNFxZuwufff`), all starting with `-`. The `push.key` is minted client-side from the millisecond timestamp + randomness — it's available immediately even when the subsequent server write is denied by rules.) |
145
+ | 28 | Two `push` calls in quick succession produce monotonically sortable keys (the timestamp prefix guarantees order) | ✓ | oracle: `scripts/oracle/observations/rtdb-push-autoid-format.json` (`monotonicallySorted: true` across 3 keys generated ~5ms apart) |
146
+
147
+ ## `rtdb_delete` / `removeData(path)` — delete
148
+
149
+ | # | Behavior | Status | Probe |
150
+ |---|---|---|---|
151
+ | 29 | Admin mode removes the value and all children; resolves `{ success: true, data: null }` | ✓ | `unit:data/handler.test.ts` |
152
+ | 30 | User mode removes via the modular SDK's `remove(ref)` | ✓ | `unit:data/handler.test.ts` |
153
+ | 31 | `remove(ref)` and `set(ref, null)` produce the same end state (no path remains, `get` returns `null`) | ✓ | oracle: `scripts/oracle/observations/rtdb-remove-vs-set-null.json` — observed `bothNull: true, equivalent: true` against blockingfun; sandbox-aligned: `unit:modular/sandbox-target.test.ts` ("remove and set(null) produce identical end-state") |
154
+ | 32 | Removing a non-existent path is a no-op that resolves successfully (matches RTDB's idempotent delete semantics) | ✓ | oracle: `scripts/oracle/observations/rtdb-modular-remove-idempotent.json` — `remove` on a never-written path observed `threw: false, afterExists: false`; sandbox-aligned: `unit:modular/sandbox-target.test.ts` ("removing a non-existent path is a no-op") |
155
+
156
+ ## `rtdb_get_rules` / `generateIR()` — fetch + parse rules
157
+
158
+ | # | Behavior | Status | Probe |
159
+ |---|---|---|---|
160
+ | 33 | Fetches `/.settings/rules.json` AND `/.json?shallow=true` in parallel, then maps to an `RtdbIR` tree | ✓ | `unit:ir/handler.test.ts` ("hits both /.settings/rules.json and /.json") |
161
+ | 34 | Returns `RULES_FETCH_FAILED` when `/.settings/rules.json` returns 403 (insufficient admin permissions) | ✓ | `unit:ir/handler.test.ts` |
162
+ | 35 | Returns `RULES_PARSE_FAILED` when the rules response body is not valid JSON | ✓ | `unit:ir/handler.test.ts` |
163
+ | 36 | Returns success even when `/.json?shallow=true` returns 404 (proceeds with `null` shallow data, `rules.exists === false`) | ✓ | `unit:ir/handler.test.ts` |
164
+ | 37 | The returned IR has `service === 'realtime-database'` and `databaseUrl === host.databaseUrl` | ✓ | `unit:ir/handler.test.ts` |
165
+ | 38 | The IR tree's root node carries `read`/`write`/`validate` expressions parsed via the Ohm grammar; expressions expose `parsed.valid` + error list | ✓ | `unit:ir/handler.test.ts`, `unit:mapper.test.ts` |
166
+ | 39 | Top-level rule structure (e.g. `rules` wrapper, `.read`/`.write`/`.validate` keys, path-variable segments `$userId`) is parsed identically to how the REST `rules.json` PUT endpoint accepts it | ✓ | oracle: `scripts/oracle/observations/rtdb-rules-json-roundtrip.json` — PUT a rules subtree containing `$userId` path-variable segments, `.indexOn: ['createdAt', 'name']`, plus `.read`/`.write`/`.validate` expressions; GET-back returned `exactRoundTrip: true` (byte-for-byte JSON equality). Confirms the deploy / fetch shape is identical to what `RtdbMapper.mapToRulesJSON` and `mapToIR` expect. |
167
+
168
+ ## `rtdb_deploy_rules` / `writeRules(ir)` — deploy rules
169
+
170
+ | # | Behavior | Status | Probe |
171
+ |---|---|---|---|
172
+ | 40 | Maps the IR to a rules-JSON payload via `RtdbMapper.mapToRulesJSON(ir)` and PUTs to `<databaseUrl>/.settings/rules.json?access_token=<admin>` | ✓ | `unit:write/handler.test.ts` |
173
+ | 41 | Returns `{ success: true }` on HTTP 200 | ✓ | `unit:write/handler.test.ts` |
174
+ | 42 | Returns `{ success: false, error: { code: 'PERMISSION_DENIED' } }` on HTTP 403 | ✓ | `unit:write/handler.test.ts` |
175
+ | 43 | Returns `{ success: false, error: { code: 'INVALID_RULES_JSON', recoverable: true } }` on HTTP 400 (includes the response body in `message`) | ✓ | `unit:write/handler.test.ts` |
176
+ | 44 | Returns `{ success: false, error: { code: 'WRITE_FAILED' } }` for any other non-OK status | ✓ | `unit:write/handler.test.ts` |
177
+ | 45 | Any thrown exception during fetch is caught and wrapped as `WRITE_FAILED` | ✓ | `unit:write/handler.test.ts` |
178
+ | 46 | Live RTDB rules-PUT endpoint takes a few seconds to propagate before subsequent reads/writes are evaluated under the new rules | ✓ | oracle: `scripts/oracle/observations/rtdb-rules-deploy-propagation-timing.json` — deployed a permissive rule then polled writes at 200ms intervals; the FIRST write succeeded at `firstSuccessElapsedMs: 154` (observed once against blockingfun on fb-js-sdk 12.13.0). Both `within5s: true` and `within10s: true`; the harness's current 5s wait is comfortably above the observed bound. Note: a single observation isn't a guaranteed upper bound; propagation can vary with load. |
179
+
180
+ ## `rtdb_crawl_structure` / `crawlStructure(options)` — shape discovery
181
+
182
+ | # | Behavior | Status | Probe |
183
+ |---|---|---|---|
184
+ | 47 | Defaults to `path: '/'`, `maxDepth: 10`, `maxChildren: 100`, `maxConcurrency: 5` (from `CRAWL_DEFAULTS`) | ✓ | `unit:crawl/handler.test.ts` |
185
+ | 48 | Recursively fetches `<path>.json?shallow=true` at each level; uses `value === true` to identify object children and recurse | ✓ | `unit:crawl/handler.test.ts` ("schema excludes object children (value === true)") |
186
+ | 49 | Leaf primitive values (non-true, non-null) populate `node.schema[key]` with their `typeof` | ✓ | `unit:crawl/handler.test.ts` ("schema infers types from leaf primitive values") |
187
+ | 50 | A leaf primitive at the crawled path itself sets `node.valueType` rather than recursing | ✓ | `unit:crawl/handler.test.ts` ("leaf primitive node has valueType set") |
188
+ | 51 | A child node that returns only true-marked keys (the RTDB shallow representation of a nested object) is recursed; the schema of that child is populated from grandchild leaves | ✓ | `unit:crawl/handler.test.ts` ("schema populated from children that are leaf primitives") |
189
+ | 52 | 403 at the root returns `{ success: false, error: { code: 'PERMISSION_DENIED' } }` | ✓ | `unit:crawl/handler.test.ts` |
190
+ | 53 | 403 at a child returns an empty node (`childCount: 0`, `children: []`) rather than failing the whole crawl | ✓ | `unit:crawl/handler.test.ts` |
191
+ | 54 | A network error mid-crawl at a child returns an empty node for that subtree; the rest of the crawl proceeds | ✓ | `unit:crawl/handler.test.ts` |
192
+ | 55 | `maxDepth` truncates: deeper nodes are reported with `childCount` set but `children: []` | ✓ | `unit:crawl/handler.test.ts` |
193
+ | 56 | `maxChildren` exceeded → `truncated: true` on that node | ✓ | `unit:crawl/handler.test.ts` |
194
+ | 57 | `maxConcurrency` is enforced via a semaphore — concurrent in-flight fetches never exceed the limit | ✓ | `unit:crawl/handler.test.ts` ("concurrency is respected") |
195
+ | 58 | Live RTDB `shallow=true` REST response shape: object → object with keys mapped to `true`; leaf primitive → the primitive itself; missing path → `null` | ✓ | oracle: `scripts/oracle/observations/rtdb-shallow-rest-response-shape.json` — seeded `{ obj: { a, b, c }, leaf: 'hello', leafNum: 42, leafBool: true }`, then GET with `?shallow=true` at each path: object node returned `{ a: true, b: true, c: true }` (all keys → `true`), string leaf returned the string `'hello'`, numeric leaf returned `42`, boolean leaf returned `true`, missing path returned `null`. Locks every assumption the `CrawlStructureHandler` depends on. |
196
+
197
+ ## `rtdb_simulate_access` / `simulate(input)` — in-process rule evaluator
198
+
199
+ | # | Behavior | Status | Probe |
200
+ |---|---|---|---|
201
+ | 59 | Returns `{ success: false, error: { code: 'IR_NOT_GENERATED' } }` when called before `generateIR()` | ✓ | `unit:simulation/handler.test.ts` |
202
+ | 60 | Returns `{ success: false, error: { code: 'INVALID_INPUT' } }` when input doesn't parse against `SimulationInputSchema` (e.g. path missing leading slash, operation not in read / write / validate) | ✓ | `unit:simulation/handler.test.ts` |
203
+ | 61 | Walks ancestors from root → target; the first ancestor whose rule expression evaluates to `true` grants access — matches RTDB's documented "rules cascade from root, true at any ancestor grants" semantics | ✓ | `unit:simulation/handler.test.ts` |
204
+ | 62 | Path variables (`$userId`) are bound from the URL path and exposed in `pathVariableBindings` (also without the `$` prefix for ergonomic access in expressions) | ✓ | `unit:simulation/handler.test.ts` |
205
+ | 63 | `auth` context: when `null`, `auth` is null inside expressions; when present, `auth.uid` and `auth.token.*` are bound | ✓ | `unit:simulation/handler.test.ts`, `unit:grammar/simulator.test.ts` |
206
+ | 64 | `mockData` becomes the value of `data` at every path during evaluation; `newData` is the proposed value for write/validate | ✓ | `unit:simulation/handler.test.ts` |
207
+ | 65 | `data.child("…")`, `data.parent()`, `data.exists()`, `data.val()` evaluate against the in-process snapshot — matches the documented `DataSnapshot` rule-context surface | ✓ | `unit:grammar/simulator.test.ts` |
208
+ | 66 | Cross-path `root.child(…).val()` reads return `null` for paths NOT present in `mockData` — divergence from real prod rules where the engine reads the live database | ⚠ | divergence: the simulator uses ONLY what's in `mockData`. Real rules engine reads from the live RTDB. Documented in `validated.ts` ("simulation uses empty mockData, so cross-path rule lookups … will evaluate as false") |
209
+ | 67 | An expression that fails to parse (`parsed.valid === false`) is skipped — the simulator falls through to the next ancestor | ✓ | `unit:simulation/handler.test.ts` |
210
+ | 68 | When no ancestor rule allows, the result is `{ allowed: false }` with `matchedPath` set to the deepest matched node | ✓ | `unit:simulation/handler.test.ts` |
211
+ | 69 | When NO ancestor has a rule for the operation at all, returns `{ success: false, error: { code: 'NO_MATCHING_RULE' } }` | ✓ | `unit:simulation/handler.test.ts` |
212
+ | 70 | Evaluation errors (grammar mismatch, unknown identifier) surface as `EVALUATION_ERROR` | ✓ | `unit:simulation/handler.test.ts` |
213
+ | 71 | Simulator's allow/deny decision matches the real RTDB rules engine for the same `{ rules, mockData, auth, operation, path, newData }` tuple, modulo the documented cross-path divergence on row #66 | ✓ | oracle: `scripts/oracle/observations/rtdb-simulator-vs-prod-agreement.json` — 8 test rules × 29 (rule, op) tuples; 28 agreements, 1 disagreement at capture time (`r4-validate-structure`: the simulator did not evaluate `.validate` on writes). The `.validate` walk is now implemented (`src/database/simulation/handler.ts`, reached from all backend write sites; grammar array-literals + `hasChildren(keys)` fixed alongside), closing the recorded disagreement — replayed as prod-conforming denial in `oracle-conformance.test.ts`. The frozen capture documents the historical divergence |
214
+
215
+ ## `rtdb_validated_write` / `validatedWrite(input)` — preflighted write
216
+
217
+ | # | Behavior | Status | Probe |
218
+ |---|---|---|---|
219
+ | 72 | Crawls the structure at `path` to infer schema; collects `SchemaWarning[]` for `type_mismatch` (existing key with different type) and `new_field` (key not seen before) | ✓ | `unit:data/validated.test.ts` |
220
+ | 73 | Simulates the write against the IR's rules using `mockData: {}` and the supplied `auth` | ✓ | `unit:data/validated.test.ts` |
221
+ | 74 | Admin mode (no `auth`): simulation denial returns `{ success: false, error: { code: 'SIMULATION_DENIED', recoverable: true } }` — blocks the live write | ✓ | `unit:data/validated.test.ts` |
222
+ | 75 | User mode (`auth` provided): simulation denial is advisory only — the live write still runs because real rules will enforce against the actual database, where cross-path lookups can succeed | ✓ | `unit:data/validated.test.ts` |
223
+ | 76 | After preflight, the actual write is dispatched through `DataHandler.execute` with the original `auth` | ✓ | `unit:data/validated.test.ts` |
224
+ | 77 | Schema warnings are returned even on success — they're advisory, not blocking | ✓ | `unit:data/validated.test.ts` |
225
+ | 78 | A failed crawl is swallowed — the handler proceeds with no schema warnings and an unchecked write | ✓ | `unit:data/validated.test.ts` |
226
+
227
+ ## `rtdb_build_expression` — rule expression authoring
228
+
229
+ | # | Behavior | Status | Probe |
230
+ |---|---|---|---|
231
+ | 79 | Returns a `RtdbRuleExpression` with `raw`, `parsed.valid`, `parsed.errors`, `parsed.warnings`, `parsed.referencedIdentifiers` | ✓ | `unit:mapper.test.ts` |
232
+ | 80 | A syntactically valid expression sets `parsed.valid === true` and lists referenced identifiers (`auth`, `auth.uid`, `data`, etc.) | ✓ | `unit:mapper.test.ts` |
233
+ | 81 | A syntactically invalid expression sets `parsed.valid === false` and populates `parsed.errors` with `{ code, message }` | ✓ | `unit:mapper.test.ts` |
234
+ | 82 | Linter warnings (e.g. always-true expressions, missing `auth` checks) populate `parsed.warnings` | ✓ | `unit:grammar/linter.test.ts` |
235
+ | 83 | The grammar accepts every documented RTDB rule operator: logical (`&&`, `or`, `!`), equality (`==`, `===`, `!=`), comparison (`<`, `<=`, `>`, `>=`), arithmetic (`+`, `-`, `*`, `/`, `%`), ternary `?:`, member access, function call, string/regex literals | ✓ | `unit:grammar/RtdbExprParser.test.ts` |
236
+
237
+ ## Constraint authoring surface (`atoms` / `policies` / `compose` / `ruleset`)
238
+
239
+ | # | Behavior | Status | Probe |
240
+ |---|---|---|---|
241
+ | 84 | `atoms` exports the documented set of primitive predicates (`authenticated`, `ownPath`, `ownField`, `isNew`, `hasChildren`, `hasChild`, `fieldIsString/Number/Boolean`, `fieldEnum`, `immutable`, `immutableSelf`, `rootExists`, `rootEquals`) — each returns an `Expr` | ✓ | `unit:constraints/atoms.test.ts` |
242
+ | 85 | `policies` exports composite predicates that compose atoms: `pathOwnerOnly`, `fieldOwnerOnly`, `ownerOrNew`, `hasRole`, `isMember`, `required`, `transition` | ✓ | `unit:constraints/policies.test.ts` |
243
+ | 86 | `compose` exports the boolean combinators `all`, `any`, `not`, `deny`, `always`, plus the raw `expr` constructor | ✓ | `unit:constraints/compose.test.ts` |
244
+ | 87 | `ruleset(...)` builds an RTDB rules JSON object from a tree of path definitions + expression objects | ✓ | `unit:constraints/ruleset.test.ts` |
245
+ | 88 | Game-domain helpers (`turnGuard`, `flip`, `winCheckHelper`) compose into legal rule expressions | ✓ | `unit:constraints/game.test.ts` |
246
+
247
+ ## `RtdbMapper` — IR ↔ rules-JSON
248
+
249
+ | # | Behavior | Status | Probe |
250
+ |---|---|---|---|
251
+ | 89 | `mapToIR(rulesJson, shallowData, databaseUrl)` produces an `RtdbIR` tree where each node carries its path, parsed expressions, and child nodes | ✓ | `unit:mapper.test.ts` |
252
+ | 90 | `mapToRulesJSON(ir)` is the inverse: produces a rules-JSON payload accepted by the `/.settings/rules.json` PUT endpoint | ✓ | `unit:mapper.test.ts` |
253
+ | 91 | Round-trip `mapToIR(mapToRulesJSON(ir))` produces an equivalent IR (locked path/expression-text equality, not object identity) | ✓ | `unit:mapper.test.ts` |
254
+ | 92 | Path-variable segments (`$userId`, `$gameId`) preserved across the round-trip | ✓ | `unit:mapper.test.ts` |
255
+ | 93 | `.indexOn` arrays preserved across the round-trip | ✓ | `unit:mapper.test.ts` |
256
+
257
+ ---
258
+
259
+ ## Modular SDK surface (Phase 3)
260
+
261
+ The modular surface mirrors `firebase/database`'s tree-shakable
262
+ free-function shape (`getDatabase`, `ref`, `child`, `get`, `set`,
263
+ `update`, `remove`, `push`, `onValue`, `serverTimestamp`,
264
+ `connectDatabaseEmulator`). Three backends dispatch on what's passed
265
+ to `getDatabase`:
266
+
267
+ - **Sandbox** — `getDatabase(ctx: SandboxContext)`. Frozen identity
268
+ baked into the handle at construction; ops route to `RtdbBackend`
269
+ (in-memory JSON tree + the `@pyric/rtdb` rule simulator).
270
+ - **Sandbox-live** — `getDatabase(sandbox: Sandbox)`. Identity read
271
+ per-op from `sandbox.currentUser`; `pyric/auth` sign-ins flip the
272
+ next op's `request.auth` without re-binding the handle.
273
+ - **Prod** — `getDatabase(app: FirebaseApp)`. Delegates to
274
+ `firebase/database`.
275
+
276
+ Dispatch lives in `packages/pyric/src/database/modular.ts`; the sandbox backend
277
+ in `packages/pyric/src/database/sandbox/` (data-tree, rules-eval glue, sentinel
278
+ resolver, push-ID generator). The sandbox backend reuses the existing
279
+ `SimulateHandler` for rule evaluation — no separate engine.
280
+
281
+ Rows below are scoped to the modular surface; the agent-tool rows
282
+ above (#1–#93) describe the IR / handler / rules-DSL surface that
283
+ remains unchanged.
284
+
285
+ | # | Behavior | Status | Probe |
286
+ |---|---|---|---|
287
+ | M1 | `getDatabase(ctx)` builds a sandbox-target `Database`; frozen `ctx.auth` baked in | ✓ | `unit:modular/sandbox-target.test.ts` ("getDatabase(ctx) returns a tagged Database handle") |
288
+ | M2 | `getDatabase(sandbox)` builds a sandbox-live target; reads `sandbox.currentUser` per op | ✓ | `unit:modular/sandbox-target.test.ts` ("reads sandbox.currentUser at op time, not at getDatabase time") |
289
+ | M3 | `getDatabase(app)` builds a prod target; delegates to `firebase/database.getDatabase(app)` | — | not yet locked by a prod-target integration test (firestore template covers the pattern; rtdb-side defers to Tier 5) |
290
+ | M4 | `ref(db, path?)` returns a path-tagged `DatabaseReference`; default is root | ✓ | `unit:modular/sandbox-target.test.ts` ("ref(db) returns a root ref" + "ref(db, ...) returns a path ref") |
291
+ | M5 | `child(ref, 'sub/path')` composes paths; result inherits the parent's target | ✓ | `unit:modular/sandbox-target.test.ts` ("child(ref, 'sub') composes paths") |
292
+ | M6 | `ref.parent` returns the parent ref; `root.parent === null` | ✓ | `unit:modular/sandbox-target.test.ts` ("ref.parent returns the parent ref; root.parent is null") |
293
+ | M7 | `ref.root` returns the root ref of the same target | ✓ | `unit:modular/sandbox-target.test.ts` ("ref.root returns the root ref") |
294
+ | M8 | `get(ref)` returns a `DataSnapshot`-shaped object with `val()`, `exists()`, `key`, `child()`, `hasChildren()`, `numChildren()`, `toJSON()` | ✓ | `unit:modular/sandbox-target.test.ts` (snapshot shape tests) |
295
+ | M9 | `get` on an absent path resolves to `{ val: null, exists: false }` (matches `DataSnapshot.val()` contract) | ✓ | `unit:modular/sandbox-target.test.ts` ("reads return null for an absent path") |
296
+ | M10 | `set(ref, value)` replaces the value at the path | ✓ | Sandbox aligned: `unit:modular/sandbox-target.test.ts` ("round-trips a primitive value" + "round-trips nested objects"); matches oracle observation `scripts/oracle/observations/rtdb-set-then-get-roundtrip.json` (prod observation blocked on rules; sandbox locks the contract directly) |
297
+ | M11 | `set(ref, null)` deletes the subtree at the path | ✓ | Sandbox aligned: `unit:modular/sandbox-target.test.ts` ("set(ref, null) deletes the path"); matches oracle observation `scripts/oracle/observations/rtdb-remove-vs-set-null.json` |
298
+ | M12 | `remove(ref)` is equivalent to `set(ref, null)` (same end state) | ✓ | Sandbox aligned: `unit:modular/sandbox-target.test.ts` ("remove and set(null) produce identical end-state"); matches oracle observation `scripts/oracle/observations/rtdb-remove-vs-set-null.json` |
299
+ | M13 | `update(ref, patch)` shallow-merges top-level keys at the ref's path | ✓ | `unit:modular/sandbox-target.test.ts` ("shallow-merges top-level keys") |
300
+ | M14 | `null` value in a shallow update deletes that key | ✓ | `unit:modular/sandbox-target.test.ts` ("null values in a shallow update delete the key") |
301
+ | M15 | `update(rootRef, { '/a/x': v1, '/b/y': v2 })` is a multi-path atomic write — all paths land or none do | ✓ | Sandbox aligned: `unit:modular/sandbox-target.test.ts` ("writes every listed path atomically" + "rejects the entire update if rules deny any one path"); matches the matrix #23 prod contract |
302
+ | M16 | Overlapping multi-path updates (one path is a descendant of another) reject before any path is written | ✓ | `unit:modular/sandbox-target.test.ts` ("rejects overlapping paths") |
303
+ | M17 | `push(ref)` mints a 20-char auto-id key starting with `-`, lexicographically sortable | ✓ | Sandbox aligned: `unit:modular/sandbox-target.test.ts` ("mints 20-char keys starting with \"-\"" + "sequential push keys are lex-sortable"); matches oracle observation `scripts/oracle/observations/rtdb-push-autoid-format.json` |
304
+ | M18 | `push(ref, value)` writes `value` at the new child path | ✓ | `unit:modular/sandbox-target.test.ts` ("push(ref, value) writes the value at the new child path") |
305
+ | M19 | `pushKey()` mints a fresh push-shaped key without writing — used by callers building multi-path updates that need the key first | ✓ | `unit:modular/sandbox-target.test.ts` ("pushKey() mints a fresh key without writing") |
306
+ | M20 | `serverTimestamp()` returns the `{ ".sv": "timestamp" }` sentinel marker the wire encoder recognises | ✓ | Sandbox aligned: `unit:modular/sandbox-target.test.ts` ("serverTimestamp() returns the documented shape"); matches the prod wire contract |
307
+ | M21 | `serverTimestamp()` resolves to a number (epoch ms) on read-back | ✓ | Sandbox aligned: `unit:modular/sandbox-target.test.ts` ("resolves to a number on read-back"); matches oracle observation `scripts/oracle/observations/rtdb-servertimestamp-resolves.json` (prod observation blocked on rules; sandbox locks the contract directly) |
308
+ | M22 | `serverTimestamp()` sentinels resolve when nested inside multi-path update payloads | ✓ | `unit:modular/sandbox-target.test.ts` ("resolves sentinels nested deep inside an update payload") |
309
+ | M23 | Rules-denied write throws a plain `Error` (NOT a `FirebaseError`) with `.code === 'PERMISSION_DENIED'` (uppercase snake-case) and `.message === 'PERMISSION_DENIED: Permission denied'` | ✓ | Sandbox aligned: `unit:modular/sandbox-target.test.ts` ("rules-denied set throws a plain Error with PERMISSION_DENIED code"); matches oracle observation `scripts/oracle/observations/rtdb-rules-denied-error-code.json` (against blockingfun, fb-js-sdk 12.13.0) |
310
+ | M24 | Rules-denied read throws the same plain-`Error` `PERMISSION_DENIED` shape as a denied write | ✓ | Sandbox aligned: `unit:modular/sandbox-target.test.ts` ("rules-denied get throws the same plain Error shape"); matches oracle observation `scripts/oracle/observations/rtdb-rules-denied-error-code.json` |
311
+ | M25 | Rules-denied remove throws the same plain-`Error` `PERMISSION_DENIED` shape | ✓ | Sandbox aligned: `unit:modular/sandbox-target.test.ts` ("rules-denied remove throws the same plain Error shape") |
312
+ | M26 | `onValue(ref, cb)` fires immediately on subscribe with the current value at the path | ✓ | `unit:modular/sandbox-target.test.ts` ("fires on subscribe with the current value") |
313
+ | M27 | `onValue` fires again after every write that CHANGES the value at the watched path; a write that leaves the watched subtree byte-identical (a no-change re-write, or an ancestor/descendant write that doesn't alter this path) is suppressed (DB-B8) | ✓ | `unit:modular/sandbox-target.test.ts` ("fires after every write that touches the watched path") + `unit:modular/no-change-suppression.test.ts` ("re-writing the same value does NOT re-fire" + "ancestor write leaving the subtree unchanged does NOT fire") |
314
+ | M28 | `onValue` fires after a descendant write (the listener sees subtree changes) | ✓ | `unit:modular/sandbox-target.test.ts` ("fires on a descendant write") |
315
+ | M29 | `onValue` initial-fire for an absent path delivers `val=null, exists=false` (matches matrix expectation locked by oracle for sentinel/listener shape) | ✓ | `unit:modular/sandbox-target.test.ts` ("absent path: initial fire delivers val=null, exists=false") |
316
+ | M30 | The `onValue` return value is an unsubscribe function; calling it stops further fires | ✓ | `unit:modular/sandbox-target.test.ts` ("fires after every write that touches the watched path" — checks unsubscribed listener doesn't fire on subsequent write) |
317
+ | M31 | `onChildAdded` / `onChildChanged` / `onChildRemoved` / `onChildMoved` — plain-ref subscription surface | ✓ | Tier 2: sandbox aligned with oracle observations under `scripts/oracle/observations/rtdb-modular-onchild*.json`. See M41–M48 for the per-event behavioral claims. |
318
+ | M41 | `onChildAdded` replays each existing direct child of the parent ref on subscribe (one fire per existing key) | ✓ | Sandbox aligned: `unit:modular/sandbox-child-events.test.ts` ("replays existing direct children on subscribe — one fire per key"); matches oracle observation `scripts/oracle/observations/rtdb-modular-onchildadded-initial-replay.json` (seeded `{k1,k2,k3}`, observed `firedKeys: ['k1','k2','k3']`). |
319
+ | M42 | After subscribe, `onChildAdded` fires exactly once per new direct child write; snapshot carries `{key, val}` of the new child | ✓ | Sandbox aligned: `unit:modular/sandbox-child-events.test.ts` ("fires exactly once per NEW direct child after subscribe"); matches oracle observation `scripts/oracle/observations/rtdb-modular-onchildadded-post-subscribe.json` (`postSubscribeFires: 1`, `lastFire: {key:'k3', val:{v:3}}`). |
320
+ | M43 | `onChildChanged` has NO initial replay; fires once when an existing direct child's value transitions; snapshot carries the NEW value | ✓ | Sandbox aligned: `unit:modular/sandbox-child-events.test.ts` ("does NOT fire on subscribe (no initial replay)" + "fires once when an existing child transitions to a new value; snapshot carries NEW val"); matches oracle observation `scripts/oracle/observations/rtdb-modular-onchildchanged-fires-on-update.json` (`firedOnInitial: 0, firedOnUpdate: 1, lastFire: {key:'k1', val:{v:2}}`). |
321
+ | M44 | `onChildChanged` does NOT fire for added or removed children — those go to the other event listeners | ✓ | Sandbox aligned: `unit:modular/sandbox-child-events.test.ts` ("does NOT fire when a child is added" + "does NOT fire when a child is removed"). |
322
+ | M45 | `onChildRemoved` has NO initial replay; fires once when a direct child is deleted (via `remove(child)` or `set(child, null)`); snapshot carries the PRIOR (now-removed) value | ✓ | Sandbox aligned: `unit:modular/sandbox-child-events.test.ts` ("fires once when a child is deleted via remove(); snapshot carries PRIOR val" + "fires once when a child is deleted via set(child, null); snapshot carries PRIOR val"); matches oracle observation `scripts/oracle/observations/rtdb-modular-onchildremoved-fires-on-delete.json` (`firedOnInitial: 0, firedOnDelete: 1, removedSnapCarriesPriorValue: true`). |
323
+ | M46 | `onChildMoved` on a plain ref (no `query(_, orderBy*)`) NEVER fires — per RTDB docs, child_moved emits only under ordered queries | ✓ | Sandbox aligned: `unit:modular/sandbox-child-events.test.ts` ("does NOT fire on a plain ref (no ordering)"); matches the upstream contract observed under ordered-query in `scripts/oracle/observations/rtdb-modular-onchildmoved-with-orderby.json` (where ordered-query did fire — Tier 3 will wire that path; Tier 2 locks the plain-ref no-fire case). |
324
+ | M47 | `off(ref)` (no event type) removes ALL listeners at that ref — value + every child event variety | ✓ | Sandbox aligned: `unit:modular/sandbox-child-events.test.ts` ("off(ref) removes ALL listeners at the ref" + "off(ref) also removes value listeners at the same path"); matches oracle observation `scripts/oracle/observations/rtdb-modular-off-stops-child-fires.json` (`postOffFires: 0`). |
325
+ | M48 | `off(ref, eventType?, callback?)` variants: `off(ref, 'value')` / `off(ref, 'child_added')` / `off(ref, eventType, cb)` remove the targeted subset; returned-unsubscribe from `onChild*` is equivalent to `off(ref, eventType, cb)` | ✓ | Sandbox aligned: `unit:modular/sandbox-child-events.test.ts` ("off(ref, \"child_added\") removes only that event variety" + "off(ref, \"value\") removes only value listeners" + "off(ref, eventType, cb) removes only the matching callback" + "returned-unsubscribe from onChildAdded is functionally equivalent to off()"). |
326
+ | M32 | `connectDatabaseEmulator(db, host, port)` is a no-op on sandbox targets (the sandbox IS a local emulator) | ✓ | `unit:modular/sandbox-target.test.ts` ("is a no-op on sandbox handles") |
327
+ | M33 | `sandbox.setRules(db, rulesJson)` deploys rules to the in-process simulator; `setRules(db, null)` clears rules (default-allow) | ✓ | `unit:modular/sandbox-target.test.ts` ("sandbox.setRules(db, null) clears rules") |
328
+ | M34 | `sandbox.setData(db, { '/path': value })` bulk-loads data, bypassing rules | ✓ | `unit:modular/sandbox-target.test.ts` ("sandbox.setData seeds the tree (rule-bypass)") |
329
+ | M35 | `sandbox.snapshotState(db)` dumps the full tree as a plain JSON object | ✓ | `unit:modular/sandbox-target.test.ts` ("sandbox.snapshotState dumps the full tree") |
330
+ | M36 | `query(ref, ...constraints)` + ordering/range constraints | ✓ | Sandbox aligned: `unit:modular/queries.test.ts` covers `query(ref, orderByChild/Key/Value, startAt/After, endAt/Before, equalTo, limitToFirst/Last)` against the executor in `packages/pyric/src/database/sandbox/query.ts`; oracle observations under `scripts/oracle/observations/rtdb-modular-{orderbychild-window,orderbykey-window,orderbyvalue-numeric,equalTo-filter,limittofirst-vs-limittolast,startafter-endbefore-exclusive,onvalue-with-query}.json` lock each constraint's behavior. See M49–M64 below for the per-claim breakdown. |
331
+ | M37 | `runTransaction(ref, fn, options?)` resolves to `{ committed: boolean, snapshot: DataSnapshot }` for the happy path — the update fn return value is written, committed is `true`, and `snapshot.val()` reflects the committed value | ✓ | Sandbox aligned: `unit:modular/transaction.test.ts` ("resolves to { committed: boolean, snapshot } with the committed value"); matches oracle observations `scripts/oracle/observations/rtdb-modular-runtransaction-success.json` + `rtdb-modular-runtransaction-returns-committed-snapshot.json` (against blockingfun, fb-js-sdk 12.13.0) |
332
+ | M37a | Returning `undefined` from the update fn ABORTS the transaction — resolves `{ committed: false, snapshot }`; no write performed, no listener fan-out | ✓ | Sandbox aligned: `unit:modular/transaction.test.ts` ("returning undefined aborts — committed: false, no write" + "aborted transaction does NOT fan out to listeners"); matches oracle observation `scripts/oracle/observations/rtdb-modular-runtransaction-abort-undefined.json` — known divergence: prod's `result.snapshot.val()` reflects the CLIENT's pre-fetch (often `null` even when the server has a value because the speculative invocation runs before the server snap arrives); the sandbox returns the actual pre-transaction value at the path (more useful in single-client harness). The agreed-upon contract callers should rely on is `committed === false` and unchanged server-side data, NOT the snapshot's `.val()` on the abort path. |
333
+ | M37b | The update fn receives the CURRENT value at the ref's path; for an absent path the argument is `null` (NOT `undefined`) | ✓ | Sandbox aligned: `unit:modular/transaction.test.ts` ("update fn receives null for an absent path" + "update fn receives the existing value for a seeded path"); matches oracle observation `scripts/oracle/observations/rtdb-modular-runtransaction-current-value-arg.json` (prod observation showed `missingArgs[0].isNull === true`) — note divergence: prod ALSO speculatively calls the fn with `null` for a seeded path before the server-snap arrives, the sandbox skips that speculative call (single invocation with the real current value) |
334
+ | M37c | The update fn arg is a defensive deep clone — mutating it does NOT corrupt the stored tree (matters for code that does `current.count++; return undefined` and expects abort to preserve state) | ✓ | `unit:modular/transaction.test.ts` ("mutating the update-fn arg does NOT corrupt the stored tree") — no separate oracle row (defensive contract; prod behavior is identical because the SDK clones on the wire boundary) |
335
+ | M37d | `options.applyLocally` controls whether the in-flight optimistic value fans out to `onValue` listeners — default `true` (apply locally before commit); `false` suppresses intermediate fires so listeners see only the committed value | ✓ | Sandbox aligned: `unit:modular/transaction.test.ts` ("applyLocally: true (default) — listener sees initial + committed value" + "applyLocally: false — listener sees only the committed value"); matches oracle observation `scripts/oracle/observations/rtdb-modular-runtransaction-options-applylocally.json` (single-client harness: both branches produce 2 fires (initial + commit) — divergence vs prod's documented multi-client suppression would surface under contention, which the sandbox doesn't model) |
336
+ | M37e | Rules-denied transaction rejects with a plain `Error` whose `message === 'permission_denied'` (lowercase) and NO `.code` field — DIFFERENT from `set`/`get`'s `'PERMISSION_DENIED: Permission denied'` shape with uppercase `.code`. **Note: the divergence between the two shapes is real at the SDK boundary, but the `DataHandler` layer normalizes both to `error.code === 'PERMISSION_DENIED'` (row #14) so consumer code only needs to branch on one value.** | ✓ | Sandbox aligned: `unit:modular/transaction.test.ts` ("rejects with a plain Error whose message is \"permission_denied\""); matches oracle observation `scripts/oracle/observations/rtdb-modular-runtransaction-on-rules-denied-path.json` (against blockingfun: `message: 'permission_denied', code: null, constructorName: 'Error'`). Handler-level unification locked by `unit:data/handler.test.ts` ("transaction-shaped rules-denied (lowercase message, no .code) surfaces as PERMISSION_DENIED"). |
337
+ | M37f | Rules-denied transaction does NOT write — pre-transaction value at the path is preserved through the rejection | ✓ | `unit:modular/transaction.test.ts` ("does not write to the path when rules deny") — locked alongside the M37e shape claim |
338
+ | M37g | Committed transaction fans out to `onValue` listeners on the watched path with the new value (default applyLocally behavior) | ✓ | `unit:modular/transaction.test.ts` ("committed write fans out to onValue listeners") |
339
+ | M37h | Concurrent contention / retry-on-conflict — single-client sandbox doesn't model real concurrency; the documented "up to 25 retries" contract is degenerate (the fn is invoked once) | — | matrix #161 documents the same gap on the spec side; oracle observation hard to obtain from a single client (oracle row stays `?`) |
340
+ | M38 | Identity-aware sandbox-live op routing — sign-in/sign-out via `pyric/auth` is observed by the next RTDB op without re-binding | ✓ | `unit:modular/sandbox-target.test.ts` ("reads sandbox.currentUser at op time, not at getDatabase time") |
341
+ | M39 | Backend identity is per-`Sandbox` — two `getDatabase(sandbox)` calls on the same sandbox share data, two on different sandboxes don't | ✓ | implicit via the WeakMap binding; tested transitively by `sandbox.setData` + `get` round-trips in the same test suite |
342
+ | M40 | Sandbox refs carry a stable `key` (last path segment) and `toString()` returning `sandbox://rtdb/<path>` | ✓ | covered by the `ref` / `child` / `parent` tests |
343
+ | M49 | `query(ref, orderByChild(p), startAt(v), endAt(w))` window is BOTH-INCLUSIVE — children whose ordered field === `v` or === `w` are included | ✓ | Sandbox aligned: `unit:modular/queries.test.ts` ("returns children whose ordered child is within [startAt, endAt] inclusive"); matches oracle observation `scripts/oracle/observations/rtdb-modular-orderbychild-window.json` (positions `[2,3,4]`, both ends inclusive). |
344
+ | M50 | `orderByKey()` orders children by RTDB `nameCompare` — integer-looking keys sort numerically FIRST (so `['1','2','10']`, not the lexicographic `['1','10','2']`), then non-integer keys lexicographically; `startAt`/`endAt` cursors + the optional `key` tie-breaker use the same order (DB-B4) | ✓ | Sandbox aligned: `unit:modular/queries.test.ts` ("orderByKey + startAt(s) + endAt(e) yields keys in [s,e] inclusive") + `unit:modular/name-compare.test.ts` ("orderByKey sorts integer keys numerically, before non-integer keys" + "numeric-key cursor uses nameCompare bounds"); matches oracle `rtdb-modular-orderbykey-window.json` and upstream `core/util/util.ts:253-276`. |
345
+ | M51 | `orderByValue()` orders primitive children by their value; `limitToFirst(N)` returns the N smallest, ascending | ✓ | Sandbox aligned: `unit:modular/queries.test.ts` ("returns the limitToFirst(N) smallest values, ascending"). Oracle observation `scripts/oracle/observations/rtdb-modular-orderbyvalue-numeric.json` shows prod threw `Index not defined` against blockingfun — the sandbox does NOT enforce `.indexOn` (rules-engine integration for query indexes is deferred); the semantic claim (ordering by value) is locked here. |
346
+ | M52 | `orderByChild(p) + equalTo(v)` returns ALL children whose field at `p` === `v` — no uniqueness enforced | ✓ | Sandbox aligned: `unit:modular/queries.test.ts` ("returns ALL children whose ordered field === the supplied value"); matches oracle observation `scripts/oracle/observations/rtdb-modular-equalTo-filter.json` (both 'b'-grouped children returned). |
347
+ | M53 | `equalTo` with no matches returns an empty snapshot (`exists() === false`, `numChildren() === 0`) | ✓ | `unit:modular/queries.test.ts` ("returns an empty snapshot when nothing matches") |
348
+ | M54 | `limitToFirst(N)` keeps the lowest-ranked N children (post-ordering, pre-filter) | ✓ | Sandbox aligned: `unit:modular/queries.test.ts` ("limitToFirst takes the lowest-ranked window"); matches oracle observation `scripts/oracle/observations/rtdb-modular-limittofirst-vs-limittolast.json` (firstPositions `[1,2]`). |
349
+ | M55 | `limitToLast(N)` keeps the highest-ranked N children | ✓ | Sandbox aligned: `unit:modular/queries.test.ts` ("limitToLast takes the highest-ranked window"); matches oracle observation `scripts/oracle/observations/rtdb-modular-limittofirst-vs-limittolast.json` (lastPositions `[4,5]`). |
350
+ | M56 | `limitToFirst(N)` larger than the result returns the full window (no padding, no throw) | ✓ | `unit:modular/queries.test.ts` ("limitToFirst(N) larger than the result returns the full window") |
351
+ | M57 | `startAfter(v)` and `endBefore(v)` are EXCLUSIVE — the boundary value is dropped from the result | ✓ | Sandbox aligned: `unit:modular/queries.test.ts` ("startAfter + endBefore drop the boundary values"); matches oracle observation `scripts/oracle/observations/rtdb-modular-startafter-endbefore-exclusive.json` (positions `[3,4]`, cursors 2 + 5 dropped). |
352
+ | M58 | `onValue(query, cb)` only fires when the windowed result changes — writes OUTSIDE the window don't re-fire the listener; writes that displace a member DO | ✓ | Sandbox aligned: `unit:modular/queries.test.ts` ("fires only when the windowed result changes"); matches oracle observation `scripts/oracle/observations/rtdb-modular-onvalue-with-query.json` (3 fires: initial + INSIDE-window write + displacing write; OUTSIDE-window write skipped). |
353
+ | M59 | `onValue(query)` initial fire delivers an empty window (`numChildren() === 0`) when the path is absent | ✓ | `unit:modular/queries.test.ts` ("initial fire on an empty path delivers an empty window") |
354
+ | M60 | `query(query(ref, c1), c2)` composes constraints — chaining folds both into one spec | ✓ | `unit:modular/queries.test.ts` ("query(query(ref, c1), c2) composes constraints") |
355
+ | M61 | Snapshot from a query exposes children via `snap.forEach` in the executor-computed order — NOT necessarily the order `Object.entries(val)` would yield | ✓ | `unit:modular/queries.test.ts` ("forEach visits children in ascending order of the child key") |
356
+ | M62 | `startAt(value, key)` uses `key` as the tie-breaker when multiple children share the same ordered value — children before `key` are dropped, the row at `key` is included (inclusive cursor) | ✓ | `unit:modular/queries.test.ts` ("startAt with key tie-breaker drops earlier same-value children") |
357
+ | M63 | `orderByChild('p')` on children missing the field treats their value as `null` (sorts FIRST per RTDB's type ordering) | ✓ | `unit:modular/queries.test.ts` ("orderByChild on a missing child path treats those children as null") |
358
+ | M64 | `query` on a path holding a primitive (or absent path) returns an empty snapshot — no rows to iterate | ✓ | `unit:modular/queries.test.ts` ("query on a path with primitive value returns no rows") |
359
+ | M65 | Write-boundary normalization (`nodeFromJSON`-equivalent): a value written as an array is stored as an integer-keyed object — `child(ref, '1')` returns the element, `forEach` iterates `0,1,2…` (DB-B2) | ✓ | Sandbox aligned: `unit:modular/normalization.test.ts` ("array write is addressable by integer-string child key" + "forEach over an array iterates its elements"); upstream `core/snap/nodeFromJSON.ts:118-128`, `core/snap/ChildrenNode.ts:194-230` |
360
+ | M66 | Read-side array coercion: a dense integer-keyed object renders back as an array on `snap.val()` (`allIntegerKeys && maxKey < 2 * numKeys`) (DB-B2) | ✓ | Sandbox aligned: `unit:modular/normalization.test.ts` ("a dense integer-keyed object reads back as an array"); upstream `core/snap/ChildrenNode.ts:196-230` |
361
+ | M67 | `null` children and empty objects are pruned at the write boundary — `set(ref, {})` is equivalent to `remove(ref)`; nested `null` collapses empty ancestors ("empty nodes don't exist") (DB-B3) | ✓ | Sandbox aligned: `unit:modular/normalization.test.ts` ("set(ref, {}) is equivalent to remove" + "null children are pruned"); upstream `core/snap/nodeFromJSON.ts:78-88,122-126` |
362
+ | M68 | Write validation: an `undefined` payload, a non-finite number (`NaN`/`±Infinity`), or a key containing a forbidden char (`.`, `#`, `$`, `/`, `[`, `]`, control chars) is rejected with a plain `Error` (DB-B1) | ✓ | Sandbox aligned: `unit:modular/normalization.test.ts` ("rejects an undefined payload" + "rejects an invalid key" + "rejects a non-finite number"); upstream `core/util/validation.ts:45,58,112-199` |
363
+ | M69 | Conflicting query constraints throw synchronously at `query(...)` construction (NOT silent last-win): multiple `orderBy*`, a second `limitToFirst`/`limitToLast`, a second start (`startAt`/`startAfter`/`equalTo`) or end (`endAt`/`endBefore`/`equalTo`) (DB-B5) | ✓ | Sandbox aligned: `unit:modular/constraint-conflicts.test.ts` (5 cases); upstream `api/Reference_impl.ts:160-165,1824-1841,1888-1905,1945-1951,2193-2206` |
364
+ | M70 | `push(ref, value?)` returns a `ThenableReference` (a `DatabaseReference` with `.then`/`.catch`). The key + ref are minted CLIENT-SIDE and available synchronously even when the optional value write is rules-denied; the write is deferred onto the promise, so a denial REJECTS the awaited push rather than throwing synchronously and discarding the key (DB-B7) | ✓ | Sandbox aligned: `unit:modular/push-thenable.test.ts` (4 cases); matches oracle `scripts/oracle/observations/rtdb-push-autoid-format.json` ("available immediately even when the subsequent server write is denied by rules") + upstream `api/Reference_impl.ts:599-630` |
365
+ | M71 | `DataSnapshot` shape: `size` (getter), `priority` (always `null` — priority deny-listed), `exportVal()`, `key`, `ref`, `val()`, `exists()`, `child()`, `hasChild()`, `hasChildren()`, `forEach()`, `toJSON()`. It does NOT ship the legacy namespaced `numChildren()` method (DB-B10) | ✓ | Sandbox aligned: `unit:modular/snapshot-shape.test.ts` ("exposes size/priority/exportVal; NOT numChildren()"); matches oracle `scripts/oracle/observations/rtdb-modular-get-snapshot-shape.json` (`hasSize: true, hasNumChildren: false`) + upstream `api/Reference_impl.ts:288-447`. **Flipped masking tests**: `modular/queries.test.ts` + `modular/sandbox-target.test.ts` asserted `snap.numChildren()` — updated to `snap.size`. |
366
+ | M72 | Object-valued children are ORDER-EQUAL — the sort/range tie is broken by key (`nameCompare`), NOT by an invented `JSON.stringify` ordering; a query re-write that only reorders object keys is "no change" and doesn't re-fire (DB-B11) | ✓ | Sandbox aligned: `unit:modular/object-order-equality.test.ts`; upstream `core/snap/ChildrenNode.ts:386-400` |
367
+ | M73 | A primitive at the ROOT is legal (`set(ref(db), 'hello')`); a subsequent child write replaces the primitive root ("writes win") (DB-B13) | ✓ | Sandbox aligned: `unit:modular/root-primitive.test.ts` (2 cases) |
368
+ | M74 | `onValue(ref, cb, { onlyOnce: true })` fires once then auto-unsubscribes (DB-B12) | ✓ | Sandbox aligned: `unit:modular/onvalue-onlyonce.test.ts`; upstream `api/Reference_impl.ts:975-980` |
369
+ | M75 | **Divergence (DB-B12, honest doc):** the onChild* callbacks do NOT receive the `previousChildName` second argument; `onValue`/`onChild*` do NOT accept a `cancelCallback`; `onChildAdded`/`Changed`/`Removed`/`Moved` accept only plain refs (not `Query`); `child_moved` never fires (ordered-query move detection unmodeled). These listener-surface holes are out of scope for the current phase — consumers needing them use `firebase/database` directly. | ⚠ | divergence documented; partial coverage: `{ onlyOnce }` IS implemented (M74). |
370
+ | M76 | **Divergence (DB-B9, honest doc):** `.validate` rules are NOT enforced on modular sandbox writes (`set`/`update`/`runTransaction`). The modular write path routes through the same `RulesEvaluator` → `SimulateHandler` as the simulator, which short-circuits on the first ancestor `.write` that grants access without also requiring every ancestor `.validate` to pass (same divergence as row #71). A write the live RTDB rejects via a deeper `.validate` still succeeds in the sandbox. | ⚠ | divergence documented (shared root with row #71); fix path noted under "Simulator-vs-prod divergences". |
371
+
372
+ ### Deferred behaviors (Tier 3+ — out of scope for the current phase unless time permits)
373
+
374
+ - **Child listeners (`onChildAdded` / `onChildChanged` / `onChildRemoved` / `onChildMoved`) — DONE in Tier 2.**
375
+ Sandbox implementation lives in `packages/pyric/src/database/sandbox/backend.ts`
376
+ (per-child diff against the parent's prior children snapshot, captured
377
+ before every write). Plain-ref subscriptions are fully aligned with
378
+ the oracle observations under `scripts/oracle/observations/rtdb-modular-onchild*.json`.
379
+ `onChildMoved` only fires under ordered queries (per the upstream
380
+ contract); the plain-ref no-fire case is locked. Ordered-query
381
+ `onChildMoved` lands with Tier 3's `query` / `orderBy*` surface.
382
+ - **Queries + constraints (`query`, `orderByChild`, `equalTo`, `limit*`).**
383
+ Tier 3. Requires sort/filter pipeline over the in-memory tree at
384
+ read + listener-fire time. Defer until a consumer needs it.
385
+ - **Transactions (`runTransaction`).** Tier 4 — **DONE.** RTDB
386
+ transactions retry-on-conflict against the server's local snapshot,
387
+ with abort-by-returning-`undefined`. Sandbox match is straightforward
388
+ (no concurrency to conflict on) and the semantics are locked by
389
+ oracle observations
390
+ `rtdb-modular-runtransaction-{success,abort-undefined,current-value-arg,returns-committed-snapshot,options-applylocally,on-rules-denied-path}.json`.
391
+ See M37 / M37a–M37h above for the full claim breakdown. The
392
+ retry-on-conflict path (#161) stays unmodeled — single-client
393
+ harness, nothing to conflict with.
394
+ - **Playground integration (`firebase/database` virtual-imports
395
+ re-export).** Tier 5 — **shipped**. App code that imports
396
+ `firebase/database` inside the playground preview iframe is
397
+ aliased to `@pyric/rtdb`'s modular surface. Three-file scope
398
+ expansion:
399
+ - `packages/playground/src/lib/preview/virtual-imports-plugin.ts`
400
+ adds a `firebase/database` entry to the `ALIASES` map listing the
401
+ exports the synthetic re-export module surfaces.
402
+ - `packages/playground/src/lib/preview/preview-scope.ts` adds
403
+ a `'firebase/database'` slot to `PreviewModuleId` and the
404
+ `PreviewScope` interface, constrained via `Pick<typeof PyricRtdbModular, ...>`.
405
+ - `packages/playground/src/components/AppPreview.tsx` installs
406
+ the slot at runtime: imports the modular surface from `@pyric/rtdb`,
407
+ wraps `getDatabase` so a bare-arg call defaults to the runner's
408
+ sandbox (mirrors the `getAuth` / `getFirestore` wrap), and supplies
409
+ the rest of the read/write family unchanged.
410
+
411
+ The `sandbox.*` test-driver namespace (`setRules`, `setData`,
412
+ `snapshotState`) is deliberately omitted from the alias — it's
413
+ runner-side only, not app code. The deploy bundler at
414
+ `packages/playground/src/lib/deploy/bundleApp.ts` already lists
415
+ `firebase/database` as `external` and resolves it to esm.sh's
416
+ upstream module; the metafile gate (`assertNoPyricLeak`) still trips
417
+ on any direct `@pyric/rtdb` import in deployed app code, so the
418
+ preview-only alias is not a deploy-time leak vector.
419
+
420
+ ## Agent-tool surface — deny-list (intentionally NOT shimmed)
421
+
422
+ The agent-tool surface deliberately excludes the parts of
423
+ `firebase/database` that don't translate to a tool-call shape or
424
+ that would require background state the host can't supply. The
425
+ **modular SDK surface** documented below DOES cover most of these —
426
+ the deny-list here is scoped to the agent-tool surface only.
427
+
428
+ | Name | Reason |
429
+ |---|---|
430
+ | `onValue` / `onChildAdded` / `onChildChanged` / `onChildRemoved` / `onChildMoved` / `onDisconnect` | Long-lived listeners don't fit the request-response tool-call shape. Live data streaming is out of scope for v0; consumers needing real-time updates use `firebase/database` directly with a host-supplied `Database` handle. |
431
+ | `query` + ordering constraints (`orderByChild`, `orderByKey`, `orderByValue`, `equalTo`, `startAt`, `endAt`, `limitToFirst`, `limitToLast`) | The tool surface today only does whole-path reads; filtered reads would require either constraint-shaped tool parameters or query objects threaded through the tool boundary. Worth revisiting if agent workflows need filtering. |
432
+ | `runTransaction` | Transactional client semantics (read-modify-write with retry) don't model cleanly as a single tool call; deferred. |
433
+ | `serverTimestamp()` / `increment(n)` sentinels in the data tools | Not yet wired through; would require sentinel-aware serialization on the REST + admin paths. |
434
+ | `goOnline` / `goOffline` / `Database.useEmulator` | No persistent client state for the data tools (admin REST + on-demand user clients); not modeled. |
435
+ | `connectDatabaseEmulator` | Out of scope; the package targets real RTDB. |
436
+ | `Database.app` / `getDatabase()` | Lifecycle is owned by the host (`host.getClientForUser`); package consumers don't construct `Database` directly. |
437
+
438
+ ---
439
+
440
+ ## Modular SDK surface
441
+
442
+ The `firebase/database` modular shim — the surface a consumer
443
+ imports as `getDatabase`, `ref`, `set`, `onValue`, etc. The sandbox
444
+ implementation lives under `packages/pyric/src/database/` (dispatch in
445
+ `modular.ts`, backend in `sandbox/`); see the "Modular SDK surface
446
+ (Phase 3)" section above for the per-row detail. The oracle probes
447
+ lock prod behavior so the sandbox is correct by construction.
448
+
449
+ Targets (same flavors as `pyric/firestore`):
450
+
451
+ - **sandbox** — frozen-ctx target built via `getDatabase(ctx: SandboxContext)`. Identity baked in at handle-construction.
452
+ - **sandbox-live** — live-identity target built via `getDatabase(sandbox: Sandbox)`. Every op re-reads `sandbox.currentUser`. The playground preview always uses this flavor.
453
+ - **prod** — `firebase/database` target built via `getDatabase(app: FirebaseApp)`. Identity comes from `firebase/auth`'s `currentUser`.
454
+
455
+ Sandbox rows lift from **—** to **✓** as the implementation lands;
456
+ prod rows sit at **?** unless an oracle observation under
457
+ `scripts/oracle/observations/` locks them. The matrix is the spec;
458
+ the oracle locks it.
459
+
460
+ ### `getDatabase(target)` — initializer
461
+
462
+ | # | Behavior | Status | Probe |
463
+ |---|---|---|---|
464
+ | 94 | `getDatabase(ctx)` returns a tagged sandbox-target handle (frozen identity) | — | Phase 3 |
465
+ | 95 | `getDatabase(sandbox)` returns a tagged sandbox-live handle (per-op identity) | — | Phase 3 |
466
+ | 96 | `getDatabase(app)` returns a tagged prod target | ? | upstream `firebase/database` contract; not currently probed in isolation |
467
+ | 97 | `getDatabase()` (no argument) — wrapped in the playground preview to default to the sandbox; raw call delegates to prod | ✓ (wrap, fixture passing) | Phase 3 Tier 5: virtualized in the playground preview scope. Wired at `packages/playground/src/components/AppPreview.tsx` (slot install with bare-call wrap), `packages/playground/src/lib/preview/virtual-imports-plugin.ts` (alias map), and `packages/playground/src/lib/preview/preview-scope.ts` (type-level slot). Mirrors the `getAuth` / `getFirestore` wrap pattern. Demo fixture: `packages/playground/scripts/fixtures/rtdb-set-get-roundtrip.tsx` (bare `getDatabase()` + `set`/`get`/`remove` round-trip with anon sign-in) — passes end-to-end through the `bun run debug:fixtures` Playwright suite (revived in the playground rtdb-fixture follow-up; the suite previously couldn't load `@pyric/rtdb` in the client because its top-level re-export of `DataHandler` pulled `firebase-admin`, now stubbed via `packages/playground/src/lib/node-shims/firebase-admin.ts`). |
468
+ | 98 | Two `getDatabase(sandbox)` calls share state (same underlying `LocalEnvironment`) | — | Phase 3 |
469
+ | 99 | Handle dispatch by `TARGET_SYMBOL` brand — refs route to their owning target via a `refToTarget` WeakMap (mirror of firestore's pattern) | — | Phase 3 |
470
+
471
+ ### `ref(db, path)` / `child` / `parent` / `root`
472
+
473
+ | # | Behavior | Status | Probe |
474
+ |---|---|---|---|
475
+ | 100 | `ref(db, path)` returns a tagged `DatabaseReference` carrying `key`, `parent`, `root`, `toString()` | ? | upstream `firebase/database` contract |
476
+ | 101 | `ref(db)` with no path returns the root ref (`key === null`, `parent === null`) | ? | upstream contract |
477
+ | 102 | `child(ref, 'a/b')` joins a relative path, including embedded slashes | ? | upstream contract |
478
+ | 103 | `ref.parent` is `null` at root, otherwise the parent ref | ? | upstream contract |
479
+ | 104 | `ref.key` is the final path segment, `null` for root | ? | upstream contract |
480
+ | 105 | Unknown ref (not produced by this package) → `TypeError` in shim ops | — | Phase 3 |
481
+
482
+ ### `get(ref)` — single read
483
+
484
+ | # | Behavior | Status | Probe |
485
+ |---|---|---|---|
486
+ | 106 | Returns a `DataSnapshot` carrying `.val()`, `.exists()`, `.key`, `.ref`, `.size` (getter, returns child count), `.hasChildren()`, `.hasChild(path)`, `.forEach(cb)`. The legacy namespaced-SDK method `.numChildren()` is **NOT** on the modular DataSnapshot — use `.size` instead. Observed: `hasNumChildren: false`, `size: 3` for a `{a,b,c}` object, `forEachKeys: ['a','b','c']` against blockingfun, fb-js-sdk 12.13.0. | ✓ | oracle: `scripts/oracle/observations/rtdb-modular-get-snapshot-shape.json` |
487
+ | 107 | `snap.val()` returns `null` for a missing path (NOT a thrown error — RTDB diverges from Firestore here; `getDoc` returns `exists()===false` but `get` on RTDB just returns a `null`-val snapshot) | ✓ | oracle: `scripts/oracle/observations/rtdb-modular-get-missing-path.json` — observed `threw: false, val: null, exists: false` on a never-written path against blockingfun. |
488
+ | 108 | `snap.exists()` is `false` when `val() === null`, `true` otherwise | ✓ | oracle: `scripts/oracle/observations/rtdb-modular-get-missing-path.json` — observed `exists: false` for `val: null`. |
489
+ | 109 | Round-trip: `set(ref, payload)` then `get(ref)` returns the payload (lock the basic write→read invariant) | ✓ | oracle: `scripts/oracle/observations/rtdb-set-then-get-roundtrip.json` — the payload round-trips structurally on both sides (this row's claim holds). NOTE — adjacent divergence pinned in `oracle-conformance.test.ts`: prod returns object children in LEXICOGRAPHIC key order (the capture's `roundTripEqual: false` — a `JSON.stringify` round-trip against a non-sorted payload fails), while the sandbox preserves insertion order (stringify round-trip succeeds). Key-order-sensitive consumers behave differently. |
490
+ | 110 | Rules-denied read throws a plain `Error` (NOT a `FirebaseError`) with `code: 'PERMISSION_DENIED'` (uppercase snake-case) — matches the agent-tool rows #15/#20 | ✓ | oracle: `scripts/oracle/observations/rtdb-rules-denied-error-code.json` |
491
+
492
+ ### `set(ref, value)` — full write
493
+
494
+ | # | Behavior | Status | Probe |
495
+ |---|---|---|---|
496
+ | 111 | Replaces the value at the path entirely; resolves to `undefined` (unlike `setDoc` which resolves to `void`, RTDB's `set` is documented as `Promise<void>`) | ? | upstream contract |
497
+ | 112 | `set(ref, null)` removes the path entirely — equivalent to `remove(ref)`, subsequent `get` returns `null`-val snapshot | ✓ | oracle: `scripts/oracle/observations/rtdb-modular-set-null-equals-remove.json` — observed `beforeExists: true → afterExists: false, afterVal: null` after `set(ref, null)`. |
498
+ | 113 | Nested objects overwrite — `set(ref, {a: 1})` after `set(ref, {a: 1, b: 2})` leaves `{a: 1}` only, NOT a merge (RTDB `set` is replacement, not merge) | ✓ | oracle: `scripts/oracle/observations/rtdb-modular-set-replaces-not-merges.json` — observed `final: {a: 1}` with `b` absent after the second set. |
499
+ | 114 | Primitive round-trip — numbers, strings, booleans, arrays all survive a set→get cycle | ✓ | oracle: `scripts/oracle/observations/rtdb-set-then-get-roundtrip.json` — the payload round-trips structurally on both sides (this row's claim holds). NOTE — adjacent divergence pinned in `oracle-conformance.test.ts`: prod returns object children in LEXICOGRAPHIC key order (the capture's `roundTripEqual: false` — a `JSON.stringify` round-trip against a non-sorted payload fails), while the sandbox preserves insertion order (stringify round-trip succeeds). Key-order-sensitive consumers behave differently. |
500
+ | 115 | Rules-denied write throws plain `Error` with `code: 'PERMISSION_DENIED'` (same shape as #110) | ✓ | oracle: `scripts/oracle/observations/rtdb-rules-denied-error-code.json` |
501
+
502
+ ### `update(ref, values)` — partial / multi-path update
503
+
504
+ | # | Behavior | Status | Probe |
505
+ |---|---|---|---|
506
+ | 116 | `update(ref, {a: 1, b: 2})` merges top-level keys at the ref; unspecified keys preserved (in contrast to `set`'s replacement) | ✓ | oracle: `scripts/oracle/observations/rtdb-modular-update-merges-keys.json` — after `set({a:1,b:2})` then `update({a:10})`, observed `final: {a:10, b:2}`. |
507
+ | 117 | Multi-path update — `update(parentRef, { 'a/x': 1, 'b/y': 2 })` lands BOTH writes atomically at distinct subtrees (RTDB's most distinctive feature; this is the "fan-out" pattern) | ✓ | oracle: `scripts/oracle/observations/rtdb-modular-update-multipath-atomic.json` — observed `aX: 1, bY: 2` both readable after a single update call. |
508
+ | 118 | Multi-path update is atomic: if any path is denied by rules, the entire update rejects and no path is written | ✓ | oracle: `scripts/oracle/observations/rtdb-modular-update-multipath-rules-denial.json` — observed `threw: true, code: 'PERMISSION_DENIED'` AND `okPathWrittenDespiteDenial: false` (the otherwise-permitted path also rolled back). |
509
+ | 119 | Setting a key to `null` inside `update` removes that key — same equivalence as `set(ref, null)` | ✓ | oracle: `scripts/oracle/observations/rtdb-modular-update-null-removes-key.json` — after `set({a:1,b:2})` then `update({a:null})`, observed `final: {b:2}` with `a` absent. |
510
+ | 120 | Update path validation — overlapping paths (e.g. `'/a'` and `'/a/x'` in the same call) throws synchronously before any write | ? | upstream contract — needs targeted probe |
511
+
512
+ ### `remove(ref)` — delete
513
+
514
+ | # | Behavior | Status | Probe |
515
+ |---|---|---|---|
516
+ | 121 | Removes the value AND all children; subsequent `get` returns `null`-val snapshot | ✓ | oracle: `scripts/oracle/observations/rtdb-remove-vs-set-null.json` |
517
+ | 122 | Idempotent — `remove` on a path that's already absent resolves successfully (no-throw) | ✓ | oracle: `scripts/oracle/observations/rtdb-modular-remove-idempotent.json` — `remove` on a never-written path observed `threw: false, afterExists: false`. |
518
+ | 123 | `remove(ref)` and `set(ref, null)` produce the same end state — locks the documented RTDB invariant | ✓ | oracle: `scripts/oracle/observations/rtdb-remove-vs-set-null.json` |
519
+
520
+ ### `push(ref, value?)` — auto-id append
521
+
522
+ | # | Behavior | Status | Probe |
523
+ |---|---|---|---|
524
+ | 124 | `push(ref).key` is a 20-char string starting with `-`, available **synchronously** (client-side mint, no server round-trip required) | ✓ | oracle: `scripts/oracle/observations/rtdb-push-autoid-format.json` |
525
+ | 125 | Sequential `push` calls produce monotonically-sortable keys (timestamp-prefixed for chronological ordering via `orderByKey`) | ✓ | oracle: `scripts/oracle/observations/rtdb-push-autoid-format.json` |
526
+ | 126 | `push(ref, value)` writes the value AND returns the new child ref (both behaviors in one call); `push(ref)` mints the ref without writing | ✓ | oracle: `scripts/oracle/observations/rtdb-modular-push-with-value.json` — `await push(parent, {hello:'world'})` returned a ref with a 20-char key; subsequent `get(r)` returned `{hello:'world'}`. |
527
+ | 127 | The returned ref `r = push(parent, value)` is usable in follow-up ops: `get(r)`, `set(r, …)`, `remove(r)` | ✓ | oracle: `scripts/oracle/observations/rtdb-modular-push-with-value.json` — observed all 4 follow-up ops succeed through the returned ref (`refIsUsableForFollowupOps: true`). |
528
+
529
+ ### `onValue(ref, cb)` — value-level listener
530
+
531
+ | # | Behavior | Status | Probe |
532
+ |---|---|---|---|
533
+ | 128 | Subscribing to a path with **existing data** fires the listener once with the current snapshot (the "initial fire") | ✓ | oracle: `scripts/oracle/observations/rtdb-modular-onvalue-initial-with-data.json` — observed exactly 1 initial fire within ~46ms of subscribe, snapshot.val() === the seeded payload. |
534
+ | 129 | Subscribing to a **nonexistent path** still fires the listener once — with a `null`-val snapshot AND `exists() === false`. Matches Firestore's `onSnapshot`-on-missing-doc semantics: prod RTDB does NOT silently skip the initial fire for empty paths. | ✓ | oracle: `scripts/oracle/observations/rtdb-modular-onvalue-initial-no-data.json` — observed 1 initial fire on a never-written path with `firstFire.val: null, firstFire.exists: false` (~55ms after subscribe). |
535
+ | 130 | Subsequent `set(ref, …)` fires the listener with the new value | ✓ | oracle: `scripts/oracle/observations/rtdb-onvalue-fires-on-set.json` — observed 1 fire per `set()` (1+1+1 = 3 total: initial-null, after-first-set, after-second-set). |
536
+ | 131 | Unsubscribe — the returned unsubscribe function stops further fires; subsequent writes produce 0 additional fires after `unsub()` | ✓ | oracle: `scripts/oracle/observations/rtdb-modular-onvalue-unsubscribe.json` — observed `preUnsubFires: 2, postUnsubFires: 2` (a write performed after `unsub()` produced 0 additional fires within a 500ms settle window). |
537
+ | 132 | The returned value from `onValue(ref, cb)` is the unsubscribe function (NOT an object); calling it removes the listener | ? | upstream contract — locked indirectly by #131 |
538
+
539
+ ### `onChildAdded` / `onChildChanged` / `onChildRemoved` / `onChildMoved`
540
+
541
+ | # | Behavior | Status | Probe |
542
+ |---|---|---|---|
543
+ | 133 | `onChildAdded` replays the existing children on subscribe — one fire per existing child key, in `orderByKey` order by default (unlike `onValue` which fires once with the parent snapshot) | ✓ | oracle: `scripts/oracle/observations/rtdb-modular-onchildadded-initial-replay.json` — seeded `{k1, k2, k3}`, observed 3 initial fires with `firedKeys: ['k1', 'k2', 'k3']` in insertion order against blockingfun. |
544
+ | 134 | After subscribe, adding a child via `push` or `set(child, …)` fires `onChildAdded` exactly once for that key | ✓ | oracle: `scripts/oracle/observations/rtdb-modular-onchildadded-post-subscribe.json` — seeded `{k1,k2}`, observed `postSubscribeFires: 1, lastFire: {key:'k3', val:{v:3}}` after writing the new child. |
545
+ | 135 | `onChildChanged` fires when an existing child's value changes; does NOT fire for added or removed children | ✓ | oracle: `scripts/oracle/observations/rtdb-modular-onchildchanged-fires-on-update.json` — observed `firedOnInitial: 0, firedOnUpdate: 1, lastFire: {key:'k1', val:{v:2}}` (the NEW value, not the prior). |
546
+ | 136 | `onChildRemoved` fires when a child is deleted (via `remove(child)` or `set(child, null)`); snapshot carries the PRIOR value | ✓ | oracle: `scripts/oracle/observations/rtdb-modular-onchildremoved-fires-on-delete.json` — observed `firedOnDelete: 1, removedSnapCarriesPriorValue: true` (snapshot.val() was the pre-delete value). |
547
+ | 137 | `onChildMoved` under an ordered query. Prod: fires when a child's `orderByChild`/`orderByValue` priority changes — emitted only on ordered queries. Sandbox: **never fires on reorder** — `onChildMoved` supports the plain-ref (no-fire) case only; the ordered-query overload is unimplemented | ⚠ | divergence, oracle-locked by `scripts/oracle/observations/rtdb-modular-onchildmoved-with-orderby.json`: prod observed `firedOnMove: 1` under `query(ref, orderByChild('priority'))` after bumping a child's priority to a new sort position; the sandbox fires 0 on reorder. Partial alignment landed: all `onChild*` now ACCEPT a `Query` (previously threw a misleading `unrecognized reference` TypeError) with window-aware `child_added`/`child_changed`/`child_removed` diffs (`src/database/sandbox/backend.ts`); `child_moved` reorder semantics and `previousChildName` ordering remain held pending two new oracle captures. Both sides pinned in `modular/oracle-conformance.test.ts` and `modular/sandbox-child-events.test.ts`. Sandbox Tier 2 locks the plain-ref no-fire case (M46). |
548
+
549
+ ### `off(ref, eventType?, callback?)` — unsubscribe variants
550
+
551
+ | # | Behavior | Status | Probe |
552
+ |---|---|---|---|
553
+ | 138 | `off(ref)` removes ALL listeners at that ref (any event type, any callback) | ✓ | oracle: `scripts/oracle/observations/rtdb-modular-off-stops-child-fires.json` — after `off(ref)` with no eventType, a subsequent write produced `postOffFires: 0` against an `onChildAdded` registration. |
554
+ | 139 | `off(ref, 'value')` removes only `value` listeners at that ref | ✓ | Sandbox aligned (M48); oracle: `scripts/oracle/observations/rtdb-off-eventtype-precision.json` — registered TWO `value` listeners + one `child_added` at the same ref; after `off(ref, 'value')` (no callback), `valueListenersStopped: true` (neither value cb fired on subsequent writes) AND `childListenerStillFiringAfterOffValue: true` (the child listener kept firing). `offValueClearsAllValueListeners: true` confirms the no-callback variant removes ALL value listeners at the ref. |
555
+ | 140 | `off(ref, 'value', cb)` removes only the specific callback | ✓ | Sandbox aligned (M48); adjacent to #141 — the upstream `off` with the cb argument removes only the matching callback. Same probe (`rtdb-onvalue-unsub-equivalence.json` Case 2) confirms `off(ref, 'value', cb)` stops only that callback. |
556
+ | 141 | The returned unsubscribe function from `onValue(ref, cb)` is equivalent to `off(ref, 'value', cb)` | ✓ | Sandbox aligned (M48); oracle: `scripts/oracle/observations/rtdb-onvalue-unsub-equivalence.json` — `unsubReturnType: 'function'`, `unsubReturnedFnStopsListener: true` (the captured return value halted fires on write), `offRefValueCbStopsListener: true` (the same effect via `off(ref, 'value', cb)`), `bothFormsEquivalent: true`. |
557
+
558
+ ### `query(ref, ...constraints)` + ordering / bounds / limits
559
+
560
+ | # | Behavior | Status | Probe |
561
+ |---|---|---|---|
562
+ | 142 | `query(ref, orderByChild('field'), limitToFirst(N))` returns a `Query` whose `get()` resolves a snapshot containing N children ordered by `field` | ✓ | oracle: `scripts/oracle/observations/rtdb-modular-query-orderbychild-limit.json` — seeded 4 children with positions `[3,1,4,2]`, observed `orderedKeys: [{key:'a',pos:1}, {key:'b',pos:2}]` (first 2 in ascending order). Requires `.indexOn` declared in rules. |
563
+ | 143 | `orderByKey()` orders by the auto-id / numeric key | ✓ | oracle: `scripts/oracle/observations/rtdb-modular-orderbykey-window.json` — seeded `{a,b,c,d,e}` in shuffled insertion order, observed `matchedKeys: ['b','c','d']` for `orderByKey() + startAt('b') + endAt('d')` (in key order). |
564
+ | 144 | `orderByValue()` orders by the primitive value of each child (for collections of primitives) | ✓ | oracle: `scripts/oracle/observations/rtdb-modular-orderbyvalue-numeric.json` — seeded `{alice:30, bob:10, carol:50, dave:20, eve:40}`, the prod call threw `Index not defined, add ".indexOn": ".value"` (so prod enforces an index requirement on `orderByValue()`); semantic ordering claim still holds, sandbox does not enforce indexes. |
565
+ | 145 | `equalTo(v)` filters children whose ordered field === v (returns 0, 1, or multiple matches — RTDB does NOT enforce uniqueness) | ✓ | oracle: `scripts/oracle/observations/rtdb-modular-query-equalto.json` — seeded `{red, blue, blue, green}`, observed `matchedKeys: ['k2', 'k3']` for `equalTo('blue')` (both blue children, none of the others). Additional probe: `scripts/oracle/observations/rtdb-modular-equalTo-filter.json` (a..b..c groups) confirms `equalTo('b')` returns the two `b` children. |
566
+ | 146 | `startAt(v)` is **inclusive** (the child whose ordered value === v is included) | ✓ | oracle: `scripts/oracle/observations/rtdb-modular-query-startat-inclusive.json` — seeded positions `[1,2,3,4]`, observed `matched: [2,3,4]` for `startAt(2)` (cursor doc included). |
567
+ | 147 | `endAt(v)` is **inclusive** | ✓ | oracle: `scripts/oracle/observations/rtdb-modular-orderbychild-window.json` — `startAt(2) + endAt(4)` matched positions `[2,3,4]` (endAt(4) included its boundary value). |
568
+ | 148 | `startAfter(v)` is **exclusive** | ✓ | oracle: `scripts/oracle/observations/rtdb-modular-startafter-endbefore-exclusive.json` — `startAfter(2) + endBefore(5)` matched positions `[3,4]` (cursor `2` dropped). |
569
+ | 149 | `endBefore(v)` is **exclusive** | ✓ | oracle: `scripts/oracle/observations/rtdb-modular-startafter-endbefore-exclusive.json` — same probe; cursor `5` dropped. |
570
+ | 150 | `limitToFirst(N)` caps the result count from the start of the ordered range | ✓ | oracle: `scripts/oracle/observations/rtdb-modular-query-orderbychild-limit.json` plus `scripts/oracle/observations/rtdb-modular-limittofirst-vs-limittolast.json` (firstPositions `[1,2]`). |
571
+ | 151 | `limitToLast(N)` caps from the end | ✓ | oracle: `scripts/oracle/observations/rtdb-modular-limittofirst-vs-limittolast.json` — observed `lastKeys: ['d','e'], lastPositions: [4,5]` for `limitToLast(2)` on a 5-child collection ordered by `pos`. |
572
+ | 152 | Listeners on a `Query` (`onValue(q, …)`) emit only the windowed snapshot — NOT the parent ref's full data | ✓ | oracle: `scripts/oracle/observations/rtdb-modular-onvalue-with-query.json` — seeded 3 children, watched first 2 by `pos`; observed 3 fires total: (1) initial `[a,b]`, (2) OUTSIDE-window write to `c/extra` did NOT fire, (3) INSIDE-window mutation of `a` re-fired, (4) new child `z` displaced `b` and re-fired. Outside-window writes are silent. |
573
+
574
+ ### Sentinels — `serverTimestamp()` / `increment(n)`
575
+
576
+ | # | Behavior | Status | Probe |
577
+ |---|---|---|---|
578
+ | 153 | `serverTimestamp()` resolves server-side to a **number** (epoch milliseconds) — diverges from Firestore's `Timestamp` instance | ✓ | oracle: `scripts/oracle/observations/rtdb-servertimestamp-resolves.json` — observed `createdAtType: 'number', createdAt: 1779075391118` (i.e. a plain JS number, NOT a `Timestamp` object). |
579
+ | 154 | `serverTimestamp()` as a field value in `set` or `update` writes the `{".sv": "timestamp"}` sentinel; the read-back value is the resolved number | ✓ | oracle: `scripts/oracle/observations/rtdb-servertimestamp-resolves.json` — read-back showed `createdAtSentinelShape: false` (sentinel resolved server-side; client sees the number, not the `.sv` placeholder). |
580
+ | 155 | `increment(n)` against a **missing** field starts at 0 (so `increment(5)` lands as `5`) | ✓ | Sandbox aligned (modular `increment` export now present): `unit:modular/increment.test.ts` ("increment against a missing field starts from 0"); matches oracle `scripts/oracle/observations/rtdb-modular-increment-from-missing.json` — observed `afterFirst: 5` from `increment(5)` against an absent `count` field. |
581
+ | 156 | `increment(n)` against an existing numeric field adds atomically; negative deltas subtract | ✓ | Sandbox aligned: `unit:modular/increment.test.ts` ("subsequent increments accumulate (positive then negative)" + "nested inside an update patch resolves per-field"); matches oracle `scripts/oracle/observations/rtdb-modular-increment-from-missing.json` — observed `afterSecond: 8` (5+3) then `afterNegative: 6` (8-2). |
582
+ | 157 | Two concurrent `increment` calls interleave correctly (last-write-wins is NOT the contract — both deltas accumulate) | ? | hard to observe deterministically from a single client; documented contract |
583
+
584
+ ### `runTransaction(ref, transactionUpdate, options?)` — optimistic concurrency
585
+
586
+ | # | Behavior | Status | Probe |
587
+ |---|---|---|---|
588
+ | 158 | Basic success — `runTransaction(ref, current => (current ?? 0) + 1)` resolves `{ committed: true, snapshot }` where `snapshot.val()` is the new value | ✓ | oracle: `scripts/oracle/observations/rtdb-modular-runtransaction-success.json` — observed `committed: true, snapVal: 1` after running `current => (current ?? 0) + 1` against an empty ref. |
589
+ | 159 | Returning `undefined` from the update fn **aborts** the transaction — resolves `{ committed: false }`, no write performed (RTDB-specific; distinct from Firestore where the only abort path is throwing) | ✓ | oracle: `scripts/oracle/observations/rtdb-modular-runtransaction-abort-undefined.json` — seeded `100` then transaction returned `undefined`; observed `committed: false, snapVal: null, afterValOnServer: 100` (existing value preserved). |
590
+ | 160 | The update fn is called with the CURRENT server value (may be `null` if the ref is empty); the fn's return value is the proposed new value | ✓ | oracle: `scripts/oracle/observations/rtdb-modular-runtransaction-success.json` — observed `seenCurrentValues: [null]` on first invocation against an empty ref (a single call, no speculative re-runs against `undefined`). NOTE — adjacent divergence pinned in `modular/oracle-conformance.test.ts`: for a SEEDED path, prod speculatively invokes the update fn twice (first with `null`, then the real value; `rtdb-modular-runtransaction-current-value-arg.json` `seededArgs.length: 2`) while the sandbox invokes once with the actual value. The argument-semantics claim of this row holds for the effective invocation on both sides. WARNING for update-fn authors: prod may invoke your fn first with `null` even when data exists — the pattern `if (current === null) return;` (abort-on-empty) silently loses writes on prod while working on the sandbox, and side effects inside the fn can run twice on prod. Held (not mimicked) deliberately: the speculative call is a cold-cache artifact, and sandbox determinism + replay depend on single invocation; re-evaluate when a warm-client capture exists. |
591
+ | 161 | Concurrent contention — if another client writes between the read and write, the update fn is retried with the new current value (typically up to 25 retries by default) | ? | hard to observe deterministically from a single client |
592
+ | 162 | Result snapshot's `.val()` reflects the committed value (or the existing value if aborted) | ✓ | oracle: `scripts/oracle/observations/rtdb-modular-runtransaction-success.json` — observed `snapVal: 1` matching the committed value. |
593
+
594
+ ### `goOnline` / `goOffline` — connection control
595
+
596
+ | # | Behavior | Status | Probe |
597
+ |---|---|---|---|
598
+ | 163 | `goOffline(db)` disconnects the client; subsequent writes queue locally and surface via `onValue` with `hasPendingWrites` (cached value) until `goOnline` flushes them | — | Phase 3 — needs the sandbox to model an offline state; in prod this is upstream contract |
599
+ | 164 | `goOnline(db)` reconnects and flushes queued writes | — | Phase 3 |
600
+
601
+ ### `connectDatabaseEmulator` — emulator hook
602
+
603
+ | # | Behavior | Status | Probe |
604
+ |---|---|---|---|
605
+ | 165 | No-op on sandbox-target handles (the sandbox IS the local emulator) | — | Phase 3 |
606
+ | 166 | Forwards to `firebase/database`'s `connectDatabaseEmulator` on prod-target handles | — | Phase 3 |
607
+
608
+ ### `sandbox.*` — sandbox-only test driver
609
+
610
+ | # | Behavior | Status | Probe |
611
+ |---|---|---|---|
612
+ | 167 | `sandbox.setData(db, {path: value, ...})` bulk-loads data, bypassing rules | — | Phase 3 — mirror of firestore's `sandbox.seedDocuments` |
613
+ | 168 | `sandbox.setRules(db, rules)` loads rules into the underlying local environment; returns `LintResult` | — | Phase 3 |
614
+ | 169 | `sandbox.snapshotState(db)` dumps every path the local store has stored | — | Phase 3 |
615
+ | 170 | All `sandbox.*` methods throw on prod-target handles with `failed-precondition` | — | Phase 3 |
616
+
617
+ ### Modular SDK surface — deny-list (intentionally NOT shimmed)
618
+
619
+ | Name | Reason |
620
+ |---|---|
621
+ | `enableLogging` | Logging is owned by the host harness, not the modular SDK shim. |
622
+ | IndexedDB persistence APIs (the Web SDK's RTDB caches in-memory; there's no `enableIndexedDbPersistence` for RTDB, but if upstream adds one, we deny-list it for sandbox parity with firestore's persistence deny-list) | Persistence is owned by `pyric/sandbox`; the modular SDK's cache APIs would conflict. |
623
+ | `.info/connected` reads (`onValue(ref(db, '.info/connected'), …)`) | The sandbox has no real connection state to model; firing `true` constantly or never would be a divergence either way. Phase 3 may model this as always-`true` on the sandbox-target. |
624
+ | `onDisconnect(ref).set(...)` / `.update(...)` / `.remove(...)` / `.cancel()` | Disconnect handlers require a real network channel; the sandbox has no equivalent. Considered for Phase 3 with explicit divergence documentation. NOT exported (no build break — nothing in the shim references it). |
625
+ | `orderByPriority()` / `setPriority(ref, p)` / `setWithPriority(ref, v, p)` and the whole `.priority` model (DB-B6, DB-GAP) | RTDB's priority model — a per-node `.priority` plus a `PriorityIndex` default ordering — is a cross-cutting data-model concern (every node carries an optional priority; the default child ordering is by priority, not key). Modeling it faithfully touches the tree, the snapshot surface, and every query path; it is not cheap and there is no agent/playground demand. Deny-listed with this note. **Divergence:** the sandbox's default child ordering is `orderByKey` (not priority); `setPriority`/`setWithPriority`/`orderByPriority` are not exported. Consumers needing priority use `firebase/database` directly. |
626
+ | `refFromURL(db, url)` | Resolving an absolute `https://<db>.firebaseio.com/path` URL has no meaning against the in-memory sandbox (no host/namespace). Use `ref(db, path)`. |
627
+
628
+ ---
629
+
630
+
631
+
632
+ ### Agent-tool surface — rows still marked **?** (need explicit probes / oracle observations)
633
+
634
+ _All previously-unprobed rows in this section are now oracle-locked. The list below tracks what was locked in the last sweep, with the probe name + matrix row for each._
635
+
636
+ - ~~#5 RTDB REST `.json`-suffix contract~~ — locked by `rtdb-rest-json-suffix-contract.json`.
637
+ - ~~#39 rules-JSON round-trip vs the REST `/.settings/rules.json` accept format~~ — locked by `rtdb-rules-json-roundtrip.json` (`exactRoundTrip: true`).
638
+ - ~~#46 rules-deploy propagation timing~~ — locked by `rtdb-rules-deploy-propagation-timing.json` (`firstSuccessElapsedMs: 154`).
639
+ - ~~#58 RTDB shallow REST response shape~~ — locked by `rtdb-shallow-rest-response-shape.json`.
640
+ - ~~#71 simulator-vs-live-RTDB allow/deny agreement~~ — observed by `rtdb-simulator-vs-prod-agreement.json` (28/29 agree; 1 divergence documented below). Status is **⚠** rather than **✓** because of the validate-rule divergence.
641
+
642
+ Rows **flipped from ? to ✓ by Phase 3** (the modular sandbox locks the contract; oracle observations remain blocked on the live RTDB rules at the oracle project, but the sandbox aligned tests pin the same end-state the documented prod behavior requires):
643
+
644
+ - #18, #31 `set(null) === remove` end-state equivalence — locked by `unit:modular/sandbox-target.test.ts`.
645
+ - #23 atomic fan-out update at root path — locked by `unit:modular/sandbox-target.test.ts`.
646
+ - #24 multi-path update overlap validation — locked by `unit:modular/sandbox-target.test.ts`.
647
+ - #32 idempotent remove on non-existent path — locked by `unit:modular/sandbox-target.test.ts`.
648
+
649
+ ### Modular SDK surface — rows still marked **?** (need explicit probes)
650
+
651
+ Rows **locked by the empirical oracle harness** (committed observations under `scripts/oracle/observations/`, captured against the `blockingfun` project):
652
+
653
+ - #96–#105 — most of the `getDatabase` + `ref` rows are still **?**. These are upstream `firebase/database` shape claims that hold by definition; they'll lift to **✓** when the Phase 3 shim's unit tests cover them.
654
+ - #111 `set()` return shape (`Promise<void>`) — trivial; first sandbox unit test will lock.
655
+ - #120 `update()` path-validation errors — same as agent-tool #24.
656
+ - #132 the unsubscribe return-value shape — locked indirectly by #131 (`unsubStopsFires: true`); a stronger one-claim probe would assert `typeof returnedValue === 'function'`.
657
+ - #134–#137 `onChildAdded` post-subscribe / `onChildChanged` / `onChildRemoved` / `onChildMoved` — fire-count claims; each needs a probe that mutates the parent after subscribe.
658
+ - #138–#141 `off(ref, ...)` variants — needs probes that register multiple listeners and verify the correct subset gets removed.
659
+ - ~~#143 `orderByKey()`, #144 `orderByValue()`, #147 `endAt`, #148 `startAfter`, #149 `endBefore`, #151 `limitToLast`, #152 query-listener windowing~~ — all locked in Phase 3 Tier 3 by 7 new oracle probes (see "Modular SDK surface — Phase 3 Tier 3" below).
660
+ - #157 concurrent `increment` interleaving — hard to observe from a single client; punt to a 2-client harness if it ever becomes load-bearing.
661
+ - #161 transaction conflict-retry — same problem; documented contract.
662
+
663
+ ### Modular SDK surface — rows locked by the empirical oracle harness (Phase 1 output)
664
+
665
+ 36 oracle observations under `scripts/oracle/observations/rtdb-modular-*.json` lock the following modular-SDK rows against `blockingfun`, fb-js-sdk 12.13.0 (20 from Phase 1 + 4 transaction probes from Tier 4 + 5 child-event probes from Tier 2 + 7 query probes from Tier 3):
666
+
667
+ - #106 `DataSnapshot` shape — `size` is a getter, `numChildren()` is NOT on the modular SDK (was the legacy namespaced API).
668
+ - #107/#108 `get()` on missing path → `{ val: null, exists: false }`, no throw.
669
+ - #112 `set(ref, null)` removes the path (subsequent `get` returns `null`).
670
+ - #113 `set` replaces (NOT merges) at the parent ref.
671
+ - #116 `update` merges top-level keys; unspecified keys preserved.
672
+ - #117 multi-path update — `update(parentRef, { 'a/x': 1, 'b/y': 2 })` lands both writes.
673
+ - #118 multi-path update atomicity — one path denied → entire update rejects, no partial application.
674
+ - #119 `update` with `null` value removes a key.
675
+ - #122 `remove` on absent path is idempotent (no-throw).
676
+ - #126/#127 `push(ref, value)` writes the value AND returns a ref usable for follow-up ops.
677
+ - #128 `onValue` initial fire with existing data — exactly 1 fire.
678
+ - #129 `onValue` initial fire on a nonexistent path — fires once with `val: null, exists: false` (matches Firestore semantics; RTDB does NOT silently skip the empty-path initial fire).
679
+ - #130 `onValue` fires on subsequent `set`.
680
+ - #131 unsubscribe stops further fires (`postUnsubFires === preUnsubFires`).
681
+ - #133 `onChildAdded` replays existing children on subscribe — one fire per existing key.
682
+ - #134 `onChildAdded` post-subscribe — exactly one fire per new direct child write.
683
+ - #135 `onChildChanged` — no initial replay, fires once when an existing child transitions, snapshot carries the NEW value.
684
+ - #136 `onChildRemoved` — no initial replay, fires once on delete, snapshot carries the PRIOR (now-removed) value.
685
+ - #137 `onChildMoved` — fires under ordered queries (`query(ref, orderByChild(...))`) when a child's ordering value moves it to a new sort position; the Tier 2 sandbox locks the plain-ref no-fire case, Tier 3 wires the ordered-query path.
686
+ - #138/#139 `off(ref)` (no event type) stops further fires — locked against an `onChildAdded` registration; covers the off() variants by extension.
687
+ - #142/#150 `query(ref, orderByChild('pos'), limitToFirst(2))` returns the first 2 ordered children.
688
+ - #145 `equalTo(v)` filters to exactly the matching children.
689
+ - #146 `startAt(v)` is inclusive.
690
+ - #153/#154 `serverTimestamp()` resolves to a JS `number` (epoch ms), NOT a `Timestamp` instance.
691
+ - #155/#156 `increment(n)` starts at 0 on missing fields and accumulates (incl. negative deltas).
692
+ - #158/#160/#162 `runTransaction` happy path — committed: true, snapshot.val() is the new value, update fn sees `null` for empty refs.
693
+ - #159 `runTransaction` abort by returning `undefined` — committed: false, existing server value preserved.
694
+ - #160 (extended) `runTransaction` current-value arg shape — `null` for absent paths (`isNull: true`); prod speculatively invokes the fn once with `null` for a seeded path before the server-snap arrives (sandbox skips that speculative call — single invocation with the real current value).
695
+ - #162 (extended) `runTransaction` result shape — `{ committed: boolean, snapshot: DataSnapshot }`; snapshot responds to `.val()`, `.exists()`, `.key`.
696
+ - M37d `runTransaction` `options.applyLocally` — both branches commit; single-client harness produces 2 fires (initial + commit) on both, confirming the option doesn't break the contract (the suppression difference would only surface under contention).
697
+ - M37e `runTransaction` rules-denied error shape — plain `Error`, `message: 'permission_denied'` (lowercase), NO `.code` field — **distinct from `set`/`get`'s `'PERMISSION_DENIED: Permission denied'` shape with uppercase `.code`**.
698
+
699
+ ### Modular SDK surface — Phase 3 Tier 3 (query semantics) oracle observations
700
+
701
+ 7 new oracle probes locked the full query-pipeline behavior against `blockingfun`, fb-js-sdk 12.13.0:
702
+
703
+ - #142/#146/#147 `rtdb-modular-orderbychild-window.json` — `orderByChild('pos') + startAt(2) + endAt(4)` matched positions `[2,3,4]` (both-inclusive window).
704
+ - #143/#146/#147 `rtdb-modular-orderbykey-window.json` — `orderByKey() + startAt('b') + endAt('d')` matched keys `[b,c,d]` in key order.
705
+ - #144/#150 `rtdb-modular-orderbyvalue-numeric.json` — `orderByValue() + limitToFirst(3)` threw `Index not defined, add ".indexOn": ".value"` against blockingfun — prod enforces a value-index requirement. (Sandbox does not enforce indexes; the semantic ordering claim is locked by unit test in `unit:modular/queries.test.ts`.)
706
+ - #145 `rtdb-modular-equalTo-filter.json` — `orderByChild('group') + equalTo('b')` returned `[k2,k4]` (both 'b'-grouped children); no uniqueness enforced.
707
+ - #150/#151 `rtdb-modular-limittofirst-vs-limittolast.json` — `limitToFirst(2)` returned `[a,b]` (positions `[1,2]`); `limitToLast(2)` returned `[d,e]` (positions `[4,5]`) on the same 5-child ordered query.
708
+ - #148/#149 `rtdb-modular-startafter-endbefore-exclusive.json` — `startAfter(2) + endBefore(5)` returned positions `[3,4]` (cursors `2` and `5` dropped — both bounds are EXCLUSIVE).
709
+ - #152 `rtdb-modular-onvalue-with-query.json` — listener on `query(ref, orderByChild('pos'), limitToFirst(2))` fired 3 times: initial `[a,b]`, then on an INSIDE-window mutation (`a` value change), then when a new child `z` displaced `b`. The OUTSIDE-window write to `c/extra` did NOT fire the listener.
710
+
711
+ ### Agent-tool surface — rows now locked by the live-RTDB happy-path observations (newly unblocked)
712
+
713
+ Once `ensureOracleRtdbRules` deployed the namespace + index, the 4 originally-blocked legacy probes captured happy-path observations:
714
+
715
+ - #16 `setData` round-trip — confirmed via `rtdb-set-then-get-roundtrip.json`.
716
+ - #18 `set(null)` removes — `rtdb-remove-vs-set-null.json` (`bothNull: true`).
717
+ - #23 atomic fan-out update — locked by the new `rtdb-modular-update-multipath-atomic.json` and `rtdb-modular-update-multipath-rules-denial.json` observations (same upstream SDK either way).
718
+ - #31 `remove` vs `set(null)` end state — `rtdb-remove-vs-set-null.json` (`equivalent: true`).
719
+ - #32 idempotent remove on absent path — `rtdb-modular-remove-idempotent.json`.
720
+
721
+ ### Rows currently marked **⚠** that we might want to upgrade to **✓**
722
+
723
+ (by aligning the package to the wrapped service or by formally documenting the divergence in `feature-matrix.md`):
724
+
725
+ - #14 user-mode `READ_FAILED` swallowing the upstream `PERMISSION_DENIED` code.
726
+ - #66 simulator cross-path lookup using empty `mockData` instead of reading the live database. (The advisory-only behavior for user-mode writes already documents this; a fix would teach the simulator to fetch cross-path values from the host on demand.)
727
+
728
+ ### Agent-tool rows currently marked **—** that we might want to fill (rough priority)
729
+
730
+ 1. `onValue` / `onChild*` listener tools — most-requested addition; would require tool-call-friendly stream semantics (poll? snapshot at time T?).
731
+ 2. `query`-with-constraints tool surface — `equalTo` / `orderByChild` / `limit*` parameter shapes.
732
+ 3. `serverTimestamp` / `increment` sentinel support in the data tools.
733
+ 4. `runTransaction` tool — atomic read-modify-write.
734
+
735
+ ### Modular SDK surface — implementation status
736
+
737
+ The sandbox implementation has landed (`packages/pyric/src/database/modular.ts` + `sandbox/`); rows locked by sandbox unit tests or oracle observations sit at `✓`, and rows still pending implementation sit at `—` (see the per-row tables above for the current status of each). The oracle observations are the spec the sandbox conforms to.
738
+
739
+ ## Probe coverage summary
740
+
741
+ - **Unit (`packages/pyric/test/database/`):** ~30 test files. Strong coverage
742
+ on handlers (`data/handler.test.ts`, `crawl/handler.test.ts`,
743
+ `write/handler.test.ts`, `ir/handler.test.ts`,
744
+ `simulation/handler.test.ts`, `data/validated.test.ts`), the host
745
+ contract (`host.test.ts`), the mapper round-trip
746
+ (`mapper.test.ts`), the resolver (`resolver.test.ts`), and the
747
+ tool factory shape (`tools.test.ts`). Constraint-authoring
748
+ surface (`constraints/`) and grammar (`grammar/`) each have a
749
+ dedicated test suite. The modular-SDK surface ships unit tests
750
+ under `test/database/modular/` (`sandbox-target.test.ts` covers Tier 1
751
+ foundation; `sandbox-child-events.test.ts` covers Tier 2 child events;
752
+ `queries.test.ts` covers Tier 3 query semantics; `transaction.test.ts`
753
+ covers Tier 4 `runTransaction`).
754
+ - **Oracle (`scripts/oracle/observations/`):** 50 RTDB probes
755
+ (`scripts/oracle/observations/rtdb-*.json`) — 14 legacy
756
+ (agent-tool surface) + 36 modular-SDK probes (20 Phase 1 + 4 Tier 4
757
+ transaction + 5 Tier 2 child-event + 7 Tier 3 query; see the "Modular
758
+ SDK surface — rows locked by the empirical oracle harness" section for
759
+ the full per-row list). A representative subset:
760
+ - Legacy:
761
+ - `rtdb-set-then-get-roundtrip` — **passing** — locks #16/#10 round-trip.
762
+ - `rtdb-onvalue-fires-on-set` — **passing** — locks listener-fire-on-set semantics.
763
+ - `rtdb-remove-vs-set-null` — **passing** — locks #18/#31.
764
+ - `rtdb-push-autoid-format` — **passing** — locks #27/#28 (client-side key minting).
765
+ - `rtdb-servertimestamp-resolves` — **passing** — locks sentinel resolution to a number.
766
+ - `rtdb-rules-denied-error-code` — **passing** — locks #15/#20 (plain `Error` + `PERMISSION_DENIED` uppercase code).
767
+ - Modular SDK Phase 1 (`rtdb-modular-*`):
768
+ - `rtdb-modular-get-snapshot-shape` — locks #106.
769
+ - `rtdb-modular-get-missing-path` — locks #107/#108.
770
+ - `rtdb-modular-set-null-equals-remove` — locks #112.
771
+ - `rtdb-modular-set-replaces-not-merges` — locks #113.
772
+ - `rtdb-modular-update-merges-keys` — locks #116.
773
+ - `rtdb-modular-update-multipath-atomic` — locks #117 + agent-tool #23.
774
+ - `rtdb-modular-update-multipath-rules-denial` — locks #118.
775
+ - `rtdb-modular-update-null-removes-key` — locks #119.
776
+ - `rtdb-modular-remove-idempotent` — locks #122 + agent-tool #32.
777
+ - `rtdb-modular-push-with-value` — locks #126/#127.
778
+ - `rtdb-modular-onvalue-initial-with-data` — locks #128.
779
+ - `rtdb-modular-onvalue-initial-no-data` — locks #129.
780
+ - `rtdb-modular-onvalue-unsubscribe` — locks #131.
781
+ - `rtdb-modular-onchildadded-initial-replay` — locks #133.
782
+ - `rtdb-modular-query-orderbychild-limit` — locks #142/#150.
783
+ - `rtdb-modular-query-equalto` — locks #145.
784
+ - `rtdb-modular-query-startat-inclusive` — locks #146.
785
+ - `rtdb-modular-increment-from-missing` — locks #155/#156.
786
+ - `rtdb-modular-runtransaction-success` — locks #158/#160/#162.
787
+ - `rtdb-modular-runtransaction-abort-undefined` — locks #159.
788
+ - `rtdb-modular-runtransaction-current-value-arg` — locks #160 (`null` for absent paths; documents the prod-vs-sandbox speculative-call divergence for seeded paths).
789
+ - `rtdb-modular-runtransaction-returns-committed-snapshot` — locks #162 (result shape: `{ committed, snapshot }`, snapshot responds to `.val()`/`.exists()`/`.key`).
790
+ - `rtdb-modular-runtransaction-options-applylocally` — locks the `options.applyLocally` branch contract; single-client harness shows both branches end at the same value with init+commit fires.
791
+ - `rtdb-modular-runtransaction-on-rules-denied-path` — locks the transaction-specific rules-denied error shape: plain `Error`, `message: 'permission_denied'` (lowercase), NO `.code` field — **distinct** from `set`/`get`'s uppercase `PERMISSION_DENIED:` shape.
792
+
793
+ ### Harness extension: `ensureOracleRtdbRules`
794
+
795
+ `scripts/oracle/run.ts` now deploys an RTDB rules namespace
796
+ analogous to `ensureOracleRules` / `ensureOracleStorageRules`. The
797
+ JSON shape:
798
+ ```json
799
+ {
800
+ "rules": {
801
+ ".read": false,
802
+ ".write": false,
803
+ "pyric_oracle": {
804
+ ".read": "auth != null",
805
+ ".write": "auth != null",
806
+ "$run": { "$probe": { "list": { ".indexOn": ["pos", "group"] } } }
807
+ }
808
+ }
809
+ }
810
+ ```
811
+ The harness mints a separate OAuth token scoped to
812
+ `https://www.googleapis.com/auth/firebase.database` (the broader
813
+ `firebase` scope used by Firestore + Storage + Management APIs is
814
+ NOT accepted by the per-database rules endpoint) and `PUT`s to
815
+ `<databaseUrl>/.settings/rules.json?print=silent` with marker-based
816
+ idempotency: if `pyric_oracle` is already a top-level key AND the
817
+ shape matches `ORACLE_RTDB_RULES_BODY`, the deploy is a no-op;
818
+ otherwise the namespace is merged (preserving any other top-level
819
+ keys) and a 5s propagation wait runs before the probes start. The
820
+ `.indexOn` inside `$run/$probe/list` is what lets the
821
+ `orderByChild`/`equalTo`/`startAt` probes run without per-probe
822
+ rule modifications.
823
+
824
+ ## Simulator-vs-prod divergences (from row #71 audit)
825
+
826
+ The simulator-vs-prod agreement audit (`scripts/oracle/observations/rtdb-simulator-vs-prod-agreement.json`) deployed 8 rule patterns and ran 29 `(rule, op)` tuples against both the live `blockingfun` RTDB and the in-process `SimulateHandler`. 28 of 29 agreed; the one divergence:
827
+
828
+ ### Divergence 1 — `.validate` rules not evaluated during writes
829
+
830
+ **Rule:**
831
+ ```json
832
+ {
833
+ ".read": "auth != null",
834
+ ".write": "auth != null",
835
+ "entry": {
836
+ ".validate": "newData.hasChildren(['title', 'body'])"
837
+ }
838
+ }
839
+ ```
840
+ **Op:** `write` at `/r4-validate-structure/entry` with `newData: { title: 't' }` (intentionally missing `body`), `auth.uid` present.
841
+
842
+ **Live RTDB:** `PERMISSION_DENIED` — the `.validate` rule rejects the write because `newData.hasChildren(['title', 'body'])` is false.
843
+
844
+ **Simulator (`SimulateHandler`):** `allowed: true, reason: "Rule expression evaluated to true"` — the simulator's walk from root finds the ancestor `.write: 'auth != null'` returns true and short-circuits there, never descending into the `entry` node to evaluate the `.validate` rule.
845
+
846
+ **Root cause:** `packages/pyric/src/database/simulation/handler.ts` only reads `ancestor.node[operation]` (one of `'read'` | `'write'` | `'validate'`) per iteration. For an `operation: 'write'` simulation, it never queries the `.validate` rule on the same or descendant ancestors. RTDB's real rules engine evaluates `.validate` rules at every ancestor of the write path in addition to the `.write` rule — a single `.validate` failure rejects the entire write.
847
+
848
+ **Implication for consumers of `validatedWrite`:** the simulator's `SIMULATION_DENIED` signal currently doesn't fire for `.validate` failures during writes. In admin mode this means a write that prod would reject via `.validate` will still be dispatched. In user mode the live rule still enforces, so the deny lands at the prod write — same end-state, but the advisory preflight signal is missing.
849
+
850
+ **Fix path (out of scope for this PR):** the simulator's write-eval loop should also walk every ancestor's `.validate` rule and require ALL of them to evaluate `true` (or be absent) in addition to a `.write` rule granting access. Tracked as a follow-up engineering task — this PR's job is to document the divergence per the row #71 methodology, not fix the simulator.