@pyric/cli 0.1.0-alpha.16 → 0.1.0-alpha.18

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 (419) hide show
  1. package/README.md +42 -12
  2. package/dist/assurance/.generated/conformance-verdicts.d.ts +80 -72
  3. package/dist/assurance/.generated/conformance-verdicts.d.ts.map +1 -1
  4. package/dist/assurance/.generated/conformance-verdicts.js +80 -72
  5. package/dist/assurance/.generated/conformance-verdicts.js.map +1 -1
  6. package/dist/bridge/server/bridge.d.ts.map +1 -1
  7. package/dist/bridge/server/bridge.js +4 -2
  8. package/dist/bridge/server/bridge.js.map +1 -1
  9. package/dist/bridge/server/peer.d.ts.map +1 -1
  10. package/dist/bridge/server/peer.js +1 -0
  11. package/dist/bridge/server/peer.js.map +1 -1
  12. package/dist/cli/database-rules.d.ts.map +1 -1
  13. package/dist/cli/database-rules.js +6 -4
  14. package/dist/cli/database-rules.js.map +1 -1
  15. package/dist/cli/dev-runner.d.ts +9 -0
  16. package/dist/cli/dev-runner.d.ts.map +1 -1
  17. package/dist/cli/dev-runner.js +18 -0
  18. package/dist/cli/dev-runner.js.map +1 -1
  19. package/dist/cli/serve.d.ts.map +1 -1
  20. package/dist/cli/serve.js +62 -6
  21. package/dist/cli/serve.js.map +1 -1
  22. package/dist/cli/verify.js +2 -2
  23. package/dist/cli/verify.js.map +1 -1
  24. package/dist/conformance/.generated/can-i-use-browser.d.ts.map +1 -1
  25. package/dist/conformance/.generated/can-i-use-browser.js +1 -1
  26. package/dist/conformance/.generated/can-i-use-browser.js.map +1 -1
  27. package/dist/conformance/.generated/can-i-use.d.ts.map +1 -1
  28. package/dist/conformance/.generated/can-i-use.js +1 -1
  29. package/dist/conformance/.generated/can-i-use.js.map +1 -1
  30. package/dist/conformance/.generated/conformance-docs.d.ts.map +1 -1
  31. package/dist/conformance/.generated/conformance-docs.js +1 -1
  32. package/dist/conformance/.generated/conformance-docs.js.map +1 -1
  33. package/dist/functions-rtdb/child.d.ts +1 -0
  34. package/dist/functions-rtdb/child.d.ts.map +1 -1
  35. package/dist/functions-rtdb/child.js +11 -1
  36. package/dist/functions-rtdb/child.js.map +1 -1
  37. package/dist/functions-rtdb/development-runtime.d.ts +1 -0
  38. package/dist/functions-rtdb/development-runtime.d.ts.map +1 -1
  39. package/dist/functions-rtdb/development-runtime.js +1 -0
  40. package/dist/functions-rtdb/development-runtime.js.map +1 -1
  41. package/dist/next/client-aliases.d.ts +11 -0
  42. package/dist/next/client-aliases.d.ts.map +1 -0
  43. package/dist/next/client-aliases.js +38 -0
  44. package/dist/next/client-aliases.js.map +1 -0
  45. package/dist/next/dev-rewrites.d.ts +9 -0
  46. package/dist/next/dev-rewrites.d.ts.map +1 -0
  47. package/dist/next/dev-rewrites.js +53 -0
  48. package/dist/next/dev-rewrites.js.map +1 -0
  49. package/dist/next/guard.d.ts +15 -0
  50. package/dist/next/guard.d.ts.map +1 -0
  51. package/dist/next/guard.js +52 -0
  52. package/dist/next/guard.js.map +1 -0
  53. package/dist/next/index.d.ts +12 -0
  54. package/dist/next/index.d.ts.map +1 -0
  55. package/dist/next/index.js +11 -0
  56. package/dist/next/index.js.map +1 -0
  57. package/dist/next/runtime-env.d.ts +7 -0
  58. package/dist/next/runtime-env.d.ts.map +1 -0
  59. package/dist/next/runtime-env.js +13 -0
  60. package/dist/next/runtime-env.js.map +1 -0
  61. package/dist/next/server-external-packages.d.ts +10 -0
  62. package/dist/next/server-external-packages.d.ts.map +1 -0
  63. package/dist/next/server-external-packages.js +34 -0
  64. package/dist/next/server-external-packages.js.map +1 -0
  65. package/dist/next/target-url.d.ts +11 -0
  66. package/dist/next/target-url.d.ts.map +1 -0
  67. package/dist/next/target-url.js +36 -0
  68. package/dist/next/target-url.js.map +1 -0
  69. package/dist/next/turbopack-config.d.ts +9 -0
  70. package/dist/next/turbopack-config.d.ts.map +1 -0
  71. package/dist/next/turbopack-config.js +39 -0
  72. package/dist/next/turbopack-config.js.map +1 -0
  73. package/dist/next/types.d.ts +40 -0
  74. package/dist/next/types.d.ts.map +1 -0
  75. package/dist/next/types.js +5 -0
  76. package/dist/next/types.js.map +1 -0
  77. package/dist/next/webpack-config.d.ts +11 -0
  78. package/dist/next/webpack-config.d.ts.map +1 -0
  79. package/dist/next/webpack-config.js +51 -0
  80. package/dist/next/webpack-config.js.map +1 -0
  81. package/dist/next/with-pyric.d.ts +22 -0
  82. package/dist/next/with-pyric.d.ts.map +1 -0
  83. package/dist/next/with-pyric.js +49 -0
  84. package/dist/next/with-pyric.js.map +1 -0
  85. package/dist/register/app-bridge.d.ts +9 -0
  86. package/dist/register/app-bridge.d.ts.map +1 -0
  87. package/dist/register/app-bridge.js +56 -0
  88. package/dist/register/app-bridge.js.map +1 -0
  89. package/dist/register/hooks.d.ts.map +1 -1
  90. package/dist/register/hooks.js +6 -1
  91. package/dist/register/hooks.js.map +1 -1
  92. package/dist/register/index.js +1 -1
  93. package/dist/register/index.js.map +1 -1
  94. package/dist/register/mapping.d.ts +3 -1
  95. package/dist/register/mapping.d.ts.map +1 -1
  96. package/dist/register/mapping.js +41 -5
  97. package/dist/register/mapping.js.map +1 -1
  98. package/dist/remote/index.d.ts.map +1 -1
  99. package/dist/remote/index.js +5 -3
  100. package/dist/remote/index.js.map +1 -1
  101. package/dist/rtdb/rules-json.d.ts +12 -0
  102. package/dist/rtdb/rules-json.d.ts.map +1 -1
  103. package/dist/rtdb/rules-json.js +98 -0
  104. package/dist/rtdb/rules-json.js.map +1 -1
  105. package/dist/serve/bundler.d.ts.map +1 -1
  106. package/dist/serve/bundler.js +2 -0
  107. package/dist/serve/bundler.js.map +1 -1
  108. package/dist/serve/discovery.d.ts +4 -4
  109. package/dist/serve/discovery.d.ts.map +1 -1
  110. package/dist/serve/discovery.js +59 -38
  111. package/dist/serve/discovery.js.map +1 -1
  112. package/dist/serve/entries/app-ai-passthrough.d.ts +8 -0
  113. package/dist/serve/entries/app-ai-passthrough.d.ts.map +1 -0
  114. package/dist/serve/entries/app-ai-passthrough.js +59 -0
  115. package/dist/serve/entries/app-ai-passthrough.js.map +1 -0
  116. package/dist/serve/entries/auth-helper-core.js +9 -7
  117. package/dist/serve/entries/auth-helper-core.js.map +1 -1
  118. package/dist/serve/entries/auth.d.ts +1 -0
  119. package/dist/serve/entries/auth.d.ts.map +1 -1
  120. package/dist/serve/entries/auth.js +1 -0
  121. package/dist/serve/entries/auth.js.map +1 -1
  122. package/dist/serve/entries/bridge-url.d.ts.map +1 -1
  123. package/dist/serve/entries/bridge-url.js +7 -2
  124. package/dist/serve/entries/bridge-url.js.map +1 -1
  125. package/dist/serve/entries/database.d.ts +1 -0
  126. package/dist/serve/entries/database.d.ts.map +1 -1
  127. package/dist/serve/entries/database.js +1 -0
  128. package/dist/serve/entries/database.js.map +1 -1
  129. package/dist/serve/entries/firestore.d.ts +1 -0
  130. package/dist/serve/entries/firestore.d.ts.map +1 -1
  131. package/dist/serve/entries/firestore.js +1 -0
  132. package/dist/serve/entries/firestore.js.map +1 -1
  133. package/dist/serve/entries/init.d.ts.map +1 -1
  134. package/dist/serve/entries/init.js +4 -2
  135. package/dist/serve/entries/init.js.map +1 -1
  136. package/dist/serve/entries/runtime.js +23 -0
  137. package/dist/serve/entries/runtime.js.map +1 -1
  138. package/dist/serve/entries/storage.d.ts +2 -0
  139. package/dist/serve/entries/storage.d.ts.map +1 -1
  140. package/dist/serve/entries/storage.js +9 -2
  141. package/dist/serve/entries/storage.js.map +1 -1
  142. package/dist/serve/html-injection.d.ts +3 -1
  143. package/dist/serve/html-injection.d.ts.map +1 -1
  144. package/dist/serve/html-injection.js +27 -1
  145. package/dist/serve/html-injection.js.map +1 -1
  146. package/dist/serve/rules.js +2 -2
  147. package/dist/serve/rules.js.map +1 -1
  148. package/dist/serve/runtime/chip-config.d.ts +1 -1
  149. package/dist/serve/runtime/chip-config.d.ts.map +1 -1
  150. package/dist/serve/runtime/chip-config.js +16 -8
  151. package/dist/serve/runtime/chip-config.js.map +1 -1
  152. package/dist/serve/runtime/chip.d.ts.map +1 -1
  153. package/dist/serve/runtime/chip.js +24 -3
  154. package/dist/serve/runtime/chip.js.map +1 -1
  155. package/dist/serve/runtime/status.d.ts +1 -0
  156. package/dist/serve/runtime/status.d.ts.map +1 -1
  157. package/dist/serve/runtime/status.js +71 -13
  158. package/dist/serve/runtime/status.js.map +1 -1
  159. package/dist/serve/sandbox-session.d.ts +1 -0
  160. package/dist/serve/sandbox-session.d.ts.map +1 -1
  161. package/dist/serve/sandbox-session.js +30 -2
  162. package/dist/serve/sandbox-session.js.map +1 -1
  163. package/dist/serve/site-ui/_astro/_slug_.C6QfVOxJ.css +1 -0
  164. package/dist/serve/site-ui/_astro/_studio_.B6b_G1ev.css +1 -0
  165. package/dist/serve/site-ui/_astro/app.B7pBzz5N.js +11 -0
  166. package/dist/serve/site-ui/_astro/app.boex5SnQ.js +1 -0
  167. package/dist/serve/site-ui/_astro/{chess-showcase.CnoxIle5.js → chess-showcase.DDqE-QGe.js} +1 -1
  168. package/dist/serve/site-ui/_astro/{example-runtime.R1d96L6-.js → example-runtime.B0byNQOl.js} +1 -1
  169. package/dist/serve/site-ui/_astro/index.uwPvjH_1.js +1 -0
  170. package/dist/serve/site-ui/_astro/reads.Dh3z0fJy.js +1 -0
  171. package/dist/serve/site-ui/_astro/sandbox-controls.C2IqtoSw.js +1252 -0
  172. package/dist/serve/site-ui/_astro/{seed.EyI3VYEu.js → seed.CCmj00su.js} +1 -1
  173. package/dist/serve/site-ui/auth/index.html +2 -2
  174. package/dist/serve/site-ui/docs/_rhythm/index.html +1 -1
  175. package/dist/serve/site-ui/docs/agent/set-up-your-agent/index.html +48 -13
  176. package/dist/serve/site-ui/docs/agent/set-up-your-agent.md +74 -11
  177. package/dist/serve/site-ui/docs/agent/watch-and-review/index.html +1 -1
  178. package/dist/serve/site-ui/docs/agent/work-with-an-agent/index.html +1 -1
  179. package/dist/serve/site-ui/docs/ai-compat/index.html +85 -85
  180. package/dist/serve/site-ui/docs/ai-compat.md +84 -84
  181. package/dist/serve/site-ui/docs/api-reference/index.html +3 -1
  182. package/dist/serve/site-ui/docs/api-reference.md +2 -0
  183. package/dist/serve/site-ui/docs/app-compat/index.html +28 -28
  184. package/dist/serve/site-ui/docs/app-compat.md +27 -27
  185. package/dist/serve/site-ui/docs/auth-compat/index.html +122 -122
  186. package/dist/serve/site-ui/docs/auth-compat.md +121 -121
  187. package/dist/serve/site-ui/docs/build/ai-logic/index.html +19 -2
  188. package/dist/serve/site-ui/docs/build/ai-logic.md +28 -0
  189. package/dist/serve/site-ui/docs/build/authentication/index.html +1 -1
  190. package/dist/serve/site-ui/docs/build/cloud-firestore/index.html +1 -1
  191. package/dist/serve/site-ui/docs/build/cloud-messaging/index.html +1 -1
  192. package/dist/serve/site-ui/docs/build/cloud-storage/index.html +1 -1
  193. package/dist/serve/site-ui/docs/build/realtime-database/index.html +1 -1
  194. package/dist/serve/site-ui/docs/conformance-scores/index.html +25 -10
  195. package/dist/serve/site-ui/docs/conformance-scores.md +23 -8
  196. package/dist/serve/site-ui/docs/create-pyric-reference-api/index.html +6 -6
  197. package/dist/serve/site-ui/docs/create-pyric-reference-api.md +5 -5
  198. package/dist/serve/site-ui/docs/database-compat/index.html +200 -448
  199. package/dist/serve/site-ui/docs/database-compat.md +198 -461
  200. package/dist/serve/site-ui/docs/examples/chess/index.html +2 -2
  201. package/dist/serve/site-ui/docs/examples/index.html +1 -1
  202. package/dist/serve/site-ui/docs/firestore-compat/index.html +186 -195
  203. package/dist/serve/site-ui/docs/firestore-compat.md +184 -195
  204. package/dist/serve/site-ui/docs/functions-rtdb-compat/index.html +15 -15
  205. package/dist/serve/site-ui/docs/functions-rtdb-compat.md +14 -14
  206. package/dist/serve/site-ui/docs/get-started/how-the-swap-works/index.html +1 -1
  207. package/dist/serve/site-ui/docs/get-started/nextjs/index.html +57 -0
  208. package/dist/serve/site-ui/docs/get-started/nextjs.md +86 -0
  209. package/dist/serve/site-ui/docs/get-started/start-building/index.html +38 -9
  210. package/dist/serve/site-ui/docs/get-started/start-building.md +62 -7
  211. package/dist/serve/site-ui/docs/get-started/vite/index.html +26 -0
  212. package/dist/serve/site-ui/docs/get-started/vite.md +45 -0
  213. package/dist/serve/site-ui/docs/index.json +711 -111
  214. package/dist/serve/site-ui/docs/messaging-compat/index.html +56 -56
  215. package/dist/serve/site-ui/docs/messaging-compat.md +55 -55
  216. package/dist/serve/site-ui/docs/observe/resolve-runtime-status/index.html +1 -1
  217. package/dist/serve/site-ui/docs/observe/see-whats-happening/index.html +1 -1
  218. package/dist/serve/site-ui/docs/observe/shape-your-data/index.html +1 -1
  219. package/dist/serve/site-ui/docs/overview/index.html +1 -1
  220. package/dist/serve/site-ui/docs/pyric-admin-app-reference-api/index.html +39 -13
  221. package/dist/serve/site-ui/docs/pyric-admin-app-reference-api.md +28 -2
  222. package/dist/serve/site-ui/docs/pyric-admin-auth-reference-api/index.html +1 -1
  223. package/dist/serve/site-ui/docs/pyric-admin-database-reference-api/index.html +38 -6
  224. package/dist/serve/site-ui/docs/pyric-admin-database-reference-api.md +43 -0
  225. package/dist/serve/site-ui/docs/pyric-admin-firestore-reference-api/index.html +9 -1
  226. package/dist/serve/site-ui/docs/pyric-admin-firestore-reference-api.md +8 -0
  227. package/dist/serve/site-ui/docs/pyric-admin-messaging-reference-api/index.html +1 -1
  228. package/dist/serve/site-ui/docs/pyric-admin-storage-reference-api/index.html +27 -4
  229. package/dist/serve/site-ui/docs/pyric-admin-storage-reference-api.md +23 -0
  230. package/dist/serve/site-ui/docs/pyric-ai-reference-api/index.html +1 -1
  231. package/dist/serve/site-ui/docs/pyric-ai-scripting-reference-api/index.html +1 -1
  232. package/dist/serve/site-ui/docs/pyric-app-reference-api/index.html +1 -1
  233. package/dist/serve/site-ui/docs/pyric-auth-reference-api/index.html +1 -1
  234. package/dist/serve/site-ui/docs/pyric-cli-assurance-browser-reference-api/index.html +1 -1
  235. package/dist/serve/site-ui/docs/pyric-cli-assurance-reference-api/index.html +1 -1
  236. package/dist/serve/site-ui/docs/pyric-cli-bridge-client-reference-api/index.html +1 -1
  237. package/dist/serve/site-ui/docs/pyric-cli-bridge-reference-api/index.html +1 -1
  238. package/dist/serve/site-ui/docs/pyric-cli-conformance-browser-reference-api/index.html +1 -1
  239. package/dist/serve/site-ui/docs/pyric-cli-conformance-docs-reference-api/index.html +1 -1
  240. package/dist/serve/site-ui/docs/pyric-cli-conformance-reference-api/index.html +1 -1
  241. package/dist/serve/site-ui/docs/pyric-cli-credentials-node-reference-api/index.html +1 -1
  242. package/dist/serve/site-ui/docs/pyric-cli-discover-reference-api/index.html +1 -1
  243. package/dist/serve/site-ui/docs/pyric-cli-next-reference-api/index.html +123 -0
  244. package/dist/serve/site-ui/docs/pyric-cli-next-reference-api.md +99 -0
  245. package/dist/serve/site-ui/docs/pyric-cli-register-app-bridge-reference-api/index.html +127 -0
  246. package/dist/serve/site-ui/docs/pyric-cli-register-app-bridge-reference-api.md +174 -0
  247. package/dist/serve/site-ui/docs/pyric-cli-register-reference-api/index.html +1 -1
  248. package/dist/serve/site-ui/docs/pyric-cli-remote-reference-api/index.html +1 -1
  249. package/dist/serve/site-ui/docs/pyric-cli-serve-worker-reference-api/index.html +570 -326
  250. package/dist/serve/site-ui/docs/pyric-cli-serve-worker-reference-api.md +198 -6
  251. package/dist/serve/site-ui/docs/pyric-cli-verify-reference-api/index.html +1 -1
  252. package/dist/serve/site-ui/docs/pyric-cli-vite-reference-api/index.html +1 -1
  253. package/dist/serve/site-ui/docs/pyric-database-reference-api/index.html +1 -1
  254. package/dist/serve/site-ui/docs/pyric-firestore-reference-api/index.html +2971 -627
  255. package/dist/serve/site-ui/docs/pyric-firestore-reference-api.md +2873 -517
  256. package/dist/serve/site-ui/docs/pyric-messaging-reference-api/index.html +1 -1
  257. package/dist/serve/site-ui/docs/pyric-messaging-sw-reference-api/index.html +1 -1
  258. package/dist/serve/site-ui/docs/pyric-rules-reference-api/index.html +43 -3
  259. package/dist/serve/site-ui/docs/pyric-rules-reference-api.md +8 -0
  260. package/dist/serve/site-ui/docs/pyric-sandbox-database-reference-api/index.html +26 -2
  261. package/dist/serve/site-ui/docs/pyric-sandbox-database-reference-api.md +24 -0
  262. package/dist/serve/site-ui/docs/pyric-sandbox-firestore-reference-api/index.html +1 -1
  263. package/dist/serve/site-ui/docs/pyric-sandbox-reference-api/index.html +77 -9
  264. package/dist/serve/site-ui/docs/pyric-sandbox-reference-api.md +28 -13
  265. package/dist/serve/site-ui/docs/pyric-storage-reference-api/index.html +240 -63
  266. package/dist/serve/site-ui/docs/pyric-storage-reference-api.md +164 -5
  267. package/dist/serve/site-ui/docs/pyric-ui-agents-reference-api/index.html +1 -1
  268. package/dist/serve/site-ui/docs/pyric-ui-auth-hooks-reference-api/index.html +1 -1
  269. package/dist/serve/site-ui/docs/pyric-ui-auth-reference-api/index.html +1 -1
  270. package/dist/serve/site-ui/docs/pyric-ui-events-hooks-reference-api/index.html +1 -1
  271. package/dist/serve/site-ui/docs/pyric-ui-events-reference-api/index.html +1 -1
  272. package/dist/serve/site-ui/docs/pyric-ui-firestore-hooks-reference-api/index.html +1 -1
  273. package/dist/serve/site-ui/docs/pyric-ui-firestore-reference-api/index.html +1 -1
  274. package/dist/serve/site-ui/docs/pyric-ui-primitives-reference-api/index.html +1 -1
  275. package/dist/serve/site-ui/docs/pyric-ui-rtdb-reference-api/index.html +1 -1
  276. package/dist/serve/site-ui/docs/pyric-ui-rules-hooks-reference-api/index.html +1 -1
  277. package/dist/serve/site-ui/docs/pyric-ui-rules-reference-api/index.html +1 -10
  278. package/dist/serve/site-ui/docs/pyric-ui-rules-reference-api.md +0 -9
  279. package/dist/serve/site-ui/docs/pyric-ui-storage-hooks-reference-api/index.html +1 -1
  280. package/dist/serve/site-ui/docs/pyric-ui-storage-reference-api/index.html +2 -1
  281. package/dist/serve/site-ui/docs/pyric-ui-storage-reference-api.md +1 -0
  282. package/dist/serve/site-ui/docs/pyric-ui-traffic-hooks-reference-api/index.html +1 -1
  283. package/dist/serve/site-ui/docs/pyric-ui-traffic-reference-api/index.html +1 -1
  284. package/dist/serve/site-ui/docs/rules-compat/index.html +87 -79
  285. package/dist/serve/site-ui/docs/rules-compat.md +86 -78
  286. package/dist/serve/site-ui/docs/secure/audit-your-rules/index.html +1 -1
  287. package/dist/serve/site-ui/docs/secure/firestore-rules-limits/index.html +1 -1
  288. package/dist/serve/site-ui/docs/secure/read-a-denial/index.html +1 -1
  289. package/dist/serve/site-ui/docs/secure/rtdb-rules-in-typescript/index.html +1 -1
  290. package/dist/serve/site-ui/docs/secure/rules-standard-library/index.html +1 -1
  291. package/dist/serve/site-ui/docs/secure/secure-it-with-rules/index.html +1 -1
  292. package/dist/serve/site-ui/docs/secure/simulate-and-lint/index.html +1 -1
  293. package/dist/serve/site-ui/docs/secure/write-a-rules-test-suite/index.html +1 -1
  294. package/dist/serve/site-ui/docs/ship/ship-to-production/index.html +1 -1
  295. package/dist/serve/site-ui/docs/ship/test-in-node/index.html +1 -1
  296. package/dist/serve/site-ui/docs/storage-compat/index.html +119 -125
  297. package/dist/serve/site-ui/docs/storage-compat.md +118 -124
  298. package/dist/serve/site-ui/docs/trust/how-we-know-it-matches-firebase/index.html +1 -1
  299. package/dist/serve/site-ui/docs/trust/rules-standard-library/index.html +1 -1
  300. package/dist/serve/site-ui/docs/trust/storage-rules-stdlib-research/index.html +1 -1
  301. package/dist/serve/site-ui/docs/trust/versioning-and-compatibility/index.html +1 -1
  302. package/dist/serve/site-ui/docs/tutorial/index.html +2 -1
  303. package/dist/serve/site-ui/docs/tutorial.md +2 -0
  304. package/dist/serve/site-ui/examples/chess/index.html +1 -1
  305. package/dist/serve/site-ui/examples/firestore-first-write/index.html +1 -1
  306. package/dist/serve/site-ui/firestore/index.html +2 -2
  307. package/dist/serve/site-ui/index.html +2 -2
  308. package/dist/serve/site-ui/llms.txt +8 -4
  309. package/dist/serve/site-ui/rtdb/index.html +2 -2
  310. package/dist/serve/site-ui/settings/index.html +2 -2
  311. package/dist/serve/site-ui/storage/index.html +2 -2
  312. package/dist/serve/site-ui/traffic/index.html +2 -2
  313. package/dist/serve/vite-ai-config.d.ts +4 -1
  314. package/dist/serve/vite-ai-config.d.ts.map +1 -1
  315. package/dist/serve/vite-ai-config.js +83 -19
  316. package/dist/serve/vite-ai-config.js.map +1 -1
  317. package/dist/serve/vite-functions-development.d.ts.map +1 -1
  318. package/dist/serve/vite-functions-development.js +1 -0
  319. package/dist/serve/vite-functions-development.js.map +1 -1
  320. package/dist/serve/vite-generation-rules-watch.d.ts.map +1 -1
  321. package/dist/serve/vite-generation-rules-watch.js +61 -14
  322. package/dist/serve/vite-generation-rules-watch.js.map +1 -1
  323. package/dist/serve/vite-module-swap.d.ts +4 -1
  324. package/dist/serve/vite-module-swap.d.ts.map +1 -1
  325. package/dist/serve/vite-module-swap.js +106 -21
  326. package/dist/serve/vite-module-swap.js.map +1 -1
  327. package/dist/serve/vite-plugin.d.ts.map +1 -1
  328. package/dist/serve/vite-plugin.js +45 -5
  329. package/dist/serve/vite-plugin.js.map +1 -1
  330. package/dist/serve/vite-sandbox-generation.js +1 -1
  331. package/dist/serve/vite-sandbox-generation.js.map +1 -1
  332. package/dist/serve/worker/client/resumable.d.ts +30 -0
  333. package/dist/serve/worker/client/resumable.d.ts.map +1 -0
  334. package/dist/serve/worker/client/resumable.js +416 -0
  335. package/dist/serve/worker/client/resumable.js.map +1 -0
  336. package/dist/serve/worker/client/rtdb-references.d.ts.map +1 -1
  337. package/dist/serve/worker/client/rtdb-references.js +4 -1
  338. package/dist/serve/worker/client/rtdb-references.js.map +1 -1
  339. package/dist/serve/worker/client/storage.d.ts +16 -2
  340. package/dist/serve/worker/client/storage.d.ts.map +1 -1
  341. package/dist/serve/worker/client/storage.js +57 -6
  342. package/dist/serve/worker/client/storage.js.map +1 -1
  343. package/dist/serve/worker/host/rules.d.ts.map +1 -1
  344. package/dist/serve/worker/host/rules.js +2 -1
  345. package/dist/serve/worker/host/rules.js.map +1 -1
  346. package/dist/serve/worker/index.d.ts +1 -1
  347. package/dist/serve/worker/index.d.ts.map +1 -1
  348. package/dist/serve/worker/index.js +1 -1
  349. package/dist/serve/worker/index.js.map +1 -1
  350. package/dist/serve/worker/serve-init.d.ts.map +1 -1
  351. package/dist/serve/worker/serve-init.js +31 -0
  352. package/dist/serve/worker/serve-init.js.map +1 -1
  353. package/package.json +15 -4
  354. package/src/assurance/.generated/conformance-verdicts.ts +80 -72
  355. package/src/bridge/server/bridge.ts +5 -2
  356. package/src/bridge/server/peer.ts +2 -1
  357. package/src/cli/cli.test.ts +14 -0
  358. package/src/cli/database-rules.ts +6 -4
  359. package/src/cli/dev-runner.ts +21 -0
  360. package/src/cli/serve.ts +63 -5
  361. package/src/cli/verify.ts +2 -2
  362. package/src/conformance/.generated/can-i-use-browser.ts +1 -1
  363. package/src/conformance/.generated/can-i-use.ts +1 -1
  364. package/src/conformance/.generated/conformance-docs.ts +1 -1
  365. package/src/functions-rtdb/child.ts +12 -1
  366. package/src/functions-rtdb/development-runtime.ts +2 -0
  367. package/src/next/client-aliases.ts +39 -0
  368. package/src/next/dev-rewrites.ts +77 -0
  369. package/src/next/guard.ts +61 -0
  370. package/src/next/index.ts +16 -0
  371. package/src/next/runtime-env.ts +18 -0
  372. package/src/next/server-external-packages.ts +43 -0
  373. package/src/next/target-url.ts +46 -0
  374. package/src/next/turbopack-config.ts +54 -0
  375. package/src/next/types.ts +47 -0
  376. package/src/next/webpack-config.ts +71 -0
  377. package/src/next/with-pyric.ts +58 -0
  378. package/src/register/app-bridge.ts +79 -0
  379. package/src/register/hooks.ts +6 -1
  380. package/src/register/index.ts +1 -1
  381. package/src/register/mapping.ts +50 -4
  382. package/src/remote/index.ts +7 -5
  383. package/src/rtdb/rules-json.ts +110 -0
  384. package/src/serve/bundler.ts +2 -0
  385. package/src/serve/discovery.ts +65 -44
  386. package/src/serve/entries/app-ai-passthrough.ts +83 -0
  387. package/src/serve/entries/auth-helper-core.ts +9 -7
  388. package/src/serve/entries/auth.ts +1 -0
  389. package/src/serve/entries/bridge-url.ts +9 -2
  390. package/src/serve/entries/database.ts +1 -0
  391. package/src/serve/entries/firestore.ts +1 -0
  392. package/src/serve/entries/init.ts +5 -2
  393. package/src/serve/entries/runtime.ts +25 -0
  394. package/src/serve/entries/storage.ts +12 -1
  395. package/src/serve/html-injection.ts +26 -1
  396. package/src/serve/rules.ts +2 -2
  397. package/src/serve/runtime/chip-config.ts +19 -6
  398. package/src/serve/runtime/chip.ts +24 -3
  399. package/src/serve/runtime/status.ts +76 -13
  400. package/src/serve/sandbox-session.ts +31 -2
  401. package/src/serve/vite-ai-config.ts +86 -19
  402. package/src/serve/vite-functions-development.ts +1 -0
  403. package/src/serve/vite-generation-rules-watch.ts +67 -15
  404. package/src/serve/vite-module-swap.ts +121 -21
  405. package/src/serve/vite-plugin.ts +44 -6
  406. package/src/serve/vite-sandbox-generation.ts +1 -1
  407. package/src/serve/worker/client/resumable.ts +506 -0
  408. package/src/serve/worker/client/rtdb-references.ts +4 -1
  409. package/src/serve/worker/client/storage.ts +77 -7
  410. package/src/serve/worker/host/rules.ts +2 -1
  411. package/src/serve/worker/index.ts +4 -0
  412. package/src/serve/worker/serve-init.ts +30 -0
  413. package/dist/serve/site-ui/_astro/_slug_.BoKXYrnu.css +0 -1
  414. package/dist/serve/site-ui/_astro/_studio_.BtYpqe1A.css +0 -1
  415. package/dist/serve/site-ui/_astro/app.4HeLP_FA.js +0 -109
  416. package/dist/serve/site-ui/_astro/app.BgSnCR5G.js +0 -1
  417. package/dist/serve/site-ui/_astro/index.BJiOemMX.js +0 -1
  418. package/dist/serve/site-ui/_astro/reads.BrKNdKAa.js +0 -1
  419. package/dist/serve/site-ui/_astro/sandbox-controls.BTb3EbQp.js +0 -1153
@@ -4,13 +4,13 @@
4
4
 
5
5
  <div class="compat-stat">
6
6
  <p class="compat-stat-figure">
7
- <span class="compat-stat-pct">53.8%</span>
7
+ <span class="compat-stat-pct">100%</span>
8
8
  <span class="compat-stat-label">of the public API supported</span>
9
9
  </p>
10
10
  <div class="compat-meters">
11
- <span class="compat-meter-track"><span class="compat-meter-fill" style="width: 53.8%"></span></span>
11
+ <span class="compat-meter-track"><span class="compat-meter-fill" style="width: 100%"></span></span>
12
12
  </div>
13
- <p class="compat-stat-denom">98 of 182 public API</p>
13
+ <p class="compat-stat-denom">182 of 182 public API</p>
14
14
  </div>
15
15
 
16
16
 
@@ -26,28 +26,28 @@
26
26
  <div class="compat-list">
27
27
  <details class="compat-row" data-status="ok">
28
28
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">getFirestore(target)</code><span class="compat-sub"><span class="compat-behavior"><code>getFirestore(ctx)</code> returns a tagged sandbox-target handle (frozen identity)</span></span></span></summary>
29
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code></div></div>
29
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L16" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a></div></div>
30
30
  </details>
31
31
  <details class="compat-row" data-status="ok">
32
32
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">getFirestore(target)</code><span class="compat-sub"><span class="compat-behavior"><code>getFirestore(sandbox)</code> returns a tagged sandbox-live handle (per-op identity)</span></span></span></summary>
33
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-live-identity.test.ts</code></div></div>
33
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-live-identity.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-live-identity.test.ts</code></a></div></div>
34
34
  </details>
35
35
  <details class="compat-row" data-status="ok">
36
36
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">getFirestore(target)</code><span class="compat-sub"><span class="compat-behavior">Package resolution owns production selection: direct <code>pyric/firestore</code> rejects a real <code>FirebaseApp</code>, while inactive canonical <code>firebase/firestore</code> imports remain the real Firebase SDK</span></span></span></summary>
37
- <div class="compat-evidence"><div class="compat-probe"><code>unit:package-resolution.test.ts</code>, <code>node-register:register-child.test.ts</code> (inactive canonical imports are not rewritten)</div></div>
37
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/package-resolution.test.ts#L8" target="_blank" rel="noopener noreferrer"><code>unit:package-resolution.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/register-child.test.ts#L140" target="_blank" rel="noopener noreferrer"><code>node-register:register-child.test.ts</code></a> (inactive canonical imports are not rewritten)</div></div>
38
38
  </details>
39
39
  <details class="compat-row" data-status="ok">
40
40
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">getFirestore(target)</code><span class="compat-sub"><span class="compat-behavior"><code>getFirestore(undefined)</code> is wrapped in the playground preview to default to the sandbox; production's unactivated canonical SDK still throws <code>app/no-app</code>, while a direct mirror call rejects the missing sandbox owner</span></span></span></summary>
41
- <div class="compat-evidence"><div class="compat-probe"><code>playground:firestore-bare-getfirestore</code> — fix from PR #397 + oracle: <code>packages/conformance/observations/firestore/firestore-bare-getfirestore-no-default-app.json</code> (<code>code: 'app/no-app'</code> against blockingfun, fb-js-sdk 12.13.0 — confirms prod throw shape)</div>
41
+ <div class="compat-evidence"><div class="compat-probe"><code>playground:firestore-bare-getfirestore</code> — fix from PR #397 + oracle: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/firestore/firestore-bare-getfirestore-no-default-app.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/firestore/firestore-bare-getfirestore-no-default-app.json</code></a> (<code>code: 'app/no-app'</code> against blockingfun, fb-js-sdk 12.13.0 — confirms prod throw shape)</div>
42
42
  <div class="compat-note">(wrap)</div></div>
43
43
  </details>
44
44
  <details class="compat-row" data-status="ok">
45
45
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">getFirestore(target)</code><span class="compat-sub"><span class="compat-behavior">Two <code>getFirestore(sandbox)</code> calls share state (same underlying <code>LocalEnvironment</code>)</span></span></span></summary>
46
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-live-identity.test.ts</code> ("two handles share the same sandbox")</div></div>
46
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-live-identity.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-live-identity.test.ts</code></a> ("two handles share the same sandbox")</div></div>
47
47
  </details>
48
48
  <details class="compat-row" data-status="ok">
49
49
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">getFirestore(target)</code><span class="compat-sub"><span class="compat-behavior">Handle dispatch by <code>TARGET_SYMBOL</code> brand — refs/queries route to their owning target via <code>refToTarget</code> WeakMap</span></span></span></summary>
50
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code> ("throws TypeError for refs not produced by this package")</div></div>
50
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L16" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a> ("throws TypeError for refs not produced by this package")</div></div>
51
51
  </details>
52
52
  </div>
53
53
 
@@ -56,39 +56,39 @@
56
56
  <div class="compat-list">
57
57
  <details class="compat-row" data-status="ok">
58
58
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Path constructors</code><span class="compat-sub"><span class="compat-behavior"><code>doc(db, path)</code> returns a tagged <code>DocumentReference</code> with <code>id</code> / <code>path</code></span></span></span></summary>
59
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code></div></div>
59
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L18" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a></div></div>
60
60
  </details>
61
61
  <details class="compat-row" data-status="ok">
62
62
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Path constructors</code><span class="compat-sub"><span class="compat-behavior"><code>doc(db, 'a', 'b', 'c', 'd')</code> joins variadic path segments</span></span></span></summary>
63
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code></div></div>
63
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L330" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a></div></div>
64
64
  </details>
65
65
  <details class="compat-row" data-status="ok">
66
66
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Path constructors</code><span class="compat-sub"><span class="compat-behavior"><code>collection(db, path)</code> returns a tagged <code>CollectionReference</code></span></span></span></summary>
67
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code></div></div>
67
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L19" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a></div></div>
68
68
  </details>
69
69
  <details class="compat-row" data-status="ok">
70
70
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Path constructors</code><span class="compat-sub"><span class="compat-behavior"><code>doc(coll, id)</code> appends under a collection ref</span></span></span></summary>
71
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code></div></div>
71
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L18" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a></div></div>
72
72
  </details>
73
73
  <details class="compat-row" data-status="ok">
74
74
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Path constructors</code><span class="compat-sub"><span class="compat-behavior"><code>doc(coll)</code> (no id) mints an auto-id <code>DocumentReference</code></span></span></span></summary>
75
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code></div></div>
75
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L18" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a></div></div>
76
76
  </details>
77
77
  <details class="compat-row" data-status="ok">
78
78
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Path constructors</code><span class="compat-sub"><span class="compat-behavior"><code>collection(docRef, name)</code> builds a subcollection ref</span></span></span></summary>
79
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code></div></div>
79
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L19" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a></div></div>
80
80
  </details>
81
81
  <details class="compat-row" data-status="ok">
82
82
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Path constructors</code><span class="compat-sub"><span class="compat-behavior"><code>collectionGroup(db, id)</code> returns a query spanning every collection with that id</span></span></span></summary>
83
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code> ("gathers documents across every parent collection")</div></div>
83
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L19" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a> ("gathers documents across every parent collection")</div></div>
84
84
  </details>
85
85
  <details class="compat-row" data-status="ok">
86
86
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Path constructors</code><span class="compat-sub"><span class="compat-behavior">Unknown ref (not produced by this package) → <code>TypeError</code> with "unrecognized reference"</span></span></span></summary>
87
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code></div></div>
87
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L18" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a></div></div>
88
88
  </details>
89
89
  <details class="compat-row" data-status="ok">
90
90
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Path constructors</code><span class="compat-sub"><span class="compat-behavior">Held doc/coll ref under <code>sandbox-live</code> re-resolves to the chainable under the current user at op time (via rebuild closure)</span></span></span></summary>
91
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-live-identity.test.ts</code> ("held doc ref re-resolves under the current user")</div></div>
91
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-live-identity.test.ts#L26" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-live-identity.test.ts</code></a> ("held doc ref re-resolves under the current user")</div></div>
92
92
  </details>
93
93
  </div>
94
94
 
@@ -97,7 +97,7 @@
97
97
  <div class="compat-list">
98
98
  <details class="compat-row" data-status="ok">
99
99
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">getDoc(ref)</code><span class="compat-sub"><span class="compat-behavior">Returns <code>DocumentSnapshot</code> with <code>id</code>, <code>exists</code> (method form), <code>data()</code></span></span></span></summary>
100
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code></div></div>
100
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L21" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a></div></div>
101
101
  </details>
102
102
  <details class="compat-row" data-status="ok">
103
103
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">getDoc(ref)</code><span class="compat-sub"><span class="compat-behavior"><code>snap.exists</code> is normalized to method form (<code>snap.exists()</code> returns boolean) to match the modular SDK</span></span></span></summary>
@@ -105,19 +105,19 @@
105
105
  </details>
106
106
  <details class="compat-row" data-status="ok">
107
107
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">getDoc(ref)</code><span class="compat-sub"><span class="compat-behavior"><code>snap.data()</code> returns <code>undefined</code> for missing doc</span></span></span></summary>
108
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code></div></div>
108
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L21" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a></div></div>
109
109
  </details>
110
110
  <details class="compat-row" data-status="ok">
111
111
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">getDoc(ref)</code><span class="compat-sub"><span class="compat-behavior"><code>snap.ref</code> is tagged so it routes through <code>targetOf</code> in follow-up ops</span></span></span></summary>
112
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code></div></div>
112
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L21" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a></div></div>
113
113
  </details>
114
114
  <details class="compat-row" data-status="ok">
115
115
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">getDoc(ref)</code><span class="compat-sub"><span class="compat-behavior">Re-evaluates rules under current user on every call (sandbox-live) — read denied throws <code>permission-denied</code></span></span></span></summary>
116
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-live-identity.test.ts</code> ("doc read denied when current user lacks read access"), oracle: <code>packages/conformance/observations/firestore/firestore-read-denied-error-code.json</code> (prod <code>getDoc</code> on a denied path throws a <code>FirebaseError</code> with <code>.code === 'permission-denied'</code>, <code>.message === 'Missing or insufficient permissions.'</code>, <code>instanceof Error</code>)</div></div>
116
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-live-identity.test.ts#L28" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-live-identity.test.ts</code></a> ("doc read denied when current user lacks read access"), oracle: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/firestore/firestore-read-denied-error-code.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/firestore/firestore-read-denied-error-code.json</code></a> (prod <code>getDoc</code> on a denied path throws a <code>FirebaseError</code> with <code>.code === 'permission-denied'</code>, <code>.message === 'Missing or insufficient permissions.'</code>, <code>instanceof Error</code>) (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/sandbox/firestore/admin-compat/query-rules-enforcement.test.ts#L4" target="_blank" rel="noopener noreferrer"><code>query-rules-enforcement.test.ts</code></a>)</div></div>
117
117
  </details>
118
118
  <details class="compat-row" data-status="ok">
119
119
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">getDoc(ref)</code><span class="compat-sub"><span class="compat-behavior">Rules-denied modular writes throw a <code>FirebaseError('permission-denied')</code>, matching the production error class and code</span></span></span></summary>
120
- <div class="compat-evidence"><div class="compat-probe">Oracle-locked by <code>packages/conformance/observations/firestore/firestore-rules-denied-error.json</code>: prod throws a <code>FirebaseError</code> (name + constructor name both <code>FirebaseError</code>), <code>.code === 'permission-denied'</code>, and the value is an <code>instanceof FirebaseError</code> and <code>Error</code>. <code>oracle:firestore#21</code> replays the same denied modular write against the sandbox and asserts that complete class shape.</div></div>
120
+ <div class="compat-evidence"><div class="compat-probe">Oracle-locked by <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/firestore/firestore-rules-denied-error.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/firestore/firestore-rules-denied-error.json</code></a>: prod throws a <code>FirebaseError</code> (name + constructor name both <code>FirebaseError</code>), <code>.code === 'permission-denied'</code>, and the value is an <code>instanceof FirebaseError</code> and <code>Error</code>. <code>oracle:firestore#21</code> replays the same denied modular write against the sandbox and asserts that complete class shape. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/oracle-conformance.test.ts#L569" target="_blank" rel="noopener noreferrer"><code>oracle-conformance.test.ts</code></a>)</div></div>
121
121
  </details>
122
122
  </div>
123
123
 
@@ -126,31 +126,31 @@
126
126
  <div class="compat-list">
127
127
  <details class="compat-row" data-status="ok">
128
128
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">getDocs(query)</code><span class="compat-sub"><span class="compat-behavior">Returns <code>QuerySnapshot</code> with <code>size</code>, <code>empty</code>, <code>docs</code> (<code>QueryDocumentSnapshot[]</code>)</span></span></span></summary>
129
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code>, <code>playground:firestore-query</code></div></div>
129
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L22" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a>, <code>playground:firestore-query</code> (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/oracle-conformance.test.ts#L41" target="_blank" rel="noopener noreferrer"><code>oracle-conformance.test.ts</code></a>)</div></div>
130
130
  </details>
131
131
  <details class="compat-row" data-status="ok">
132
132
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">getDocs(query)</code><span class="compat-sub"><span class="compat-behavior">Each <code>snap.docs[i].ref</code> is tagged for follow-up ops</span></span></span></summary>
133
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code></div></div>
133
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L22" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a></div></div>
134
134
  </details>
135
135
  <details class="compat-row" data-status="ok">
136
136
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">getDocs(query)</code><span class="compat-sub"><span class="compat-behavior">Sandbox-live: re-evaluates filters under the current user (different docs visible per identity)</span></span></span></summary>
137
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-live-identity.test.ts</code> ("query results re-evaluate under the current user")</div></div>
137
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-live-identity.test.ts#L29" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-live-identity.test.ts</code></a> ("query results re-evaluate under the current user")</div></div>
138
138
  </details>
139
139
  <details class="compat-row" data-status="ok">
140
140
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">getDocs(query)</code><span class="compat-sub"><span class="compat-behavior"><strong>Query reads enforce security rules (FS-B1)</strong> — a deny-all / auth-gated rule set throws <code>permission-denied</code>. Pre-FS-B1 query reads went through the rules-bypassing <code>listDocuments</code> and returned the whole collection.</span></span></span></summary>
141
- <div class="compat-evidence"><div class="compat-probe"><code>unit:admin-compat/query-rules-enforcement.test.ts</code> (deny-all + auth-gated <code>getDocs</code>/aggregate), <code>unit:admin-compat/per-op-auth.test.ts</code> ("Query.get enforces rules")</div></div>
141
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/sandbox/firestore/admin-compat/query-rules-enforcement.test.ts#L4" target="_blank" rel="noopener noreferrer"><code>unit:admin-compat/query-rules-enforcement.test.ts</code></a> (deny-all + auth-gated <code>getDocs</code>/aggregate), <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/sandbox/firestore/admin-compat/per-op-auth.test.ts#L158" target="_blank" rel="noopener noreferrer"><code>unit:admin-compat/per-op-auth.test.ts</code></a> ("Query.get enforces rules")</div></div>
142
142
  </details>
143
143
  <details class="compat-row" data-status="ok">
144
144
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">getDocs(query)</code><span class="compat-sub"><span class="compat-behavior"><strong>Enforcement follows production's QUERY-PROOF model (RULES-B11)</strong> — "rules are not filters": a doc-data-dependent <code>list</code> rule (<code>resource.data.visibility == 'public'</code>, <code>resource.data.owner == request.auth.uid</code>) is ALLOWED when the query's <code>where()</code> equalities discharge it and the whole query is <code>permission-denied</code> otherwise — never silently truncated to the readable subset. Per-doc <code>get</code> rules do NOT filter query results (the <code>list</code> rule alone governs queries — granular-operations docs). Applies to <code>getDocs</code>, aggregates, and <code>onSnapshot</code> alike. Pre-fix: rules-as-filters (per-doc <code>get</code> omission) + blanket denial of every doc-data-dependent list, even provable ones.</span></span></span></summary>
145
- <div class="compat-evidence"><div class="compat-probe"><code>unit:firestore/query-proof-enforcement.test.ts</code> (provable/unprovable getDocs + onSnapshot, owner-pinned uid, get-rules-don't-filter, request.query.limit; verified failing pre-fix), <code>unit:simulator/local-environment.test.ts</code> (Slice 6 — flipped from per-doc-filter assertions); prod truth: firebase.google.com/docs/firestore/security/rules-query</div></div>
145
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/query-proof-enforcement.test.ts#L19" target="_blank" rel="noopener noreferrer"><code>unit:firestore/query-proof-enforcement.test.ts</code></a> (provable/unprovable getDocs + onSnapshot, owner-pinned uid, get-rules-don't-filter, request.query.limit; verified failing pre-fix), <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/sandbox/firestore/simulator/local-environment.test.ts#L617" target="_blank" rel="noopener noreferrer"><code>unit:simulator/local-environment.test.ts</code></a> (Slice 6 — flipped from per-doc-filter assertions); prod truth: firebase.google.com/docs/firestore/security/rules-query</div></div>
146
146
  </details>
147
147
  <details class="compat-row" data-status="diverged">
148
148
  <summary class="compat-line"><span class="compat-dot" data-status="diverged" role="img" aria-label="Diverged (documented)" title="Diverged (documented)"></span><span class="compat-main"><code class="compat-api">getDocs(query)</code><span class="compat-sub"><span class="compat-behavior">Query-proof <strong>prover scope is conservative, with full accounting and a fail-closed classifier</strong> — user functions are inlined (nested helper-calling-helper and multi-parameter helpers included, matching production's rules-function inlining), and a <code>list</code> rule is provable only when its <strong>entire</strong> doc-dependence reduces to top-level AND-conjunct <code>resource.data.&lt;field&gt; == &lt;literal&gt;</code> equalities (with <code>request.auth.uid</code> pinned to the caller) each discharged by a <code>where(field, '==', value)</code> filter. Doc-dependence is decided by a fail-closed classifier: an expression counts as doc-independent only when every node is positively recognized as such — any touch of the <code>resource</code> root in any syntactic form (<code>resource.data</code>, <code>resource['data']</code>, slices, <code>resource.id</code>, path-literal segments feeding <code>exists</code>/<code>get</code> lookups) and any unrecognized node shape classifies doc-dependent. Any doc-dependent conjunct that is not a discharged equality — disjunctions over doc data, inequality/range predicates (<code>resource.data.score &gt; 10</code> + <code>where('score','&gt;',10)</code>), <code>in</code> / negated-<code>in</code> membership checks, <code>get(key, default)</code>, <code>keys().hasOnly(...)</code>, <code>is</code> type checks, nested-path predicates, data-keyed document lookups — makes the whole query conservatively DENY, even when every equality conjunct is discharged; production's prover may allow some of these. Divergence is deny-only: mixed equality + non-equality shapes are rejected up front rather than trusted to a residual evaluation that absent-tolerant predicates could pass vacuously.</span></span></span></summary>
149
- <div class="compat-evidence"><div class="compat-probe"><code>unit:rules/simulator/query-proof.test.ts</code> (function-inlining provable cases; full-accounting rejections for mixed equality + <code>in</code>/<code>get</code>/<code>hasOnly</code>/range/<code>is</code> shapes in inline and helper form; fail-closed classifier probes: bracket-access <code>resource['data']</code>, <code>exists()</code> keyed by <code>resource.data</code> through path-literal segments, slice access, <code>resource.id</code>, and an unrecognized node shape), <code>unit:firestore/query-proof-enforcement.test.ts</code> (seeded end-to-end denials of the demonstrated attacks: forbidden-field absence check in member and bracket form, banned-owner <code>exists()</code> lookup, slice conjunct — each returning zero documents)</div></div>
149
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/rules/simulator/query-proof.test.ts#L7" target="_blank" rel="noopener noreferrer"><code>unit:rules/simulator/query-proof.test.ts</code></a> (function-inlining provable cases; full-accounting rejections for mixed equality + <code>in</code>/<code>get</code>/<code>hasOnly</code>/range/<code>is</code> shapes in inline and helper form; fail-closed classifier probes: bracket-access <code>resource['data']</code>, <code>exists()</code> keyed by <code>resource.data</code> through path-literal segments, slice access, <code>resource.id</code>, and an unrecognized node shape), <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/query-proof-enforcement.test.ts#L19" target="_blank" rel="noopener noreferrer"><code>unit:firestore/query-proof-enforcement.test.ts</code></a> (seeded end-to-end denials of the demonstrated attacks: forbidden-field absence check in member and bracket form, banned-owner <code>exists()</code> lookup, slice conjunct — each returning zero documents)</div></div>
150
150
  </details>
151
151
  <details class="compat-row" data-status="ok">
152
152
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">getDocs(query)</code><span class="compat-sub"><span class="compat-behavior">Empty result for a collection with no docs (<code>size === 0</code>, <code>empty === true</code>)</span></span></span></summary>
153
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code></div></div>
153
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L22" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a></div></div>
154
154
  </details>
155
155
  </div>
156
156
 
@@ -159,31 +159,31 @@
159
159
  <div class="compat-list">
160
160
  <details class="compat-row" data-status="ok">
161
161
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">setDoc(ref, data[, options])</code><span class="compat-sub"><span class="compat-behavior">No options → replaces the existing document entirely</span></span></span></summary>
162
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code> ("setDoc default replaces")</div></div>
162
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L24" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a> ("setDoc default replaces")</div></div>
163
163
  </details>
164
164
  <details class="compat-row" data-status="ok">
165
165
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">setDoc(ref, data[, options])</code><span class="compat-sub"><span class="compat-behavior"><code>{ merge: true }</code> → <strong>deep-merges nested maps</strong> (FS-B6), preserving unspecified fields at every level: <code>setDoc({a:{b:2}}, {merge:true})</code> over <code>{a:{c:1}}</code> yields <code>{a:{b:2,c:1}}</code>. Pre-FS-B6 the wrapper shallow-replaced the whole <code>a</code> map.</span></span></span></summary>
166
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code>, <code>unit:admin-compat/field-path-merge.test.ts</code> (FS-B6 nested deep-merge; verified failing pre-fix)</div></div>
166
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L24" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/sandbox/firestore/admin-compat/field-path-merge.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:admin-compat/field-path-merge.test.ts</code></a> (FS-B6 nested deep-merge; verified failing pre-fix)</div></div>
167
167
  </details>
168
168
  <details class="compat-row" data-status="ok">
169
169
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">setDoc(ref, data[, options])</code><span class="compat-sub"><span class="compat-behavior"><code>{ mergeFields: [...] }</code> → writes only the listed <strong>dot-separated field paths</strong> into the existing doc (FS-B6); other keys in <code>data</code> are ignored, other fields in the existing doc preserved. <code>mergeFields: ['a.b']</code> reaches into a nested map.</span></span></span></summary>
170
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code>, <code>unit:admin-compat/field-path-merge.test.ts</code> (dotted mergeField); mask edges (delete/transform outside mask, empty mask, deleteField in mask): <code>unit:upstream-write-aggregate-probes.test.ts</code></div></div>
170
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L24" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/sandbox/firestore/admin-compat/field-path-merge.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:admin-compat/field-path-merge.test.ts</code></a> (dotted mergeField); mask edges (delete/transform outside mask, empty mask, deleteField in mask): <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/upstream-write-aggregate-probes.test.ts#L19" target="_blank" rel="noopener noreferrer"><code>unit:upstream-write-aggregate-probes.test.ts</code></a></div></div>
171
171
  </details>
172
172
  <details class="compat-row" data-status="ok">
173
173
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">setDoc(ref, data[, options])</code><span class="compat-sub"><span class="compat-behavior">Passing both <code>merge</code> and <code>mergeFields</code> — <code>mergeFields</code> wins on sandbox (matches JS SDK effective behavior)</span></span></span></summary>
174
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code></div></div>
174
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L24" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a></div></div>
175
175
  </details>
176
176
  <details class="compat-row" data-status="ok">
177
177
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">setDoc(ref, data[, options])</code><span class="compat-sub"><span class="compat-behavior">Sentinels (<code>serverTimestamp</code>, <code>increment</code>, <code>arrayUnion</code>, <code>arrayRemove</code>, <code>deleteField</code>) resolve in the same call</span></span></span></summary>
178
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code>, <code>playground:firestore-sentinels</code>, oracle: <code>packages/conformance/observations/firestore/firestore-row-30-sentinels-in-setdoc.json</code> — <code>setDoc({createdAt: serverTimestamp(), count: 5, tags: ['a']})</code> followed by <code>getDoc</code> returns <code>createdAt</code> as a <code>Timestamp</code> instance (constructor name <code>Timestamp</code>, has <code>seconds</code> + <code>nanoseconds</code>), <code>count === 5</code> (number), <code>tags === ['a']</code>. Sentinels resolve server-side and the follow-up read sees concrete values, not the sentinel placeholders.</div></div>
178
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L24" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a>, <code>playground:firestore-sentinels</code>, oracle: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/firestore/firestore-row-30-sentinels-in-setdoc.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/firestore/firestore-row-30-sentinels-in-setdoc.json</code></a> — <code>setDoc({createdAt: serverTimestamp(), count: 5, tags: ['a']})</code> followed by <code>getDoc</code> returns <code>createdAt</code> as a <code>Timestamp</code> instance (constructor name <code>Timestamp</code>, has <code>seconds</code> + <code>nanoseconds</code>), <code>count === 5</code> (number), <code>tags === ['a']</code>. Sentinels resolve server-side and the follow-up read sees concrete values, not the sentinel placeholders.</div></div>
179
179
  </details>
180
180
  <details class="compat-row" data-status="ok">
181
181
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">setDoc(ref, data[, options])</code><span class="compat-sub"><span class="compat-behavior">Converter (via <code>withConverter</code>) runs <code>toFirestore(data)</code> before the write</span></span></span></summary>
182
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code> ("withConverter on a DocumentReference round-trips")</div></div>
182
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L24" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a> ("withConverter on a DocumentReference round-trips")</div></div>
183
183
  </details>
184
184
  <details class="compat-row" data-status="ok">
185
185
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">setDoc(ref, data[, options])</code><span class="compat-sub"><span class="compat-behavior">Rules-denied <code>setDoc</code> throws a <code>FirebaseError</code> with code <code>permission-denied</code>, matching production</span></span></span></summary>
186
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code> ("getDoc denies when rules reject"), <code>playground:rules-data-validation</code>, oracle: <code>packages/conformance/observations/firestore/firestore-write-denied-error-code.json</code> (prod <code>setDoc</code> on a denied path throws a <code>FirebaseError</code> with <code>.code === 'permission-denied'</code>, <code>.message === '7 PERMISSION_DENIED: Missing or insufficient permissions.'</code>, <code>instanceof Error</code>); <code>oracle:firestore#32</code> pins the sandbox error shape</div></div>
186
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L24" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a> ("getDoc denies when rules reject"), <code>playground:rules-data-validation</code>, oracle: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/firestore/firestore-write-denied-error-code.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/firestore/firestore-write-denied-error-code.json</code></a> (prod <code>setDoc</code> on a denied path throws a <code>FirebaseError</code> with <code>.code === 'permission-denied'</code>, <code>.message === '7 PERMISSION_DENIED: Missing or insufficient permissions.'</code>, <code>instanceof Error</code>); <code>oracle:firestore#32</code> pins the sandbox error shape (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/oracle-conformance.test.ts#L557" target="_blank" rel="noopener noreferrer"><code>oracle-conformance.test.ts</code></a>)</div></div>
187
187
  </details>
188
188
  </div>
189
189
 
@@ -192,11 +192,11 @@
192
192
  <div class="compat-list">
193
193
  <details class="compat-row" data-status="ok">
194
194
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">updateDoc(ref, data)</code><span class="compat-sub"><span class="compat-behavior">Merges <code>data</code> into the existing doc; missing fields preserved. <strong>Top-level keys are dot-separated FieldPaths</strong> (FS-B5): <code>updateDoc({'a.b': 2})</code> sets the nested leaf <code>a.b</code> (preserving <code>a.c</code>), not a literal <code>"a.b"</code> key; a single-segment map value replaces that field wholesale; <code>deleteField()</code> at a dotted path removes the nested leaf.</span></span></span></summary>
195
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code>, <code>unit:admin-compat/field-path-merge.test.ts</code> (FS-B5 dot-path nested write + delete; verified failing pre-fix)</div></div>
195
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L25" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/sandbox/firestore/admin-compat/field-path-merge.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:admin-compat/field-path-merge.test.ts</code></a> (FS-B5 dot-path nested write + delete; verified failing pre-fix) (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/firestore/firestore-updatedoc-dotpath-fieldpath.json" target="_blank" rel="noopener noreferrer"><code>firestore-updatedoc-dotpath-fieldpath</code></a>)</div></div>
196
196
  </details>
197
197
  <details class="compat-row" data-status="ok">
198
198
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">updateDoc(ref, data)</code><span class="compat-sub"><span class="compat-behavior">Throws <code>not-found</code> (sandbox) / <code>FirebaseError('not-found')</code> (prod) on missing doc</span></span></span></summary>
199
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code> (implicit in writes-fail-on-missing tests), oracle: <code>packages/conformance/observations/firestore/firestore-updatedoc-missing-error.json</code> (prod throws <code>FirebaseError</code> with <code>code: 'not-found'</code>, message <code>"5 NOT_FOUND: No document to update: …"</code>)</div></div>
199
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L25" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a> (implicit in writes-fail-on-missing tests), oracle: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/firestore/firestore-updatedoc-missing-error.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/firestore/firestore-updatedoc-missing-error.json</code></a> (prod throws <code>FirebaseError</code> with <code>code: 'not-found'</code>, message <code>"5 NOT_FOUND: No document to update: …"</code>)</div></div>
200
200
  </details>
201
201
  <details class="compat-row" data-status="ok">
202
202
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">updateDoc(ref, data)</code><span class="compat-sub"><span class="compat-behavior">Does NOT run a converter — partial updates don't have a typed home (matches JS SDK)</span></span></span></summary>
@@ -204,11 +204,11 @@
204
204
  </details>
205
205
  <details class="compat-row" data-status="ok">
206
206
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">updateDoc(ref, data)</code><span class="compat-sub"><span class="compat-behavior">Sentinels resolve mid-update (<code>increment(1)</code> against an existing numeric field, etc.)</span></span></span></summary>
207
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code>, <code>playground:firestore-sentinels</code>, oracle: <code>packages/conformance/observations/firestore/firestore-row-36-sentinels-in-updatedoc.json</code> — after <code>setDoc({count: 5, tags: ['a'], oldField: 'keep-then-remove'})</code> then <code>updateDoc({count: increment(3), tags: arrayUnion('b'), oldField: deleteField()})</code>, the follow-up <code>getDoc</code> returns <code>count: 8</code>, <code>tags: ['a', 'b']</code>, and <code>oldField</code> absent from the doc (the deleteField sentinel actually removes the key). All three sentinels apply in one mid-update commit.</div></div>
207
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L25" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a>, <code>playground:firestore-sentinels</code>, oracle: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/firestore/firestore-row-36-sentinels-in-updatedoc.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/firestore/firestore-row-36-sentinels-in-updatedoc.json</code></a> — after <code>setDoc({count: 5, tags: ['a'], oldField: 'keep-then-remove'})</code> then <code>updateDoc({count: increment(3), tags: arrayUnion('b'), oldField: deleteField()})</code>, the follow-up <code>getDoc</code> returns <code>count: 8</code>, <code>tags: ['a', 'b']</code>, and <code>oldField</code> absent from the doc (the deleteField sentinel actually removes the key). All three sentinels apply in one mid-update commit.</div></div>
208
208
  </details>
209
209
  <details class="compat-row" data-status="ok">
210
210
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">updateDoc(ref, data)</code><span class="compat-sub"><span class="compat-behavior">Sandbox-live: each call re-evaluates auth (alice → bob between writes uses bob's auth)</span></span></span></summary>
211
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-live-identity.test.ts</code> ("updateDoc re-evaluates auth per call")</div></div>
211
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-live-identity.test.ts#L31" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-live-identity.test.ts</code></a> ("updateDoc re-evaluates auth per call")</div></div>
212
212
  </details>
213
213
  </div>
214
214
 
@@ -217,15 +217,15 @@
217
217
  <div class="compat-list">
218
218
  <details class="compat-row" data-status="ok">
219
219
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">deleteDoc(ref)</code><span class="compat-sub"><span class="compat-behavior">Removes the document; subsequent <code>getDoc</code> returns <code>exists()===false</code></span></span></span></summary>
220
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code></div></div>
220
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L26" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a></div></div>
221
221
  </details>
222
222
  <details class="compat-row" data-status="ok">
223
223
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">deleteDoc(ref)</code><span class="compat-sub"><span class="compat-behavior">Idempotent — <code>deleteDoc</code> on missing doc resolves without throwing (matches JS SDK)</span></span></span></summary>
224
- <div class="compat-evidence"><div class="compat-probe"><code>unit:deletedoc-missing.test.ts</code>, <code>playground:firestore-deletedoc-missing</code>, oracle: <code>packages/conformance/observations/firestore/firestore-deletedoc-missing.json</code></div></div>
224
+ <div class="compat-evidence"><div class="compat-probe"><code>unit:deletedoc-missing.test.ts</code>, <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/firestore/firestore-deletedoc-missing.json" target="_blank" rel="noopener noreferrer"><code>playground:firestore-deletedoc-missing</code></a>, oracle: <code>packages/conformance/observations/firestore/firestore-deletedoc-missing.json</code></div></div>
225
225
  </details>
226
226
  <details class="compat-row" data-status="ok">
227
227
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">deleteDoc(ref)</code><span class="compat-sub"><span class="compat-behavior">Rules-denied delete throws <code>permission-denied</code></span></span></span></summary>
228
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code> (rules-reject branch), oracle: <code>packages/conformance/observations/firestore/firestore-delete-denied-error-code.json</code> (prod <code>deleteDoc</code> on a denied path throws a <code>FirebaseError</code> with <code>.code === 'permission-denied'</code>, <code>.message === '7 PERMISSION_DENIED: Missing or insufficient permissions.'</code>, <code>instanceof Error</code>)</div></div>
228
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L26" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a> (rules-reject branch), oracle: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/firestore/firestore-delete-denied-error-code.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/firestore/firestore-delete-denied-error-code.json</code></a> (prod <code>deleteDoc</code> on a denied path throws a <code>FirebaseError</code> with <code>.code === 'permission-denied'</code>, <code>.message === '7 PERMISSION_DENIED: Missing or insufficient permissions.'</code>, <code>instanceof Error</code>)</div></div>
229
229
  </details>
230
230
  </div>
231
231
 
@@ -234,23 +234,23 @@
234
234
  <div class="compat-list">
235
235
  <details class="compat-row" data-status="ok">
236
236
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">addDoc(coll, data)</code><span class="compat-sub"><span class="compat-behavior">Returns a tagged <code>DocumentReference</code> with auto-id</span></span></span></summary>
237
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code></div></div>
237
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L27" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a></div></div>
238
238
  </details>
239
239
  <details class="compat-row" data-status="ok">
240
240
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">addDoc(coll, data)</code><span class="compat-sub"><span class="compat-behavior">Returned ref is usable in subsequent ops (<code>getDoc</code>, <code>setDoc</code>, <code>onSnapshot</code>)</span></span></span></summary>
241
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code>, oracle: <code>packages/conformance/observations/firestore/firestore-row-42-adddoc-returned-ref-usable.json</code> — <code>addDoc(coll, {v:1})</code> returned a ref whose <code>.id</code> is a 20-char auto-id; <code>getDoc(ref)</code> returned <code>{v:1}</code> (round-trip), <code>setDoc(ref, {v:2})</code> overwrote without error, follow-up <code>getDoc</code> returned <code>{v:2}</code>, and <code>onSnapshot(ref, cb)</code> registered cleanly and fired once with <code>{exists:true, v:2}</code>. All four follow-up ops succeed on the returned ref without re-tagging.</div></div>
241
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L27" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a>, oracle: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/firestore/firestore-row-42-adddoc-returned-ref-usable.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/firestore/firestore-row-42-adddoc-returned-ref-usable.json</code></a> — <code>addDoc(coll, {v:1})</code> returned a ref whose <code>.id</code> is a 20-char auto-id; <code>getDoc(ref)</code> returned <code>{v:1}</code> (round-trip), <code>setDoc(ref, {v:2})</code> overwrote without error, follow-up <code>getDoc</code> returned <code>{v:2}</code>, and <code>onSnapshot(ref, cb)</code> registered cleanly and fired once with <code>{exists:true, v:2}</code>. All four follow-up ops succeed on the returned ref without re-tagging.</div></div>
242
242
  </details>
243
243
  <details class="compat-row" data-status="ok">
244
244
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">addDoc(coll, data)</code><span class="compat-sub"><span class="compat-behavior">Sandbox-live: returned ref is a <em>live</em> ref (rebuild closure recorded) so follow-ups re-resolve auth</span></span></span></summary>
245
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-live-identity.test.ts</code> ("addDoc result is a tagged live ref")</div></div>
245
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-live-identity.test.ts#L32" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-live-identity.test.ts</code></a> ("addDoc result is a tagged live ref")</div></div>
246
246
  </details>
247
247
  <details class="compat-row" data-status="ok">
248
248
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">addDoc(coll, data)</code><span class="compat-sub"><span class="compat-behavior">Converter on the parent collection propagates onto the returned ref</span></span></span></summary>
249
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code> ("addDoc through a converted collection")</div></div>
249
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L27" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a> ("addDoc through a converted collection")</div></div>
250
250
  </details>
251
251
  <details class="compat-row" data-status="ok">
252
252
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">addDoc(coll, data)</code><span class="compat-sub"><span class="compat-behavior">Auto-id format — production and sandbox mint 20-character IDs from <code>[A-Za-z0-9]</code></span></span></span></summary>
253
- <div class="compat-evidence"><div class="compat-probe">Oracle-locked: <code>packages/conformance/observations/firestore/firestore-adddoc-autoid-format.json</code> records production's 20-character alphanumeric shape; <code>packages/pyric/test/firestore/oracle-conformance.test.ts</code> generates 25 sandbox IDs and checks both invariants against that observation.</div></div>
253
+ <div class="compat-evidence"><div class="compat-probe">Oracle-locked: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/firestore/firestore-adddoc-autoid-format.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/firestore/firestore-adddoc-autoid-format.json</code></a> records production's 20-character alphanumeric shape; <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/oracle-conformance.test.ts#L185" target="_blank" rel="noopener noreferrer"><code>packages/pyric/test/firestore/oracle-conformance.test.ts</code></a> generates 25 sandbox IDs and checks both invariants against that observation.</div></div>
254
254
  </details>
255
255
  </div>
256
256
 
@@ -259,31 +259,31 @@
259
259
  <div class="compat-list">
260
260
  <details class="compat-row" data-status="ok">
261
261
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">withConverter</code><span class="compat-sub"><span class="compat-behavior"><code>withConverter(docRef, converter)</code> returns a shell that runs <code>toFirestore</code> on writes, <code>fromFirestore</code> on reads</span></span></span></summary>
262
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code></div></div>
262
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L51" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a></div></div>
263
263
  </details>
264
264
  <details class="compat-row" data-status="ok">
265
265
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">withConverter</code><span class="compat-sub"><span class="compat-behavior"><code>withConverter(collRef, converter)</code> propagates onto <code>doc(typedColl, id)</code></span></span></span></summary>
266
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code></div></div>
266
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L51" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a></div></div>
267
267
  </details>
268
268
  <details class="compat-row" data-status="ok">
269
269
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">withConverter</code><span class="compat-sub"><span class="compat-behavior"><code>withConverter(collRef, converter)</code> propagates through <code>query(typedColl, …)</code> + <code>getDocs()</code></span></span></span></summary>
270
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code></div></div>
270
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L51" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a></div></div>
271
271
  </details>
272
272
  <details class="compat-row" data-status="ok">
273
273
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">withConverter</code><span class="compat-sub"><span class="compat-behavior"><code>withConverter(ref, null)</code> strips the converter, returns the underlying untyped view</span></span></span></summary>
274
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code></div></div>
274
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L51" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a></div></div>
275
275
  </details>
276
276
  <details class="compat-row" data-status="ok">
277
277
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">withConverter</code><span class="compat-sub"><span class="compat-behavior">Original untyped ref keeps its identity after <code>withConverter(ref, c)</code> (two views, one path)</span></span></span></summary>
278
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code></div></div>
278
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L51" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a></div></div>
279
279
  </details>
280
280
  <details class="compat-row" data-status="ok">
281
281
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">withConverter</code><span class="compat-sub"><span class="compat-behavior"><code>setDoc</code> through a converted ref invokes <code>toFirestore(data)</code></span></span></span></summary>
282
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code></div></div>
282
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L51" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a></div></div>
283
283
  </details>
284
284
  <details class="compat-row" data-status="ok">
285
285
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">withConverter</code><span class="compat-sub"><span class="compat-behavior"><code>getDoc</code> through a converted ref invokes <code>fromFirestore(snapshot)</code>; <code>.data()</code> returns the typed model</span></span></span></summary>
286
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code></div></div>
286
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L51" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a></div></div>
287
287
  </details>
288
288
  <details class="compat-row" data-status="ok">
289
289
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">withConverter</code><span class="compat-sub"><span class="compat-behavior"><code>updateDoc</code> through a converted ref does NOT invoke the converter</span></span></span></summary>
@@ -296,67 +296,67 @@
296
296
  <div class="compat-list">
297
297
  <details class="compat-row" data-status="ok">
298
298
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Query construction</code><span class="compat-sub"><span class="compat-behavior"><code>query(coll, where(…), orderBy(…), limit(…))</code> composes constraints in order</span></span></span></summary>
299
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code>, <code>playground:firestore-query</code></div></div>
299
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L29" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a>, <code>playground:firestore-query</code></div></div>
300
300
  </details>
301
301
  <details class="compat-row" data-status="ok">
302
302
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Query construction</code><span class="compat-sub"><span class="compat-behavior"><code>where(field, op, value)</code> — all 10 ops: <code>&lt;</code>, <code>&lt;=</code>, <code>==</code>, <code>&gt;=</code>, <code>&gt;</code>, <code>!=</code>, <code>in</code>, <code>not-in</code>, <code>array-contains</code>, <code>array-contains-any</code></span></span></span></summary>
303
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code> (canonical query test); membership ops + OR/<code>in</code>/<code>array-contains</code> composites: <code>unit:upstream-query-probes.test.ts</code></div></div>
303
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L30" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a> (canonical query test); membership ops + OR/<code>in</code>/<code>array-contains</code> composites: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/upstream-query-probes.test.ts#L9" target="_blank" rel="noopener noreferrer"><code>unit:upstream-query-probes.test.ts</code></a></div></div>
304
304
  </details>
305
305
  <details class="compat-row" data-status="ok">
306
306
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Query construction</code><span class="compat-sub"><span class="compat-behavior"><strong>Existence + null filter guards (FS-B7)</strong> — a doc missing the filter field is never returned by <code>==</code>/<code>&lt;</code>/<code>&lt;=</code>/<code>&gt;</code>/<code>&gt;=</code>/<code>in</code>/<code>!=</code>/<code>not-in</code>; <code>!=</code> and <code>not-in</code> additionally exclude null-valued docs and require the field to exist; a <code>null</code> in a <code>not-in</code> operand list matches nothing. Pre-FS-B7, <code>!=</code>/<code>not-in</code> matched missing-field and null docs.</span></span></span></summary>
307
- <div class="compat-evidence"><div class="compat-probe"><code>unit:admin-compat/inequality-existence-guards.test.ts</code> (verified failing pre-fix)</div></div>
307
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/sandbox/firestore/admin-compat/inequality-existence-guards.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:admin-compat/inequality-existence-guards.test.ts</code></a> (verified failing pre-fix)</div></div>
308
308
  </details>
309
309
  <details class="compat-row" data-status="ok">
310
310
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Query construction</code><span class="compat-sub"><span class="compat-behavior"><code>or(...)</code> composite — at least one sub-filter matches</span></span></span></summary>
311
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code> ("or() matches docs where any sub-filter matches"), oracle: <code>packages/conformance/observations/firestore/firestore-or-composite.json</code> (4 seeded docs; <code>or(where('x','==',1), where('y','==',2))</code> returned the exact union <code>{match-both, match-x, match-y}</code> — no implicit index required against cloud Firestore)</div></div>
311
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L28" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a> ("or() matches docs where any sub-filter matches"), oracle: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/firestore/firestore-or-composite.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/firestore/firestore-or-composite.json</code></a> (4 seeded docs; <code>or(where('x','==',1), where('y','==',2))</code> returned the exact union <code>{match-both, match-x, match-y}</code> — no implicit index required against cloud Firestore)</div></div>
312
312
  </details>
313
313
  <details class="compat-row" data-status="ok">
314
314
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Query construction</code><span class="compat-sub"><span class="compat-behavior"><code>and(...)</code> composite — every sub-filter matches</span></span></span></summary>
315
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code> ("and() requires every sub-filter"), oracle: <code>packages/conformance/observations/firestore/firestore-and-composite.json</code> (4 seeded docs; <code>and(where('x','==',1), where('y','==',2))</code> returned only the intersection <code>{match-both}</code>)</div></div>
315
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a> ("and() requires every sub-filter"), oracle: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/firestore/firestore-and-composite.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/firestore/firestore-and-composite.json</code></a> (4 seeded docs; <code>and(where('x','==',1), where('y','==',2))</code> returned only the intersection <code>{match-both}</code>)</div></div>
316
316
  </details>
317
317
  <details class="compat-row" data-status="ok">
318
318
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Query construction</code><span class="compat-sub"><span class="compat-behavior">Nested <code>or</code> / <code>and</code> — full composite tree</span></span></span></summary>
319
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code> ("nested or/and — the canonical composite pattern"), oracle: <code>packages/conformance/observations/firestore/firestore-nested-or-and-composite.json</code> (6 seeded docs; <code>or(and(where('x','==',1), where('y','==',2)), where('z','==',3))</code> returned <code>{inner-and-match, outer-z-match, both-branches}</code> — exact boolean union as predicted)</div></div>
319
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a> ("nested or/and — the canonical composite pattern"), oracle: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/firestore/firestore-nested-or-and-composite.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/firestore/firestore-nested-or-and-composite.json</code></a> (6 seeded docs; <code>or(and(where('x','==',1), where('y','==',2)), where('z','==',3))</code> returned <code>{inner-and-match, outer-z-match, both-branches}</code> — exact boolean union as predicted)</div></div>
320
320
  </details>
321
321
  <details class="compat-row" data-status="ok">
322
322
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Query construction</code><span class="compat-sub"><span class="compat-behavior"><code>orderBy(field, 'asc'|'desc')</code> — direction parameter</span></span></span></summary>
323
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code></div></div>
323
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L31" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a></div></div>
324
324
  </details>
325
325
  <details class="compat-row" data-status="ok">
326
326
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Query construction</code><span class="compat-sub"><span class="compat-behavior"><strong>Canonical type-order comparison (FS-B3)</strong> — orderBy + range filters compare by Firestore's canonical type order (<code>null &lt; bool &lt; number &lt; timestamp &lt; string &lt; bytes &lt; ref &lt; geopoint &lt; array &lt; map</code>), then within-type; numbers sort numerically (not lexicographically), NaN sorts as the smallest number, and range filters (<code>&lt;</code>/<code>&lt;=</code>/<code>&gt;</code>/<code>&gt;=</code>) only match same-type values. Pre-FS-B3 the comparator fell back to <code>String(a).localeCompare(String(b))</code>.</span></span></span></summary>
327
- <div class="compat-evidence"><div class="compat-probe"><code>unit:firestore/sandbox/query-value-order.test.ts</code> (cross-type ranking, numeric sort, NaN, timestamps, arrays; verified failing pre-fix)</div></div>
327
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox/query-value-order.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:firestore/sandbox/query-value-order.test.ts</code></a> (cross-type ranking, numeric sort, NaN, timestamps, arrays; verified failing pre-fix)</div></div>
328
328
  </details>
329
329
  <details class="compat-row" data-status="ok">
330
330
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Query construction</code><span class="compat-sub"><span class="compat-behavior"><strong>orderBy excludes missing-field docs (FS-B3)</strong> — a doc lacking an orderBy field is omitted from the result (matches prod); pre-fix it was sorted in via <code>compareValues(undefined, …)</code>.</span></span></span></summary>
331
- <div class="compat-evidence"><div class="compat-probe"><code>unit:firestore/sandbox/query-value-order.test.ts</code> ("excludes the missing-field doc")</div></div>
331
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox/query-value-order.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:firestore/sandbox/query-value-order.test.ts</code></a> ("excludes the missing-field doc")</div></div>
332
332
  </details>
333
333
  <details class="compat-row" data-status="ok">
334
334
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Query construction</code><span class="compat-sub"><span class="compat-behavior"><strong>Implicit orderBy + <code>__name__</code> tiebreak (FS-B8)</strong> — the query's sort is normalized to: explicit orderBy clauses, then an implicit order on each inequality-filtered field, then a final document-key (<code>__name__</code>) clause. Equal-valued docs sort deterministically by key; a <code>where('x','&gt;',v)</code> with no explicit orderBy returns docs ordered by <code>x</code>. Mirrors <code>clones/.../core/query.ts:queryNormalizedOrderBy</code>. Pre-FS-B8 equal-valued docs were nondeterministic and inequality results came back in insertion order.</span></span></span></summary>
335
- <div class="compat-evidence"><div class="compat-probe"><code>unit:admin-compat/implicit-order-name.test.ts</code> (key tiebreak, snapshot-cursor disambiguation, implicit inequality order; verified failing pre-fix)</div></div>
335
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/sandbox/firestore/admin-compat/implicit-order-name.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:admin-compat/implicit-order-name.test.ts</code></a> (key tiebreak, snapshot-cursor disambiguation, implicit inequality order; verified failing pre-fix)</div></div>
336
336
  </details>
337
337
  <details class="compat-row" data-status="ok">
338
338
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Query construction</code><span class="compat-sub"><span class="compat-behavior"><code>limit(n)</code> — caps result count</span></span></span></summary>
339
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code></div></div>
339
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L32" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a></div></div>
340
340
  </details>
341
341
  <details class="compat-row" data-status="ok">
342
342
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Query construction</code><span class="compat-sub"><span class="compat-behavior"><code>limitToLast(n)</code> returns the trailing n documents in an ordered result; without <code>orderBy</code>, it throws a <code>FirestoreError</code> with production's <code>unimplemented</code> code</span></span></span></summary>
343
- <div class="compat-evidence"><div class="compat-probe">Oracle-locked by <code>packages/conformance/observations/firestore/firestore-limittolast-preconditions.json</code>: production's no-orderBy precondition throws code <code>unimplemented</code>, while ordered trailing-window semantics return <code>["b"]</code>. <code>oracle:firestore#61</code> replays both claims. Cursor composition + descending: <code>unit:upstream-query-probes.test.ts</code>; cursor/empty-snapshot preconditions: <code>unit:sandbox-target.test.ts</code> + <code>unit:admin-compat/cursors.test.ts</code>.</div></div>
343
+ <div class="compat-evidence"><div class="compat-probe">Oracle-locked by <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/firestore/firestore-limittolast-preconditions.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/firestore/firestore-limittolast-preconditions.json</code></a>: production's no-orderBy precondition throws code <code>unimplemented</code>, while ordered trailing-window semantics return <code>["b"]</code>. <code>oracle:firestore#61</code> replays both claims. Cursor composition + descending: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/upstream-query-probes.test.ts#L10" target="_blank" rel="noopener noreferrer"><code>unit:upstream-query-probes.test.ts</code></a>; cursor/empty-snapshot preconditions: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L560" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a> + <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/sandbox/firestore/admin-compat/cursors.test.ts#L198" target="_blank" rel="noopener noreferrer"><code>unit:admin-compat/cursors.test.ts</code></a>. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/oracle-conformance.test.ts#L331" target="_blank" rel="noopener noreferrer"><code>oracle-conformance.test.ts</code></a>)</div></div>
344
344
  </details>
345
345
  <details class="compat-row" data-status="ok">
346
346
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Query construction</code><span class="compat-sub"><span class="compat-behavior">Composite filters AND with other constraints — <code>query(coll, or(...), orderBy(...), limit(...))</code></span></span></span></summary>
347
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code></div></div>
347
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L29" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a></div></div>
348
348
  </details>
349
349
  <details class="compat-row" data-status="ok">
350
350
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Query construction</code><span class="compat-sub"><span class="compat-behavior">Passing <code>orderBy</code> / <code>limit</code> into <code>or()</code> / <code>and()</code> → <code>TypeError</code></span></span></span></summary>
351
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code></div></div>
351
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a></div></div>
352
352
  </details>
353
353
  <details class="compat-row" data-status="ok">
354
354
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Query construction</code><span class="compat-sub"><span class="compat-behavior">Zero-arg <code>or()</code> / <code>and()</code> → <code>TypeError</code></span></span></span></summary>
355
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code></div></div>
355
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a></div></div>
356
356
  </details>
357
357
  <details class="compat-row" data-status="ok">
358
358
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Query construction</code><span class="compat-sub"><span class="compat-behavior">Chained queries re-tag for further constraints (<code>query(query(coll, where), orderBy)</code>)</span></span></span></summary>
359
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code> ("chained queries are taggable")</div></div>
359
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L29" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a> ("chained queries are taggable")</div></div>
360
360
  </details>
361
361
  <details class="compat-row" data-status="diverged">
362
362
  <summary class="compat-line"><span class="compat-dot" data-status="diverged" role="img" aria-label="Diverged (documented)" title="Diverged (documented)"></span><span class="compat-main"><code class="compat-api">Query construction</code><span class="compat-sub"><span class="compat-behavior">Index validation against <code>firestore.indexes.json</code> — sandbox uses <code>LocalEnvironment</code>'s lint pass; prod has its own server-side validation</span></span></span></summary>
@@ -369,31 +369,31 @@
369
369
  <div class="compat-list">
370
370
  <details class="compat-row" data-status="ok">
371
371
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Cursor pagination</code><span class="compat-sub"><span class="compat-behavior"><code>startAt(...values)</code> — inclusive value cursor (one positional per <code>orderBy</code> clause)</span></span></span></summary>
372
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code>, oracle: <code>packages/conformance/observations/firestore/firestore-cursor-startat-inclusive.json</code> (5 seeded docs at pos=[1..5]; <code>query(c, orderBy('pos'), startAt(3))</code> returned exactly <code>[pos-3, pos-4, pos-5]</code> — the cursor doc IS included)</div></div>
372
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L577" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a>, oracle: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/firestore/firestore-cursor-startat-inclusive.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/firestore/firestore-cursor-startat-inclusive.json</code></a> (5 seeded docs at pos=[1..5]; <code>query(c, orderBy('pos'), startAt(3))</code> returned exactly <code>[pos-3, pos-4, pos-5]</code> — the cursor doc IS included)</div></div>
373
373
  </details>
374
374
  <details class="compat-row" data-status="ok">
375
375
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Cursor pagination</code><span class="compat-sub"><span class="compat-behavior"><code>startAfter(...values)</code> — exclusive value cursor</span></span></span></summary>
376
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code>, oracle: <code>packages/conformance/observations/firestore/firestore-cursor-startafter-exclusive.json</code> (5 seeded docs at pos=[1..5]; <code>query(c, orderBy('pos'), startAfter(3))</code> returned exactly <code>[pos-4, pos-5]</code> — the cursor doc is EXCLUDED)</div></div>
376
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L586" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a>, oracle: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/firestore/firestore-cursor-startafter-exclusive.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/firestore/firestore-cursor-startafter-exclusive.json</code></a> (5 seeded docs at pos=[1..5]; <code>query(c, orderBy('pos'), startAfter(3))</code> returned exactly <code>[pos-4, pos-5]</code> — the cursor doc is EXCLUDED)</div></div>
377
377
  </details>
378
378
  <details class="compat-row" data-status="ok">
379
379
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Cursor pagination</code><span class="compat-sub"><span class="compat-behavior"><code>endAt(...values)</code> — inclusive end cursor</span></span></span></summary>
380
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code>, oracle: <code>packages/conformance/observations/firestore/firestore-cursor-endat-inclusive.json</code> (5 seeded docs at pos=[1..5]; <code>query(c, orderBy('pos'), endAt(3))</code> returned exactly <code>[pos-1, pos-2, pos-3]</code> — the cursor doc IS included)</div></div>
380
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L595" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a>, oracle: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/firestore/firestore-cursor-endat-inclusive.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/firestore/firestore-cursor-endat-inclusive.json</code></a> (5 seeded docs at pos=[1..5]; <code>query(c, orderBy('pos'), endAt(3))</code> returned exactly <code>[pos-1, pos-2, pos-3]</code> — the cursor doc IS included)</div></div>
381
381
  </details>
382
382
  <details class="compat-row" data-status="ok">
383
383
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Cursor pagination</code><span class="compat-sub"><span class="compat-behavior"><code>endBefore(...values)</code> — exclusive end cursor</span></span></span></summary>
384
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code>, oracle: <code>packages/conformance/observations/firestore/firestore-cursor-endbefore-exclusive.json</code> (5 seeded docs at pos=[1..5]; <code>query(c, orderBy('pos'), endBefore(3))</code> returned exactly <code>[pos-1, pos-2]</code> — the cursor doc is EXCLUDED)</div></div>
384
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L604" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a>, oracle: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/firestore/firestore-cursor-endbefore-exclusive.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/firestore/firestore-cursor-endbefore-exclusive.json</code></a> (5 seeded docs at pos=[1..5]; <code>query(c, orderBy('pos'), endBefore(3))</code> returned exactly <code>[pos-1, pos-2]</code> — the cursor doc is EXCLUDED)</div></div>
385
385
  </details>
386
386
  <details class="compat-row" data-status="ok">
387
387
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Cursor pagination</code><span class="compat-sub"><span class="compat-behavior"><code>startAt(snapshot)</code> overload — extracts orderBy field values from the snapshot, positioning against the NORMALIZED orderBy (implicit <code>__name__</code>), so it disambiguates equal-valued docs and is <strong>legal without an explicit orderBy</strong> (FS-B8). A VALUE cursor with more values than explicit orderBy clauses throws <code>invalid-argument</code> ("Too many arguments").</span></span></span></summary>
388
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code>, <code>unit:admin-compat/implicit-order-name.test.ts</code> (snapshot cursor w/o orderBy), <code>unit:admin-compat/cursors.test.ts</code> (value-cursor too-many-args throws with <code>.code</code>)</div></div>
388
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L577" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/sandbox/firestore/admin-compat/implicit-order-name.test.ts#L15" target="_blank" rel="noopener noreferrer"><code>unit:admin-compat/implicit-order-name.test.ts</code></a> (snapshot cursor w/o orderBy), <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/sandbox/firestore/admin-compat/cursors.test.ts#L6" target="_blank" rel="noopener noreferrer"><code>unit:admin-compat/cursors.test.ts</code></a> (value-cursor too-many-args throws with <code>.code</code>) (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/firestore/firestore-startat-snapshot-implicit-name.json" target="_blank" rel="noopener noreferrer"><code>firestore-startat-snapshot-implicit-name</code></a>)</div></div>
389
389
  </details>
390
390
  <details class="compat-row" data-status="ok">
391
391
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Cursor pagination</code><span class="compat-sub"><span class="compat-behavior"><code>endAt(snapshot)</code> overload</span></span></span></summary>
392
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code> ("endAt(snapshot) trims to-and-including the anchor")</div></div>
392
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L595" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a> ("endAt(snapshot) trims to-and-including the anchor")</div></div>
393
393
  </details>
394
394
  <details class="compat-row" data-status="ok">
395
395
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Cursor pagination</code><span class="compat-sub"><span class="compat-behavior"><code>startAfter + limit</code> — canonical pagination pattern</span></span></span></summary>
396
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code></div></div>
396
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L586" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a></div></div>
397
397
  </details>
398
398
  </div>
399
399
 
@@ -402,27 +402,27 @@
402
402
  <div class="compat-list">
403
403
  <details class="compat-row" data-status="ok">
404
404
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Aggregates</code><span class="compat-sub"><span class="compat-behavior"><code>getCountFromServer(query)</code> returns <code>{ data: () =&gt; ({ count: N }) }</code></span></span></span></summary>
405
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code>; collectionGroup: <code>unit:upstream-write-aggregate-probes.test.ts</code></div></div>
405
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L71" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a>; collectionGroup: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/upstream-write-aggregate-probes.test.ts#L21" target="_blank" rel="noopener noreferrer"><code>unit:upstream-write-aggregate-probes.test.ts</code></a></div></div>
406
406
  </details>
407
407
  <details class="compat-row" data-status="ok">
408
408
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Aggregates</code><span class="compat-sub"><span class="compat-behavior"><code>getCountFromServer</code> honors <code>where</code> filters</span></span></span></summary>
409
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code></div></div>
409
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L791" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a></div></div>
410
410
  </details>
411
411
  <details class="compat-row" data-status="ok">
412
412
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Aggregates</code><span class="compat-sub"><span class="compat-behavior"><code>getAggregateFromServer(query, spec)</code> returns <code>{ data: () =&gt; Record&lt;alias, number|null&gt; }</code></span></span></span></summary>
413
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code>; collectionGroup + nested paths: <code>unit:upstream-write-aggregate-probes.test.ts</code></div></div>
413
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L807" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a>; collectionGroup + nested paths: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/upstream-write-aggregate-probes.test.ts#L22" target="_blank" rel="noopener noreferrer"><code>unit:upstream-write-aggregate-probes.test.ts</code></a></div></div>
414
414
  </details>
415
415
  <details class="compat-row" data-status="ok">
416
416
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Aggregates</code><span class="compat-sub"><span class="compat-behavior"><code>count()</code> / <code>sum(field)</code> / <code>average(field)</code> compose under one spec — <code>field</code> may be a dotted nested path</span></span></span></summary>
417
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code>; nested <code>sum('metadata.pages')</code>: <code>unit:upstream-write-aggregate-probes.test.ts</code></div></div>
417
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L7" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a>; nested <code>sum('metadata.pages')</code>: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/upstream-write-aggregate-probes.test.ts#L23" target="_blank" rel="noopener noreferrer"><code>unit:upstream-write-aggregate-probes.test.ts</code></a></div></div>
418
418
  </details>
419
419
  <details class="compat-row" data-status="ok">
420
420
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Aggregates</code><span class="compat-sub"><span class="compat-behavior"><code>average</code> returns <code>null</code> on empty input (matches JS SDK)</span></span></span></summary>
421
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code></div></div>
421
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L807" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a></div></div>
422
422
  </details>
423
423
  <details class="compat-row" data-status="diverged">
424
424
  <summary class="compat-line"><span class="compat-dot" data-status="diverged" role="img" aria-label="Diverged (documented)" title="Diverged (documented)"></span><span class="compat-main"><code class="compat-api">Aggregates</code><span class="compat-sub"><span class="compat-behavior">Aggregates count documents server-side without paying read cost per doc in prod; sandbox computes locally (no cost model)</span></span></span></summary>
425
- <div class="compat-evidence"><div class="compat-probe">divergence: cost behavior differs, observable shape identical. Oracle-locked: <code>packages/conformance/observations/firestore/firestore-count-aggregate-shape.json</code> — <code>getCountFromServer().data()</code> returns <code>{ count: &lt;number&gt; }</code> (single key, no other fields). Empty query returns <code>count: 0</code> (not <code>null</code>/<code>undefined</code>); seeded 3 docs returns <code>count: 3</code>; filtered query honors the <code>where</code> constraint (<code>count: 2</code>).</div></div>
425
+ <div class="compat-evidence"><div class="compat-probe">divergence: cost behavior differs, observable shape identical. Oracle-locked: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/firestore/firestore-count-aggregate-shape.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/firestore/firestore-count-aggregate-shape.json</code></a> — <code>getCountFromServer().data()</code> returns <code>{ count: &lt;number&gt; }</code> (single key, no other fields). Empty query returns <code>count: 0</code> (not <code>null</code>/<code>undefined</code>); seeded 3 docs returns <code>count: 3</code>; filtered query honors the <code>where</code> constraint (<code>count: 2</code>).</div></div>
426
426
  </details>
427
427
  </div>
428
428
 
@@ -431,51 +431,51 @@
431
431
  <div class="compat-list">
432
432
  <details class="compat-row" data-status="ok">
433
433
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">onSnapshot(refOrQuery, …)</code><span class="compat-sub"><span class="compat-behavior"><code>onSnapshot(docRef, cb)</code> fires the initial snapshot <strong>asynchronously</strong> — never synchronously during the registering call. Prod empirically lands after a <code>setTimeout(0)</code> macrotask (the fire travels the network listener channel); the sandbox defers through its delivery scheduler (microtask). The matrix contract is "asynchronous, never during register", not "exactly the next microtask"</span></span></span></summary>
434
- <div class="compat-evidence"><div class="compat-probe">Aligned via the listener delivery scheduler (<code>src/sandbox/firestore/local-environment.ts</code>): the initial fire is enqueued and delivered on a microtask, never during register — closing the divergence this row previously documented (the sandbox used to fire synchronously during registration; the sync-body tests were migrated to the flush/await idiom). Machine-checked against <code>packages/conformance/observations/firestore/firestore-row-80-onsnapshot-fires-initial.json</code> (<code>firstFireSyncDuringRegister: false</code>, fire count + contents) in <code>oracle-conformance.test.ts</code>; also <code>unit:sandbox-target.test.ts</code>, <code>playground:firestore-onsnapshot</code> (bundled) + <code>playground:firestore-row-80-onsnapshot-fires-initial</code> (one-claim).</div></div>
434
+ <div class="compat-evidence"><div class="compat-probe">Aligned via the listener delivery scheduler (<code>src/sandbox/firestore/local-environment.ts</code>): the initial fire is enqueued and delivered on a microtask, never during register — closing the divergence this row previously documented (the sandbox used to fire synchronously during registration; the sync-body tests were migrated to the flush/await idiom). Machine-checked against <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/firestore/firestore-row-80-onsnapshot-fires-initial.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/firestore/firestore-row-80-onsnapshot-fires-initial.json</code></a> (<code>firstFireSyncDuringRegister: false</code>, fire count + contents) in <code>oracle-conformance.test.ts</code>; also <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L330" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a>, <code>playground:firestore-onsnapshot</code> (bundled) + <code>playground:firestore-row-80-onsnapshot-fires-initial</code> (one-claim).</div></div>
435
435
  </details>
436
436
  <details class="compat-row" data-status="ok">
437
437
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">onSnapshot(refOrQuery, …)</code><span class="compat-sub"><span class="compat-behavior"><code>onSnapshot(query, cb)</code> fires on collection writes; <code>QuerySnapshot.docChanges()</code> reports <code>added</code> / <code>modified</code> / <code>removed</code> with <code>oldIndex</code> / <code>newIndex</code></span></span></span></summary>
438
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code>, oracle: <code>packages/conformance/observations/firestore/firestore-row-81-onsnapshot-query-fires-on-write.json</code> — listener on <code>query(coll)</code> saw 1 initial fire (empty, <code>size:0</code>), then one fire per write: <code>addDoc</code> → <code>size:1</code>, <code>setDoc(coll, 'known-id')</code> → <code>size:2</code>, <code>deleteDoc(addedRef)</code> → <code>size:1</code>. Total 4 fires, each reflecting the current collection state. Every collection-level write produces a distinct fire. (Note: this oracle used a <em>filterless</em> <code>query(coll)</code>, which masked FS-B2 — see row 81a.) Modular <code>docChanges</code> indexes: <code>unit:upstream-transform-txn-listener-probes.test.ts</code></div></div>
438
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L34" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a>, oracle: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/firestore/firestore-row-81-onsnapshot-query-fires-on-write.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/firestore/firestore-row-81-onsnapshot-query-fires-on-write.json</code></a> — listener on <code>query(coll)</code> saw 1 initial fire (empty, <code>size:0</code>), then one fire per write: <code>addDoc</code> → <code>size:1</code>, <code>setDoc(coll, 'known-id')</code> → <code>size:2</code>, <code>deleteDoc(addedRef)</code> → <code>size:1</code>. Total 4 fires, each reflecting the current collection state. Every collection-level write produces a distinct fire. (Note: this oracle used a <em>filterless</em> <code>query(coll)</code>, which masked FS-B2 — see row 81a.) Modular <code>docChanges</code> indexes: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/upstream-transform-txn-listener-probes.test.ts#L12" target="_blank" rel="noopener noreferrer"><code>unit:upstream-transform-txn-listener-probes.test.ts</code></a></div></div>
439
439
  </details>
440
440
  <details class="compat-row" data-status="ok">
441
441
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">onSnapshot(refOrQuery, …)</code><span class="compat-sub"><span class="compat-behavior"><strong>Filtered listeners honor <code>where</code> / <code>orderBy</code> / <code>limit</code> (FS-B2)</strong> — <code>onSnapshot(query(coll, where(…), orderBy(…), limit(…)), cb)</code> delivers the same membership as <code>getDocs(sameQuery)</code>: non-matching docs are excluded on the initial fire and on writes; ordering + limit are applied. Pre-FS-B2 the <code>SnapshotTarget</code> dropped all constraints and delivered the whole collection.</span></span></span></summary>
442
- <div class="compat-evidence"><div class="compat-probe"><code>unit:onsnapshot-query-constraints.test.ts</code> (filtered/ordered/limited listeners; verified failing pre-fix)</div></div>
442
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/onsnapshot-query-constraints.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:onsnapshot-query-constraints.test.ts</code></a> (filtered/ordered/limited listeners; verified failing pre-fix)</div></div>
443
443
  </details>
444
444
  <details class="compat-row" data-status="ok">
445
445
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">onSnapshot(refOrQuery, …)</code><span class="compat-sub"><span class="compat-behavior"><strong>Listener <code>.data()</code> matches <code>getDoc</code> shape (FS-B10)</strong> — the <code>onSnapshot</code> doc + query snapshot path runs the same read-path translation as <code>getDoc</code>/<code>getDocs</code>, so <code>snap.data().createdAt</code> is a compat <code>Timestamp</code> (<code>{seconds, nanoseconds}</code>), not the rules-internal wrapper (<code>{seconds, nanos}</code> + <code>typeName</code>, no <code>nanoseconds</code>). Pre-FS-B10 a listener leaked the internal shape while the single-doc read returned the compat shape.</span></span></span></summary>
446
- <div class="compat-evidence"><div class="compat-probe"><code>unit:simulator/listener-read-translation.test.ts</code> (doc + query listener Timestamp shape; verified failing pre-fix)</div></div>
446
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/sandbox/firestore/simulator/listener-read-translation.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:simulator/listener-read-translation.test.ts</code></a> (doc + query listener Timestamp shape; verified failing pre-fix)</div></div>
447
447
  </details>
448
448
  <details class="compat-row" data-status="ok">
449
449
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">onSnapshot(refOrQuery, …)</code><span class="compat-sub"><span class="compat-behavior">Initial fire for a missing doc has <code>exists() === false</code> and <code>data() === undefined</code></span></span></span></summary>
450
- <div class="compat-evidence"><div class="compat-probe"><code>playground:firestore-onsnapshot</code> (bundled) + <code>playground:firestore-row-82-onsnapshot-missing-initial</code> (one-claim), oracle: <code>packages/conformance/observations/firestore/firestore-row-82-onsnapshot-missing-initial.json</code> — single initial fire with <code>snap.exists() === false</code>, <code>snap.data() === undefined</code>, <code>hasPendingWrites: false</code>, <code>fromCache: false</code>. The missing-doc fire is server-confirmed, not a cache speculation.</div></div>
450
+ <div class="compat-evidence"><div class="compat-probe"><code>playground:firestore-onsnapshot</code> (bundled) + <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/firestore/firestore-row-82-onsnapshot-missing-initial.json" target="_blank" rel="noopener noreferrer"><code>playground:firestore-row-82-onsnapshot-missing-initial</code></a> (one-claim), oracle: <code>packages/conformance/observations/firestore/firestore-row-82-onsnapshot-missing-initial.json</code> — single initial fire with <code>snap.exists() === false</code>, <code>snap.data() === undefined</code>, <code>hasPendingWrites: false</code>, <code>fromCache: false</code>. The missing-doc fire is server-confirmed, not a cache speculation.</div></div>
451
451
  </details>
452
452
  <details class="compat-row" data-status="ok">
453
453
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">onSnapshot(refOrQuery, …)</code><span class="compat-sub"><span class="compat-behavior">Returned <code>Unsubscribe</code> stops further fires</span></span></span></summary>
454
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code>, oracle: <code>packages/conformance/observations/firestore/firestore-row-83-unsubscribe-stops-fires.json</code> — pre-unsubscribe write fired the listener (initial fire + write fire = 2 fires); after <code>unsub()</code>, a subsequent <code>setDoc</code> produced 0 additional fires (<code>postUnsubFireCount: 0</code>). Unsubscribe is durable; no fires arrive on the released callback after a 1.5s settle window.</div></div>
454
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L34" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a>, oracle: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/firestore/firestore-row-83-unsubscribe-stops-fires.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/firestore/firestore-row-83-unsubscribe-stops-fires.json</code></a> — pre-unsubscribe write fired the listener (initial fire + write fire = 2 fires); after <code>unsub()</code>, a subsequent <code>setDoc</code> produced 0 additional fires (<code>postUnsubFireCount: 0</code>). Unsubscribe is durable; no fires arrive on the released callback after a 1.5s settle window.</div></div>
455
455
  </details>
456
456
  <details class="compat-row" data-status="ok">
457
457
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">onSnapshot(refOrQuery, …)</code><span class="compat-sub"><span class="compat-behavior">Observer object form <code>{next, error, complete}</code> works alongside the function form. <strong>Partial observers are accepted — <code>{ error: fn }</code> with no <code>next</code> registers and routes denials to <code>error</code> (FS-B14, <code>isPartialObserver</code> semantics from upstream <code>api/observer.ts</code>); pre-fix it was misrouted as <code>SnapshotListenOptions</code> and threw "missing next handler".</strong></span></span></span></summary>
458
- <div class="compat-evidence"><div class="compat-probe">oracle: <code>packages/conformance/observations/firestore/firestore-row-84-observer-object-form.json</code> — registered two listeners on the same doc: one as a bare function <code>(snap) =&gt; …</code>, one as <code>{next, error, complete}</code>. Both fired once on initial (<code>{v:0}</code>) and again after a write (<code>{v:1}</code>), capturing identical data. <code>error</code> never fired (no rule denial), <code>complete</code> never fired on <code>unsub()</code> (Firebase treats unsubscribe as a teardown, not a "complete" signal — the observer's <code>complete</code> callback is reserved for terminal stream end, which <code>onSnapshot</code> does not produce). The two registration shapes are interchangeable for fire dispatch. <code>unit:onsnapshot-observer-discriminator.test.ts</code> (error-only observer; verified failing pre-fix)</div></div>
458
+ <div class="compat-evidence"><div class="compat-probe">oracle: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/firestore/firestore-row-84-observer-object-form.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/firestore/firestore-row-84-observer-object-form.json</code></a> — registered two listeners on the same doc: one as a bare function <code>(snap) =&gt; …</code>, one as <code>{next, error, complete}</code>. Both fired once on initial (<code>{v:0}</code>) and again after a write (<code>{v:1}</code>), capturing identical data. <code>error</code> never fired (no rule denial), <code>complete</code> never fired on <code>unsub()</code> (Firebase treats unsubscribe as a teardown, not a "complete" signal — the observer's <code>complete</code> callback is reserved for terminal stream end, which <code>onSnapshot</code> does not produce). The two registration shapes are interchangeable for fire dispatch. <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/onsnapshot-observer-discriminator.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:onsnapshot-observer-discriminator.test.ts</code></a> (error-only observer; verified failing pre-fix)</div></div>
459
459
  </details>
460
460
  <details class="compat-row" data-status="ok">
461
461
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">onSnapshot(refOrQuery, …)</code><span class="compat-sub"><span class="compat-behavior"><code>SnapshotListenOptions.includeMetadataChanges</code> — one write yields the pending-write local echo (<code>hasPendingWrites: true</code>) then, for metadata listeners, the settled ack fire: default listener 2 fires, metadata listener 3</span></span></span></summary>
462
- <div class="compat-evidence"><div class="compat-probe">Aligned via the listener delivery scheduler (<code>src/sandbox/firestore/local-environment.ts</code> + <code>snapshot-listeners.ts</code>): the write echo carries <code>hasPendingWrites: true</code> and <code>includeMetadataChanges</code> listeners receive the settled metadata-only ack, reproducing prod's recorded 2/3-fire sequences exactly. Machine-checked against <code>packages/conformance/observations/firestore/firestore-include-metadata-changes.json</code> in <code>oracle-conformance.test.ts</code> (fire counts and per-fire <code>hasPendingWrites</code> sequence asserted from the capture)</div></div>
462
+ <div class="compat-evidence"><div class="compat-probe">Aligned via the listener delivery scheduler (<code>src/sandbox/firestore/local-environment.ts</code> + <code>snapshot-listeners.ts</code>): the write echo carries <code>hasPendingWrites: true</code> and <code>includeMetadataChanges</code> listeners receive the settled metadata-only ack, reproducing prod's recorded 2/3-fire sequences exactly. Machine-checked against <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/firestore/firestore-include-metadata-changes.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/firestore/firestore-include-metadata-changes.json</code></a> in <code>oracle-conformance.test.ts</code> (fire counts and per-fire <code>hasPendingWrites</code> sequence asserted from the capture)</div></div>
463
463
  </details>
464
464
  <details class="compat-row" data-status="ok">
465
465
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">onSnapshot(refOrQuery, …)</code><span class="compat-sub"><span class="compat-behavior">Snapshot's <code>.ref</code> / <code>.docs[i].ref</code> are tagged so consumer code can pass them to follow-up ops</span></span></span></summary>
466
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code></div></div>
466
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L34" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a></div></div>
467
467
  </details>
468
468
  <details class="compat-row" data-status="ok">
469
469
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">onSnapshot(refOrQuery, …)</code><span class="compat-sub"><span class="compat-behavior">Sandbox-live: listener registered as alice keeps emitting alice's view after <code>setUser → bob</code> (identity frozen at subscribe)</span></span></span></summary>
470
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-live-identity.test.ts</code> ("listener registered as alice keeps emitting alice's view")</div></div>
470
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-live-identity.test.ts#L35" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-live-identity.test.ts</code></a> ("listener registered as alice keeps emitting alice's view")</div></div>
471
471
  </details>
472
472
  <details class="compat-row" data-status="ok">
473
473
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">onSnapshot(refOrQuery, …)</code><span class="compat-sub"><span class="compat-behavior">Sandbox-live: listener registered as anonymous keeps firing after sign-in (anonymous → signed-in identity persists per listener)</span></span></span></summary>
474
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-live-identity.test.ts</code> ("listener registered as anonymous on /public keeps firing after sign-in")</div></div>
474
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-live-identity.test.ts#L35" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-live-identity.test.ts</code></a> ("listener registered as anonymous on /public keeps firing after sign-in")</div></div>
475
475
  </details>
476
476
  <details class="compat-row" data-status="ok">
477
477
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">onSnapshot(refOrQuery, …)</code><span class="compat-sub"><span class="compat-behavior">Snapshot's ref is usable in follow-up ops under the new user (the ref is live, the listener identity is frozen — distinct)</span></span></span></summary>
478
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-live-identity.test.ts</code> ("snapshot ref is usable in subsequent ops under the new user"), oracle: <code>packages/conformance/observations/firestore/firestore-row-89-snapshot-ref-usable.json</code> — captured <code>snap.ref</code> from a docRef listener's first fire and <code>snap.docs[0].ref</code> from a query listener's first fire; both refs round-trip via <code>getDoc</code> (returning the same data) and <code>setDoc</code> (writes succeed and a follow-up <code>getDoc</code> confirms the new payload). <code>snap.ref.path</code> equals the original <code>doc(coll, id).path</code>. Both snap-ref shapes are first-class refs in prod, matching sandbox's tagged-ref guarantee.</div></div>
478
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-live-identity.test.ts#L35" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-live-identity.test.ts</code></a> ("snapshot ref is usable in subsequent ops under the new user"), oracle: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/firestore/firestore-row-89-snapshot-ref-usable.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/firestore/firestore-row-89-snapshot-ref-usable.json</code></a> — captured <code>snap.ref</code> from a docRef listener's first fire and <code>snap.docs[0].ref</code> from a query listener's first fire; both refs round-trip via <code>getDoc</code> (returning the same data) and <code>setDoc</code> (writes succeed and a follow-up <code>getDoc</code> confirms the new payload). <code>snap.ref.path</code> equals the original <code>doc(coll, id).path</code>. Both snap-ref shapes are first-class refs in prod, matching sandbox's tagged-ref guarantee.</div></div>
479
479
  </details>
480
480
  <details class="compat-row" data-status="ok">
481
481
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">onSnapshot(refOrQuery, …)</code><span class="compat-sub"><span class="compat-behavior">Preview tree mounts the user's component exactly once per session load — no observer subscriptions leak across parallel <code>AppPreview</code> instances. Root cause: <code>PlaygroundPage</code> rendered both <code>WorkspacePanel</code>'s and the mobile <code>AppPanel</code>'s <code>AppPreview</code> unconditionally (the latter <code>md:hidden</code> on desktop but still mounted), producing two live preview trees subscribing in parallel. Fixed by gating <code>AppPanel</code> on <code>useIsMobile() &amp;&amp; mobileTab === 'app'</code>.</span></span></span></summary>
@@ -488,19 +488,19 @@
488
488
  <div class="compat-list">
489
489
  <details class="compat-row" data-status="ok">
490
490
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">runTransaction(db, fn)</code><span class="compat-sub"><span class="compat-behavior">Atomic read-write — all reads in <code>fn</code> see a consistent snapshot, writes commit together</span></span></span></summary>
491
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code>, <code>playground:firestore-transaction</code>; get-missing/deleted + empty txn + nested update: <code>unit:upstream-transform-txn-listener-probes.test.ts</code></div></div>
491
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L36" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a>, <code>playground:firestore-transaction</code>; get-missing/deleted + empty txn + nested update: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/upstream-transform-txn-listener-probes.test.ts#L27" target="_blank" rel="noopener noreferrer"><code>unit:upstream-transform-txn-listener-probes.test.ts</code></a></div></div>
492
492
  </details>
493
493
  <details class="compat-row" data-status="ok">
494
494
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">runTransaction(db, fn)</code><span class="compat-sub"><span class="compat-behavior">Identity is frozen at <code>runTransaction</code> start — mid-transaction <code>setUser</code> does NOT re-auth in-flight reads</span></span></span></summary>
495
- <div class="compat-evidence"><div class="compat-probe"><code>unit:firestore/sandbox-live-identity.test.ts</code> changes the live session from alice to bob inside the callback; the transaction still reads and commits under alice's captured identity.</div></div>
495
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-live-identity.test.ts#L36" target="_blank" rel="noopener noreferrer"><code>unit:firestore/sandbox-live-identity.test.ts</code></a> changes the live session from alice to bob inside the callback; the transaction still reads and commits under alice's captured identity.</div></div>
496
496
  </details>
497
497
  <details class="compat-row" data-status="ok">
498
498
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">runTransaction(db, fn)</code><span class="compat-sub"><span class="compat-behavior">Retry behavior — a read-document conflict reruns the callback against fresh data; <code>maxAttempts</code> bounds persistent contention</span></span></span></summary>
499
- <div class="compat-evidence"><div class="compat-probe">Oracle: <code>packages/conformance/observations/firestore/firestore-transaction-contention-retries.json</code> — two production Web SDK clients force one retry (<code>[0, 40]</code>, final <code>42</code>) and persistent contention with <code>maxAttempts: 2</code> runs exactly twice then throws <code>failed-precondition</code>. <code>packages/pyric/test/firestore/oracle-conformance.test.ts</code> replays both cases through the sandbox.</div></div>
499
+ <div class="compat-evidence"><div class="compat-probe">Oracle: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/firestore/firestore-transaction-contention-retries.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/firestore/firestore-transaction-contention-retries.json</code></a> — two production Web SDK clients force one retry (<code>[0, 40]</code>, final <code>42</code>) and persistent contention with <code>maxAttempts: 2</code> runs exactly twice then throws <code>failed-precondition</code>. <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/oracle-conformance.test.ts#L603" target="_blank" rel="noopener noreferrer"><code>packages/pyric/test/firestore/oracle-conformance.test.ts</code></a> replays both cases through the sandbox. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L36" target="_blank" rel="noopener noreferrer"><code>sandbox-target.test.ts</code></a>)</div></div>
500
500
  </details>
501
501
  <details class="compat-row" data-status="ok">
502
502
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">runTransaction(db, fn)</code><span class="compat-sub"><span class="compat-behavior">Throws <code>FirebaseError('permission-denied')</code> on rule denial inside the transaction (the inner write's denial — not a generic <code>aborted</code>). Sandbox throws <code>FirestoreCompatError</code> with the same <code>code: 'permission-denied'</code></span></span></span></summary>
503
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code> (writes-reject branch), oracle: <code>packages/conformance/observations/firestore/firestore-transaction-rules-denied-error.json</code> (prod throws <code>FirebaseError</code> with <code>code: 'permission-denied'</code>, NOT <code>aborted</code>; the inner callback ran once and the rules-rejected write surfaces as a regular permission-denied at commit)</div></div>
503
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L36" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a> (writes-reject branch), oracle: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/firestore/firestore-transaction-rules-denied-error.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/firestore/firestore-transaction-rules-denied-error.json</code></a> (prod throws <code>FirebaseError</code> with <code>code: 'permission-denied'</code>, NOT <code>aborted</code>; the inner callback ran once and the rules-rejected write surfaces as a regular permission-denied at commit)</div></div>
504
504
  </details>
505
505
  </div>
506
506
 
@@ -509,19 +509,19 @@
509
509
  <div class="compat-list">
510
510
  <details class="compat-row" data-status="ok">
511
511
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">writeBatch(db)</code><span class="compat-sub"><span class="compat-behavior"><code>batch.set</code> / <code>batch.update</code> / <code>batch.delete</code> queue mutations</span></span></span></summary>
512
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code>, <code>playground:firestore-batch</code></div></div>
512
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L37" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a>, <code>playground:firestore-batch</code></div></div>
513
513
  </details>
514
514
  <details class="compat-row" data-status="ok">
515
515
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">writeBatch(db)</code><span class="compat-sub"><span class="compat-behavior"><code>batch.commit()</code> applies all queued writes atomically — success path commits all queued mutations together; failure path (one write violating rules) rejects the <strong>whole</strong> batch with no partial application</span></span></span></summary>
516
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code>, oracle: <code>packages/conformance/observations/firestore/firestore-row-96-batch-commit-atomic.json</code> — success path: a batch with <code>set</code> (fresh doc), <code>update</code> (existing doc), and <code>delete</code> (existing doc) all land in a single commit (<code>allApplied: true</code>). Failure path: a batch with one write targeting a path <strong>outside</strong> <code>pyric_oracle/*</code> rejects with <code>code: 'permission-denied'</code> and leaves the would-have-set doc absent and the would-have-updated doc at its original value (<code>noPartialApply: true</code>) — atomicity verified end-to-end.</div></div>
516
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L37" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a>, oracle: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/firestore/firestore-row-96-batch-commit-atomic.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/firestore/firestore-row-96-batch-commit-atomic.json</code></a> — success path: a batch with <code>set</code> (fresh doc), <code>update</code> (existing doc), and <code>delete</code> (existing doc) all land in a single commit (<code>allApplied: true</code>). Failure path: a batch with one write targeting a path <strong>outside</strong> <code>pyric_oracle/*</code> rejects with <code>code: 'permission-denied'</code> and leaves the would-have-set doc absent and the would-have-updated doc at its original value (<code>noPartialApply: true</code>) — atomicity verified end-to-end.</div></div>
517
517
  </details>
518
518
  <details class="compat-row" data-status="ok">
519
519
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">writeBatch(db)</code><span class="compat-sub"><span class="compat-behavior">Batch is tagged on construction and remains bound to the sandbox owner that created it</span></span></span></summary>
520
- <div class="compat-evidence"><div class="compat-probe"><code>unit:firestore/sandbox-live-identity.test.ts</code> constructs a batch from sandbox A with a reference from sandbox B and proves the commit remains isolated to A.</div></div>
520
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-live-identity.test.ts#L37" target="_blank" rel="noopener noreferrer"><code>unit:firestore/sandbox-live-identity.test.ts</code></a> constructs a batch from sandbox A with a reference from sandbox B and proves the commit remains isolated to A.</div></div>
521
521
  </details>
522
522
  <details class="compat-row" data-status="ok">
523
523
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">writeBatch(db)</code><span class="compat-sub"><span class="compat-behavior">Batch identity is frozen at construction (per current implementation)</span></span></span></summary>
524
- <div class="compat-evidence"><div class="compat-probe"><code>unit:firestore/sandbox-live-identity.test.ts</code> constructs under alice, switches the live session to bob, and proves commit still succeeds under alice's captured identity.</div></div>
524
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-live-identity.test.ts#L37" target="_blank" rel="noopener noreferrer"><code>unit:firestore/sandbox-live-identity.test.ts</code></a> constructs under alice, switches the live session to bob, and proves commit still succeeds under alice's captured identity.</div></div>
525
525
  </details>
526
526
  </div>
527
527
 
@@ -530,35 +530,35 @@
530
530
  <div class="compat-list">
531
531
  <details class="compat-row" data-status="ok">
532
532
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Sentinels</code><span class="compat-sub"><span class="compat-behavior"><code>serverTimestamp()</code> resolves to a <code>Timestamp</code> after the write commits</span></span></span></summary>
533
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code>, <code>playground:firestore-sentinels</code> (bundled) + <code>playground:firestore-row-99-servertimestamp-resolves</code> (one-claim), oracle: <code>packages/conformance/observations/firestore/firestore-row-99-servertimestamp-resolves-to-timestamp.json</code> — <code>setDoc({at: serverTimestamp()})</code> then <code>getDoc</code> yields <code>at instanceof Timestamp === true</code>, <code>constructor.name === 'Timestamp'</code>, with both <code>.seconds</code> (number) and <code>.nanoseconds</code> (number) present.</div></div>
533
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L40" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a>, <code>playground:firestore-sentinels</code> (bundled) + <code>playground:firestore-row-99-servertimestamp-resolves</code> (one-claim), oracle: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/firestore/firestore-row-99-servertimestamp-resolves-to-timestamp.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/firestore/firestore-row-99-servertimestamp-resolves-to-timestamp.json</code></a> — <code>setDoc({at: serverTimestamp()})</code> then <code>getDoc</code> yields <code>at instanceof Timestamp === true</code>, <code>constructor.name === 'Timestamp'</code>, with both <code>.seconds</code> (number) and <code>.nanoseconds</code> (number) present.</div></div>
534
534
  </details>
535
535
  <details class="compat-row" data-status="ok">
536
536
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Sentinels</code><span class="compat-sub"><span class="compat-behavior"><code>increment(n)</code> atomically bumps a numeric field; <code>null</code>/missing field starts from 0</span></span></span></summary>
537
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code>, <code>playground:firestore-sentinels</code> (bundled) + <code>playground:firestore-row-100-increment-bumps-numeric</code> (one-claim), oracle: <code>packages/conformance/observations/firestore/firestore-row-100-increment-bumps-numeric.json</code> — <code>setDoc</code> with no <code>count</code> field then <code>updateDoc({count: increment(5)})</code> yields <code>count === 5</code> (starts from 0). Follow-up <code>increment(3)</code> → 8, then <code>increment(-2)</code> → 6 (negative deltas apply, increments accumulate). Merge-create + int↔double + batch-across-docs: <code>unit:upstream-transform-txn-listener-probes.test.ts</code></div></div>
537
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L41" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a>, <code>playground:firestore-sentinels</code> (bundled) + <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/firestore/firestore-row-100-increment-bumps-numeric.json" target="_blank" rel="noopener noreferrer"><code>playground:firestore-row-100-increment-bumps-numeric</code></a> (one-claim), oracle: <code>packages/conformance/observations/firestore/firestore-row-100-increment-bumps-numeric.json</code> — <code>setDoc</code> with no <code>count</code> field then <code>updateDoc({count: increment(5)})</code> yields <code>count === 5</code> (starts from 0). Follow-up <code>increment(3)</code> → 8, then <code>increment(-2)</code> → 6 (negative deltas apply, increments accumulate). Merge-create + int↔double + batch-across-docs: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/upstream-transform-txn-listener-probes.test.ts#L5" target="_blank" rel="noopener noreferrer"><code>unit:upstream-transform-txn-listener-probes.test.ts</code></a></div></div>
538
538
  </details>
539
539
  <details class="compat-row" data-status="ok">
540
540
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Sentinels</code><span class="compat-sub"><span class="compat-behavior"><code>arrayUnion(...values)</code> de-dupes against existing members <strong>and</strong> against duplicate args within the same call</span></span></span></summary>
541
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code>, <code>playground:firestore-sentinels</code> (bundled) + <code>playground:firestore-row-101-arrayunion-dedupes</code> (one-claim), oracle: <code>packages/conformance/observations/firestore/firestore-row-101-arrayunion-dedupes.json</code> — <code>setDoc({tags: ['a','b']})</code> then <code>updateDoc({tags: arrayUnion('b','c')})</code> yields <code>['a','b','c']</code> (single <code>b</code>, not double). Follow-up <code>updateDoc({tags: arrayUnion('d','d','a')})</code> yields <code>['a','b','c','d']</code> — both inline duplicate args and existing-member duplicates are de-duped. Merge-path + object members: <code>unit:upstream-transform-txn-listener-probes.test.ts</code></div></div>
541
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L42" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a>, <code>playground:firestore-sentinels</code> (bundled) + <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/firestore/firestore-row-101-arrayunion-dedupes.json" target="_blank" rel="noopener noreferrer"><code>playground:firestore-row-101-arrayunion-dedupes</code></a> (one-claim), oracle: <code>packages/conformance/observations/firestore/firestore-row-101-arrayunion-dedupes.json</code> — <code>setDoc({tags: ['a','b']})</code> then <code>updateDoc({tags: arrayUnion('b','c')})</code> yields <code>['a','b','c']</code> (single <code>b</code>, not double). Follow-up <code>updateDoc({tags: arrayUnion('d','d','a')})</code> yields <code>['a','b','c','d']</code> — both inline duplicate args and existing-member duplicates are de-duped. Merge-path + object members: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/upstream-transform-txn-listener-probes.test.ts#L4" target="_blank" rel="noopener noreferrer"><code>unit:upstream-transform-txn-listener-probes.test.ts</code></a></div></div>
542
542
  </details>
543
543
  <details class="compat-row" data-status="ok">
544
544
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Sentinels</code><span class="compat-sub"><span class="compat-behavior"><code>arrayRemove(...values)</code> strips matching members; values not present in the array are silent no-ops</span></span></span></summary>
545
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code>, <code>playground:firestore-sentinels</code> (bundled) + <code>playground:firestore-row-102-arrayremove-strips</code> (one-claim), oracle: <code>packages/conformance/observations/firestore/firestore-row-102-arrayremove-strips.json</code> — <code>setDoc({tags: ['a','b','c']})</code> then <code>updateDoc({tags: arrayRemove('b','d')})</code> yields <code>['a','c']</code>: <code>'b'</code> removed, <code>'d'</code> (absent) was a silent no-op (no error). Merge-path: <code>unit:upstream-transform-txn-listener-probes.test.ts</code></div></div>
545
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L43" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a>, <code>playground:firestore-sentinels</code> (bundled) + <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/firestore/firestore-row-102-arrayremove-strips.json" target="_blank" rel="noopener noreferrer"><code>playground:firestore-row-102-arrayremove-strips</code></a> (one-claim), oracle: <code>packages/conformance/observations/firestore/firestore-row-102-arrayremove-strips.json</code> — <code>setDoc({tags: ['a','b','c']})</code> then <code>updateDoc({tags: arrayRemove('b','d')})</code> yields <code>['a','c']</code>: <code>'b'</code> removed, <code>'d'</code> (absent) was a silent no-op (no error). Merge-path: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/upstream-transform-txn-listener-probes.test.ts#L30" target="_blank" rel="noopener noreferrer"><code>unit:upstream-transform-txn-listener-probes.test.ts</code></a></div></div>
546
546
  </details>
547
547
  <details class="compat-row" data-status="ok">
548
548
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Sentinels</code><span class="compat-sub"><span class="compat-behavior"><code>deleteField()</code> removes a field on update — the field is fully absent from the returned data, not merely undefined-valued. Legal at the top level or via a <strong>dot-path</strong> (<code>{'a.b': deleteField()}</code> removes the nested leaf — FS-B5). <strong>Nested inside a map literal (<code>{a: {b: deleteField()}}</code>) it throws <code>invalid-argument</code> (FS-B13)</strong> instead of destroying the sibling map.</span></span></span></summary>
549
- <div class="compat-evidence"><div class="compat-probe"><code>playground:firestore-sentinels</code> (bundled) + <code>playground:firestore-row-103-deletefield-removes-field</code> (one-claim), oracle: <code>packages/conformance/observations/firestore/firestore-row-103-deletefield-removes-field.json</code> — <code>setDoc({keep:1, remove:2})</code> then <code>updateDoc({remove: deleteField()})</code> yields a doc whose <code>data()</code> has keys <code>['keep']</code> only, <code>keep === 1</code> preserved; <code>unit:admin-compat/nested-delete-field.test.ts</code> (nested → invalid-argument; dot-path + top-level still valid; verified failing pre-fix)</div></div>
549
+ <div class="compat-evidence"><div class="compat-probe"><code>playground:firestore-sentinels</code> (bundled) + <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/firestore/firestore-row-103-deletefield-removes-field.json" target="_blank" rel="noopener noreferrer"><code>playground:firestore-row-103-deletefield-removes-field</code></a> (one-claim), oracle: <code>packages/conformance/observations/firestore/firestore-row-103-deletefield-removes-field.json</code> — <code>setDoc({keep:1, remove:2})</code> then <code>updateDoc({remove: deleteField()})</code> yields a doc whose <code>data()</code> has keys <code>['keep']</code> only, <code>keep === 1</code> preserved; <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/sandbox/firestore/admin-compat/nested-delete-field.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:admin-compat/nested-delete-field.test.ts</code></a> (nested → invalid-argument; dot-path + top-level still valid; verified failing pre-fix)</div></div>
550
550
  </details>
551
551
  <details class="compat-row" data-status="ok">
552
552
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Sentinels</code><span class="compat-sub"><span class="compat-behavior"><code>Timestamp</code> shape (<code>{seconds, nanoseconds}</code>) is identical between prod and sandbox — round-trips cleanly</span></span></span></summary>
553
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code></div></div>
553
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L40" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a></div></div>
554
554
  </details>
555
555
  <details class="compat-row" data-status="ok">
556
556
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Sentinels</code><span class="compat-sub"><span class="compat-behavior"><strong><code>Timestamp</code> nanos normalization + value API (FS-B12)</strong> — <code>fromMillis</code>/<code>fromDate</code>/<code>now</code> derive <code>nanoseconds</code> as <code>floor((ms - seconds<em>1000) </em> 1e6)</code> so it is always non-negative; <code>fromMillis(-500).toMillis()</code> round-trips to -500 (was -1500). The class ships <code>isEqual</code> / <code>toString</code> / <code>toJSON</code> / <code>valueOf</code>, mirroring <code>clones/.../lite-api/timestamp.ts</code>.</span></span></span></summary>
557
- <div class="compat-evidence"><div class="compat-probe"><code>unit:admin-compat/timestamp-api.test.ts</code> (negative-millis round-trip + value API; pre-fix lacked the methods and mis-normalized)</div></div>
557
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/sandbox/firestore/admin-compat/timestamp-api.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:admin-compat/timestamp-api.test.ts</code></a> (negative-millis round-trip + value API; pre-fix lacked the methods and mis-normalized)</div></div>
558
558
  </details>
559
559
  <details class="compat-row" data-status="ok">
560
560
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Sentinels</code><span class="compat-sub"><span class="compat-behavior"><strong>Unified Timestamp storage (FS-B4)</strong> — a <code>Timestamp</code> written directly via the modular SDK (<code>setDoc({createdAt: Timestamp.now()})</code>) is stored as the same rules-internal <code>Timestamp</code> that <code>serverTimestamp()</code>/<code>Date</code> resolve to. Pre-FS-B4 a user-written <code>Timestamp</code> was the compat class only (not a <code>RulesValue</code>), so <code>request.resource.data.createdAt is timestamp</code> returned <strong>false</strong> for it while a <code>serverTimestamp()</code> write passed the same rule, and the two paths stored two different classes. A write-boundary converter now normalizes both.</span></span></span></summary>
561
- <div class="compat-evidence"><div class="compat-probe"><code>unit:firestore/sandbox-converters/user-timestamp.test.ts</code> (<code>is timestamp</code> passes for a user Timestamp; unified storage class; range-filter regression guard — verified failing pre-fix by removing the converter registration)</div></div>
561
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-converters/user-timestamp.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:firestore/sandbox-converters/user-timestamp.test.ts</code></a> (<code>is timestamp</code> passes for a user Timestamp; unified storage class; range-filter regression guard — verified failing pre-fix by removing the converter registration)</div></div>
562
562
  </details>
563
563
  <details class="compat-row" data-status="ok">
564
564
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Sentinels</code><span class="compat-sub"><span class="compat-behavior"><code>FieldValue</code> re-exported from <code>pyric-admin</code> (alias of <code>ChainFieldValue</code>)</span></span></span></summary>
@@ -566,7 +566,7 @@
566
566
  </details>
567
567
  <details class="compat-row" data-status="ok">
568
568
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Sentinels</code><span class="compat-sub"><span class="compat-behavior"><strong>Sentinel overwrite on type mismatch (FS-B11)</strong> — <code>increment(n)</code> on a non-numeric (or absent) prior OVERWRITES using a base value of 0 (result <code>n</code>); <code>arrayUnion</code>/<code>arrayRemove</code> on a non-array prior coerce the base to <code>[]</code>. Pre-FS-B11 these threw and surfaced as <code>invalid-argument</code> denials. Mirrors <code>clones/.../model/transform_operation.ts</code> (<code>computeTransformOperationBaseValue</code>, <code>coercedFieldValuesArray</code>).</span></span></span></summary>
569
- <div class="compat-evidence"><div class="compat-probe"><code>unit:simulator/converters/fieldvalue.test.ts</code> (FS-B11 overwrite block + flipped unit/integration/batch cases; verified failing pre-fix)</div></div>
569
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/sandbox/firestore/simulator/converters/fieldvalue.test.ts#L5" target="_blank" rel="noopener noreferrer"><code>unit:simulator/converters/fieldvalue.test.ts</code></a> (FS-B11 overwrite block + flipped unit/integration/batch cases; verified failing pre-fix)</div></div>
570
570
  </details>
571
571
  </div>
572
572
 
@@ -575,27 +575,27 @@
575
575
  <div class="compat-list">
576
576
  <details class="compat-row" data-status="ok">
577
577
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Scalar types</code><span class="compat-sub"><span class="compat-behavior">The sandbox mirror owns compatible scalar constructors — <code>Bytes.fromUint8Array(...)</code>, <code>new GeoPoint(lat, lng)</code>, <code>new FieldPath(...)</code>, and <code>documentId()</code> — without importing <code>firebase/firestore</code></span></span></span></summary>
578
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code> (constructibility + round trips), <code>package-edge:package-dependencies.test.ts</code></div></div>
578
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L46" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a> (constructibility + round trips), <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/package-dependencies.test.ts#L4" target="_blank" rel="noopener noreferrer"><code>package-edge:package-dependencies.test.ts</code></a></div></div>
579
579
  </details>
580
580
  <details class="compat-row" data-status="ok">
581
581
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Scalar types</code><span class="compat-sub"><span class="compat-behavior"><code>documentId()</code> works in <code>where(documentId(), …)</code> / <code>orderBy(documentId())</code> against the sandbox — string ids, DocumentReference operands, ranges, and id sort</span></span></span></summary>
582
- <div class="compat-evidence"><div class="compat-probe"><code>unit:upstream-query-probes.test.ts</code> (<code>documentId() filters + orderBy</code>); modular <code>where</code>/<code>orderBy</code> accept <code>FieldPath</code></div></div>
582
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/upstream-query-probes.test.ts#L9" target="_blank" rel="noopener noreferrer"><code>unit:upstream-query-probes.test.ts</code></a> (<code>documentId() filters + orderBy</code>); modular <code>where</code>/<code>orderBy</code> accept <code>FieldPath</code></div></div>
583
583
  </details>
584
584
  <details class="compat-row" data-status="ok">
585
585
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Scalar types</code><span class="compat-sub"><span class="compat-behavior"><code>FieldPath</code> (nested) works in queries against sandbox</span></span></span></summary>
586
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code></div></div>
586
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L870" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a></div></div>
587
587
  </details>
588
588
  <details class="compat-row" data-status="ok">
589
589
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Scalar types</code><span class="compat-sub"><span class="compat-behavior"><code>Bytes</code> round-trip through the sandbox wire encoder — <code>Bytes</code> written via <code>setDoc</code> reads back as a <code>Bytes</code> instance with the same base64 representation</span></span></span></summary>
590
- <div class="compat-evidence"><div class="compat-probe"><code>unit:packages/pyric/test/sandbox/firestore/wire-encoder-bytes-geopoint.test.ts</code> + <code>unit:packages/pyric/test/firestore/sandbox-target.test.ts</code> ("Bytes + GeoPoint round-trip"), oracle: <code>packages/conformance/observations/firestore/firestore-row-109-bytes-roundtrip.json</code> — <code>setDoc({payload: Bytes.fromUint8Array([1,2,3,4])})</code> then <code>getDoc</code> yields <code>payload instanceof Bytes === true</code>, <code>payload.constructor.name === 'Bytes'</code>, <code>payload.toBase64() === 'AQIDBA=='</code>, and <code>payload.toUint8Array()</code> returns <code>[1,2,3,4]</code> against blockingfun. The sandbox converter stores the rules <code>Bytes</code> wrapper; <code>pyric/firestore</code> finalizes reads into its locally owned <code>Bytes</code> class with the same observed methods and values.</div></div>
590
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/sandbox/firestore/wire-encoder-bytes-geopoint.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:packages/pyric/test/sandbox/firestore/wire-encoder-bytes-geopoint.test.ts</code></a> + <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L46" target="_blank" rel="noopener noreferrer"><code>unit:packages/pyric/test/firestore/sandbox-target.test.ts</code></a> ("Bytes + GeoPoint round-trip"), oracle: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/firestore/firestore-row-109-bytes-roundtrip.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/firestore/firestore-row-109-bytes-roundtrip.json</code></a> — <code>setDoc({payload: Bytes.fromUint8Array([1,2,3,4])})</code> then <code>getDoc</code> yields <code>payload instanceof Bytes === true</code>, <code>payload.constructor.name === 'Bytes'</code>, <code>payload.toBase64() === 'AQIDBA=='</code>, and <code>payload.toUint8Array()</code> returns <code>[1,2,3,4]</code> against blockingfun. The sandbox converter stores the rules <code>Bytes</code> wrapper; <code>pyric/firestore</code> finalizes reads into its locally owned <code>Bytes</code> class with the same observed methods and values.</div></div>
591
591
  </details>
592
592
  <details class="compat-row" data-status="ok">
593
593
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Scalar types</code><span class="compat-sub"><span class="compat-behavior"><code>GeoPoint</code> round-trip through the sandbox wire encoder — <code>GeoPoint</code> written via <code>setDoc</code> reads back as a <code>GeoPoint</code> instance with the same latitude / longitude</span></span></span></summary>
594
- <div class="compat-evidence"><div class="compat-probe"><code>unit:packages/pyric/test/sandbox/firestore/wire-encoder-bytes-geopoint.test.ts</code> + <code>unit:packages/pyric/test/firestore/sandbox-target.test.ts</code> ("Bytes + GeoPoint round-trip"), oracle: <code>packages/conformance/observations/firestore/firestore-row-110-geopoint-roundtrip.json</code> — <code>setDoc({loc: new GeoPoint(37.7749, -122.4194)})</code> then <code>getDoc</code> yields <code>loc instanceof GeoPoint === true</code>, <code>loc.constructor.name === 'GeoPoint'</code>, <code>loc.latitude === 37.7749</code>, <code>loc.longitude === -122.4194</code> against blockingfun. Sandbox storage uses the rules <code>LatLng</code> wrapper; <code>pyric/firestore</code> finalizes reads into its locally owned <code>GeoPoint</code> class.</div></div>
594
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/sandbox/firestore/wire-encoder-bytes-geopoint.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:packages/pyric/test/sandbox/firestore/wire-encoder-bytes-geopoint.test.ts</code></a> + <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L47" target="_blank" rel="noopener noreferrer"><code>unit:packages/pyric/test/firestore/sandbox-target.test.ts</code></a> ("Bytes + GeoPoint round-trip"), oracle: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/firestore/firestore-row-110-geopoint-roundtrip.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/firestore/firestore-row-110-geopoint-roundtrip.json</code></a> — <code>setDoc({loc: new GeoPoint(37.7749, -122.4194)})</code> then <code>getDoc</code> yields <code>loc instanceof GeoPoint === true</code>, <code>loc.constructor.name === 'GeoPoint'</code>, <code>loc.latitude === 37.7749</code>, <code>loc.longitude === -122.4194</code> against blockingfun. Sandbox storage uses the rules <code>LatLng</code> wrapper; <code>pyric/firestore</code> finalizes reads into its locally owned <code>GeoPoint</code> class.</div></div>
595
595
  </details>
596
596
  <details class="compat-row" data-status="ok">
597
597
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Scalar types</code><span class="compat-sub"><span class="compat-behavior">Vector value type (<code>vector()</code> + <code>VectorValue</code>) round-trip: a vector written via <code>setDoc</code> reads back as a <code>VectorValue</code> with the same components</span></span></span></summary>
598
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code> ("Bytes + GeoPoint + VectorValue round-trip", top-level + nested). The locally owned <code>vector()</code> / <code>VectorValue</code> preserve Firebase's observable value shape; the sandbox converter stores the rules <code>Vector</code> wrapper and <code>pyric/firestore</code> finalizes reads back to <code>VectorValue</code>. Oracle observation to follow (cf. #109/#110). <strong>CLIENT surface only:</strong> the web SDK exposes <code>vector()</code> + <code>VectorValue</code> (read/write) but has NO <code>findNearest</code> and NO <code>FieldValue.vector</code>; vector SEARCH is admin/server-only (<code>firebase-admin</code> <code>Query</code>/<code>CollectionReference.findNearest</code> + <code>FieldValue.vector()</code>), out of scope for this client matrix; the admin surface is tracked in the design rationale.</div></div>
598
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L48" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a> ("Bytes + GeoPoint + VectorValue round-trip", top-level + nested). The locally owned <code>vector()</code> / <code>VectorValue</code> preserve Firebase's observable value shape; the sandbox converter stores the rules <code>Vector</code> wrapper and <code>pyric/firestore</code> finalizes reads back to <code>VectorValue</code>. Oracle observation to follow (cf. #109/#110). <strong>CLIENT surface only:</strong> the web SDK exposes <code>vector()</code> + <code>VectorValue</code> (read/write) but has NO <code>findNearest</code> and NO <code>FieldValue.vector</code>; vector SEARCH is admin/server-only (<code>firebase-admin</code> <code>Query</code>/<code>CollectionReference.findNearest</code> + <code>FieldValue.vector()</code>), out of scope for this client matrix; the admin surface is tracked in the design rationale.</div></div>
599
599
  </details>
600
600
  </div>
601
601
 
@@ -604,31 +604,31 @@
604
604
  <div class="compat-list">
605
605
  <details class="compat-row" data-status="ok">
606
606
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Equality helpers</code><span class="compat-sub"><span class="compat-behavior"><code>refEqual(a, b)</code> — true when paths match under the same target</span></span></span></summary>
607
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code></div></div>
607
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L840" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a></div></div>
608
608
  </details>
609
609
  <details class="compat-row" data-status="ok">
610
610
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Equality helpers</code><span class="compat-sub"><span class="compat-behavior"><code>refEqual</code> is <code>true</code> for cross-flavor sandbox vs sandbox-live refs at the same path</span></span></span></summary>
611
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-live-identity.test.ts</code> ("refEqual returns true for live and frozen refs at the same path")</div></div>
611
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-live-identity.test.ts#L38" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-live-identity.test.ts</code></a> ("refEqual returns true for live and frozen refs at the same path")</div></div>
612
612
  </details>
613
613
  <details class="compat-row" data-status="ok">
614
614
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Equality helpers</code><span class="compat-sub"><span class="compat-behavior"><code>refEqual</code> is <code>false</code> for refs at different paths</span></span></span></summary>
615
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-live-identity.test.ts</code></div></div>
615
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-live-identity.test.ts#L38" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-live-identity.test.ts</code></a></div></div>
616
616
  </details>
617
617
  <details class="compat-row" data-status="ok">
618
618
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Equality helpers</code><span class="compat-sub"><span class="compat-behavior"><code>refEqual(sandboxRef, foreignRef)</code> throws <code>TypeError</code> — references not created by this sandbox mirror are unrecognized</span></span></span></summary>
619
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code> (foreign refs throw unrecognized-reference TypeError)</div></div>
619
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L840" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a> (foreign refs throw unrecognized-reference TypeError)</div></div>
620
620
  </details>
621
621
  <details class="compat-row" data-status="ok">
622
622
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Equality helpers</code><span class="compat-sub"><span class="compat-behavior"><code>queryEqual(a, b)</code> structurally compares collection and collection-group scope, filter/order/limit/cursor constraint structure, converter identity, and construction-time Firestore value snapshots for both equality and execution without re-observing caller objects. Snapshots cover maps/arrays, scalar objects, references (including raw and converted <code>addDoc()</code> results), and vectors; normalize Date to Timestamp; preserve the distinction between -0 and 0; compare snapshot and explicit bounds for all four cursor overloads by value without invoking snapshot converters, including across live-target rebuilds; reject undefined, bigint, nested arrays for ordinary and array-membership operands, and recursively nested cross-database references during query construction; and preserve Firebase's nested-array allowance for <code>in</code>/<code>not-in</code> candidate lists.</span></span></span></summary>
623
- <div class="compat-evidence"><div class="compat-probe">Oracle-locked by <code>packages/conformance/observations/firestore/firestore-queryequal-structural.json</code>: collection and collection-group scope, order sequence/direction, limits, composite filters, cursor values/inclusivity, converters, and Firestore operands all distinguish equal from changed queries as production does. The capture executes mutable maps, Timestamp, Bytes, GeoPoint, DocumentReference (including raw and converted <code>addDoc()</code> results), and Vector queries; proves Bytes/Vector source-array mutation cannot change existing or new queries; proves all four snapshot cursor overloads compare with explicit bounds and execute twice without invoking a consumer converter; and locks recursive foreign-reference rejection, Date/Timestamp normalization, and -0 handling. <code>firestore-query-nested-array-validation.json</code> separately pins the production SDK's client-side rejection of nested arrays without implying a cloud round trip. The replay forces live-target identity rebuilds for every snapshot cursor overload. <code>oracle:firestore#116</code> replays every claimed fact.</div></div>
623
+ <div class="compat-evidence"><div class="compat-probe">Oracle-locked by <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/firestore/firestore-queryequal-structural.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/firestore/firestore-queryequal-structural.json</code></a>: collection and collection-group scope, order sequence/direction, limits, composite filters, cursor values/inclusivity, converters, and Firestore operands all distinguish equal from changed queries as production does. The capture executes mutable maps, Timestamp, Bytes, GeoPoint, DocumentReference (including raw and converted <code>addDoc()</code> results), and Vector queries; proves Bytes/Vector source-array mutation cannot change existing or new queries; proves all four snapshot cursor overloads compare with explicit bounds and execute twice without invoking a consumer converter; and locks recursive foreign-reference rejection, Date/Timestamp normalization, and -0 handling. <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/firestore/firestore-query-nested-array-validation.json" target="_blank" rel="noopener noreferrer"><code>firestore-query-nested-array-validation.json</code></a> separately pins the production SDK's client-side rejection of nested arrays without implying a cloud round trip. The replay forces live-target identity rebuilds for every snapshot cursor overload. <code>oracle:firestore#116</code> replays every claimed fact. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/oracle-conformance.test.ts#L1047" target="_blank" rel="noopener noreferrer"><code>oracle-conformance.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/equality.test.ts#L14" target="_blank" rel="noopener noreferrer"><code>equality.test.ts</code></a>)</div></div>
624
624
  </details>
625
625
  <details class="compat-row" data-status="ok">
626
626
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Equality helpers</code><span class="compat-sub"><span class="compat-behavior"><code>snapshotEqual(a, b)</code> follows production snapshot state. A query's first one-shot read differs from the next identical read, then repeated settled reads (including an independently built equivalent query) compare structurally until query state changes and settles again. Listener snapshots compare query, documents, changes, and metadata. Document snapshots compare path, existence, Firestore-typed data, converter identity, and snapshot kind, so two gets of the same document are equal while a query-child snapshot and a direct get are distinct; scalar-shaped plain maps remain distinct from Timestamp, reference, GeoPoint, and Vector values.</span></span></span></summary>
627
- <div class="compat-evidence"><div class="compat-probe">Oracle-locked by <code>packages/conformance/observations/firestore/firestore-snapshotequal-structural.json</code>: sequential and equivalent-query reads prove the initial-to-settled transition; a changed result restarts that transition; listener sensitivity cases vary query, documents/change history, and metadata-only state; direct document cases vary path, existence, data, converter, and snapshot kind; and four collision probes distinguish scalar-shaped maps from their Timestamp, reference, GeoPoint, and Vector counterparts. <code>oracle:firestore#117</code> replays every reachable fact. JSON reconstruction remains raw characterization only because Pyric does not expose that separate API.</div></div>
627
+ <div class="compat-evidence"><div class="compat-probe">Oracle-locked by <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/firestore/firestore-snapshotequal-structural.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/firestore/firestore-snapshotequal-structural.json</code></a>: sequential and equivalent-query reads prove the initial-to-settled transition; a changed result restarts that transition; listener sensitivity cases vary query, documents/change history, and metadata-only state; direct document cases vary path, existence, data, converter, and snapshot kind; and four collision probes distinguish scalar-shaped maps from their Timestamp, reference, GeoPoint, and Vector counterparts. <code>oracle:firestore#117</code> replays every reachable fact. JSON reconstruction remains raw characterization only because Pyric does not expose that separate API. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/oracle-conformance.test.ts#L1454" target="_blank" rel="noopener noreferrer"><code>oracle-conformance.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/equality.test.ts#L16" target="_blank" rel="noopener noreferrer"><code>equality.test.ts</code></a>)</div></div>
628
628
  </details>
629
629
  <details class="compat-row" data-status="ok">
630
630
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Equality helpers</code><span class="compat-sub"><span class="compat-behavior">Cross-flavor <code>refEqual</code> via <code>QuerySnapshot.docs[i].ref</code> works</span></span></span></summary>
631
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-live-identity.test.ts</code> ("cross-flavor refEqual via QuerySnapshot doc refs")</div></div>
631
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-live-identity.test.ts#L38" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-live-identity.test.ts</code></a> ("cross-flavor refEqual via QuerySnapshot doc refs")</div></div>
632
632
  </details>
633
633
  </div>
634
634
 
@@ -637,11 +637,11 @@
637
637
  <div class="compat-list">
638
638
  <details class="compat-row" data-status="ok">
639
639
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">connectFirestoreEmulator</code><span class="compat-sub"><span class="compat-behavior">No-op on sandbox-target handles (the sandbox already IS a local emulator)</span></span></span></summary>
640
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-target.test.ts</code></div></div>
640
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-target.test.ts#L861" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-target.test.ts</code></a></div></div>
641
641
  </details>
642
642
  <details class="compat-row" data-status="ok">
643
643
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">connectFirestoreEmulator</code><span class="compat-sub"><span class="compat-behavior">Production does not enter the mirror: inactive package resolution leaves Firebase's <code>connectFirestoreEmulator</code> implementation unchanged</span></span></span></summary>
644
- <div class="compat-evidence"><div class="compat-probe"><code>node-register:register-child.test.ts</code> (inactive canonical Firestore is not rewritten)</div></div>
644
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/register-child.test.ts#L1" target="_blank" rel="noopener noreferrer"><code>node-register:register-child.test.ts</code></a> (inactive canonical Firestore is not rewritten)</div></div>
645
645
  </details>
646
646
  <details class="compat-row" data-status="ok">
647
647
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">connectFirestoreEmulator</code><span class="compat-sub"><span class="compat-behavior">The sandbox mirror accepts Firebase's <code>mockUserToken</code> option shape as an inert compatibility argument; production uses Firebase's untouched implementation</span></span></span></summary>
@@ -656,27 +656,27 @@
656
656
  <div class="compat-list">
657
657
  <details class="compat-row" data-status="ok">
658
658
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">enableIndexedDbPersistence</code><span class="compat-sub"><span class="compat-behavior">Resolves before Firestore starts and rejects with <code>failed-precondition</code> after any operation has started the service</span></span></span></summary>
659
- <div class="compat-evidence"><div class="compat-probe">Real Chromium: <code>firestore-browser-lifecycle.json</code>; replayed through the public modular API by <code>oracle-conformance.test.ts</code> and focused lifecycle coverage.</div></div>
659
+ <div class="compat-evidence"><div class="compat-probe">Real Chromium: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/firestore/firestore-browser-lifecycle.json" target="_blank" rel="noopener noreferrer"><code>firestore-browser-lifecycle.json</code></a>; replayed through the public modular API by <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/oracle-conformance.test.ts#L653" target="_blank" rel="noopener noreferrer"><code>oracle-conformance.test.ts</code></a> and focused lifecycle coverage. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/persistence-network.test.ts#L7" target="_blank" rel="noopener noreferrer"><code>persistence-network.test.ts</code></a>)</div></div>
660
660
  </details>
661
661
  <details class="compat-row" data-status="ok">
662
662
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">enableMultiTabIndexedDbPersistence</code><span class="compat-sub"><span class="compat-behavior">Enables multi-client shared persistence before first use, permits two clients to opt in, and rejects initialization after a client has started with <code>failed-precondition</code></span></span></span></summary>
663
- <div class="compat-evidence"><div class="compat-probe">Real Chromium: <code>firestore-browser-lifecycle.json</code> exercises fresh, after-use, and two-client initialization; replayed through public APIs by <code>oracle-conformance.test.ts</code> and focused persistence tests.</div></div>
663
+ <div class="compat-evidence"><div class="compat-probe">Real Chromium: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/firestore/firestore-browser-lifecycle.json" target="_blank" rel="noopener noreferrer"><code>firestore-browser-lifecycle.json</code></a> exercises fresh, after-use, and two-client initialization; replayed through public APIs by <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/oracle-conformance.test.ts#L653" target="_blank" rel="noopener noreferrer"><code>oracle-conformance.test.ts</code></a> and focused persistence tests. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/persistence-network.test.ts#L14" target="_blank" rel="noopener noreferrer"><code>persistence-network.test.ts</code></a>)</div></div>
664
664
  </details>
665
665
  <details class="compat-row" data-status="ok">
666
666
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">clearIndexedDbPersistence</code><span class="compat-sub"><span class="compat-behavior">Maps to <code>Sandbox.clearPersistence()</code> — actually wipes the persisted blob (honest, not a no-op); already a no-op when persistence was never enabled</span></span></span></summary>
667
- <div class="compat-evidence"><div class="compat-probe"><code>unit:firestore/persistence-network.test.ts</code></div></div>
667
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/persistence-network.test.ts#L14" target="_blank" rel="noopener noreferrer"><code>unit:firestore/persistence-network.test.ts</code></a></div></div>
668
668
  </details>
669
669
  <details class="compat-row" data-status="ok">
670
670
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">enableNetwork / disableNetwork</code><span class="compat-sub"><span class="compat-behavior">Disabling network exposes local mutations with cache/pending-write metadata while holding write acknowledgement until network is re-enabled</span></span></span></summary>
671
- <div class="compat-evidence"><div class="compat-probe">Real Chromium: <code>firestore-browser-lifecycle.json</code>; replayed through the public modular API by <code>oracle-conformance.test.ts</code> and focused network coverage.</div></div>
671
+ <div class="compat-evidence"><div class="compat-probe">Real Chromium: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/firestore/firestore-browser-lifecycle.json" target="_blank" rel="noopener noreferrer"><code>firestore-browser-lifecycle.json</code></a>; replayed through the public modular API by <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/oracle-conformance.test.ts#L653" target="_blank" rel="noopener noreferrer"><code>oracle-conformance.test.ts</code></a> and focused network coverage. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/persistence-network.test.ts#L9" target="_blank" rel="noopener noreferrer"><code>persistence-network.test.ts</code></a>)</div></div>
672
672
  </details>
673
673
  <details class="compat-row" data-status="ok">
674
674
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">waitForPendingWrites</code><span class="compat-sub"><span class="compat-behavior">Waits for outstanding offline write acknowledgements and resolves when network re-enable drains them</span></span></span></summary>
675
- <div class="compat-evidence"><div class="compat-probe">Real Chromium: <code>firestore-browser-lifecycle.json</code>; replayed through the public modular API by <code>oracle-conformance.test.ts</code> and focused pending-write coverage.</div></div>
675
+ <div class="compat-evidence"><div class="compat-probe">Real Chromium: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/firestore/firestore-browser-lifecycle.json" target="_blank" rel="noopener noreferrer"><code>firestore-browser-lifecycle.json</code></a>; replayed through the public modular API by <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/oracle-conformance.test.ts#L653" target="_blank" rel="noopener noreferrer"><code>oracle-conformance.test.ts</code></a> and focused pending-write coverage. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/persistence-network.test.ts#L11" target="_blank" rel="noopener noreferrer"><code>persistence-network.test.ts</code></a>)</div></div>
676
676
  </details>
677
677
  <details class="compat-row" data-status="ok">
678
678
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">terminate</code><span class="compat-sub"><span class="compat-behavior">Terminates only the selected Firestore service instance: held refs reject with <code>failed-precondition</code>, owned listeners stop, and sibling Firestore or Auth services remain usable</span></span></span></summary>
679
- <div class="compat-evidence"><div class="compat-probe">Real Chromium: <code>packages/conformance/observations/firestore/firestore-browser-lifecycle.json</code> records <code>failed-precondition</code> on the terminated instance while Auth and a sibling Firestore instance continue. Replayed by <code>oracle-conformance.test.ts</code>; focused ownership coverage in <code>terminate.test.ts</code>.</div></div>
679
+ <div class="compat-evidence"><div class="compat-probe">Real Chromium: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/firestore/firestore-browser-lifecycle.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/firestore/firestore-browser-lifecycle.json</code></a> records <code>failed-precondition</code> on the terminated instance while Auth and a sibling Firestore instance continue. Replayed by <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/oracle-conformance.test.ts#L653" target="_blank" rel="noopener noreferrer"><code>oracle-conformance.test.ts</code></a>; focused ownership coverage in <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/terminate.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>terminate.test.ts</code></a>.</div></div>
680
680
  </details>
681
681
  </div>
682
682
 
@@ -687,30 +687,54 @@
687
687
  <div class="compat-list">
688
688
  <details class="compat-row" data-status="diverged">
689
689
  <summary class="compat-line"><span class="compat-dot" data-status="diverged" role="img" aria-label="Diverged (documented)" title="Diverged (documented)"></span><span class="compat-main"><code class="compat-api">initializeFirestore</code><span class="compat-sub"><span class="compat-behavior">Delegates to sandbox <code>getFirestore(app)</code> and returns the same handle. Accepts the <code>settings</code> argument (so the explicit-init pattern doesn't crash at import) but no-ops the cache/network settings — persistence is always on</span></span></span></summary>
690
- <div class="compat-evidence"><div class="compat-probe"><code>unit:firestore/tier1-cache-init-align.test.ts</code></div>
690
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/tier1-cache-init-align.test.ts#L7" target="_blank" rel="noopener noreferrer"><code>unit:firestore/tier1-cache-init-align.test.ts</code></a></div>
691
691
  <div class="compat-note">settings accepted but cache/network settings are no-ops</div></div>
692
692
  </details>
693
693
  <details class="compat-row" data-status="diverged">
694
694
  <summary class="compat-line"><span class="compat-dot" data-status="diverged" role="img" aria-label="Diverged (documented)" title="Diverged (documented)"></span><span class="compat-main"><code class="compat-api">persistentLocalCache / memoryLocalCache / persistentSingleTabManager / persistentMultipleTabManager / memoryEagerGarbageCollector / memoryLruGarbageCollector</code><span class="compat-sub"><span class="compat-behavior">Config token accepted, inert — each returns a small tagged object so identity/usage doesn't crash. Persistence is the sandbox default; there is no cache tier left to configure</span></span></span></summary>
695
- <div class="compat-evidence"><div class="compat-probe"><code>unit:firestore/tier1-cache-init-align.test.ts</code></div>
695
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/tier1-cache-init-align.test.ts#L8" target="_blank" rel="noopener noreferrer"><code>unit:firestore/tier1-cache-init-align.test.ts</code></a></div>
696
696
  <div class="compat-note">inert config tokens; no cache tier to configure</div></div>
697
697
  </details>
698
698
  <details class="compat-row" data-status="ok">
699
699
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">getDocFromServer / getDocsFromServer</code><span class="compat-sub"><span class="compat-behavior">Delegates to <code>getDoc</code> / <code>getDocs</code> in the sandbox mirror — the sandbox store IS the authoritative source, so there is no separate server round-trip to force and no observable divergence from the default read</span></span></span></summary>
700
- <div class="compat-evidence"><div class="compat-probe"><code>unit:firestore/tier1-cache-init-align.test.ts</code></div></div>
700
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/tier1-cache-init-align.test.ts#L12" target="_blank" rel="noopener noreferrer"><code>unit:firestore/tier1-cache-init-align.test.ts</code></a></div></div>
701
701
  </details>
702
702
  <details class="compat-row" data-status="ok">
703
703
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">getDocFromCache / getDocsFromCache</code><span class="compat-sub"><span class="compat-behavior">Explicit cache reads distinguish cold and warm state: cold document reads reject with <code>unavailable</code>, cold queries return empty, and server/default reads populate subsequent cache reads</span></span></span></summary>
704
- <div class="compat-evidence"><div class="compat-probe">Real Chromium: <code>firestore-browser-lifecycle.json</code> captures cold and warm document and query cache behavior; <code>oracle-conformance.test.ts</code> replays it through public APIs.</div></div>
704
+ <div class="compat-evidence"><div class="compat-probe">Real Chromium: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/firestore/firestore-browser-lifecycle.json" target="_blank" rel="noopener noreferrer"><code>firestore-browser-lifecycle.json</code></a> captures cold and warm document and query cache behavior; <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/oracle-conformance.test.ts#L653" target="_blank" rel="noopener noreferrer"><code>oracle-conformance.test.ts</code></a> replays it through public APIs. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/tier1-cache-init-align.test.ts#L13" target="_blank" rel="noopener noreferrer"><code>tier1-cache-init-align.test.ts</code></a>)</div></div>
705
705
  </details>
706
706
  <details class="compat-row" data-status="diverged">
707
707
  <summary class="compat-line"><span class="compat-dot" data-status="diverged" role="img" aria-label="Diverged (documented)" title="Diverged (documented)"></span><span class="compat-main"><code class="compat-api">setLogLevel</code><span class="compat-sub"><span class="compat-behavior">Accepted no-op — the sandbox has no modular-SDK-style logger to wire a level into; it uses host-level <code>console</code> logging directly, gated by <code>pyric dev</code>'s own flags, not this call</span></span></span></summary>
708
- <div class="compat-evidence"><div class="compat-probe"><code>unit:firestore/tier1-cache-init-align.test.ts</code></div>
708
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/tier1-cache-init-align.test.ts#L13" target="_blank" rel="noopener noreferrer"><code>unit:firestore/tier1-cache-init-align.test.ts</code></a></div>
709
709
  <div class="compat-note">accepted no-op; no sandbox logger wired</div></div>
710
710
  </details>
711
711
  <details class="compat-row" data-status="ok">
712
712
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">onSnapshotsInSync</code><span class="compat-sub"><span class="compat-behavior">Emits an initial in-sync signal, batches another signal after snapshot listener delivery, and stops after unsubscribe</span></span></span></summary>
713
- <div class="compat-evidence"><div class="compat-probe">Real Chromium: <code>firestore-browser-lifecycle.json</code> records initial sync, write snapshot, then sync; replayed by <code>oracle-conformance.test.ts</code> and focused listener tests.</div></div>
713
+ <div class="compat-evidence"><div class="compat-probe">Real Chromium: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/firestore/firestore-browser-lifecycle.json" target="_blank" rel="noopener noreferrer"><code>firestore-browser-lifecycle.json</code></a> records initial sync, write snapshot, then sync; replayed by <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/oracle-conformance.test.ts#L653" target="_blank" rel="noopener noreferrer"><code>oracle-conformance.test.ts</code></a> and focused listener tests. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/tier1-cache-init-align.test.ts#L14" target="_blank" rel="noopener noreferrer"><code>tier1-cache-init-align.test.ts</code></a>)</div></div>
714
+ </details>
715
+ <details class="compat-row" data-status="ok">
716
+ <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">Runtime ES Classes / Constructor Tokens</code><span class="compat-sub"><span class="compat-behavior">Exports ES classes and constructor tokens matching structural type handles for instanceof checks and prototype inheritance</span></span></span></summary>
717
+ <div class="compat-evidence"><div class="compat-probe"><code>unit:firestore/tier1-cache-init-align.test.ts</code> (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/runtime-classes.test.ts#L5" target="_blank" rel="noopener noreferrer"><code>runtime-classes.test.ts</code></a>)</div></div>
718
+ </details>
719
+ <details class="compat-row" data-status="ok">
720
+ <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">PersistentCacheIndexManager / Index auto-creation</code><span class="compat-sub"><span class="compat-behavior">Config tokens and index manager controller accepted as honest inert runtime tokens without failing execution</span></span></span></summary>
721
+ <div class="compat-evidence"><div class="compat-probe"><code>unit:firestore/tier1-cache-init-align.test.ts</code> (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/persistence-tokens.test.ts#L5" target="_blank" rel="noopener noreferrer"><code>persistence-tokens.test.ts</code></a>)</div></div>
722
+ </details>
723
+ <details class="compat-row" data-status="ok">
724
+ <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">loadBundle / namedQuery</code><span class="compat-sub"><span class="compat-behavior">Decodes string/ArrayBuffer bundle payloads into local sandbox store and registers named queries</span></span></span></summary>
725
+ <div class="compat-evidence"><div class="compat-probe"><code>unit:firestore/tier1-cache-init-align.test.ts</code> (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/bundles.test.ts#L5" target="_blank" rel="noopener noreferrer"><code>bundles.test.ts</code></a>)</div></div>
726
+ </details>
727
+ <details class="compat-row" data-status="ok">
728
+ <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">documentSnapshotFromJSON / querySnapshotFromJSON / onSnapshotResume</code><span class="compat-sub"><span class="compat-behavior">Deserializes JSON snapshot objects into active Pyric snapshot wrappers and attaches resume listener</span></span></span></summary>
729
+ <div class="compat-evidence"><div class="compat-probe"><code>unit:firestore/tier1-cache-init-align.test.ts</code> (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/ssr-snapshots.test.ts#L5" target="_blank" rel="noopener noreferrer"><code>ssr-snapshots.test.ts</code></a>)</div></div>
730
+ </details>
731
+ <details class="compat-row" data-status="ok">
732
+ <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">aggregateFieldEqual / aggregateQuerySnapshotEqual</code><span class="compat-sub"><span class="compat-behavior">Compares aggregate descriptors and snapshot data for value equality</span></span></span></summary>
733
+ <div class="compat-evidence"><div class="compat-probe"><code>unit:firestore/tier1-cache-init-align.test.ts</code> (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/runtime-classes.test.ts#L50" target="_blank" rel="noopener noreferrer"><code>runtime-classes.test.ts</code></a>)</div></div>
734
+ </details>
735
+ <details class="compat-row" data-status="ok">
736
+ <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">ensureFirestoreConfigured / executeWrite</code><span class="compat-sub"><span class="compat-behavior">Validates sandbox initialization and executes write callback within mutation evaluation pipeline</span></span></span></summary>
737
+ <div class="compat-evidence"><div class="compat-probe"><code>unit:firestore/tier1-cache-init-align.test.ts</code> (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/writes-plumbing.test.ts#L5" target="_blank" rel="noopener noreferrer"><code>writes-plumbing.test.ts</code></a>)</div></div>
714
738
  </details>
715
739
  </div>
716
740
 
@@ -728,19 +752,19 @@ shape consumer code depends on.
728
752
  </details>
729
753
  <details class="compat-row" data-status="ok">
730
754
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><span class="compat-behavior"><code>request.auth == null</code> when sandbox.currentUser is null (anonymous path)</span></span></summary>
731
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-live-identity.test.ts</code> ("anonymous fallback")</div></div>
755
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/sandbox-live-identity.test.ts#L23" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-live-identity.test.ts</code></a> ("anonymous fallback")</div></div>
732
756
  </details>
733
757
  <details class="compat-row" data-status="ok">
734
758
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><span class="compat-behavior">Cross-doc <code>get(/databases/$(database)/documents/...)</code> in rules works under sandbox; <code>get()</code> of a <strong>missing</strong> doc ERRORS (guard with <code>exists()</code>), and <code>get(p).id</code> / <code>get(p).__name__</code> expose the doc identity (RULES-B8)</span></span></summary>
735
- <div class="compat-evidence"><div class="compat-probe"><code>playground:rules-cross-doc-get</code>, <code>unit:rules/simulator/evaluator.test.ts</code> (RULES-B8 block)</div></div>
759
+ <div class="compat-evidence"><div class="compat-probe"><code>playground:rules-cross-doc-get</code>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/rules/simulator/evaluator.test.ts#L140" target="_blank" rel="noopener noreferrer"><code>unit:rules/simulator/evaluator.test.ts</code></a> (RULES-B8 block)</div></div>
736
760
  </details>
737
761
  <details class="compat-row" data-status="ok">
738
762
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><span class="compat-behavior"><code>request.resource.data.&lt;field&gt;</code> field validation in rules works under sandbox; an <strong>undefined</strong> field read ERRORS (deny), it does NOT read as null (RULES-B2) — guard with <code>'f' in data</code> / <code>data.get('f', d)</code></span></span></summary>
739
- <div class="compat-evidence"><div class="compat-probe"><code>playground:rules-data-validation</code>, <code>unit:rules/simulator/evaluator.test.ts</code> (RULES-B2 block)</div></div>
763
+ <div class="compat-evidence"><div class="compat-probe"><code>playground:rules-data-validation</code>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/rules/simulator/evaluator.test.ts#L140" target="_blank" rel="noopener noreferrer"><code>unit:rules/simulator/evaluator.test.ts</code></a> (RULES-B2 block)</div></div>
740
764
  </details>
741
765
  <details class="compat-row" data-status="ok">
742
766
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><span class="compat-behavior"><code>resource.data.&lt;field&gt;</code> (existing doc on writes) works under sandbox; undefined-field reads ERROR (RULES-B2)</span></span></summary>
743
- <div class="compat-evidence"><div class="compat-probe"><code>playground:rules-resource-data-field</code>, <code>unit:rules/simulator/evaluator.test.ts</code> (RULES-B2 block)</div></div>
767
+ <div class="compat-evidence"><div class="compat-probe"><code>playground:rules-resource-data-field</code>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/rules/simulator/evaluator.test.ts#L140" target="_blank" rel="noopener noreferrer"><code>unit:rules/simulator/evaluator.test.ts</code></a> (RULES-B2 block)</div></div>
744
768
  </details>
745
769
  <details class="compat-row" data-status="ok">
746
770
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><span class="compat-behavior">Custom claims in <code>request.auth.token.&lt;claim&gt;</code></span></span></summary>
@@ -748,43 +772,43 @@ shape consumer code depends on.
748
772
  </details>
749
773
  <details class="compat-row" data-status="ok">
750
774
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><span class="compat-behavior">Tri-state error semantics: DOTTED field access of a missing key (<code>resource.data.typo</code>), access on null/undefined, undefined variables, and <code>get()</code>-of-missing ERROR → deny; <code>&amp;&amp;</code>/<code>||</code> absorb operand errors <strong>commutatively</strong> (CEL: <code>error || true</code> → true, <code>error &amp;&amp; false</code> → false). NOTE: DYNAMIC index access <code>data[expr]</code> stays null-on-miss (the documented may-be-absent-lookup idiom; only dotted access is doc-confirmed to error). (RULES-B2/B3/B8)</span></span></summary>
751
- <div class="compat-evidence"><div class="compat-probe"><code>unit:rules/simulator/evaluator.test.ts</code> (RULES-B2 / RULES-B3 / RULES-B8 blocks)</div></div>
775
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/rules/simulator/evaluator.test.ts#L140" target="_blank" rel="noopener noreferrer"><code>unit:rules/simulator/evaluator.test.ts</code></a> (RULES-B2 / RULES-B3 / RULES-B8 blocks)</div></div>
752
776
  </details>
753
777
  <details class="compat-row" data-status="ok">
754
778
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><span class="compat-behavior"><code>matches()</code> is a <strong>full-string</strong> anchored RE2 test; <code>replace()</code>/<code>split()</code> take regexes (<code>replace</code> = all occurrences) (RULES-B4)</span></span></summary>
755
- <div class="compat-evidence"><div class="compat-probe"><code>unit:rules/simulator/evaluator.test.ts</code> (RULES-B4 block)</div></div>
779
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/rules/simulator/evaluator.test.ts#L140" target="_blank" rel="noopener noreferrer"><code>unit:rules/simulator/evaluator.test.ts</code></a> (RULES-B4 block)</div></div>
756
780
  </details>
757
781
  <details class="compat-row" data-status="ok">
758
782
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><span class="compat-behavior">No JS prototype-chain leakage: <code>'toString' in data</code> → false, <code>data.constructor</code> errors; <code>in</code>/<code>hasAll</code>/<code>get</code> use own keys only (RULES-B7)</span></span></summary>
759
- <div class="compat-evidence"><div class="compat-probe"><code>unit:rules/simulator/evaluator.test.ts</code> (RULES-B7 block)</div></div>
783
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/rules/simulator/evaluator.test.ts#L140" target="_blank" rel="noopener noreferrer"><code>unit:rules/simulator/evaluator.test.ts</code></a> (RULES-B7 block)</div></div>
760
784
  </details>
761
785
  <details class="compat-row" data-status="ok">
762
786
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><span class="compat-behavior">Type-strict operators: <code>+</code> requires matching operand types (<code>'a' + 1</code> errors; <code>[1]+[2]</code> concatenates); ordered compares (<code>&lt; &gt; &lt;= &gt;=</code>) error across types; list membership uses value equality; <code>is map</code> excludes MapDiff/Set (RULES-B6 partial / B9 / B12 partial)</span></span></summary>
763
- <div class="compat-evidence"><div class="compat-probe"><code>unit:rules/simulator/evaluator.test.ts</code> (RULES-B6 / B9 / B12 blocks)</div></div>
787
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/rules/simulator/evaluator.test.ts#L140" target="_blank" rel="noopener noreferrer"><code>unit:rules/simulator/evaluator.test.ts</code></a> (RULES-B6 / B9 / B12 blocks)</div></div>
764
788
  </details>
765
789
  <details class="compat-row" data-status="ok">
766
790
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><span class="compat-behavior"><code>FirestoreSet</code> VALUE equality: <code>diff.addedKeys() == [uid].toSet()</code> compares set contents (order-insensitive); <code>set == list</code> is false, not an error (RULES-B13). Pre-fix, ANY two sets compared EQUAL (generic-object deep-equals saw no enumerable keys) — a false-PERMISSIVE divergence found by joining validation</span></span></summary>
767
- <div class="compat-evidence"><div class="compat-probe"><code>unit:rules/simulator/set-equality.test.ts</code>; live validation: 10/10 both engines</div></div>
791
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/rules/simulator/set-equality.test.ts#L6" target="_blank" rel="noopener noreferrer"><code>unit:rules/simulator/set-equality.test.ts</code></a>; live validation: 10/10 both engines</div></div>
768
792
  </details>
769
793
  <details class="compat-row" data-status="ok">
770
794
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><span class="compat-behavior"><code>update</code> exposes <code>request.resource.data</code> / <code>getAfter()</code> as the existing doc <strong>merged</strong> with the payload via the <code>writeMode: { kind: 'update' }</code> path (the agent-facing <code>simulate()</code> opt-in); a sparse no-writeMode payload that drops a field now ERRORS on that field (RULES-B2) rather than silently reading null (RULES-B10)</span></span></summary>
771
- <div class="compat-evidence"><div class="compat-probe"><code>unit:rules/simulator/handler.test.ts</code> (RULES-B10 block)</div></div>
795
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/rules/simulator/handler.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:rules/simulator/handler.test.ts</code></a> (RULES-B10 block)</div></div>
772
796
  </details>
773
797
  <details class="compat-row" data-status="ok">
774
798
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><span class="compat-behavior">Int/float distinction (<code>1.5 is int</code>→false, <code>1 is float</code>→false, <code>1.0 is float</code>→true) + integer division (<code>10 / 4 == 2</code>) + int div/mod-by-zero ERRORS (RULES-B5); strict <code>int('12abc')</code>/<code>float('abc')</code>/<code>bool('false')</code>/<code>bool('yes')</code> parsing (RULES-B6 rest); <code>string(1.0)</code>→"1.0" (RULES-B12 rest)</span></span></summary>
775
- <div class="compat-evidence"><div class="compat-probe"><code>unit:rules/simulator/evaluator.test.ts</code> (RULES-B5 + "RULES-B6 remainder" blocks); <code>unit:rules/simulator/handler.test.ts</code> ("RULES-B5 end-to-end" block)</div></div>
799
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/rules/simulator/evaluator.test.ts#L140" target="_blank" rel="noopener noreferrer"><code>unit:rules/simulator/evaluator.test.ts</code></a> (RULES-B5 + "RULES-B6 remainder" blocks); <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/rules/simulator/handler.test.ts#L1089" target="_blank" rel="noopener noreferrer"><code>unit:rules/simulator/handler.test.ts</code></a> ("RULES-B5 end-to-end" block)</div></div>
776
800
  </details>
777
801
  <details class="compat-row" data-status="ok">
778
802
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><span class="compat-behavior">Strict boolean control flow matches production: non-booleans in <code>&amp;&amp;</code>, <code>||</code>, or a ternary condition error and deny. Firestore double payloads retain float identity in the simulator (fractional JSON numbers are revived directly; <code>{ __type:'float', value }</code> preserves an explicitly tagged double). On create, <code>resource == null</code> denies while <code>request.resource</code> exposes the incoming document.</span></span></summary>
779
- <div class="compat-evidence"><div class="compat-probe">Production behavior is recorded by Firestore Rules rows <code>firestore-rules#188</code>, <code>firestore-rules#167</code>, and <code>firestore-rules#166</code>. Direct evaluator/handler tests guard strict operands, fractional-number revival, the explicit float tag, and create-time resource verdicts on the SDK-facing simulator path.</div></div>
803
+ <div class="compat-evidence"><div class="compat-probe">Production behavior is recorded by Firestore Rules rows <code>firestore-rules#188</code>, <code>firestore-rules#167</code>, and <code>firestore-rules#166</code>. Direct evaluator/handler tests guard strict operands, fractional-number revival, the explicit float tag, and create-time resource verdicts on the SDK-facing simulator path. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/rules/simulator/evaluator.test.ts#L140" target="_blank" rel="noopener noreferrer"><code>evaluator.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/rules/simulator/handler.test.ts#L1089" target="_blank" rel="noopener noreferrer"><code>handler.test.ts</code></a>)</div></div>
780
804
  </details>
781
805
  <details class="compat-row" data-status="ok">
782
806
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><span class="compat-behavior">Query-proof EVALUATION — the rules-side decision ("rules are not filters"): given a <code>list</code> rule + query constraints, decide provable-or-reject (a doc-dependent rule like <code>resource.data.visibility == 'public'</code> is provable ONLY with a matching <code>where('visibility','==','public')</code>; otherwise the whole query is rejected) (RULES-B11 rules-side)</span></span></summary>
783
- <div class="compat-evidence"><div class="compat-probe"><code>unit:rules/simulator/query-proof.test.ts</code></div></div>
807
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/rules/simulator/query-proof.test.ts#L9" target="_blank" rel="noopener noreferrer"><code>unit:rules/simulator/query-proof.test.ts</code></a></div></div>
784
808
  </details>
785
809
  <details class="compat-row" data-status="ok">
786
810
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><span class="compat-behavior">Query-proof ENFORCEMENT wiring — <code>silentReadCollection</code> + <code>readQueryCandidates</code> call <code>evaluateQueryProof</code> (via <code>sandbox/firestore/list-query-proof.ts</code>) instead of the per-doc silent-omission filter; structured <code>where</code>/<code>limit</code>/<code>orderBy</code> constraints are threaded from <code>QueryImpl.structuredConstraints()</code> through both the one-shot (<code>getDocs</code>/aggregate) and listener (<code>SnapshotTarget</code> applier <code>.structured</code>) paths, and <code>request.query.{limit,offset,orderBy}</code> is populated on list test cases (RULES-B11 cross-file)</span></span></summary>
787
- <div class="compat-evidence"><div class="compat-probe"><code>unit:firestore/query-proof-enforcement.test.ts</code> (both paths; verified failing pre-fix); prover scope caveat: row 24c</div></div>
811
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/query-proof-enforcement.test.ts#L29" target="_blank" rel="noopener noreferrer"><code>unit:firestore/query-proof-enforcement.test.ts</code></a> (both paths; verified failing pre-fix); prover scope caveat: row 24c</div></div>
788
812
  </details>
789
813
  </div>
790
814
 
@@ -796,50 +820,15 @@ Known differences between Pyric and production Firebase. Each remains tracked as
796
820
 
797
821
  <div class="compat-list">
798
822
  <details class="compat-row" data-status="diverged"><summary class="compat-line"><span class="compat-dot" data-status="diverged" role="img" aria-label="Diverged (documented)" title="Diverged (documented)"></span><span class="compat-main"><code class="compat-api">getDocs(query)</code><span class="compat-sub"><span class="compat-behavior">Query-proof **prover scope is conservative, with full accounting and a fail-closed classifier** — user functions are inlined (nested helper-calling-helper and multi-parameter helpers included, matching production's rules-function inlining), and a <code>list</code> rule is provable only when its **entire** doc-dependence reduces to top-level AND-conjunct <code>resource.data.&lt;field&gt; == &lt;literal&gt;</code> equalities (with <code>request.auth.uid</code> pinned to the caller) each discharged by a <code>where(field, '==', value)</code> filter. Doc-dependence is decided by a fail-closed classifier: an expression counts as doc-independent only when every node is positively recognized as such — any touch of the <code>resource</code> root in any syntactic form (<code>resource.data</code>, <code>resource['data']</code>, slices, <code>resource.id</code>, path-literal segments feeding <code>exists</code>/<code>get</code> lookups) and any unrecognized node shape classifies doc-dependent. Any doc-dependent conjunct that is not a discharged equality — disjunctions over doc data, inequality/range predicates (<code>resource.data.score &gt; 10</code> + <code>where('score','&gt;',10)</code>), <code>in</code> / negated-<code>in</code> membership checks, <code>get(key, default)</code>, <code>keys().hasOnly(...)</code>, <code>is</code> type checks, nested-path predicates, data-keyed document lookups — makes the whole query conservatively DENY, even when every equality conjunct is discharged; production's prover may allow some of these. Divergence is deny-only: mixed equality + non-equality shapes are rejected up front rather than trusted to a residual evaluation that absent-tolerant predicates could pass vacuously.</span></span></span></summary>
799
- <div class="compat-evidence"><div class="compat-note"><code>unit:rules/simulator/query-proof.test.ts</code> (function-inlining provable cases; full-accounting rejections for mixed equality + <code>in</code>/<code>get</code>/<code>hasOnly</code>/range/<code>is</code> shapes in inline and helper form; fail-closed classifier probes: bracket-access <code>resource['data']</code>, <code>exists()</code> keyed by <code>resource.data</code> through path-literal segments, slice access, <code>resource.id</code>, and an unrecognized node shape), <code>unit:firestore/query-proof-enforcement.test.ts</code> (seeded end-to-end denials of the demonstrated attacks: forbidden-field absence check in member and bracket form, banned-owner <code>exists()</code> lookup, slice conjunct — each returning zero documents)</div></div></details>
823
+ <div class="compat-evidence"><div class="compat-note"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/rules/simulator/query-proof.test.ts#L7" target="_blank" rel="noopener noreferrer"><code>unit:rules/simulator/query-proof.test.ts</code></a> (function-inlining provable cases; full-accounting rejections for mixed equality + <code>in</code>/<code>get</code>/<code>hasOnly</code>/range/<code>is</code> shapes in inline and helper form; fail-closed classifier probes: bracket-access <code>resource['data']</code>, <code>exists()</code> keyed by <code>resource.data</code> through path-literal segments, slice access, <code>resource.id</code>, and an unrecognized node shape), <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/query-proof-enforcement.test.ts#L19" target="_blank" rel="noopener noreferrer"><code>unit:firestore/query-proof-enforcement.test.ts</code></a> (seeded end-to-end denials of the demonstrated attacks: forbidden-field absence check in member and bracket form, banned-owner <code>exists()</code> lookup, slice conjunct — each returning zero documents)</div></div></details>
800
824
  <details class="compat-row" data-status="diverged"><summary class="compat-line"><span class="compat-dot" data-status="diverged" role="img" aria-label="Diverged (documented)" title="Diverged (documented)"></span><span class="compat-main"><code class="compat-api">Query construction</code><span class="compat-sub"><span class="compat-behavior">Index validation against <code>firestore.indexes.json</code> — sandbox uses <code>LocalEnvironment</code>'s lint pass; prod has its own server-side validation</span></span></span></summary>
801
825
  <div class="compat-evidence"><div class="compat-note">divergence: sandbox can mis-pass a query that prod would reject at the server with <code>failed-precondition</code> if no index exists</div></div></details>
802
826
  <details class="compat-row" data-status="diverged"><summary class="compat-line"><span class="compat-dot" data-status="diverged" role="img" aria-label="Diverged (documented)" title="Diverged (documented)"></span><span class="compat-main"><code class="compat-api">Aggregates</code><span class="compat-sub"><span class="compat-behavior">Aggregates count documents server-side without paying read cost per doc in prod; sandbox computes locally (no cost model)</span></span></span></summary>
803
- <div class="compat-evidence"><div class="compat-note">divergence: cost behavior differs, observable shape identical. Oracle-locked: <code>packages/conformance/observations/firestore/firestore-count-aggregate-shape.json</code> — <code>getCountFromServer().data()</code> returns <code>{ count: &lt;number&gt; }</code> (single key, no other fields). Empty query returns <code>count: 0</code> (not <code>null</code>/<code>undefined</code>); seeded 3 docs returns <code>count: 3</code>; filtered query honors the <code>where</code> constraint (<code>count: 2</code>).</div></div></details>
827
+ <div class="compat-evidence"><div class="compat-note">divergence: cost behavior differs, observable shape identical. Oracle-locked: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/firestore/firestore-count-aggregate-shape.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/firestore/firestore-count-aggregate-shape.json</code></a> — <code>getCountFromServer().data()</code> returns <code>{ count: &lt;number&gt; }</code> (single key, no other fields). Empty query returns <code>count: 0</code> (not <code>null</code>/<code>undefined</code>); seeded 3 docs returns <code>count: 3</code>; filtered query honors the <code>where</code> constraint (<code>count: 2</code>).</div></div></details>
804
828
  <details class="compat-row" data-status="diverged"><summary class="compat-line"><span class="compat-dot" data-status="diverged" role="img" aria-label="Diverged (documented)" title="Diverged (documented)"></span><span class="compat-main"><code class="compat-api">initializeFirestore</code><span class="compat-sub"><span class="compat-behavior">Delegates to sandbox <code>getFirestore(app)</code> and returns the same handle. Accepts the <code>settings</code> argument (so the explicit-init pattern doesn't crash at import) but no-ops the cache/network settings — persistence is always on</span></span></span></summary>
805
- <div class="compat-evidence"><div class="compat-note"><code>unit:firestore/tier1-cache-init-align.test.ts</code></div></div></details>
829
+ <div class="compat-evidence"><div class="compat-note"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/tier1-cache-init-align.test.ts#L7" target="_blank" rel="noopener noreferrer"><code>unit:firestore/tier1-cache-init-align.test.ts</code></a></div></div></details>
806
830
  <details class="compat-row" data-status="diverged"><summary class="compat-line"><span class="compat-dot" data-status="diverged" role="img" aria-label="Diverged (documented)" title="Diverged (documented)"></span><span class="compat-main"><code class="compat-api">persistentLocalCache / memoryLocalCache / persistentSingleTabManager / persistentMultipleTabManager / memoryEagerGarbageCollector / memoryLruGarbageCollector</code><span class="compat-sub"><span class="compat-behavior">Config token accepted, inert — each returns a small tagged object so identity/usage doesn't crash. Persistence is the sandbox default; there is no cache tier left to configure</span></span></span></summary>
807
- <div class="compat-evidence"><div class="compat-note"><code>unit:firestore/tier1-cache-init-align.test.ts</code></div></div></details>
831
+ <div class="compat-evidence"><div class="compat-note"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/tier1-cache-init-align.test.ts#L8" target="_blank" rel="noopener noreferrer"><code>unit:firestore/tier1-cache-init-align.test.ts</code></a></div></div></details>
808
832
  <details class="compat-row" data-status="diverged"><summary class="compat-line"><span class="compat-dot" data-status="diverged" role="img" aria-label="Diverged (documented)" title="Diverged (documented)"></span><span class="compat-main"><code class="compat-api">setLogLevel</code><span class="compat-sub"><span class="compat-behavior">Accepted no-op — the sandbox has no modular-SDK-style logger to wire a level into; it uses host-level <code>console</code> logging directly, gated by <code>pyric dev</code>'s own flags, not this call</span></span></span></summary>
809
- <div class="compat-evidence"><div class="compat-note"><code>unit:firestore/tier1-cache-init-align.test.ts</code></div></div></details>
810
- </div>
811
-
812
- ## Reviewed public-runtime gaps
813
-
814
- <div class="compat-list">
815
- <details class="compat-row" data-status="unsupported">
816
- <summary class="compat-line"><span class="compat-dot" data-status="unsupported" role="img" aria-label="Not implemented yet" title="Not implemented yet"></span><span class="compat-main"><code class="compat-api">firestore.runtime-class-values</code><span class="compat-sub"><span class="compat-behavior">These Firebase classes and constraint objects are present in Pyric's type surface but are not exported as runtime constructor values. Runtime identity and instanceof-compatible tokens remain unbuilt and cannot be credited by type-only mirrors.</span></span></span></summary>
817
- <div class="compat-evidence"><div class="compat-note"><code>AbstractUserDataWriter</code> <code>AggregateField</code> <code>AggregateQuerySnapshot</code> <code>CollectionReference</code> <code>DocumentReference</code> <code>DocumentSnapshot</code> <code>Firestore</code> <code>FirestoreError</code> <code>Query</code> <code>QueryCompositeFilterConstraint</code> <code>QueryConstraint</code> <code>QueryDocumentSnapshot</code> <code>QueryEndAtConstraint</code> <code>QueryFieldFilterConstraint</code> <code>QueryLimitConstraint</code> <code>QueryOrderByConstraint</code> <code>QuerySnapshot</code> <code>QueryStartAtConstraint</code> <code>SnapshotMetadata</code> <code>Transaction</code> <code>WriteBatch</code></div>
818
- <div class="compat-note">upstream:firebase/firestore</div></div>
819
- </details>
820
- <details class="compat-row" data-status="unsupported">
821
- <summary class="compat-line"><span class="compat-dot" data-status="unsupported" role="img" aria-label="Not implemented yet" title="Not implemented yet"></span><span class="compat-main"><code class="compat-api">firestore.cache-indexes</code><span class="compat-sub"><span class="compat-behavior">Index-tuning / GC-policy APIs have no real knob to turn in an in-memory sandbox today, but sibling cache-factory tokens are already honest inert tokens; these are buildable the same way and not genuinely out of scope.</span></span></span></summary>
822
- <div class="compat-evidence"><div class="compat-note"><code>CACHE_SIZE_UNLIMITED</code> <code>PersistentCacheIndexManager</code> <code>deleteAllPersistentCacheIndexes</code> <code>disablePersistentCacheIndexAutoCreation</code> <code>enablePersistentCacheIndexAutoCreation</code> <code>getPersistentCacheIndexManager</code> <code>setIndexConfiguration</code></div>
823
- <div class="compat-note">upstream:firebase/firestore</div></div>
824
- </details>
825
- <details class="compat-row" data-status="unsupported">
826
- <summary class="compat-line"><span class="compat-dot" data-status="unsupported" role="img" aria-label="Not implemented yet" title="Not implemented yet"></span><span class="compat-main"><code class="compat-api">firestore.bundle-loading</code><span class="compat-sub"><span class="compat-behavior">Bundle-loading depends on a protobuf packaging format not modeled in the sandbox yet — a data/parsing problem, not external infrastructure, so it is buildable rather than genuinely un-modelable.</span></span></span></summary>
827
- <div class="compat-evidence"><div class="compat-note"><code>LoadBundleTask</code> <code>loadBundle</code> <code>namedQuery</code></div>
828
- <div class="compat-note">upstream:firebase/firestore</div></div>
829
- </details>
830
- <details class="compat-row" data-status="unsupported">
831
- <summary class="compat-line"><span class="compat-dot" data-status="unsupported" role="img" aria-label="Not implemented yet" title="Not implemented yet"></span><span class="compat-main"><code class="compat-api">firestore.aggregate-equality-helpers</code><span class="compat-sub"><span class="compat-behavior">Aggregate-field and aggregate-snapshot equality helpers are exported upstream but have no Pyric runtime implementation yet.</span></span></span></summary>
832
- <div class="compat-evidence"><div class="compat-note"><code>aggregateFieldEqual</code> <code>aggregateQuerySnapshotEqual</code></div>
833
- <div class="compat-note">upstream:firebase/firestore</div></div>
834
- </details>
835
- <details class="compat-row" data-status="unsupported">
836
- <summary class="compat-line"><span class="compat-dot" data-status="unsupported" role="img" aria-label="Not implemented yet" title="Not implemented yet"></span><span class="compat-main"><code class="compat-api">firestore.snapshot-json-resume</code><span class="compat-sub"><span class="compat-behavior">Snapshot JSON reconstruction and listener-resume helpers depend on serialized snapshot state that the sandbox has not modeled.</span></span></span></summary>
837
- <div class="compat-evidence"><div class="compat-note"><code>documentSnapshotFromJSON</code> <code>onSnapshotResume</code> <code>querySnapshotFromJSON</code></div>
838
- <div class="compat-note">upstream:firebase/firestore</div></div>
839
- </details>
840
- <details class="compat-row" data-status="unsupported">
841
- <summary class="compat-line"><span class="compat-dot" data-status="unsupported" role="img" aria-label="Not implemented yet" title="Not implemented yet"></span><span class="compat-main"><code class="compat-api">firestore.public-write-plumbing</code><span class="compat-sub"><span class="compat-behavior">The upstream package publicly exports these low-level configuration/write helpers, but Pyric does not expose equivalent runtime plumbing yet.</span></span></span></summary>
842
- <div class="compat-evidence"><div class="compat-note"><code>ensureFirestoreConfigured</code> <code>executeWrite</code></div>
843
- <div class="compat-note">upstream:firebase/firestore</div></div>
844
- </details>
833
+ <div class="compat-evidence"><div class="compat-note"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/firestore/tier1-cache-init-align.test.ts#L13" target="_blank" rel="noopener noreferrer"><code>unit:firestore/tier1-cache-init-align.test.ts</code></a></div></div></details>
845
834
  </div>