@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
@@ -1,5 +1,5 @@
1
1
  <!DOCTYPE html><html lang="en"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>pyric/auth compatibility matrix · Pyric Studio</title><link rel="icon" type="image/svg+xml" href="/__pyric/ui/pyric-logo.svg"><link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet"><link rel="stylesheet" href="/__pyric/ui/_astro/_slug_.CXh9yTP1.css">
2
- <link rel="stylesheet" href="/__pyric/ui/_astro/_slug_.BoKXYrnu.css"></head> <body> <header class="studio__bar" data-astro-cid-mw7aashj> <div class="studio__bar-inner" data-astro-cid-mw7aashj> <a class="studio__brand" href="/__pyric/ui/" aria-label="Pyric Studio" data-astro-cid-mw7aashj>Pyric</a> <nav class="studio__nav" aria-label="Studio tabs" data-astro-cid-mw7aashj> <a class="studio__nav-tab" href="/__pyric/ui/" data-astro-cid-mw7aashj> Home </a><a class="studio__nav-tab" href="/__pyric/ui/firestore" data-astro-cid-mw7aashj> Firestore </a><a class="studio__nav-tab" href="/__pyric/ui/auth" data-astro-cid-mw7aashj> Auth </a><a class="studio__nav-tab" href="/__pyric/ui/rtdb" data-astro-cid-mw7aashj> RTDB </a><a class="studio__nav-tab" href="/__pyric/ui/storage" data-astro-cid-mw7aashj> Storage </a><a class="studio__nav-tab" href="/__pyric/ui/traffic" data-astro-cid-mw7aashj> Traffic </a><a class="studio__nav-tab" href="/__pyric/ui/settings" data-astro-cid-mw7aashj> Settings </a><a class="studio__nav-tab" href="/__pyric/ui/docs" aria-current="page" data-astro-cid-mw7aashj> Docs </a> </nav> </div> </header> <div class="docs-shell" data-astro-cid-mw7aashj> <details class="docs-nav-drawer" data-astro-cid-mw7aashj> <summary data-astro-cid-mw7aashj>Navigate</summary> <nav class="docs-nav" aria-label="Documentation"> <div class="docs-search" data-index-url="/__pyric/ui/docs/index.json"> <div class="docs-search-field"> <svg class="docs-search-icon" viewBox="0 0 16 16" width="14" height="14" aria-hidden="true"> <path d="M11.5 11.5 15 15M13 7A6 6 0 1 1 1 7a6 6 0 0 1 12 0Z" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"></path> </svg> <input type="text" class="docs-search-input" placeholder="Search docs" aria-label="Search docs" autocomplete="off" spellcheck="false"> <kbd class="docs-search-kbd">⌘K</kbd> </div> <ul class="docs-search-results" hidden></ul> </div> <script type="module">let i=null;function d(t){return i||(i=fetch(t).then(e=>e.json())),i}function l(t,e){return t.title.toLowerCase().includes(e)||t.excerpt.toLowerCase().includes(e)?!0:t.headings.some(r=>r.text.toLowerCase().includes(e))}function u(t,e,r){if(e.innerHTML="",t.length===0){e.hidden=!1;{const n=document.createElement("li");n.className="docs-search-empty",n.textContent="No matches",e.appendChild(n)}return}e.hidden=!1;for(const n of t.slice(0,8)){const s=document.createElement("li"),c=document.createElement("a");c.href=n.path;const o=document.createElement("span");o.className="docs-search-result-title",o.textContent=n.title;const a=document.createElement("span");a.className="docs-search-result-meta",a.textContent=[n.group,n.section].filter(Boolean).join(" / "),c.append(o,a),s.appendChild(c),e.appendChild(s)}}document.querySelectorAll(".docs-search").forEach(t=>{const e=t.dataset.indexUrl,r=t.querySelector(".docs-search-input"),n=t.querySelector(".docs-search-results");r.addEventListener("input",async()=>{const s=r.value.trim().toLowerCase();if(!s){n.hidden=!0,n.innerHTML="";return}const{pages:c}=await d(e);r.value.trim().toLowerCase()===s&&u(c.filter(o=>l(o,s)),n)}),r.addEventListener("focus",()=>{d(e),n.childElementCount>0&&(n.hidden=!1)}),r.addEventListener("keydown",s=>{s.key==="Escape"&&(r.value="",n.hidden=!0,n.innerHTML="",r.blur())}),r.addEventListener("blur",()=>{window.setTimeout(()=>{n.hidden=!0},150)})});document.addEventListener("keydown",t=>{if(!(t.metaKey||t.ctrlKey)||t.key.toLowerCase()!=="k")return;const e=[...document.querySelectorAll(".docs-search-input")].find(r=>r.offsetParent!==null);e&&(t.preventDefault(),e.focus(),e.select())});</script> <details class="docs-nav-group"> <summary>Overview</summary> <div class="docs-nav-group-body"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/overview/"> Overview </a> </li><li> <a href="/__pyric/ui/docs/tutorial/"> Tutorial </a> </li><li> <a href="/__pyric/ui/docs/examples/"> Examples </a> </li> </ul> </div> </div> </details><details class="docs-nav-group"> <summary>Get started</summary> <div class="docs-nav-group-body"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/get-started/start-building/"> Quickstart </a> </li><li> <a href="/__pyric/ui/docs/get-started/how-the-swap-works/"> How the swap works </a> </li> </ul> </div> </div> </details><details class="docs-nav-group"> <summary>Build</summary> <div class="docs-nav-group-body"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/build/authentication/"> Authentication </a> </li><li> <a href="/__pyric/ui/docs/build/cloud-firestore/"> Cloud Firestore </a> </li><li> <a href="/__pyric/ui/docs/build/realtime-database/"> Realtime Database </a> </li><li> <a href="/__pyric/ui/docs/build/cloud-storage/"> Cloud Storage </a> </li><li> <a href="/__pyric/ui/docs/build/cloud-messaging/"> Cloud Messaging </a> </li><li> <a href="/__pyric/ui/docs/build/ai-logic/"> AI Logic </a> </li> </ul> </div> </div> </details><details class="docs-nav-group"> <summary>Secure &amp; debug</summary> <div class="docs-nav-group-body"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/secure/secure-it-with-rules/"> Security Rules </a> </li><li> <a href="/__pyric/ui/docs/secure/simulate-and-lint/"> Simulate and lint before you deploy </a> </li><li> <a href="/__pyric/ui/docs/secure/write-a-rules-test-suite/"> Write a rules test suite </a> </li><li> <a href="/__pyric/ui/docs/secure/read-a-denial/"> Read a denial and understand it </a> </li><li> <a href="/__pyric/ui/docs/secure/rules-standard-library/"> The rules standard library </a> </li><li> <a href="/__pyric/ui/docs/secure/rtdb-rules-in-typescript/"> RTDB rules in TypeScript </a> </li><li> <a href="/__pyric/ui/docs/secure/firestore-rules-limits/"> Rules limits </a> </li><li> <a href="/__pyric/ui/docs/secure/audit-your-rules/"> Audit rules and data </a> </li> </ul> </div><div class="docs-nav-section"> <h2 class="docs-nav-heading"> Examples </h2> <ul> <li> <a href="/__pyric/ui/docs/examples/chess/"> Chess showcase </a> </li> </ul> </div> </div> </details><details class="docs-nav-group"> <summary>Observe &amp; shape</summary> <div class="docs-nav-group-body"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/observe/see-whats-happening/"> Traffic &amp; rule verdicts </a> </li><li> <a href="/__pyric/ui/docs/observe/resolve-runtime-status/"> Runtime status </a> </li><li> <a href="/__pyric/ui/docs/observe/shape-your-data/"> Seed, snapshot, replay </a> </li> </ul> </div> </div> </details><details class="docs-nav-group"> <summary>Ship &amp; test</summary> <div class="docs-nav-group-body"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/ship/ship-to-production/"> Ship to production </a> </li><li> <a href="/__pyric/ui/docs/ship/test-in-node/"> Test in Node </a> </li> </ul> </div> </div> </details><details class="docs-nav-group"> <summary>Work with an agent</summary> <div class="docs-nav-group-body"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/agent/set-up-your-agent/"> Connect an agent </a> </li><li> <a href="/__pyric/ui/docs/agent/work-with-an-agent/"> Use the MCP tools </a> </li><li> <a href="/__pyric/ui/docs/agent/watch-and-review/"> Watch in Pyric Studio </a> </li> </ul> </div> </div> </details><details class="docs-nav-group"> <summary>Trust</summary> <div class="docs-nav-group-body"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/trust/versioning-and-compatibility/"> Versioning and the fb tag </a> </li><li> <a href="/__pyric/ui/docs/trust/storage-rules-stdlib-research/"> Storage Rules modules: evidence and design </a> </li><li> <a href="/__pyric/ui/docs/trust/rules-standard-library/"> Rules standard library reference </a> </li> </ul> </div> </div> </details><details class="docs-nav-group" open> <summary>Conformance</summary> <div class="docs-nav-group-body"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/conformance-scores/"> Public API coverage </a> </li><li> <a href="/__pyric/ui/docs/trust/how-we-know-it-matches-firebase/"> How we know it matches Firebase </a> </li><li> <a href="/__pyric/ui/docs/app-compat/"> App </a> </li><li> <a href="/__pyric/ui/docs/ai-compat/"> AI Logic </a> </li><li> <a href="/__pyric/ui/docs/auth-compat/" aria-current="page"> Auth </a> </li><li> <a href="/__pyric/ui/docs/firestore-compat/"> Firestore </a> </li><li> <a href="/__pyric/ui/docs/database-compat/"> Realtime Database </a> </li><li> <a href="/__pyric/ui/docs/storage-compat/"> Storage </a> </li><li> <a href="/__pyric/ui/docs/messaging-compat/"> Messaging </a> </li><li> <a href="/__pyric/ui/docs/functions-rtdb-compat/"> Functions · RTDB </a> </li><li> <a href="/__pyric/ui/docs/rules-compat/"> Rules </a> </li> </ul> </div> </div> </details> <details class="docs-nav-group"> <summary>Reference</summary> <div class="docs-nav-group-body docs-nav-group-body--reference"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/api-reference/"> API reference </a> </li> </ul> </div><div class="docs-nav-section"> <h2 class="docs-nav-heading">pyric</h2> <ul> <li> <a href="/__pyric/ui/docs/pyric-app-reference-api/"> pyric/app </a> </li><li> <a href="/__pyric/ui/docs/pyric-firestore-reference-api/"> pyric/firestore </a> </li><li> <a href="/__pyric/ui/docs/pyric-auth-reference-api/"> pyric/auth </a> </li><li> <a href="/__pyric/ui/docs/pyric-ai-reference-api/"> pyric/ai </a> </li><li> <a href="/__pyric/ui/docs/pyric-ai-scripting-reference-api/"> pyric/ai/scripting </a> </li><li> <a href="/__pyric/ui/docs/pyric-database-reference-api/"> pyric/database </a> </li><li> <a href="/__pyric/ui/docs/pyric-storage-reference-api/"> pyric/storage </a> </li><li> <a href="/__pyric/ui/docs/pyric-messaging-reference-api/"> pyric/messaging </a> </li><li> <a href="/__pyric/ui/docs/pyric-messaging-sw-reference-api/"> pyric/messaging/sw </a> </li><li> <a href="/__pyric/ui/docs/pyric-rules-reference-api/"> pyric/rules </a> </li><li> <a href="/__pyric/ui/docs/pyric-sandbox-reference-api/"> pyric/sandbox </a> </li><li> <a href="/__pyric/ui/docs/pyric-sandbox-firestore-reference-api/"> pyric/sandbox/firestore </a> </li><li> <a href="/__pyric/ui/docs/pyric-sandbox-database-reference-api/"> pyric/sandbox/database </a> </li> </ul> </div><div class="docs-nav-section"> <h2 class="docs-nav-heading">pyric-admin</h2> <ul> <li> <a href="/__pyric/ui/docs/pyric-admin-app-reference-api/"> pyric-admin/app </a> </li><li> <a href="/__pyric/ui/docs/pyric-admin-firestore-reference-api/"> pyric-admin/firestore </a> </li><li> <a href="/__pyric/ui/docs/pyric-admin-auth-reference-api/"> pyric-admin/auth </a> </li><li> <a href="/__pyric/ui/docs/pyric-admin-database-reference-api/"> pyric-admin/database </a> </li><li> <a href="/__pyric/ui/docs/pyric-admin-storage-reference-api/"> pyric-admin/storage </a> </li><li> <a href="/__pyric/ui/docs/pyric-admin-messaging-reference-api/"> pyric-admin/messaging </a> </li> </ul> </div><div class="docs-nav-section"> <h2 class="docs-nav-heading">create-pyric</h2> <ul> <li> <a href="/__pyric/ui/docs/create-pyric-reference-api/"> create-pyric </a> </li> </ul> </div><div class="docs-nav-section"> <h2 class="docs-nav-heading">@pyric/cli</h2> <ul> <li> <a href="/__pyric/ui/docs/pyric-cli-credentials-node-reference-api/"> @pyric/cli/credentials/node </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-verify-reference-api/"> @pyric/cli/verify </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-conformance-reference-api/"> @pyric/cli/conformance </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-conformance-browser-reference-api/"> @pyric/cli/conformance/browser </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-conformance-docs-reference-api/"> @pyric/cli/conformance/docs </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-assurance-reference-api/"> @pyric/cli/assurance </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-assurance-browser-reference-api/"> @pyric/cli/assurance/browser </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-bridge-reference-api/"> @pyric/cli/bridge </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-bridge-client-reference-api/"> @pyric/cli/bridge/client </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-discover-reference-api/"> @pyric/cli/discover </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-vite-reference-api/"> @pyric/cli/vite </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-serve-worker-reference-api/"> @pyric/cli/serve/worker </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-remote-reference-api/"> @pyric/cli/remote </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-register-reference-api/"> @pyric/cli/register </a> </li> </ul> </div><div class="docs-nav-section"> <h2 class="docs-nav-heading">@pyric/ui</h2> <ul> <li> <a href="/__pyric/ui/docs/pyric-ui-auth-reference-api/"> @pyric/ui/auth </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-auth-hooks-reference-api/"> @pyric/ui/auth/hooks </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-primitives-reference-api/"> @pyric/ui/primitives </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-firestore-reference-api/"> @pyric/ui/firestore </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-firestore-hooks-reference-api/"> @pyric/ui/firestore/hooks </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-rtdb-reference-api/"> @pyric/ui/rtdb </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-storage-reference-api/"> @pyric/ui/storage </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-storage-hooks-reference-api/"> @pyric/ui/storage/hooks </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-traffic-reference-api/"> @pyric/ui/traffic </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-traffic-hooks-reference-api/"> @pyric/ui/traffic/hooks </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-events-reference-api/"> @pyric/ui/events </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-events-hooks-reference-api/"> @pyric/ui/events/hooks </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-agents-reference-api/"> @pyric/ui/agents </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-rules-reference-api/"> @pyric/ui/rules </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-rules-hooks-reference-api/"> @pyric/ui/rules/hooks </a> </li> </ul> </div> </div> </details> </nav> </details> <nav class="docs-nav" aria-label="Documentation"> <div class="docs-search" data-index-url="/__pyric/ui/docs/index.json"> <div class="docs-search-field"> <svg class="docs-search-icon" viewBox="0 0 16 16" width="14" height="14" aria-hidden="true"> <path d="M11.5 11.5 15 15M13 7A6 6 0 1 1 1 7a6 6 0 0 1 12 0Z" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"></path> </svg> <input type="text" class="docs-search-input" placeholder="Search docs" aria-label="Search docs" autocomplete="off" spellcheck="false"> <kbd class="docs-search-kbd">⌘K</kbd> </div> <ul class="docs-search-results" hidden></ul> </div> <details class="docs-nav-group" id="nav-group-overview"> <summary>Overview</summary> <div class="docs-nav-group-body"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/overview/"> Overview </a> </li><li> <a href="/__pyric/ui/docs/tutorial/"> Tutorial </a> </li><li> <a href="/__pyric/ui/docs/examples/"> Examples </a> </li> </ul> </div> </div> </details><details class="docs-nav-group" id="nav-group-get-started"> <summary>Get started</summary> <div class="docs-nav-group-body"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/get-started/start-building/"> Quickstart </a> </li><li> <a href="/__pyric/ui/docs/get-started/how-the-swap-works/"> How the swap works </a> </li> </ul> </div> </div> </details><details class="docs-nav-group" id="nav-group-build"> <summary>Build</summary> <div class="docs-nav-group-body"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/build/authentication/"> Authentication </a> </li><li> <a href="/__pyric/ui/docs/build/cloud-firestore/"> Cloud Firestore </a> </li><li> <a href="/__pyric/ui/docs/build/realtime-database/"> Realtime Database </a> </li><li> <a href="/__pyric/ui/docs/build/cloud-storage/"> Cloud Storage </a> </li><li> <a href="/__pyric/ui/docs/build/cloud-messaging/"> Cloud Messaging </a> </li><li> <a href="/__pyric/ui/docs/build/ai-logic/"> AI Logic </a> </li> </ul> </div> </div> </details><details class="docs-nav-group" id="nav-group-secure-debug"> <summary>Secure &amp; debug</summary> <div class="docs-nav-group-body"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/secure/secure-it-with-rules/"> Security Rules </a> </li><li> <a href="/__pyric/ui/docs/secure/simulate-and-lint/"> Simulate and lint before you deploy </a> </li><li> <a href="/__pyric/ui/docs/secure/write-a-rules-test-suite/"> Write a rules test suite </a> </li><li> <a href="/__pyric/ui/docs/secure/read-a-denial/"> Read a denial and understand it </a> </li><li> <a href="/__pyric/ui/docs/secure/rules-standard-library/"> The rules standard library </a> </li><li> <a href="/__pyric/ui/docs/secure/rtdb-rules-in-typescript/"> RTDB rules in TypeScript </a> </li><li> <a href="/__pyric/ui/docs/secure/firestore-rules-limits/"> Rules limits </a> </li><li> <a href="/__pyric/ui/docs/secure/audit-your-rules/"> Audit rules and data </a> </li> </ul> </div><div class="docs-nav-section"> <h2 class="docs-nav-heading" id="nav-section-secure-debug-examples"> Examples </h2> <ul> <li> <a href="/__pyric/ui/docs/examples/chess/"> Chess showcase </a> </li> </ul> </div> </div> </details><details class="docs-nav-group" id="nav-group-observe-shape"> <summary>Observe &amp; shape</summary> <div class="docs-nav-group-body"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/observe/see-whats-happening/"> Traffic &amp; rule verdicts </a> </li><li> <a href="/__pyric/ui/docs/observe/resolve-runtime-status/"> Runtime status </a> </li><li> <a href="/__pyric/ui/docs/observe/shape-your-data/"> Seed, snapshot, replay </a> </li> </ul> </div> </div> </details><details class="docs-nav-group" id="nav-group-ship-test"> <summary>Ship &amp; test</summary> <div class="docs-nav-group-body"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/ship/ship-to-production/"> Ship to production </a> </li><li> <a href="/__pyric/ui/docs/ship/test-in-node/"> Test in Node </a> </li> </ul> </div> </div> </details><details class="docs-nav-group" id="nav-group-work-with-an-agent"> <summary>Work with an agent</summary> <div class="docs-nav-group-body"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/agent/set-up-your-agent/"> Connect an agent </a> </li><li> <a href="/__pyric/ui/docs/agent/work-with-an-agent/"> Use the MCP tools </a> </li><li> <a href="/__pyric/ui/docs/agent/watch-and-review/"> Watch in Pyric Studio </a> </li> </ul> </div> </div> </details><details class="docs-nav-group" id="nav-group-trust"> <summary>Trust</summary> <div class="docs-nav-group-body"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/trust/versioning-and-compatibility/"> Versioning and the fb tag </a> </li><li> <a href="/__pyric/ui/docs/trust/storage-rules-stdlib-research/"> Storage Rules modules: evidence and design </a> </li><li> <a href="/__pyric/ui/docs/trust/rules-standard-library/"> Rules standard library reference </a> </li> </ul> </div> </div> </details><details class="docs-nav-group" id="nav-group-conformance" open> <summary>Conformance</summary> <div class="docs-nav-group-body"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/conformance-scores/"> Public API coverage </a> </li><li> <a href="/__pyric/ui/docs/trust/how-we-know-it-matches-firebase/"> How we know it matches Firebase </a> </li><li> <a href="/__pyric/ui/docs/app-compat/"> App </a> </li><li> <a href="/__pyric/ui/docs/ai-compat/"> AI Logic </a> </li><li> <a href="/__pyric/ui/docs/auth-compat/" aria-current="page"> Auth </a> </li><li> <a href="/__pyric/ui/docs/firestore-compat/"> Firestore </a> </li><li> <a href="/__pyric/ui/docs/database-compat/"> Realtime Database </a> </li><li> <a href="/__pyric/ui/docs/storage-compat/"> Storage </a> </li><li> <a href="/__pyric/ui/docs/messaging-compat/"> Messaging </a> </li><li> <a href="/__pyric/ui/docs/functions-rtdb-compat/"> Functions · RTDB </a> </li><li> <a href="/__pyric/ui/docs/rules-compat/"> Rules </a> </li> </ul> </div> </div> </details> <details class="docs-nav-group" id="nav-group-reference"> <summary>Reference</summary> <div class="docs-nav-group-body docs-nav-group-body--reference"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/api-reference/"> API reference </a> </li> </ul> </div><div class="docs-nav-section"> <h2 class="docs-nav-heading">pyric</h2> <ul> <li> <a href="/__pyric/ui/docs/pyric-app-reference-api/"> pyric/app </a> </li><li> <a href="/__pyric/ui/docs/pyric-firestore-reference-api/"> pyric/firestore </a> </li><li> <a href="/__pyric/ui/docs/pyric-auth-reference-api/"> pyric/auth </a> </li><li> <a href="/__pyric/ui/docs/pyric-ai-reference-api/"> pyric/ai </a> </li><li> <a href="/__pyric/ui/docs/pyric-ai-scripting-reference-api/"> pyric/ai/scripting </a> </li><li> <a href="/__pyric/ui/docs/pyric-database-reference-api/"> pyric/database </a> </li><li> <a href="/__pyric/ui/docs/pyric-storage-reference-api/"> pyric/storage </a> </li><li> <a href="/__pyric/ui/docs/pyric-messaging-reference-api/"> pyric/messaging </a> </li><li> <a href="/__pyric/ui/docs/pyric-messaging-sw-reference-api/"> pyric/messaging/sw </a> </li><li> <a href="/__pyric/ui/docs/pyric-rules-reference-api/"> pyric/rules </a> </li><li> <a href="/__pyric/ui/docs/pyric-sandbox-reference-api/"> pyric/sandbox </a> </li><li> <a href="/__pyric/ui/docs/pyric-sandbox-firestore-reference-api/"> pyric/sandbox/firestore </a> </li><li> <a href="/__pyric/ui/docs/pyric-sandbox-database-reference-api/"> pyric/sandbox/database </a> </li> </ul> </div><div class="docs-nav-section"> <h2 class="docs-nav-heading">pyric-admin</h2> <ul> <li> <a href="/__pyric/ui/docs/pyric-admin-app-reference-api/"> pyric-admin/app </a> </li><li> <a href="/__pyric/ui/docs/pyric-admin-firestore-reference-api/"> pyric-admin/firestore </a> </li><li> <a href="/__pyric/ui/docs/pyric-admin-auth-reference-api/"> pyric-admin/auth </a> </li><li> <a href="/__pyric/ui/docs/pyric-admin-database-reference-api/"> pyric-admin/database </a> </li><li> <a href="/__pyric/ui/docs/pyric-admin-storage-reference-api/"> pyric-admin/storage </a> </li><li> <a href="/__pyric/ui/docs/pyric-admin-messaging-reference-api/"> pyric-admin/messaging </a> </li> </ul> </div><div class="docs-nav-section"> <h2 class="docs-nav-heading">create-pyric</h2> <ul> <li> <a href="/__pyric/ui/docs/create-pyric-reference-api/"> create-pyric </a> </li> </ul> </div><div class="docs-nav-section"> <h2 class="docs-nav-heading">@pyric/cli</h2> <ul> <li> <a href="/__pyric/ui/docs/pyric-cli-credentials-node-reference-api/"> @pyric/cli/credentials/node </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-verify-reference-api/"> @pyric/cli/verify </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-conformance-reference-api/"> @pyric/cli/conformance </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-conformance-browser-reference-api/"> @pyric/cli/conformance/browser </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-conformance-docs-reference-api/"> @pyric/cli/conformance/docs </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-assurance-reference-api/"> @pyric/cli/assurance </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-assurance-browser-reference-api/"> @pyric/cli/assurance/browser </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-bridge-reference-api/"> @pyric/cli/bridge </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-bridge-client-reference-api/"> @pyric/cli/bridge/client </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-discover-reference-api/"> @pyric/cli/discover </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-vite-reference-api/"> @pyric/cli/vite </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-serve-worker-reference-api/"> @pyric/cli/serve/worker </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-remote-reference-api/"> @pyric/cli/remote </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-register-reference-api/"> @pyric/cli/register </a> </li> </ul> </div><div class="docs-nav-section"> <h2 class="docs-nav-heading">@pyric/ui</h2> <ul> <li> <a href="/__pyric/ui/docs/pyric-ui-auth-reference-api/"> @pyric/ui/auth </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-auth-hooks-reference-api/"> @pyric/ui/auth/hooks </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-primitives-reference-api/"> @pyric/ui/primitives </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-firestore-reference-api/"> @pyric/ui/firestore </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-firestore-hooks-reference-api/"> @pyric/ui/firestore/hooks </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-rtdb-reference-api/"> @pyric/ui/rtdb </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-storage-reference-api/"> @pyric/ui/storage </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-storage-hooks-reference-api/"> @pyric/ui/storage/hooks </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-traffic-reference-api/"> @pyric/ui/traffic </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-traffic-hooks-reference-api/"> @pyric/ui/traffic/hooks </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-events-reference-api/"> @pyric/ui/events </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-events-hooks-reference-api/"> @pyric/ui/events/hooks </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-agents-reference-api/"> @pyric/ui/agents </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-rules-reference-api/"> @pyric/ui/rules </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-rules-hooks-reference-api/"> @pyric/ui/rules/hooks </a> </li> </ul> </div> </div> </details> </nav> <main class="doc-main" data-astro-cid-mw7aashj> <nav class="docs-breadcrumbs" aria-label="Breadcrumb"> <ol> <li> <a href="/__pyric/ui/docs/conformance-scores/">Conformance</a> </li><li> <span class="docs-breadcrumbs-sep" aria-hidden="true">/</span> <span aria-current="page">Auth</span> </li> </ol> </nav> <article class="prose" data-astro-cid-mw7aashj> <!-- Generated from the conformance model (registry rows + surface contracts). Do not edit by hand; run bun run compat:generate. -->
2
+ <link rel="stylesheet" href="/__pyric/ui/_astro/_slug_.C6QfVOxJ.css"></head> <body> <header class="studio__bar" data-astro-cid-mw7aashj> <div class="studio__bar-inner" data-astro-cid-mw7aashj> <a class="studio__brand" href="/__pyric/ui/" aria-label="Pyric Studio" data-astro-cid-mw7aashj>Pyric</a> <nav class="studio__nav" aria-label="Studio tabs" data-astro-cid-mw7aashj> <a class="studio__nav-tab" href="/__pyric/ui/" data-astro-cid-mw7aashj> Home </a><a class="studio__nav-tab" href="/__pyric/ui/firestore" data-astro-cid-mw7aashj> Firestore </a><a class="studio__nav-tab" href="/__pyric/ui/auth" data-astro-cid-mw7aashj> Auth </a><a class="studio__nav-tab" href="/__pyric/ui/rtdb" data-astro-cid-mw7aashj> RTDB </a><a class="studio__nav-tab" href="/__pyric/ui/storage" data-astro-cid-mw7aashj> Storage </a><a class="studio__nav-tab" href="/__pyric/ui/traffic" data-astro-cid-mw7aashj> Traffic </a><a class="studio__nav-tab" href="/__pyric/ui/settings" data-astro-cid-mw7aashj> Settings </a><a class="studio__nav-tab" href="/__pyric/ui/docs" aria-current="page" data-astro-cid-mw7aashj> Docs </a> </nav> </div> </header> <div class="docs-shell" data-astro-cid-mw7aashj> <details class="docs-nav-drawer" data-astro-cid-mw7aashj> <summary data-astro-cid-mw7aashj>Navigate</summary> <nav class="docs-nav" aria-label="Documentation"> <div class="docs-search" data-index-url="/__pyric/ui/docs/index.json"> <div class="docs-search-field"> <svg class="docs-search-icon" viewBox="0 0 16 16" width="14" height="14" aria-hidden="true"> <path d="M11.5 11.5 15 15M13 7A6 6 0 1 1 1 7a6 6 0 0 1 12 0Z" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"></path> </svg> <input type="text" class="docs-search-input" placeholder="Search docs" aria-label="Search docs" autocomplete="off" spellcheck="false"> <kbd class="docs-search-kbd">⌘K</kbd> </div> <ul class="docs-search-results" hidden></ul> </div> <script type="module">let i=null;function d(t){return i||(i=fetch(t).then(e=>e.json())),i}function l(t,e){return t.title.toLowerCase().includes(e)||t.excerpt.toLowerCase().includes(e)?!0:t.headings.some(r=>r.text.toLowerCase().includes(e))}function u(t,e,r){if(e.innerHTML="",t.length===0){e.hidden=!1;{const n=document.createElement("li");n.className="docs-search-empty",n.textContent="No matches",e.appendChild(n)}return}e.hidden=!1;for(const n of t.slice(0,8)){const s=document.createElement("li"),c=document.createElement("a");c.href=n.path;const o=document.createElement("span");o.className="docs-search-result-title",o.textContent=n.title;const a=document.createElement("span");a.className="docs-search-result-meta",a.textContent=[n.group,n.section].filter(Boolean).join(" / "),c.append(o,a),s.appendChild(c),e.appendChild(s)}}document.querySelectorAll(".docs-search").forEach(t=>{const e=t.dataset.indexUrl,r=t.querySelector(".docs-search-input"),n=t.querySelector(".docs-search-results");r.addEventListener("input",async()=>{const s=r.value.trim().toLowerCase();if(!s){n.hidden=!0,n.innerHTML="";return}const{pages:c}=await d(e);r.value.trim().toLowerCase()===s&&u(c.filter(o=>l(o,s)),n)}),r.addEventListener("focus",()=>{d(e),n.childElementCount>0&&(n.hidden=!1)}),r.addEventListener("keydown",s=>{s.key==="Escape"&&(r.value="",n.hidden=!0,n.innerHTML="",r.blur())}),r.addEventListener("blur",()=>{window.setTimeout(()=>{n.hidden=!0},150)})});document.addEventListener("keydown",t=>{if(!(t.metaKey||t.ctrlKey)||t.key.toLowerCase()!=="k")return;const e=[...document.querySelectorAll(".docs-search-input")].find(r=>r.offsetParent!==null);e&&(t.preventDefault(),e.focus(),e.select())});</script> <details class="docs-nav-group"> <summary>Overview</summary> <div class="docs-nav-group-body"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/overview/"> Overview </a> </li><li> <a href="/__pyric/ui/docs/tutorial/"> Tutorial </a> </li><li> <a href="/__pyric/ui/docs/examples/"> Examples </a> </li> </ul> </div> </div> </details><details class="docs-nav-group"> <summary>Get started</summary> <div class="docs-nav-group-body"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/get-started/start-building/"> Quickstart </a> </li><li> <a href="/__pyric/ui/docs/get-started/vite/"> Vite </a> </li><li> <a href="/__pyric/ui/docs/get-started/nextjs/"> Next.js </a> </li><li> <a href="/__pyric/ui/docs/get-started/how-the-swap-works/"> How the swap works </a> </li> </ul> </div> </div> </details><details class="docs-nav-group"> <summary>Build</summary> <div class="docs-nav-group-body"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/build/authentication/"> Authentication </a> </li><li> <a href="/__pyric/ui/docs/build/cloud-firestore/"> Cloud Firestore </a> </li><li> <a href="/__pyric/ui/docs/build/realtime-database/"> Realtime Database </a> </li><li> <a href="/__pyric/ui/docs/build/cloud-storage/"> Cloud Storage </a> </li><li> <a href="/__pyric/ui/docs/build/cloud-messaging/"> Cloud Messaging </a> </li><li> <a href="/__pyric/ui/docs/build/ai-logic/"> AI Logic </a> </li> </ul> </div> </div> </details><details class="docs-nav-group"> <summary>Secure &amp; debug</summary> <div class="docs-nav-group-body"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/secure/secure-it-with-rules/"> Security Rules </a> </li><li> <a href="/__pyric/ui/docs/secure/simulate-and-lint/"> Simulate and lint before you deploy </a> </li><li> <a href="/__pyric/ui/docs/secure/write-a-rules-test-suite/"> Write a rules test suite </a> </li><li> <a href="/__pyric/ui/docs/secure/read-a-denial/"> Read a denial and understand it </a> </li><li> <a href="/__pyric/ui/docs/secure/rules-standard-library/"> The rules standard library </a> </li><li> <a href="/__pyric/ui/docs/secure/rtdb-rules-in-typescript/"> RTDB rules in TypeScript </a> </li><li> <a href="/__pyric/ui/docs/secure/firestore-rules-limits/"> Rules limits </a> </li><li> <a href="/__pyric/ui/docs/secure/audit-your-rules/"> Audit rules and data </a> </li> </ul> </div><div class="docs-nav-section"> <h2 class="docs-nav-heading"> Examples </h2> <ul> <li> <a href="/__pyric/ui/docs/examples/chess/"> Chess showcase </a> </li> </ul> </div> </div> </details><details class="docs-nav-group"> <summary>Observe &amp; shape</summary> <div class="docs-nav-group-body"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/observe/see-whats-happening/"> Traffic &amp; rule verdicts </a> </li><li> <a href="/__pyric/ui/docs/observe/resolve-runtime-status/"> Runtime status </a> </li><li> <a href="/__pyric/ui/docs/observe/shape-your-data/"> Seed, snapshot, replay </a> </li> </ul> </div> </div> </details><details class="docs-nav-group"> <summary>Ship &amp; test</summary> <div class="docs-nav-group-body"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/ship/ship-to-production/"> Ship to production </a> </li><li> <a href="/__pyric/ui/docs/ship/test-in-node/"> Test in Node </a> </li> </ul> </div> </div> </details><details class="docs-nav-group"> <summary>Work with an agent</summary> <div class="docs-nav-group-body"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/agent/set-up-your-agent/"> Connect an agent </a> </li><li> <a href="/__pyric/ui/docs/agent/work-with-an-agent/"> Use the MCP tools </a> </li><li> <a href="/__pyric/ui/docs/agent/watch-and-review/"> Watch in Pyric Studio </a> </li> </ul> </div> </div> </details><details class="docs-nav-group"> <summary>Trust</summary> <div class="docs-nav-group-body"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/trust/versioning-and-compatibility/"> Versioning and the fb tag </a> </li><li> <a href="/__pyric/ui/docs/trust/storage-rules-stdlib-research/"> Storage Rules modules: evidence and design </a> </li><li> <a href="/__pyric/ui/docs/trust/rules-standard-library/"> Rules standard library reference </a> </li> </ul> </div> </div> </details><details class="docs-nav-group" open> <summary>Conformance</summary> <div class="docs-nav-group-body"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/conformance-scores/"> Conformance scores </a> </li><li> <a href="/__pyric/ui/docs/trust/how-we-know-it-matches-firebase/"> How we know it matches Firebase </a> </li><li> <a href="/__pyric/ui/docs/app-compat/"> App </a> </li><li> <a href="/__pyric/ui/docs/ai-compat/"> AI Logic </a> </li><li> <a href="/__pyric/ui/docs/auth-compat/" aria-current="page"> Auth </a> </li><li> <a href="/__pyric/ui/docs/firestore-compat/"> Firestore </a> </li><li> <a href="/__pyric/ui/docs/database-compat/"> Realtime Database </a> </li><li> <a href="/__pyric/ui/docs/storage-compat/"> Storage </a> </li><li> <a href="/__pyric/ui/docs/messaging-compat/"> Messaging </a> </li><li> <a href="/__pyric/ui/docs/functions-rtdb-compat/"> Functions · RTDB </a> </li><li> <a href="/__pyric/ui/docs/rules-compat/"> Rules </a> </li> </ul> </div> </div> </details> <details class="docs-nav-group"> <summary>Reference</summary> <div class="docs-nav-group-body docs-nav-group-body--reference"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/api-reference/"> API reference </a> </li> </ul> </div><div class="docs-nav-section"> <h2 class="docs-nav-heading">pyric</h2> <ul> <li> <a href="/__pyric/ui/docs/pyric-app-reference-api/"> pyric/app </a> </li><li> <a href="/__pyric/ui/docs/pyric-firestore-reference-api/"> pyric/firestore </a> </li><li> <a href="/__pyric/ui/docs/pyric-auth-reference-api/"> pyric/auth </a> </li><li> <a href="/__pyric/ui/docs/pyric-ai-reference-api/"> pyric/ai </a> </li><li> <a href="/__pyric/ui/docs/pyric-ai-scripting-reference-api/"> pyric/ai/scripting </a> </li><li> <a href="/__pyric/ui/docs/pyric-database-reference-api/"> pyric/database </a> </li><li> <a href="/__pyric/ui/docs/pyric-storage-reference-api/"> pyric/storage </a> </li><li> <a href="/__pyric/ui/docs/pyric-messaging-reference-api/"> pyric/messaging </a> </li><li> <a href="/__pyric/ui/docs/pyric-messaging-sw-reference-api/"> pyric/messaging/sw </a> </li><li> <a href="/__pyric/ui/docs/pyric-rules-reference-api/"> pyric/rules </a> </li><li> <a href="/__pyric/ui/docs/pyric-sandbox-reference-api/"> pyric/sandbox </a> </li><li> <a href="/__pyric/ui/docs/pyric-sandbox-firestore-reference-api/"> pyric/sandbox/firestore </a> </li><li> <a href="/__pyric/ui/docs/pyric-sandbox-database-reference-api/"> pyric/sandbox/database </a> </li> </ul> </div><div class="docs-nav-section"> <h2 class="docs-nav-heading">pyric-admin</h2> <ul> <li> <a href="/__pyric/ui/docs/pyric-admin-app-reference-api/"> pyric-admin/app </a> </li><li> <a href="/__pyric/ui/docs/pyric-admin-firestore-reference-api/"> pyric-admin/firestore </a> </li><li> <a href="/__pyric/ui/docs/pyric-admin-auth-reference-api/"> pyric-admin/auth </a> </li><li> <a href="/__pyric/ui/docs/pyric-admin-database-reference-api/"> pyric-admin/database </a> </li><li> <a href="/__pyric/ui/docs/pyric-admin-storage-reference-api/"> pyric-admin/storage </a> </li><li> <a href="/__pyric/ui/docs/pyric-admin-messaging-reference-api/"> pyric-admin/messaging </a> </li> </ul> </div><div class="docs-nav-section"> <h2 class="docs-nav-heading">create-pyric</h2> <ul> <li> <a href="/__pyric/ui/docs/create-pyric-reference-api/"> create-pyric </a> </li> </ul> </div><div class="docs-nav-section"> <h2 class="docs-nav-heading">@pyric/cli</h2> <ul> <li> <a href="/__pyric/ui/docs/pyric-cli-credentials-node-reference-api/"> @pyric/cli/credentials/node </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-verify-reference-api/"> @pyric/cli/verify </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-conformance-reference-api/"> @pyric/cli/conformance </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-conformance-browser-reference-api/"> @pyric/cli/conformance/browser </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-conformance-docs-reference-api/"> @pyric/cli/conformance/docs </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-assurance-reference-api/"> @pyric/cli/assurance </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-assurance-browser-reference-api/"> @pyric/cli/assurance/browser </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-bridge-reference-api/"> @pyric/cli/bridge </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-bridge-client-reference-api/"> @pyric/cli/bridge/client </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-discover-reference-api/"> @pyric/cli/discover </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-vite-reference-api/"> @pyric/cli/vite </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-next-reference-api/"> @pyric/cli/next </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-serve-worker-reference-api/"> @pyric/cli/serve/worker </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-remote-reference-api/"> @pyric/cli/remote </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-register-reference-api/"> @pyric/cli/register </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-register-app-bridge-reference-api/"> @pyric/cli/register/app-bridge </a> </li> </ul> </div><div class="docs-nav-section"> <h2 class="docs-nav-heading">@pyric/ui</h2> <ul> <li> <a href="/__pyric/ui/docs/pyric-ui-auth-reference-api/"> @pyric/ui/auth </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-auth-hooks-reference-api/"> @pyric/ui/auth/hooks </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-primitives-reference-api/"> @pyric/ui/primitives </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-firestore-reference-api/"> @pyric/ui/firestore </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-firestore-hooks-reference-api/"> @pyric/ui/firestore/hooks </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-rtdb-reference-api/"> @pyric/ui/rtdb </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-storage-reference-api/"> @pyric/ui/storage </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-storage-hooks-reference-api/"> @pyric/ui/storage/hooks </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-traffic-reference-api/"> @pyric/ui/traffic </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-traffic-hooks-reference-api/"> @pyric/ui/traffic/hooks </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-events-reference-api/"> @pyric/ui/events </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-events-hooks-reference-api/"> @pyric/ui/events/hooks </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-agents-reference-api/"> @pyric/ui/agents </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-rules-reference-api/"> @pyric/ui/rules </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-rules-hooks-reference-api/"> @pyric/ui/rules/hooks </a> </li> </ul> </div> </div> </details> </nav> </details> <nav class="docs-nav" aria-label="Documentation"> <div class="docs-search" data-index-url="/__pyric/ui/docs/index.json"> <div class="docs-search-field"> <svg class="docs-search-icon" viewBox="0 0 16 16" width="14" height="14" aria-hidden="true"> <path d="M11.5 11.5 15 15M13 7A6 6 0 1 1 1 7a6 6 0 0 1 12 0Z" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"></path> </svg> <input type="text" class="docs-search-input" placeholder="Search docs" aria-label="Search docs" autocomplete="off" spellcheck="false"> <kbd class="docs-search-kbd">⌘K</kbd> </div> <ul class="docs-search-results" hidden></ul> </div> <details class="docs-nav-group" id="nav-group-overview"> <summary>Overview</summary> <div class="docs-nav-group-body"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/overview/"> Overview </a> </li><li> <a href="/__pyric/ui/docs/tutorial/"> Tutorial </a> </li><li> <a href="/__pyric/ui/docs/examples/"> Examples </a> </li> </ul> </div> </div> </details><details class="docs-nav-group" id="nav-group-get-started"> <summary>Get started</summary> <div class="docs-nav-group-body"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/get-started/start-building/"> Quickstart </a> </li><li> <a href="/__pyric/ui/docs/get-started/vite/"> Vite </a> </li><li> <a href="/__pyric/ui/docs/get-started/nextjs/"> Next.js </a> </li><li> <a href="/__pyric/ui/docs/get-started/how-the-swap-works/"> How the swap works </a> </li> </ul> </div> </div> </details><details class="docs-nav-group" id="nav-group-build"> <summary>Build</summary> <div class="docs-nav-group-body"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/build/authentication/"> Authentication </a> </li><li> <a href="/__pyric/ui/docs/build/cloud-firestore/"> Cloud Firestore </a> </li><li> <a href="/__pyric/ui/docs/build/realtime-database/"> Realtime Database </a> </li><li> <a href="/__pyric/ui/docs/build/cloud-storage/"> Cloud Storage </a> </li><li> <a href="/__pyric/ui/docs/build/cloud-messaging/"> Cloud Messaging </a> </li><li> <a href="/__pyric/ui/docs/build/ai-logic/"> AI Logic </a> </li> </ul> </div> </div> </details><details class="docs-nav-group" id="nav-group-secure-debug"> <summary>Secure &amp; debug</summary> <div class="docs-nav-group-body"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/secure/secure-it-with-rules/"> Security Rules </a> </li><li> <a href="/__pyric/ui/docs/secure/simulate-and-lint/"> Simulate and lint before you deploy </a> </li><li> <a href="/__pyric/ui/docs/secure/write-a-rules-test-suite/"> Write a rules test suite </a> </li><li> <a href="/__pyric/ui/docs/secure/read-a-denial/"> Read a denial and understand it </a> </li><li> <a href="/__pyric/ui/docs/secure/rules-standard-library/"> The rules standard library </a> </li><li> <a href="/__pyric/ui/docs/secure/rtdb-rules-in-typescript/"> RTDB rules in TypeScript </a> </li><li> <a href="/__pyric/ui/docs/secure/firestore-rules-limits/"> Rules limits </a> </li><li> <a href="/__pyric/ui/docs/secure/audit-your-rules/"> Audit rules and data </a> </li> </ul> </div><div class="docs-nav-section"> <h2 class="docs-nav-heading" id="nav-section-secure-debug-examples"> Examples </h2> <ul> <li> <a href="/__pyric/ui/docs/examples/chess/"> Chess showcase </a> </li> </ul> </div> </div> </details><details class="docs-nav-group" id="nav-group-observe-shape"> <summary>Observe &amp; shape</summary> <div class="docs-nav-group-body"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/observe/see-whats-happening/"> Traffic &amp; rule verdicts </a> </li><li> <a href="/__pyric/ui/docs/observe/resolve-runtime-status/"> Runtime status </a> </li><li> <a href="/__pyric/ui/docs/observe/shape-your-data/"> Seed, snapshot, replay </a> </li> </ul> </div> </div> </details><details class="docs-nav-group" id="nav-group-ship-test"> <summary>Ship &amp; test</summary> <div class="docs-nav-group-body"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/ship/ship-to-production/"> Ship to production </a> </li><li> <a href="/__pyric/ui/docs/ship/test-in-node/"> Test in Node </a> </li> </ul> </div> </div> </details><details class="docs-nav-group" id="nav-group-work-with-an-agent"> <summary>Work with an agent</summary> <div class="docs-nav-group-body"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/agent/set-up-your-agent/"> Connect an agent </a> </li><li> <a href="/__pyric/ui/docs/agent/work-with-an-agent/"> Use the MCP tools </a> </li><li> <a href="/__pyric/ui/docs/agent/watch-and-review/"> Watch in Pyric Studio </a> </li> </ul> </div> </div> </details><details class="docs-nav-group" id="nav-group-trust"> <summary>Trust</summary> <div class="docs-nav-group-body"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/trust/versioning-and-compatibility/"> Versioning and the fb tag </a> </li><li> <a href="/__pyric/ui/docs/trust/storage-rules-stdlib-research/"> Storage Rules modules: evidence and design </a> </li><li> <a href="/__pyric/ui/docs/trust/rules-standard-library/"> Rules standard library reference </a> </li> </ul> </div> </div> </details><details class="docs-nav-group" id="nav-group-conformance" open> <summary>Conformance</summary> <div class="docs-nav-group-body"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/conformance-scores/"> Conformance scores </a> </li><li> <a href="/__pyric/ui/docs/trust/how-we-know-it-matches-firebase/"> How we know it matches Firebase </a> </li><li> <a href="/__pyric/ui/docs/app-compat/"> App </a> </li><li> <a href="/__pyric/ui/docs/ai-compat/"> AI Logic </a> </li><li> <a href="/__pyric/ui/docs/auth-compat/" aria-current="page"> Auth </a> </li><li> <a href="/__pyric/ui/docs/firestore-compat/"> Firestore </a> </li><li> <a href="/__pyric/ui/docs/database-compat/"> Realtime Database </a> </li><li> <a href="/__pyric/ui/docs/storage-compat/"> Storage </a> </li><li> <a href="/__pyric/ui/docs/messaging-compat/"> Messaging </a> </li><li> <a href="/__pyric/ui/docs/functions-rtdb-compat/"> Functions · RTDB </a> </li><li> <a href="/__pyric/ui/docs/rules-compat/"> Rules </a> </li> </ul> </div> </div> </details> <details class="docs-nav-group" id="nav-group-reference"> <summary>Reference</summary> <div class="docs-nav-group-body docs-nav-group-body--reference"> <div class="docs-nav-section"> <ul> <li> <a href="/__pyric/ui/docs/api-reference/"> API reference </a> </li> </ul> </div><div class="docs-nav-section"> <h2 class="docs-nav-heading">pyric</h2> <ul> <li> <a href="/__pyric/ui/docs/pyric-app-reference-api/"> pyric/app </a> </li><li> <a href="/__pyric/ui/docs/pyric-firestore-reference-api/"> pyric/firestore </a> </li><li> <a href="/__pyric/ui/docs/pyric-auth-reference-api/"> pyric/auth </a> </li><li> <a href="/__pyric/ui/docs/pyric-ai-reference-api/"> pyric/ai </a> </li><li> <a href="/__pyric/ui/docs/pyric-ai-scripting-reference-api/"> pyric/ai/scripting </a> </li><li> <a href="/__pyric/ui/docs/pyric-database-reference-api/"> pyric/database </a> </li><li> <a href="/__pyric/ui/docs/pyric-storage-reference-api/"> pyric/storage </a> </li><li> <a href="/__pyric/ui/docs/pyric-messaging-reference-api/"> pyric/messaging </a> </li><li> <a href="/__pyric/ui/docs/pyric-messaging-sw-reference-api/"> pyric/messaging/sw </a> </li><li> <a href="/__pyric/ui/docs/pyric-rules-reference-api/"> pyric/rules </a> </li><li> <a href="/__pyric/ui/docs/pyric-sandbox-reference-api/"> pyric/sandbox </a> </li><li> <a href="/__pyric/ui/docs/pyric-sandbox-firestore-reference-api/"> pyric/sandbox/firestore </a> </li><li> <a href="/__pyric/ui/docs/pyric-sandbox-database-reference-api/"> pyric/sandbox/database </a> </li> </ul> </div><div class="docs-nav-section"> <h2 class="docs-nav-heading">pyric-admin</h2> <ul> <li> <a href="/__pyric/ui/docs/pyric-admin-app-reference-api/"> pyric-admin/app </a> </li><li> <a href="/__pyric/ui/docs/pyric-admin-firestore-reference-api/"> pyric-admin/firestore </a> </li><li> <a href="/__pyric/ui/docs/pyric-admin-auth-reference-api/"> pyric-admin/auth </a> </li><li> <a href="/__pyric/ui/docs/pyric-admin-database-reference-api/"> pyric-admin/database </a> </li><li> <a href="/__pyric/ui/docs/pyric-admin-storage-reference-api/"> pyric-admin/storage </a> </li><li> <a href="/__pyric/ui/docs/pyric-admin-messaging-reference-api/"> pyric-admin/messaging </a> </li> </ul> </div><div class="docs-nav-section"> <h2 class="docs-nav-heading">create-pyric</h2> <ul> <li> <a href="/__pyric/ui/docs/create-pyric-reference-api/"> create-pyric </a> </li> </ul> </div><div class="docs-nav-section"> <h2 class="docs-nav-heading">@pyric/cli</h2> <ul> <li> <a href="/__pyric/ui/docs/pyric-cli-credentials-node-reference-api/"> @pyric/cli/credentials/node </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-verify-reference-api/"> @pyric/cli/verify </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-conformance-reference-api/"> @pyric/cli/conformance </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-conformance-browser-reference-api/"> @pyric/cli/conformance/browser </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-conformance-docs-reference-api/"> @pyric/cli/conformance/docs </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-assurance-reference-api/"> @pyric/cli/assurance </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-assurance-browser-reference-api/"> @pyric/cli/assurance/browser </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-bridge-reference-api/"> @pyric/cli/bridge </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-bridge-client-reference-api/"> @pyric/cli/bridge/client </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-discover-reference-api/"> @pyric/cli/discover </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-vite-reference-api/"> @pyric/cli/vite </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-next-reference-api/"> @pyric/cli/next </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-serve-worker-reference-api/"> @pyric/cli/serve/worker </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-remote-reference-api/"> @pyric/cli/remote </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-register-reference-api/"> @pyric/cli/register </a> </li><li> <a href="/__pyric/ui/docs/pyric-cli-register-app-bridge-reference-api/"> @pyric/cli/register/app-bridge </a> </li> </ul> </div><div class="docs-nav-section"> <h2 class="docs-nav-heading">@pyric/ui</h2> <ul> <li> <a href="/__pyric/ui/docs/pyric-ui-auth-reference-api/"> @pyric/ui/auth </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-auth-hooks-reference-api/"> @pyric/ui/auth/hooks </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-primitives-reference-api/"> @pyric/ui/primitives </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-firestore-reference-api/"> @pyric/ui/firestore </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-firestore-hooks-reference-api/"> @pyric/ui/firestore/hooks </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-rtdb-reference-api/"> @pyric/ui/rtdb </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-storage-reference-api/"> @pyric/ui/storage </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-storage-hooks-reference-api/"> @pyric/ui/storage/hooks </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-traffic-reference-api/"> @pyric/ui/traffic </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-traffic-hooks-reference-api/"> @pyric/ui/traffic/hooks </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-events-reference-api/"> @pyric/ui/events </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-events-hooks-reference-api/"> @pyric/ui/events/hooks </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-agents-reference-api/"> @pyric/ui/agents </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-rules-reference-api/"> @pyric/ui/rules </a> </li><li> <a href="/__pyric/ui/docs/pyric-ui-rules-hooks-reference-api/"> @pyric/ui/rules/hooks </a> </li> </ul> </div> </div> </details> </nav> <main class="doc-main" data-astro-cid-mw7aashj> <nav class="docs-breadcrumbs" aria-label="Breadcrumb"> <ol> <li> <a href="/__pyric/ui/docs/conformance-scores/">Conformance</a> </li><li> <span class="docs-breadcrumbs-sep" aria-hidden="true">/</span> <span aria-current="page">Auth</span> </li> </ol> </nav> <article class="prose" data-astro-cid-mw7aashj> <!-- Generated from the conformance model (registry rows + surface contracts). Do not edit by hand; run bun run compat:generate. -->
3
3
  <h1 id="pyricauth-compatibility-matrix"><code>pyric/auth</code> compatibility matrix</h1>
4
4
  <div class="compat-stat">
5
5
  <p class="compat-stat-figure">
@@ -22,11 +22,11 @@
22
22
  <div class="compat-list">
23
23
  <details class="compat-row" data-status="ok">
24
24
  <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">getAuth(target)</code><span class="compat-sub"><span class="compat-behavior">Returns a stable <code>Auth</code> handle for repeated calls with the same input</span></span></span></summary>
25
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-anonymous.test.ts</code> + canonical Node register child (<code>register-child.test.ts</code>)</div></div>
25
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-anonymous.test.ts#L13" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-anonymous.test.ts</code></a> + canonical Node register child (<a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/register-child.test.ts#L152" target="_blank" rel="noopener noreferrer"><code>register-child.test.ts</code></a>)</div></div>
26
26
  </details>
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">getAuth(target)</code><span class="compat-sub"><span class="compat-behavior"><code>getAuth(sandbox)</code> dispatches to the sandbox backend</span></span></span></summary>
29
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-anonymous.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/auth/sandbox-anonymous.test.ts#L13" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-anonymous.test.ts</code></a></div></div>
30
30
  </details>
31
31
  <details class="compat-row" data-status="unverified">
32
32
  <summary class="compat-line"><span class="compat-dot" data-status="unverified" role="img" aria-label="Unverified" title="Unverified"></span><span class="compat-main"><code class="compat-api">getAuth(target)</code><span class="compat-sub"><span class="compat-behavior">Without sandbox package swapping, canonical <code>firebase/auth</code> imports remain Firebase and never enter this mirror</span></span></span></summary>
@@ -34,51 +34,51 @@
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">getAuth(target)</code><span class="compat-sub"><span class="compat-behavior">After package swapping, bare <code>getAuth()</code> resolves the registered default sandbox app; without swapping, Firebase retains its <code>app/no-app</code> behavior when no default app exists</span></span></span></summary>
37
- <div class="compat-evidence"><div class="compat-probe">canonical Node register child (<code>register-child.test.ts</code>) + oracle: <code>packages/conformance/observations/auth/auth-bare-getauth-no-default-app.json</code> (<code>code: 'app/no-app'</code> against blockingfun, fb-js-sdk 12.13.0 — confirms unswapped Firebase behavior)</div>
37
+ <div class="compat-evidence"><div class="compat-probe">canonical Node register child (<a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/register-child.test.ts#L152" target="_blank" rel="noopener noreferrer"><code>register-child.test.ts</code></a>) + oracle: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/auth/auth-bare-getauth-no-default-app.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/auth/auth-bare-getauth-no-default-app.json</code></a> (<code>code: 'app/no-app'</code> against blockingfun, fb-js-sdk 12.13.0 — confirms unswapped Firebase behavior)</div>
38
38
  <div class="compat-note">(wrap)</div></div>
39
39
  </details>
40
40
  <details class="compat-row" data-status="ok">
41
41
  <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">getAuth(target)</code><span class="compat-sub"><span class="compat-behavior"><code>auth.currentUser</code> is a live getter, not a snapshot — reads through to the backend on every access</span></span></span></summary>
42
- <div class="compat-evidence"><div class="compat-probe">implicit in <code>unit:sandbox-anonymous.test.ts</code></div></div>
42
+ <div class="compat-evidence"><div class="compat-probe">implicit in <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-anonymous.test.ts#L13" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-anonymous.test.ts</code></a></div></div>
43
43
  </details>
44
44
  </div>
45
45
  <h2 id="multi-app-auth-session-topology">Multi-app Auth session topology</h2>
46
46
  <div class="compat-list">
47
47
  <details class="compat-row" data-status="ok">
48
48
  <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">getAuth(app)</code><span class="compat-sub"><span class="compat-behavior">Equal-config Firebase apps have independent active Auth sessions; signing in or out on one app does not change another app's currentUser</span></span></span></summary>
49
- <div class="compat-evidence"><div class="compat-probe">oracle: <code>app-production-multi-app-topology</code> (firebase 12.13.0, real Chromium against production) + twin replay: <code>packages/pyric/test/app/production-multi-app-oracle.test.ts</code></div></div>
49
+ <div class="compat-evidence"><div class="compat-probe">oracle: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/app/app-production-multi-app-topology.json" target="_blank" rel="noopener noreferrer"><code>app-production-multi-app-topology</code></a> (firebase 12.13.0, real Chromium against production) + twin replay: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/app/production-multi-app-oracle.test.ts#L15" target="_blank" rel="noopener noreferrer"><code>packages/pyric/test/app/production-multi-app-oracle.test.ts</code></a></div></div>
50
50
  </details>
51
51
  <details class="compat-row" data-status="ok">
52
52
  <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">browserLocalPersistence across same-named app instances in sibling tabs</code><span class="compat-sub"><span class="compat-behavior">After the sibling receives the LOCAL persistence event, same-named Firebase app instances in sibling tabs keep independent active Auth sessions; an anonymous sign-in in one tab does not update the sibling tab's currentUser during the bounded post-signal observation window</span></span></span></summary>
53
- <div class="compat-evidence"><div class="compat-probe">oracle: <code>app-production-cross-tab-auth-persistence</code> (firebase 12.13.0, two real Chromium pages in one browser context) + served twin: <code>packages/cli/test/e2e/app-multi-app.pw.ts</code></div></div>
53
+ <div class="compat-evidence"><div class="compat-probe">oracle: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/app/app-production-cross-tab-auth-persistence.json" target="_blank" rel="noopener noreferrer"><code>app-production-cross-tab-auth-persistence</code></a> (firebase 12.13.0, two real Chromium pages in one browser context) + served twin: <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/e2e/app-multi-app.pw.ts#L12" target="_blank" rel="noopener noreferrer"><code>packages/cli/test/e2e/app-multi-app.pw.ts</code></a></div></div>
54
54
  </details>
55
55
  </div>
56
56
  <h2 id="signinanonymouslyauth--anonymous"><code>signInAnonymously(auth)</code> — anonymous</h2>
57
57
  <div class="compat-list">
58
58
  <details class="compat-row" data-status="ok">
59
59
  <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">signInAnonymously(auth)</code><span class="compat-sub"><span class="compat-behavior">Returns <code>UserCredential</code> with <code>providerId: null</code>, <code>operationType: 'signIn'</code>, and a <code>User</code> with <code>isAnonymous: true</code>, <code>email: null</code>, <code>displayName: null</code></span></span></span></summary>
60
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-anonymous.test.ts</code>, <code>playground:auth-anonymous</code> (bundled) + <code>playground:auth-row-6-anon-credential-shape</code> (one-claim) + oracle: <code>packages/conformance/observations/auth/auth-anonymous-credential-providerid.json</code> (<code>providerId: null</code> against blockingfun, fb-js-sdk 12.13.0). Prior matrix language said <code>providerId: 'anonymous'</code>; corrected after empirical observation. Sandbox aligned to prod in the same commit.</div></div>
60
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-anonymous.test.ts#L30" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-anonymous.test.ts</code></a>, <code>playground:auth-anonymous</code> (bundled) + <code>playground:auth-row-6-anon-credential-shape</code> (one-claim) + oracle: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/auth/auth-anonymous-credential-providerid.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/auth/auth-anonymous-credential-providerid.json</code></a> (<code>providerId: null</code> against blockingfun, fb-js-sdk 12.13.0). Prior matrix language said <code>providerId: 'anonymous'</code>; corrected after empirical observation. Sandbox aligned to prod in the same commit.</div></div>
61
61
  </details>
62
62
  <details class="compat-row" data-status="diverged">
63
63
  <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">signInAnonymously(auth)</code><span class="compat-sub"><span class="compat-behavior">Auto-generates a uid for fresh sign-ins (sandbox format: <code>anonymous-{N}</code>)</span></span></span></summary>
64
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-anonymous.test.ts</code> — prod uids are 28-char base64-ish; sandbox uses a readable counter for debuggability</div>
64
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-anonymous.test.ts#L7" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-anonymous.test.ts</code></a> — prod uids are 28-char base64-ish; sandbox uses a readable counter for debuggability</div>
65
65
  <div class="compat-note">format</div></div>
66
66
  </details>
67
67
  <details class="compat-row" data-status="ok">
68
68
  <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">signInAnonymously(auth)</code><span class="compat-sub"><span class="compat-behavior">If an anonymous user is already signed in, returns the SAME user (no fresh uid mint)</span></span></span></summary>
69
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-anonymous.test.ts</code> ("idempotent while signed in") — fix from #399</div></div>
69
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-anonymous.test.ts#L7" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-anonymous.test.ts</code></a> ("idempotent while signed in") — fix from #399</div></div>
70
70
  </details>
71
71
  <details class="compat-row" data-status="ok">
72
72
  <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">signInAnonymously(auth)</code><span class="compat-sub"><span class="compat-behavior">After <code>signOut</code>, a subsequent <code>signInAnonymously</code> mints a fresh uid</span></span></span></summary>
73
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-anonymous.test.ts</code>, <code>playground:auth-anonymous</code> (bundled) + <code>playground:auth-row-9-anon-fresh-uid-after-signout</code> (one-claim)</div></div>
73
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-anonymous.test.ts#L7" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-anonymous.test.ts</code></a>, <code>playground:auth-anonymous</code> (bundled) + <code>playground:auth-row-9-anon-fresh-uid-after-signout</code> (one-claim)</div></div>
74
74
  </details>
75
75
  <details class="compat-row" data-status="ok">
76
76
  <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">signInAnonymously(auth)</code><span class="compat-sub"><span class="compat-behavior">Fires <code>onAuthStateChanged</code> exactly once per state transition (no same-value double-fire)</span></span></span></summary>
77
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-listeners.test.ts</code>, <code>playground:auth-anonymous</code> (bundled) + <code>playground:auth-row-10-onauthstatechanged-one-per-transition</code> (one-claim) — fix from #399 + oracle: <code>packages/conformance/observations/auth/auth-row-10-onauthstatechanged-one-per-transition.json</code> (against blockingfun, fb-js-sdk 12.13.0: subscribe → 1 initial null fire → <code>signInAnonymously</code> → +1 → <code>signOut</code> → +1 → <code>signInAnonymously</code> → +1. <code>eachTransitionFiredExactlyOnce: true</code> — every transition produces exactly one fire)</div></div>
77
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-listeners.test.ts#L24" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-listeners.test.ts</code></a>, <code>playground:auth-anonymous</code> (bundled) + <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/auth/auth-row-10-onauthstatechanged-one-per-transition.json" target="_blank" rel="noopener noreferrer"><code>playground:auth-row-10-onauthstatechanged-one-per-transition</code></a> (one-claim) — fix from #399 + oracle: <code>packages/conformance/observations/auth/auth-row-10-onauthstatechanged-one-per-transition.json</code> (against blockingfun, fb-js-sdk 12.13.0: subscribe → 1 initial null fire → <code>signInAnonymously</code> → +1 → <code>signOut</code> → +1 → <code>signInAnonymously</code> → +1. <code>eachTransitionFiredExactlyOnce: true</code> — every transition produces exactly one fire)</div></div>
78
78
  </details>
79
79
  <details class="compat-row" data-status="ok">
80
80
  <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">signInAnonymously(auth)</code><span class="compat-sub"><span class="compat-behavior">Writes through to <code>sandbox.currentUser</code> so rules engines see <code>request.auth.uid</code></span></span></span></summary>
81
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-anonymous.test.ts</code>, <code>playground:auth-anonymous</code> (bundled) + <code>playground:auth-row-11-anon-uid-visible-to-rules</code> (one-claim)</div></div>
81
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-anonymous.test.ts#L7" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-anonymous.test.ts</code></a>, <code>playground:auth-anonymous</code> (bundled) + <code>playground:auth-row-11-anon-uid-visible-to-rules</code> (one-claim)</div></div>
82
82
  </details>
83
83
  <details class="compat-row" data-status="diverged">
84
84
  <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">signInAnonymously(auth)</code><span class="compat-sub"><span class="compat-behavior">Anonymous users persist across page reload via configured <code>Persistence</code> (prod only — sandbox has no persistence layer)</span></span></span></summary>
@@ -89,19 +89,19 @@
89
89
  <div class="compat-list">
90
90
  <details class="compat-row" data-status="ok">
91
91
  <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">signInWithEmailAndPassword(auth, email, password)</code><span class="compat-sub"><span class="compat-behavior">Returns <code>UserCredential</code> with <code>providerId: null</code> (NOT <code>'password'</code> — only OAuth/phone responses carry a providerId; upstream <code>providerIdForResponse</code> returns null for email/password), <code>operationType: 'signIn'</code>, and a <code>User</code> with the stored uid + email</span></span></span></summary>
92
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-email-password.test.ts</code> + oracle: <code>packages/conformance/observations/auth/auth-createUser-operationType.json</code> (<code>providerId: null</code> against blockingfun, fb-js-sdk 12.13.0). Prior matrix language said <code>providerId: 'password'</code>; corrected after the oracle contradicted it (AUTH-B2).</div></div>
92
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-email-password.test.ts#L29" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-email-password.test.ts</code></a> + oracle: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/auth/auth-createUser-operationType.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/auth/auth-createUser-operationType.json</code></a> (<code>providerId: null</code> against blockingfun, fb-js-sdk 12.13.0). Prior matrix language said <code>providerId: 'password'</code>; corrected after the oracle contradicted it (AUTH-B2).</div></div>
93
93
  </details>
94
94
  <details class="compat-row" data-status="ok">
95
95
  <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">signInWithEmailAndPassword(auth, email, password)</code><span class="compat-sub"><span class="compat-behavior">Throws <code>auth/user-not-found</code> when the email isn't seeded / hasn't been created</span></span></span></summary>
96
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-email-password.test.ts</code> + oracle: <code>packages/conformance/observations/auth/auth-user-not-found-error-code.json</code> (<code>code: 'auth/user-not-found'</code> against blockingfun, fb-js-sdk 12.13.0; matches sandbox)</div></div>
96
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-email-password.test.ts#L4" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-email-password.test.ts</code></a> + oracle: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/auth/auth-user-not-found-error-code.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/auth/auth-user-not-found-error-code.json</code></a> (<code>code: 'auth/user-not-found'</code> against blockingfun, fb-js-sdk 12.13.0; matches sandbox)</div></div>
97
97
  </details>
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">signInWithEmailAndPassword(auth, email, password)</code><span class="compat-sub"><span class="compat-behavior">Throws <code>auth/wrong-password</code> when the password doesn't match</span></span></span></summary>
100
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-email-password.test.ts</code>, <code>playground:auth-email-password</code> (bundled) + <code>playground:auth-row-15-wrong-password-rejects</code> (one-claim) + oracle: <code>packages/conformance/observations/auth/auth-wrong-password-error-code.json</code> (<code>code: 'auth/wrong-password'</code> against blockingfun, fb-js-sdk 12.13.0; matches sandbox)</div></div>
100
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-email-password.test.ts#L4" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-email-password.test.ts</code></a>, <code>playground:auth-email-password</code> (bundled) + <code>playground:auth-row-15-wrong-password-rejects</code> (one-claim) + oracle: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/auth/auth-wrong-password-error-code.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/auth/auth-wrong-password-error-code.json</code></a> (<code>code: 'auth/wrong-password'</code> against blockingfun, fb-js-sdk 12.13.0; matches sandbox)</div></div>
101
101
  </details>
102
102
  <details class="compat-row" data-status="diverged">
103
103
  <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">signInWithEmailAndPassword(auth, email, password)</code><span class="compat-sub"><span class="compat-behavior">An EMPTY password throws <code>auth/missing-password</code> (message "A non-empty password must be provided"), fired before the user-DB lookup so it can't be used to enumerate seeded emails. Upstream maps the <code>MISSING_PASSWORD</code> server error (<code>core/errors.ts:92,282,563</code>). ⚠ best-known semantics — message text not yet captured against a live project (STOP-flagged for an oracle pass; the <code>.code</code> is the load-bearing part).</span></span></span></summary>
104
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-cluster-b9-b12.test.ts</code> (locks AUTH-B11)</div></div>
104
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-cluster-b9-b12.test.ts#L18" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-cluster-b9-b12.test.ts</code></a> (locks AUTH-B11)</div></div>
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">signInWithEmailAndPassword(auth, email, password)</code><span class="compat-sub"><span class="compat-behavior">Re-signing in after <code>signOut</code> returns the <strong>same</strong> uid (passwords persist within the sandbox lifetime)</span></span></span></summary>
@@ -109,19 +109,19 @@
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">signInWithEmailAndPassword(auth, email, password)</code><span class="compat-sub"><span class="compat-behavior">Fires <code>onAuthStateChanged</code> with the new user once</span></span></span></summary>
112
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-listeners.test.ts</code> + oracle: <code>packages/conformance/observations/auth/auth-row-17-signin-email-password-fires-once.json</code> (against blockingfun, fb-js-sdk 12.13.0: createUser → signOut → subscribe (1 initial null fire) → <code>signInWithEmailAndPassword</code> → <code>firesForSignIn: 1</code> with the signed-in uid, <code>lastFireUidMatches: true</code>)</div></div>
112
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-listeners.test.ts#L25" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-listeners.test.ts</code></a> + oracle: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/auth/auth-row-17-signin-email-password-fires-once.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/auth/auth-row-17-signin-email-password-fires-once.json</code></a> (against blockingfun, fb-js-sdk 12.13.0: createUser → signOut → subscribe (1 initial null fire) → <code>signInWithEmailAndPassword</code> → <code>firesForSignIn: 1</code> with the signed-in uid, <code>lastFireUidMatches: true</code>)</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">signInWithEmailAndPassword(auth, email, password)</code><span class="compat-sub"><span class="compat-behavior">Email validation (RFC 5322ish) — rejects empty, missing <code>@</code>, missing local-part, missing domain with <code>auth/invalid-email</code>. Runs on both <code>signInWithEmailAndPassword</code> and <code>createUserWithEmailAndPassword</code> before any user-DB lookup, so consumers shipping malformed input see the same error sandbox vs prod.</span></span></span></summary>
116
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-email-password.test.ts</code> + oracle: <code>packages/conformance/observations/auth/auth-row-18-invalid-email-error-code.json</code> (<code>code: 'auth/invalid-email'</code>, message <code>Firebase: Error (auth/invalid-email).</code> against blockingfun, fb-js-sdk 12.13.0)</div></div>
116
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-email-password.test.ts#L151" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-email-password.test.ts</code></a> + oracle: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/auth/auth-row-18-invalid-email-error-code.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/auth/auth-row-18-invalid-email-error-code.json</code></a> (<code>code: 'auth/invalid-email'</code>, message <code>Firebase: Error (auth/invalid-email).</code> against blockingfun, fb-js-sdk 12.13.0)</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">signInWithEmailAndPassword(auth, email, password)</code><span class="compat-sub"><span class="compat-behavior">Password strength requirements — rejects passwords shorter than 6 chars with <code>auth/weak-password</code> on <code>createUserWithEmailAndPassword</code>. Strength is NOT enforced on <code>signInWithEmailAndPassword</code> so previously-seeded weak passwords still let the user in (matches prod's separation of registration vs sign-in).</span></span></span></summary>
120
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-email-password.test.ts</code> + oracle: <code>packages/conformance/observations/auth/auth-row-19-weak-password-error-code.json</code> (<code>code: 'auth/weak-password'</code>, message <code>Firebase: Password should be at least 6 characters (auth/weak-password).</code> against blockingfun, fb-js-sdk 12.13.0; matrix language "≥6 chars per prod default" empirically confirmed)</div></div>
120
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-email-password.test.ts#L225" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-email-password.test.ts</code></a> + oracle: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/auth/auth-row-19-weak-password-error-code.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/auth/auth-row-19-weak-password-error-code.json</code></a> (<code>code: 'auth/weak-password'</code>, message <code>Firebase: Password should be at least 6 characters (auth/weak-password).</code> against blockingfun, fb-js-sdk 12.13.0; matrix language "≥6 chars per prod default" empirically confirmed)</div></div>
121
121
  </details>
122
122
  <details class="compat-row" data-status="ok">
123
123
  <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">signInWithEmailAndPassword(auth, email, password)</code><span class="compat-sub"><span class="compat-behavior">Disabled accounts (<code>sandbox.updateUser(…, {disabled: true})</code>) reject sign-in with <code>auth/user-disabled</code> and prod's documented message (<code>The user account has been disabled by an administrator.</code>). Sandbox checks disabled BEFORE the password compare (anti-probing); the exact prod ordering of disabled-vs-wrong-password needs an oracle capture</span></span></span></summary>
124
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-user-admin.test.ts</code> ("disabled users")</div>
124
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-user-admin.test.ts#L19" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-user-admin.test.ts</code></a> ("disabled users")</div>
125
125
  <div class="compat-note">code / ? ordering</div></div>
126
126
  </details>
127
127
  </div>
@@ -129,38 +129,38 @@
129
129
  <div class="compat-list">
130
130
  <details class="compat-row" data-status="ok">
131
131
  <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">createUserWithEmailAndPassword(auth, email, password)</code><span class="compat-sub"><span class="compat-behavior">Creates a new user, signs them in automatically (currentUser becomes the new user)</span></span></span></summary>
132
- <div class="compat-evidence"><div class="compat-probe"><code>playground:auth-email-password</code> (bundled) + <code>playground:auth-row-20-create-user-auto-signs-in</code> (one-claim), <code>unit:sandbox-email-password.test.ts</code></div></div>
132
+ <div class="compat-evidence"><div class="compat-probe"><code>playground:auth-email-password</code> (bundled) + <code>playground:auth-row-20-create-user-auto-signs-in</code> (one-claim), <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-email-password.test.ts#L6" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-email-password.test.ts</code></a></div></div>
133
133
  </details>
134
134
  <details class="compat-row" data-status="ok">
135
135
  <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">createUserWithEmailAndPassword(auth, email, password)</code><span class="compat-sub"><span class="compat-behavior">Returns <code>UserCredential</code> with <code>operationType: 'signIn'</code> (NOT <code>'register'</code> — matches prod)</span></span></span></summary>
136
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-email-password.test.ts</code> + oracle: <code>packages/conformance/observations/auth/auth-createUser-operationType.json</code> (<code>operationType: 'signIn'</code> against blockingfun, fb-js-sdk 12.13.0; matches sandbox)</div></div>
136
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-email-password.test.ts#L29" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-email-password.test.ts</code></a> + oracle: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/auth/auth-createUser-operationType.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/auth/auth-createUser-operationType.json</code></a> (<code>operationType: 'signIn'</code> against blockingfun, fb-js-sdk 12.13.0; matches sandbox) (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/oracle-conformance.test.ts#L145" target="_blank" rel="noopener noreferrer"><code>oracle-conformance.test.ts</code></a>)</div></div>
137
137
  </details>
138
138
  <details class="compat-row" data-status="ok">
139
139
  <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">createUserWithEmailAndPassword(auth, email, password)</code><span class="compat-sub"><span class="compat-behavior">Throws <code>auth/email-already-in-use</code> when the email is already registered</span></span></span></summary>
140
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-email-password.test.ts</code> + oracle: <code>packages/conformance/observations/auth/auth-email-already-in-use-error-code.json</code> (<code>code: 'auth/email-already-in-use'</code> against blockingfun, fb-js-sdk 12.13.0; matches sandbox)</div></div>
140
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-email-password.test.ts#L6" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-email-password.test.ts</code></a> + oracle: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/auth/auth-email-already-in-use-error-code.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/auth/auth-email-already-in-use-error-code.json</code></a> (<code>code: 'auth/email-already-in-use'</code> against blockingfun, fb-js-sdk 12.13.0; matches sandbox)</div></div>
141
141
  </details>
142
142
  <details class="compat-row" data-status="ok">
143
143
  <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">createUserWithEmailAndPassword(auth, email, password)</code><span class="compat-sub"><span class="compat-behavior">The created user has <code>isAnonymous: false</code>, <code>email: &#x3C;input></code>, <code>displayName: null</code></span></span></span></summary>
144
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-email-password.test.ts</code> + <code>playground:auth-row-23-create-user-shape</code> (one-claim)</div></div>
144
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-email-password.test.ts#L6" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-email-password.test.ts</code></a> + <code>playground:auth-row-23-create-user-shape</code> (one-claim)</div></div>
145
145
  </details>
146
146
  <details class="compat-row" data-status="ok">
147
147
  <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">createUserWithEmailAndPassword(auth, email, password)</code><span class="compat-sub"><span class="compat-behavior">Fires <code>onAuthStateChanged</code> with the new user once</span></span></span></summary>
148
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-listeners.test.ts</code> + oracle: <code>packages/conformance/observations/auth/auth-row-24-createuser-fires-once.json</code> (against blockingfun, fb-js-sdk 12.13.0: subscribe (1 initial null fire) → <code>createUserWithEmailAndPassword</code> → <code>firesForCreate: 1</code> with the newly-created uid, <code>lastFireUidMatches: true</code>)</div></div>
148
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-listeners.test.ts#L23" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-listeners.test.ts</code></a> + oracle: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/auth/auth-row-24-createuser-fires-once.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/auth/auth-row-24-createuser-fires-once.json</code></a> (against blockingfun, fb-js-sdk 12.13.0: subscribe (1 initial null fire) → <code>createUserWithEmailAndPassword</code> → <code>firesForCreate: 1</code> with the newly-created uid, <code>lastFireUidMatches: true</code>)</div></div>
149
149
  </details>
150
150
  </div>
151
151
  <h2 id="signoutauth"><code>signOut(auth)</code></h2>
152
152
  <div class="compat-list">
153
153
  <details class="compat-row" data-status="ok">
154
154
  <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">signOut(auth)</code><span class="compat-sub"><span class="compat-behavior">Sets <code>currentUser</code> to <code>null</code> synchronously after resolution</span></span></span></summary>
155
- <div class="compat-evidence"><div class="compat-probe"><code>playground:auth-anonymous</code> (bundled) + <code>playground:auth-row-25-signout-currentuser-null</code> (one-claim), <code>unit:sandbox-anonymous.test.ts</code> + oracle: <code>packages/conformance/observations/auth/auth-row-25-signout-currentuser-null-sync.json</code> (<code>currentUserIsNullSync: true</code> against blockingfun, fb-js-sdk 12.13.0 — <code>auth.currentUser</code> read in the synchronous continuation immediately after <code>await signOut(auth)</code> is already <code>null</code>, with no microtask/macrotask required to settle)</div></div>
155
+ <div class="compat-evidence"><div class="compat-probe"><code>playground:auth-anonymous</code> (bundled) + <code>playground:auth-row-25-signout-currentuser-null</code> (one-claim), <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-anonymous.test.ts#L7" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-anonymous.test.ts</code></a> + oracle: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/auth/auth-row-25-signout-currentuser-null-sync.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/auth/auth-row-25-signout-currentuser-null-sync.json</code></a> (<code>currentUserIsNullSync: true</code> against blockingfun, fb-js-sdk 12.13.0 — <code>auth.currentUser</code> read in the synchronous continuation immediately after <code>await signOut(auth)</code> is already <code>null</code>, with no microtask/macrotask required to settle)</div></div>
156
156
  </details>
157
157
  <details class="compat-row" data-status="ok">
158
158
  <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">signOut(auth)</code><span class="compat-sub"><span class="compat-behavior">Fires <code>onAuthStateChanged</code> with <code>null</code> exactly once</span></span></span></summary>
159
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-listeners.test.ts</code>, <code>playground:auth-anonymous</code> (bundled) + <code>playground:auth-row-26-signout-fires-null-once</code> (one-claim) + oracle: <code>packages/conformance/observations/auth/auth-row-26-signout-fires-null-once.json</code> (against blockingfun, fb-js-sdk 12.13.0: subscribe (1 initial null fire) → <code>signInAnonymously</code> → <code>signOut</code> → <code>firesForSignOut: 1</code> with <code>lastFireUidWasNull: true</code>)</div></div>
159
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-listeners.test.ts#L26" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-listeners.test.ts</code></a>, <code>playground:auth-anonymous</code> (bundled) + <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/auth/auth-row-26-signout-fires-null-once.json" target="_blank" rel="noopener noreferrer"><code>playground:auth-row-26-signout-fires-null-once</code></a> (one-claim) + oracle: <code>packages/conformance/observations/auth/auth-row-26-signout-fires-null-once.json</code> (against blockingfun, fb-js-sdk 12.13.0: subscribe (1 initial null fire) → <code>signInAnonymously</code> → <code>signOut</code> → <code>firesForSignOut: 1</code> with <code>lastFireUidWasNull: true</code>)</div></div>
160
160
  </details>
161
161
  <details class="compat-row" data-status="ok">
162
162
  <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">signOut(auth)</code><span class="compat-sub"><span class="compat-behavior">Idempotent — <code>signOut</code> on already-signed-out user is a no-op (no listener fire)</span></span></span></summary>
163
- <div class="compat-evidence"><div class="compat-probe"><code>playground:auth-signout-idempotent</code> + oracle-confirmed: <code>packages/conformance/observations/auth/auth-signout-idempotent.json</code> (<code>threw: false, redundantSignOutFiredListener: false</code> against blockingfun)</div></div>
163
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/auth/auth-signout-idempotent.json" target="_blank" rel="noopener noreferrer"><code>playground:auth-signout-idempotent</code></a> + oracle-confirmed: <code>packages/conformance/observations/auth/auth-signout-idempotent.json</code> (<code>threw: false, redundantSignOutFiredListener: false</code> against blockingfun)</div></div>
164
164
  </details>
165
165
  <details class="compat-row" data-status="diverged">
166
166
  <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">signOut(auth)</code><span class="compat-sub"><span class="compat-behavior">Clears the active session's persistence in prod; sandbox has no persistence</span></span></span></summary>
@@ -171,77 +171,77 @@
171
171
  <div class="compat-list">
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">onAuthStateChanged(auth, observer)</code><span class="compat-sub"><span class="compat-behavior">Fires immediately on subscribe with the current value (microtask-deferred)</span></span></span></summary>
174
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-listeners.test.ts</code> + oracle: <code>packages/conformance/observations/auth/auth-row-29-onauthstatechanged-initial-fire-timing.json</code> (<code>firedSynchronously: 0, firedAfterMicrotask: 1</code> against blockingfun, fb-js-sdk 12.13.0 — initial fire does NOT arrive in the synchronous tick of <code>onAuthStateChanged(...)</code>; it lands after the first microtask flush)</div></div>
174
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-listeners.test.ts#L5" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-listeners.test.ts</code></a> + oracle: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/auth/auth-row-29-onauthstatechanged-initial-fire-timing.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/auth/auth-row-29-onauthstatechanged-initial-fire-timing.json</code></a> (<code>firedSynchronously: 0, firedAfterMicrotask: 1</code> against blockingfun, fb-js-sdk 12.13.0 — initial fire does NOT arrive in the synchronous tick of <code>onAuthStateChanged(...)</code>; it lands after the first microtask flush)</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">onAuthStateChanged(auth, observer)</code><span class="compat-sub"><span class="compat-behavior">Fires on every subsequent identity change</span></span></span></summary>
178
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-listeners.test.ts</code>, <code>playground:auth-anonymous</code> + oracle: <code>packages/conformance/observations/auth/auth-row-30-onauthstatechanged-fires-on-every-transition.json</code> (against blockingfun, fb-js-sdk 12.13.0: subscribe → <code>signIn</code> → <code>signOut</code> → <code>signIn</code> → <code>signOut</code>, each of the 4 transitions produced exactly 1 fire; <code>eachTransitionFiredExactlyOnce: true</code>)</div></div>
178
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-listeners.test.ts#L5" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-listeners.test.ts</code></a>, <code>playground:auth-anonymous</code> + oracle: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/auth/auth-row-30-onauthstatechanged-fires-on-every-transition.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/auth/auth-row-30-onauthstatechanged-fires-on-every-transition.json</code></a> (against blockingfun, fb-js-sdk 12.13.0: subscribe → <code>signIn</code> → <code>signOut</code> → <code>signIn</code> → <code>signOut</code>, each of the 4 transitions produced exactly 1 fire; <code>eachTransitionFiredExactlyOnce: true</code>)</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">onAuthStateChanged(auth, observer)</code><span class="compat-sub"><span class="compat-behavior">Dedups by uid: a same-uid update (profile-shape change, or a same-uid re-sign-in) does NOT re-fire <code>onAuthStateChanged</code> — only an actual uid change does. Mirrors upstream <code>notifyAuthListeners</code>'s <code>lastNotifiedUid</code> gate (<code>auth_impl.ts:718-723</code>). (<code>onIdTokenChanged</code> still fires on those same-uid updates — see row 38a.)</span></span></span></summary>
182
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-uid-dedup.test.ts</code> (locks AUTH-B7 / B8)</div></div>
182
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-uid-dedup.test.ts#L7" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-uid-dedup.test.ts</code></a> (locks AUTH-B7 / B8)</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">onAuthStateChanged(auth, observer)</code><span class="compat-sub"><span class="compat-behavior"><strong>No duplicate fire</strong> when subscribe is followed by a synchronous <code>setCurrentUser</code> — dedup ensures observer sees the new value once, not twice. Sandbox-only concern: prod has no synchronous state-change API, so the dedup window can't be exercised against the cloud SDK; subscribe-then-async-signIn naturally fires twice (initial + new value) because the microtask between them flushes the initial fire</span></span></span></summary>
186
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-listeners.test.ts</code> (regression test from #399), <code>playground:auth-anonymous</code> + oracle baseline: <code>packages/conformance/observations/auth/auth-row-31-onauthstatechanged-no-dup-on-sync-transition.json</code> (against blockingfun, fb-js-sdk 12.13.0: subscribe + async <code>signInAnonymously</code> in the same tick produces <code>totalFires: 2</code> — <code>{uid: null, ts: 0}</code> then <code>{uid: &#x3C;user>, ts: ~400ms}</code>. Confirms prod cannot exhibit the same-tick race; the dedup behavior remains a sandbox-only property)</div></div>
186
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-listeners.test.ts#L5" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-listeners.test.ts</code></a> (regression test from #399), <code>playground:auth-anonymous</code> + oracle baseline: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/auth/auth-row-31-onauthstatechanged-no-dup-on-sync-transition.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/auth/auth-row-31-onauthstatechanged-no-dup-on-sync-transition.json</code></a> (against blockingfun, fb-js-sdk 12.13.0: subscribe + async <code>signInAnonymously</code> in the same tick produces <code>totalFires: 2</code> — <code>{uid: null, ts: 0}</code> then <code>{uid: &#x3C;user>, ts: ~400ms}</code>. Confirms prod cannot exhibit the same-tick race; the dedup behavior remains a sandbox-only property)</div></div>
187
187
  </details>
188
188
  <details class="compat-row" data-status="ok">
189
189
  <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">onAuthStateChanged(auth, observer)</code><span class="compat-sub"><span class="compat-behavior">Returned <code>Unsubscribe</code> removes the observer; subsequent state changes do NOT fire it</span></span></span></summary>
190
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-listeners.test.ts</code>, <code>playground:auth-listener-unsub</code> (bundled) + <code>playground:auth-row-32-unsubscribe-stops-fires</code> (one-claim) + oracle: <code>packages/conformance/observations/auth/auth-row-32-unsubscribe-stops-fires.json</code> (against blockingfun, fb-js-sdk 12.13.0: subscribe → 1 fire on <code>signInAnonymously</code> → call <code>unsub()</code> → <code>signOut</code> + <code>signInAnonymously</code> + <code>signOut</code> produce zero further fires; <code>postUnsubFires: 0, unsubscribeStoppedFires: true</code>)</div></div>
190
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-listeners.test.ts#L5" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-listeners.test.ts</code></a>, <code>playground:auth-listener-unsub</code> (bundled) + <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/auth/auth-row-32-unsubscribe-stops-fires.json" target="_blank" rel="noopener noreferrer"><code>playground:auth-row-32-unsubscribe-stops-fires</code></a> (one-claim) + oracle: <code>packages/conformance/observations/auth/auth-row-32-unsubscribe-stops-fires.json</code> (against blockingfun, fb-js-sdk 12.13.0: subscribe → 1 fire on <code>signInAnonymously</code> → call <code>unsub()</code> → <code>signOut</code> + <code>signInAnonymously</code> + <code>signOut</code> produce zero further fires; <code>postUnsubFires: 0, unsubscribeStoppedFires: true</code>)</div></div>
191
191
  </details>
192
192
  <details class="compat-row" data-status="ok">
193
193
  <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">onAuthStateChanged(auth, observer)</code><span class="compat-sub"><span class="compat-behavior">Multiple subscribers all fire on each change</span></span></span></summary>
194
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-listeners.test.ts</code> + oracle: <code>packages/conformance/observations/auth/auth-row-33-multiple-subscribers-all-fire.json</code> (against blockingfun, fb-js-sdk 12.13.0: two subscribers registered back-to-back each see 1 initial null fire, +1 on <code>signInAnonymously</code>, +1 on <code>signOut</code>; <code>bothFiredOnSignIn: true, bothFiredOnSignOut: true</code>)</div></div>
194
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-listeners.test.ts#L5" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-listeners.test.ts</code></a> + oracle: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/auth/auth-row-33-multiple-subscribers-all-fire.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/auth/auth-row-33-multiple-subscribers-all-fire.json</code></a> (against blockingfun, fb-js-sdk 12.13.0: two subscribers registered back-to-back each see 1 initial null fire, +1 on <code>signInAnonymously</code>, +1 on <code>signOut</code>; <code>bothFiredOnSignIn: true, bothFiredOnSignOut: true</code>)</div></div>
195
195
  </details>
196
196
  <details class="compat-row" data-status="ok">
197
197
  <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">onAuthStateChanged(auth, observer)</code><span class="compat-sub"><span class="compat-behavior">Registry is array-backed (matches upstream <code>util/subscribe.ts</code>): the SAME observer fn registered N times produces N independent registrations that each fire, and one <code>Unsubscribe</code> removes exactly one registration. A resubscribe of a previously-unsubscribed fn fires its initial value again. (Per-registration initial-fire bookkeeping, not a shared per-observer dedup.)</span></span></span></summary>
198
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-listener-registry.test.ts</code> (locks AUTH-B3 + AUTH-B4)</div></div>
198
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-listener-registry.test.ts#L19" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-listener-registry.test.ts</code></a> (locks AUTH-B3 + AUTH-B4)</div></div>
199
199
  </details>
200
200
  <details class="compat-row" data-status="ok">
201
201
  <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">onAuthStateChanged(auth, observer)</code><span class="compat-sub"><span class="compat-behavior">Unsubscribing during emission does not skip remaining subscribers (snapshotted iteration)</span></span></span></summary>
202
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-listeners.test.ts</code></div></div>
202
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-listeners.test.ts#L5" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-listeners.test.ts</code></a></div></div>
203
203
  </details>
204
204
  <details class="compat-row" data-status="ok">
205
205
  <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">onAuthStateChanged(auth, observer)</code><span class="compat-sub"><span class="compat-behavior">A throwing observer doesn't block other observers from firing</span></span></span></summary>
206
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-listeners.test.ts</code> + oracle: <code>packages/conformance/observations/auth/auth-row-35-throwing-observer-doesnt-block-others.json</code> (<code>secondObserverContinuedFiring: true</code> against blockingfun, fb-js-sdk 12.13.0 — observer #1 throws on every call, observer #2 still counts the initial fire AND the post-sign-in fire)</div></div>
206
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-listeners.test.ts#L5" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-listeners.test.ts</code></a> + oracle: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/auth/auth-row-35-throwing-observer-doesnt-block-others.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/auth/auth-row-35-throwing-observer-doesnt-block-others.json</code></a> (<code>secondObserverContinuedFiring: true</code> against blockingfun, fb-js-sdk 12.13.0 — observer #1 throws on every call, observer #2 still counts the initial fire AND the post-sign-in fire)</div></div>
207
207
  </details>
208
208
  <details class="compat-row" data-status="ok">
209
209
  <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">onAuthStateChanged(auth, observer)</code><span class="compat-sub"><span class="compat-behavior">Observer object form (<code>{next, error, complete}</code>) works alongside the function form</span></span></span></summary>
210
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-listeners.test.ts</code> + oracle: <code>packages/conformance/observations/auth/auth-row-36-observer-object-form-works.json</code> (against blockingfun, fb-js-sdk 12.13.0: one observer as <code>NextFn</code>, another as <code>{next, error, complete}</code>. Both forms see 1 initial null fire, +1 on <code>signInAnonymously</code>, +1 on <code>signOut</code>; <code>bothFormsFiredOnSignIn: true, bothFormsFiredOnSignOut: true</code>)</div></div>
210
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-listeners.test.ts#L5" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-listeners.test.ts</code></a> + oracle: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/auth/auth-row-36-observer-object-form-works.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/auth/auth-row-36-observer-object-form-works.json</code></a> (against blockingfun, fb-js-sdk 12.13.0: one observer as <code>NextFn</code>, another as <code>{next, error, complete}</code>. Both forms see 1 initial null fire, +1 on <code>signInAnonymously</code>, +1 on <code>signOut</code>; <code>bothFormsFiredOnSignIn: true, bothFormsFiredOnSignOut: true</code>)</div></div>
211
211
  </details>
212
212
  <details class="compat-row" data-status="ok">
213
213
  <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">onAuthStateChanged(auth, observer)</code><span class="compat-sub"><span class="compat-behavior">Setting the same user twice does NOT double-fire (structural-equality no-op). Sandbox-internal <code>setCurrentUser</code> claim; the prod analog is <code>signInAnonymously</code> called twice in a row (per fix #399, the second call returns the same user).</span></span></span></summary>
214
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-listeners.test.ts</code> + oracle: <code>packages/conformance/observations/auth/auth-row-37-same-user-no-double-fire.json</code> (against blockingfun, fb-js-sdk 12.13.0: first <code>signInAnonymously</code> fires the listener once; second call returns the same uid (<code>sameUserAcrossCalls: true</code>) and does NOT produce a fresh fire (<code>secondSignInProducedFire: false</code>). Prod also recognizes the same-user no-op)</div></div>
214
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-listeners.test.ts#L5" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-listeners.test.ts</code></a> + oracle: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/auth/auth-row-37-same-user-no-double-fire.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/auth/auth-row-37-same-user-no-double-fire.json</code></a> (against blockingfun, fb-js-sdk 12.13.0: first <code>signInAnonymously</code> fires the listener once; second call returns the same uid (<code>sameUserAcrossCalls: true</code>) and does NOT produce a fresh fire (<code>secondSignInProducedFire: false</code>). Prod also recognizes the same-user no-op)</div></div>
215
215
  </details>
216
216
  </div>
217
217
  <h2 id="onidtokenchangedauth-observer"><code>onIdTokenChanged(auth, observer)</code></h2>
218
218
  <div class="compat-list">
219
219
  <details class="compat-row" data-status="ok">
220
220
  <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">onIdTokenChanged(auth, observer)</code><span class="compat-sub"><span class="compat-behavior">Fires on user change (sandbox shares the auth-state path)</span></span></span></summary>
221
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-listeners.test.ts</code> + oracle: <code>packages/conformance/observations/auth/auth-row-38-onidtokenchanged-fires-on-user-change.json</code> (against blockingfun, fb-js-sdk 12.13.0: subscribe → 1 initial fire (null) → <code>signInAnonymously</code> → +1 (user₁) → <code>signOut</code> → +1 (null) → <code>signInAnonymously</code> → +1 (user₂ with fresh uid). Every identity transition produces exactly one fire, matching <code>onAuthStateChanged</code>)</div></div>
221
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-listeners.test.ts#L12" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-listeners.test.ts</code></a> + oracle: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/auth/auth-row-38-onidtokenchanged-fires-on-user-change.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/auth/auth-row-38-onidtokenchanged-fires-on-user-change.json</code></a> (against blockingfun, fb-js-sdk 12.13.0: subscribe → 1 initial fire (null) → <code>signInAnonymously</code> → +1 (user₁) → <code>signOut</code> → +1 (null) → <code>signInAnonymously</code> → +1 (user₂ with fresh uid). Every identity transition produces exactly one fire, matching <code>onAuthStateChanged</code>)</div></div>
222
222
  </details>
223
223
  <details class="compat-row" data-status="ok">
224
224
  <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">onIdTokenChanged(auth, observer)</code><span class="compat-sub"><span class="compat-behavior">Fires on EVERY sign-in, including a same-uid re-sign-in (no <code>signOut</code> first) — which mints a fresh token, so a subsequent <code>getIdToken()</code> returns a new string. Mirrors upstream <code>notifyAuthListeners</code>, which calls <code>idTokenSubscription.next</code> on every identity update (<code>auth_impl.ts:716</code>). <code>onAuthStateChanged</code> stays silent on the same-uid case (row 30a).</span></span></span></summary>
225
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-uid-dedup.test.ts</code> (locks AUTH-B8)</div></div>
225
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-uid-dedup.test.ts#L5" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-uid-dedup.test.ts</code></a> (locks AUTH-B8)</div></div>
226
226
  </details>
227
227
  <details class="compat-row" data-status="ok">
228
228
  <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">onIdTokenChanged(auth, observer)</code><span class="compat-sub"><span class="compat-behavior">Fires on token refresh (<code>getIdToken(true)</code>)</span></span></span></summary>
229
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-token-refresh.test.ts</code> — was ⚠ (documented divergence); aligned to prod in commit — sandbox now mints a fresh token on forceRefresh and fires <code>onIdTokenChanged</code> (NOT <code>onAuthStateChanged</code>, since identity is unchanged). Oracle: <code>packages/conformance/observations/auth/auth-onidtokenchanged-force-refresh.json</code> defines the target shape (<code>refreshFiredListener: true</code> against blockingfun; subscribe → null fire → <code>signInAnonymously</code> → +1 → <code>getIdToken(true)</code> → +1 for a total of 3 fires).</div></div>
229
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-token-refresh.test.ts#L12" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-token-refresh.test.ts</code></a> — was ⚠ (documented divergence); aligned to prod in commit — sandbox now mints a fresh token on forceRefresh and fires <code>onIdTokenChanged</code> (NOT <code>onAuthStateChanged</code>, since identity is unchanged). Oracle: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/auth/auth-onidtokenchanged-force-refresh.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/auth/auth-onidtokenchanged-force-refresh.json</code></a> defines the target shape (<code>refreshFiredListener: true</code> against blockingfun; subscribe → null fire → <code>signInAnonymously</code> → +1 → <code>getIdToken(true)</code> → +1 for a total of 3 fires).</div></div>
230
230
  </details>
231
231
  <details class="compat-row" data-status="ok">
232
232
  <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">onIdTokenChanged(auth, observer)</code><span class="compat-sub"><span class="compat-behavior">Initial-fire semantics match <code>onAuthStateChanged</code></span></span></span></summary>
233
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-listeners.test.ts</code> + oracle: <code>packages/conformance/observations/auth/auth-row-40-onidtokenchanged-matches-onauthstatechanged-initial-fire.json</code> (against blockingfun, fb-js-sdk 12.13.0: subscribing both listeners in the same tick yields <code>sync: {auth: 0, idToken: 0}</code> → <code>microtask: {auth: 1, idToken: 1}</code> → no further fires. Both listeners share the microtask-deferred initial-fire timing)</div></div>
233
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-listeners.test.ts#L12" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-listeners.test.ts</code></a> + oracle: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/auth/auth-row-40-onidtokenchanged-matches-onauthstatechanged-initial-fire.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/auth/auth-row-40-onidtokenchanged-matches-onauthstatechanged-initial-fire.json</code></a> (against blockingfun, fb-js-sdk 12.13.0: subscribing both listeners in the same tick yields <code>sync: {auth: 0, idToken: 0}</code> → <code>microtask: {auth: 1, idToken: 1}</code> → no further fires. Both listeners share the microtask-deferred initial-fire timing)</div></div>
234
234
  </details>
235
235
  </div>
236
236
  <h2 id="setpersistenceauth-persistence"><code>setPersistence(auth, persistence)</code></h2>
237
237
  <div class="compat-list">
238
238
  <details class="compat-row" data-status="ok">
239
239
  <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">setPersistence(auth, persistence)</code><span class="compat-sub"><span class="compat-behavior">Accepts <code>inMemoryPersistence</code> / <code>browserSessionPersistence</code> / <code>browserLocalPersistence</code> markers without throwing</span></span></span></summary>
240
- <div class="compat-evidence"><div class="compat-probe"><code>unit:types.test.ts</code></div></div>
240
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/types.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:types.test.ts</code></a></div></div>
241
241
  </details>
242
242
  <details class="compat-row" data-status="ok">
243
243
  <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">setPersistence(auth, persistence)</code><span class="compat-sub"><span class="compat-behavior">Returns <code>Promise&#x3C;void></code></span></span></span></summary>
244
- <div class="compat-evidence"><div class="compat-probe"><code>unit:types.test.ts</code></div></div>
244
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/types.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:types.test.ts</code></a></div></div>
245
245
  </details>
246
246
  <details class="compat-row" data-status="diverged">
247
247
  <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">setPersistence(auth, persistence)</code><span class="compat-sub"><span class="compat-behavior">Actually changes where the auth state is persisted</span></span></span></summary>
@@ -249,30 +249,30 @@
249
249
  </details>
250
250
  <details class="compat-row" data-status="ok">
251
251
  <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">setPersistence(auth, persistence)</code><span class="compat-sub"><span class="compat-behavior">An unrecognized persistence marker is rejected with <code>auth/argument-error</code> rather than silently coerced to LOCAL</span></span></span></summary>
252
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-cluster-b9-b12.test.ts</code> (locks AUTH-B12)</div></div>
252
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-cluster-b9-b12.test.ts#L17" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-cluster-b9-b12.test.ts</code></a> (locks AUTH-B12)</div></div>
253
253
  </details>
254
254
  </div>
255
255
  <h2 id="signinwithpopupauth-provider--signinwithcredentialauth-credential"><code>signInWithPopup(auth, provider)</code> / <code>signInWithCredential(auth, credential)</code></h2>
256
256
  <div class="compat-list">
257
257
  <details class="compat-row" data-status="ok">
258
258
  <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">signInWithPopup(auth, provider)<code> / </code>signInWithCredential(auth, credential)</code><span class="compat-sub"><span class="compat-behavior">Returns the pre-staged <code>UserCredential</code> registered via <code>sandbox.mockSignInResult(auth, …)</code></span></span></span></summary>
259
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-providers.test.ts</code></div></div>
259
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-providers.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-providers.test.ts</code></a></div></div>
260
260
  </details>
261
261
  <details class="compat-row" data-status="ok">
262
262
  <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">signInWithPopup(auth, provider)<code> / </code>signInWithCredential(auth, credential)</code><span class="compat-sub"><span class="compat-behavior">Throws <code>auth/no-mock-configured</code> when no mock is pre-staged</span></span></span></summary>
263
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-providers.test.ts</code></div></div>
263
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-providers.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-providers.test.ts</code></a></div></div>
264
264
  </details>
265
265
  <details class="compat-row" data-status="ok">
266
266
  <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">signInWithPopup(auth, provider)<code> / </code>signInWithCredential(auth, credential)</code><span class="compat-sub"><span class="compat-behavior">Mock is consumed after one read (subsequent call without a fresh stage throws again)</span></span></span></summary>
267
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-providers.test.ts</code></div></div>
267
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-providers.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-providers.test.ts</code></a></div></div>
268
268
  </details>
269
269
  <details class="compat-row" data-status="ok">
270
270
  <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">signInWithPopup(auth, provider)<code> / </code>signInWithCredential(auth, credential)</code><span class="compat-sub"><span class="compat-behavior">After successful sign-in, <code>currentUser</code> becomes the mock's <code>user</code>, listeners fire</span></span></span></summary>
271
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-providers.test.ts</code></div></div>
271
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-providers.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-providers.test.ts</code></a></div></div>
272
272
  </details>
273
273
  <details class="compat-row" data-status="ok">
274
274
  <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">signInWithPopup(auth, provider)<code> / </code>signInWithCredential(auth, credential)</code><span class="compat-sub"><span class="compat-behavior">The credential's rich <code>User</code> (email / displayName / isAnonymous) survives the transition — popup/redirect/credential/<code>setUser</code> do NOT clobber it down to the bare <code>AuthState</code>; <code>cred.user === auth.currentUser</code> (reference identity, matches upstream <code>_updateCurrentUser(userCredential.user)</code>)</span></span></span></summary>
275
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-user-identity.test.ts</code> (locks AUTH-B1)</div></div>
275
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-user-identity.test.ts#L21" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-user-identity.test.ts</code></a> (locks AUTH-B1)</div></div>
276
276
  </details>
277
277
  <details class="compat-row" data-status="diverged">
278
278
  <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">signInWithPopup(auth, provider)<code> / </code>signInWithCredential(auth, credential)</code><span class="compat-sub"><span class="compat-behavior">Opens a popup window in prod</span></span></span></summary>
@@ -284,52 +284,52 @@
284
284
  </details>
285
285
  <details class="compat-row" data-status="ok">
286
286
  <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">signInWithPopup(auth, provider)<code> / </code>signInWithCredential(auth, credential)</code><span class="compat-sub"><span class="compat-behavior">Provider-flow sign-ins (popup / redirect / credential) record the flow's <code>providerId</code> on the identity in the user DB (upsert for unknown uids; append-if-missing for known ones) and reject disabled accounts with <code>auth/user-disabled</code> before any state change</span></span></span></summary>
287
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-user-admin.test.ts</code> ("provider tracking", "disabled users") — provider recording is sandbox bookkeeping for <code>listIdentities</code>/<code>listUsers</code>; prod's auto-link semantics are narrower (same-email Google auto-link only) and not modeled</div></div>
287
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-user-admin.test.ts#L20" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-user-admin.test.ts</code></a> ("provider tracking", "disabled users") — provider recording is sandbox bookkeeping for <code>listIdentities</code>/<code>listUsers</code>; prod's auto-link semantics are narrower (same-email Google auto-link only) and not modeled</div></div>
288
288
  </details>
289
289
  </div>
290
290
  <h2 id="signinwithredirect--getredirectresult--resolver-seam"><code>signInWithRedirect</code> / <code>getRedirectResult</code> / resolver seam</h2>
291
291
  <div class="compat-list">
292
292
  <details class="compat-row" data-status="ok">
293
293
  <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">signInWithRedirect<code> / </code>getRedirectResult` / resolver seam</code><span class="compat-sub"><span class="compat-behavior"><code>signInWithRedirect(auth, provider, resolver?)</code> resolves the flow (per-call resolver → injected → one-shot mock → <code>auth/argument-error</code>), signs the user in, and stashes the credential for one <code>getRedirectResult</code></span></span></span></summary>
294
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-resolver.test.ts</code></div></div>
294
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-resolver.test.ts#L5" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-resolver.test.ts</code></a></div></div>
295
295
  </details>
296
296
  <details class="compat-row" data-status="ok">
297
297
  <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">signInWithRedirect<code> / </code>getRedirectResult` / resolver seam</code><span class="compat-sub"><span class="compat-behavior"><code>getRedirectResult(auth)</code> returns the stashed credential once, then <code>null</code> (one-shot, matches prod)</span></span></span></summary>
298
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-resolver.test.ts</code></div></div>
298
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-resolver.test.ts#L5" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-resolver.test.ts</code></a></div></div>
299
299
  </details>
300
300
  <details class="compat-row" data-status="ok">
301
301
  <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">signInWithRedirect<code> / </code>getRedirectResult` / resolver seam</code><span class="compat-sub"><span class="compat-behavior"><code>sandbox.setAuthFlowResolver(auth, resolver | null)</code> installs / clears the popup/redirect resolver (the analog of browser <code>getAuth</code> wiring <code>browserPopupRedirectResolver</code>)</span></span></span></summary>
302
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-resolver.test.ts</code></div></div>
302
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-resolver.test.ts#L5" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-resolver.test.ts</code></a></div></div>
303
303
  </details>
304
304
  <details class="compat-row" data-status="ok">
305
305
  <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">signInWithRedirect<code> / </code>getRedirectResult` / resolver seam</code><span class="compat-sub"><span class="compat-behavior"><code>sandbox.listIdentities(auth)</code> snapshots every seeded/created identity for a host account-picker (sandbox-only — no <code>firebase/auth</code> equivalent)</span></span></span></summary>
306
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-resolver.test.ts</code></div></div>
306
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-resolver.test.ts#L5" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-resolver.test.ts</code></a></div></div>
307
307
  </details>
308
308
  </div>
309
309
  <h2 id="auth-surface--error-shape"><code>Auth</code> surface + error shape</h2>
310
310
  <div class="compat-list">
311
311
  <details class="compat-row" data-status="ok">
312
312
  <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">Auth` surface + error shape</code><span class="compat-sub"><span class="compat-behavior"><code>auth.signOut()</code> method form works alongside the free <code>signOut(auth)</code> function (<code>firebase/auth</code>'s <code>Auth</code> exposes both) (AUTH-GAP)</span></span></span></summary>
313
- <div class="compat-evidence"><div class="compat-probe"><code>unit:auth-gap-surface.test.ts</code></div></div>
313
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/auth-gap-surface.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:auth-gap-surface.test.ts</code></a></div></div>
314
314
  </details>
315
315
  <details class="compat-row" data-status="ok">
316
316
  <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">Auth` surface + error shape</code><span class="compat-sub"><span class="compat-behavior">Sandbox auth errors are real <code>FirebaseError</code> instances (<code>err instanceof FirebaseError</code>) carrying the prod message wrapper <code>Firebase: &#x3C;message> (&#x3C;auth/...>).</code> — e.g. <code>Firebase: Error (auth/invalid-email).</code>, matching the oracle (AUTH-GAP)</span></span></span></summary>
317
- <div class="compat-evidence"><div class="compat-probe"><code>unit:auth-gap-surface.test.ts</code></div></div>
317
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/auth-gap-surface.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:auth-gap-surface.test.ts</code></a></div></div>
318
318
  </details>
319
319
  </div>
320
320
  <h2 id="provider-classes-googleauthprovider-emailauthprovider-etc">Provider classes (<code>GoogleAuthProvider</code>, <code>EmailAuthProvider</code>, etc.)</h2>
321
321
  <div class="compat-list">
322
322
  <details class="compat-row" data-status="ok">
323
323
  <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">Exports the same constructor signatures as <code>firebase/auth</code> for each provider</span></span></summary>
324
- <div class="compat-evidence"><div class="compat-probe">type-only smoke in <code>unit:types.test.ts</code></div></div>
324
+ <div class="compat-evidence"><div class="compat-probe">type-only smoke in <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/types.test.ts#L8" target="_blank" rel="noopener noreferrer"><code>unit:types.test.ts</code></a></div></div>
325
325
  </details>
326
326
  <details class="compat-row" data-status="ok">
327
327
  <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>Provider.credential(...)</code> static factories produce <code>AuthCredential</code>-shaped objects</span></span></summary>
328
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-providers.test.ts</code></div></div>
328
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-providers.test.ts#L14" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-providers.test.ts</code></a></div></div>
329
329
  </details>
330
330
  <details class="compat-row" data-status="ok">
331
331
  <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>GoogleAuthProvider.providerId === 'google.com'</code> (and per-provider analogs)</span></span></summary>
332
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-providers.test.ts</code></div></div>
332
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-providers.test.ts#L18" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-providers.test.ts</code></a></div></div>
333
333
  </details>
334
334
  <details class="compat-row" data-status="unsupported">
335
335
  <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"><span class="compat-behavior">Custom scopes / params / language code</span></span></summary>
@@ -340,15 +340,15 @@
340
340
  <div class="compat-list">
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">User` methods</code><span class="compat-sub"><span class="compat-behavior"><code>user.getIdToken()</code> returns a stable opaque token in sandbox (<code>sandbox-id-token-…</code>)</span></span></span></summary>
343
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-anonymous.test.ts</code></div></div>
343
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-anonymous.test.ts#L4" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-anonymous.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">User` methods</code><span class="compat-sub"><span class="compat-behavior"><code>user.getIdToken(true)</code> (forceRefresh) returns a NEW token; subsequent <code>getIdToken(false)</code> returns the cached new token</span></span></span></summary>
347
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-token-refresh.test.ts</code> — was ⚠ (documented divergence); aligned to prod in commit — sandbox now mints a fresh token on forceRefresh and fires <code>onIdTokenChanged</code>. Oracle: <code>packages/conformance/observations/auth/auth-getidtoken-force-refresh.json</code> defines the target shape (<code>forceRefreshReturnedDifferentString: true</code>, <code>token1EqualsToken2: true</code> against blockingfun — the refreshed token is cached, so a subsequent non-forced read returns it, not yet another fresh one). Sandbox tokens stay <code>sandbox-id-token-&#x3C;uid>-&#x3C;hash></code> strings; prod's are real JWTs.</div></div>
347
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-token-refresh.test.ts#L8" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-token-refresh.test.ts</code></a> — was ⚠ (documented divergence); aligned to prod in commit — sandbox now mints a fresh token on forceRefresh and fires <code>onIdTokenChanged</code>. Oracle: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/auth/auth-getidtoken-force-refresh.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/auth/auth-getidtoken-force-refresh.json</code></a> defines the target shape (<code>forceRefreshReturnedDifferentString: true</code>, <code>token1EqualsToken2: true</code> against blockingfun — the refreshed token is cached, so a subsequent non-forced read returns it, not yet another fresh one). Sandbox tokens stay <code>sandbox-id-token-&#x3C;uid>-&#x3C;serial>:&#x3C;json></code> strings; prod's are real JWTs.</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">User` methods</code><span class="compat-sub"><span class="compat-behavior"><code>user.getIdTokenResult()</code> returns claims</span></span></span></summary>
351
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-providers.test.ts</code> (custom-claims path)</div></div>
351
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-providers.test.ts#L24" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-providers.test.ts</code></a> (custom-claims path)</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">User` methods</code><span class="compat-sub"><span class="compat-behavior"><code>user.uid</code>, <code>user.email</code>, <code>user.displayName</code>, <code>user.isAnonymous</code> reflect the source</span></span></span></summary>
@@ -356,15 +356,15 @@
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">User` methods</code><span class="compat-sub"><span class="compat-behavior"><code>user.emailVerified</code> — present on every sandbox-minted <code>User</code> (default <code>false</code>; sandbox has no verification flow). Prod passes the real value through (no longer stripped). The admin record (<code>sandbox.listUsers</code>) carries it too</span></span></span></summary>
359
- <div class="compat-evidence"><div class="compat-probe"><code>unit:auth-gap-surface.test.ts</code> (locks AUTH-GAP)</div></div>
359
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/auth-gap-surface.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:auth-gap-surface.test.ts</code></a> (locks AUTH-GAP)</div></div>
360
360
  </details>
361
361
  <details class="compat-row" data-status="ok">
362
362
  <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">User` methods</code><span class="compat-sub"><span class="compat-behavior"><code>user.photoURL</code> / <code>user.phoneNumber</code> — present (sandbox default <code>null</code>; prod passes through, no longer stripped)</span></span></span></summary>
363
- <div class="compat-evidence"><div class="compat-probe"><code>unit:auth-gap-surface.test.ts</code></div></div>
363
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/auth-gap-surface.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:auth-gap-surface.test.ts</code></a></div></div>
364
364
  </details>
365
365
  <details class="compat-row" data-status="ok">
366
366
  <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">User` methods</code><span class="compat-sub"><span class="compat-behavior"><code>user.providerId</code> (aggregate, <code>'firebase'</code>) + <code>user.providerData: UserInfo[]</code> — sandbox synthesizes one provider entry for non-anonymous users, empty for anonymous; prod passes the real array through (no longer stripped). The admin record carries the emulator-shaped <code>providerUserInfo</code></span></span></span></summary>
367
- <div class="compat-evidence"><div class="compat-probe"><code>unit:auth-gap-surface.test.ts</code></div></div>
367
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/auth-gap-surface.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:auth-gap-surface.test.ts</code></a></div></div>
368
368
  </details>
369
369
  <details class="compat-row" data-status="unsupported">
370
370
  <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">User` methods</code><span class="compat-sub"><span class="compat-behavior"><code>user.metadata.creationTime</code> / <code>lastSignInTime</code></span></span></span></summary>
@@ -372,11 +372,11 @@
372
372
  </details>
373
373
  <details class="compat-row" data-status="ok">
374
374
  <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">User` methods</code><span class="compat-sub"><span class="compat-behavior"><code>IdTokenResult.signInProvider</code> reflects the session's provider per flow (<code>'anonymous'</code> / <code>'password'</code> / <code>'google.com'</code> / …); claims include the reserved <code>firebase.sign_in_provider</code> (custom claims can't shadow it)</span></span></span></summary>
375
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-user-admin.test.ts</code> ("IdTokenResult.signInProvider") — prod shape is documented SDK behavior; no oracle capture yet</div></div>
375
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-user-admin.test.ts#L9" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-user-admin.test.ts</code></a> ("IdTokenResult.signInProvider") — prod shape is documented SDK behavior; no oracle capture yet (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/auth/auth-signinprovider-per-flow.json" target="_blank" rel="noopener noreferrer"><code>auth-signinprovider-per-flow</code></a>)</div></div>
376
376
  </details>
377
377
  <details class="compat-row" data-status="ok">
378
378
  <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">User` methods</code><span class="compat-sub"><span class="compat-behavior">Custom-claims changes (<code>sandbox.updateUser</code> / re-seed) reach an active session on the next FORCED token refresh, not immediately — claims are read live from the user DB at mint time (prod's refresh-propagation story; AUTH-B10)</span></span></span></summary>
379
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-user-admin.test.ts</code>, <code>unit:sandbox-cluster-b9-b12.test.ts</code></div></div>
379
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-user-admin.test.ts#L9" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-user-admin.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-cluster-b9-b12.test.ts#L24" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-cluster-b9-b12.test.ts</code></a> (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/auth/auth-claims-forced-refresh-propagation.json" target="_blank" rel="noopener noreferrer"><code>auth-claims-forced-refresh-propagation</code></a>)</div></div>
380
380
  </details>
381
381
  <details class="compat-row" data-status="unsupported">
382
382
  <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">User` methods</code><span class="compat-sub"><span class="compat-behavior"><code>user.reload()</code> / <code>user.delete()</code> / <code>user.toJSON()</code> / <code>user.refreshToken</code> / <code>user.tenantId</code></span></span></span></summary>
@@ -384,26 +384,26 @@
384
384
  </details>
385
385
  <details class="compat-row" data-status="ok">
386
386
  <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">updateProfile(user, {displayName, photoURL})</code><span class="compat-sub"><span class="compat-behavior"><code>updateProfile(user, {displayName, photoURL})</code> mutates the held user + stored record in place (null clears a field; omitted fields untouched) without an extra <code>onAuthStateChanged</code> fire. Profile survives <code>signOut</code> → <code>signInWithEmailAndPassword</code>.</span></span></span></summary>
387
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-update-profile.test.ts</code> (in-place mutation, listUsers persist, null clear, no extra listener); re-sign-in persistence: <code>unit:upstream-auth-probes.test.ts</code></div></div>
387
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-update-profile.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-update-profile.test.ts</code></a> (in-place mutation, listUsers persist, null clear, no extra listener); re-sign-in persistence: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/upstream-auth-probes.test.ts#L8" target="_blank" rel="noopener noreferrer"><code>unit:upstream-auth-probes.test.ts</code></a></div></div>
388
388
  </details>
389
389
  </div>
390
390
  <h2 id="beforeauthstatechangedauth-callback-onabort"><code>beforeAuthStateChanged(auth, callback, onAbort?)</code></h2>
391
391
  <div class="compat-list">
392
392
  <details class="compat-row" data-status="ok">
393
393
  <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">beforeAuthStateChanged(auth, callback, onAbort?)</code><span class="compat-sub"><span class="compat-behavior">Registers a BLOCKING gate that runs before a real sign-in/sign-out transition commits. Callbacks run in registration order; a callback that throws (or returns a rejected promise) aborts the transition entirely: the pending <code>signInWith…</code>/<code>signOut</code> call rejects with <code>auth/login-blocked</code>, <code>currentUser</code> is unchanged, and <code>onAuthStateChanged</code>/<code>onIdTokenChanged</code> do NOT fire. Covers every sign-in path that exists in <code>pyric/auth</code>: <code>signInAnonymously</code>, <code>signInWithEmailAndPassword</code>, <code>createUserWithEmailAndPassword</code>, <code>signInWithPopup</code>, <code>signInWithRedirect</code>, <code>signInWithCredential</code>, <code>signInWithCustomToken</code>, and <code>signOut</code>. Modeled after the real <code>@firebase/auth</code> <code>AuthMiddlewareQueue.runMiddleware</code> (<code>auth_impl.ts</code>), read directly from the installed SDK source, not just its <code>.d.ts</code>.</span></span></span></summary>
394
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-before-auth-state-changed.test.ts</code> — implementation cross-checked against <code>@firebase/auth</code>'s <code>AuthMiddlewareQueue</code> source (registration-order queue, per-callback try/await, <code>auth/login-blocked</code> wrap). Custom-token allow/block: <code>unit:upstream-auth-probes.test.ts</code>. No live-oracle capture (no observable client-visible signal to probe beyond the documented+sourced contract already read).</div></div>
394
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-before-auth-state-changed.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-before-auth-state-changed.test.ts</code></a> — implementation cross-checked against <code>@firebase/auth</code>'s <code>AuthMiddlewareQueue</code> source (registration-order queue, per-callback try/await, <code>auth/login-blocked</code> wrap). Custom-token allow/block: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/upstream-auth-probes.test.ts#L9" target="_blank" rel="noopener noreferrer"><code>unit:upstream-auth-probes.test.ts</code></a>. No live-oracle capture (no observable client-visible signal to probe beyond the documented+sourced contract already read).</div></div>
395
395
  </details>
396
396
  <details class="compat-row" data-status="ok">
397
397
  <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">beforeAuthStateChanged(auth, callback, onAbort?)</code><span class="compat-sub"><span class="compat-behavior"><code>onAbort</code> semantics: when a callback throws, every <code>onAbort</code> registered by a callback that ALREADY SUCCEEDED in the same pass runs, in REVERSE registration order — matches upstream's rollback-stack (<code>runMiddleware</code>'s <code>onAbortStack</code>). An <code>onAbort</code> that itself throws is swallowed so it can't mask the original block reason or skip the remaining rollbacks.</span></span></span></summary>
398
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-before-auth-state-changed.test.ts</code> ("onAbort runs (in reverse order)…", "a callback whose own onAbort throws…")</div></div>
398
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-before-auth-state-changed.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-before-auth-state-changed.test.ts</code></a> ("onAbort runs (in reverse order)…", "a callback whose own onAbort throws…")</div></div>
399
399
  </details>
400
400
  <details class="compat-row" data-status="ok">
401
401
  <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">beforeAuthStateChanged(auth, callback, onAbort?)</code><span class="compat-sub"><span class="compat-behavior">Fires on BOTH directions: a real sign-in (<code>nextUser</code> non-null) and a real sign-out (<code>nextUser === null</code>) — a throwing callback blocks <code>signOut</code> too, leaving the previous user signed in. Matches upstream, where the same middleware queue gates <code>_updateCurrentUser</code> and <code>signOut</code>.</span></span></span></summary>
402
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-before-auth-state-changed.test.ts</code> ("fires on sign-out too…")</div></div>
402
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-before-auth-state-changed.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-before-auth-state-changed.test.ts</code></a> ("fires on sign-out too…")</div></div>
403
403
  </details>
404
404
  <details class="compat-row" data-status="diverged">
405
405
  <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">beforeAuthStateChanged(auth, callback, onAbort?)</code><span class="compat-sub"><span class="compat-behavior"><code>sandbox.setUser</code> (the sandbox-only test driver) BYPASSES the gate entirely — it is a raw identity force with no prod analog (same bypass it already has for provider enforcement / <code>signInProvider</code> tracking), so no registered <code>beforeAuthStateChanged</code> callback runs and none can block it.</span></span></span></summary>
406
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-before-auth-state-changed.test.ts</code> ("sandbox.setUser test driver bypasses the gate")</div></div>
406
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-before-auth-state-changed.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-before-auth-state-changed.test.ts</code></a> ("sandbox.setUser test driver bypasses the gate")</div></div>
407
407
  </details>
408
408
  <details class="compat-row" data-status="diverged">
409
409
  <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">beforeAuthStateChanged(auth, callback, onAbort?)</code><span class="compat-sub"><span class="compat-behavior">Served-worker path (SharedWorker-backed auth in <code>@pyric/cli</code>): the worker owns the shared user pool and commits transitions on its own side of the port, so a page-local <code>beforeAuthStateChanged</code> registration can't actually gate a worker-driven sign-in. Rather than silently accept a callback that could never run, registering THROWS immediately (<code>auth/operation-not-supported-in-this-environment</code>) — same defensive pattern as <code>signInWithCredential</code> over the worker.</span></span></span></summary>
@@ -414,33 +414,33 @@
414
414
  <div class="compat-list">
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">initializeAuth(app, deps?)</code><span class="compat-sub"><span class="compat-behavior">Aliases <code>getAuth(app)</code> and returns the same stable <code>Auth</code> handle — an app that calls <code>initializeAuth</code> instead of <code>getAuth</code> gets an equivalent, working instance. The optional <code>Dependencies</code> arg (persistence / popupRedirectResolver) is accepted for signature parity but not applied (persistence is already a documented sandbox no-op). Repeated calls return the cached handle rather than throwing <code>auth/already-initialized</code></span></span></span></summary>
417
- <div class="compat-evidence"><div class="compat-probe"><code>unit:fruit-aliases.test.ts</code> — returns the same instance as <code>getAuth</code>, with a live <code>currentUser</code></div></div>
417
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/fruit-aliases.test.ts#L13" target="_blank" rel="noopener noreferrer"><code>unit:fruit-aliases.test.ts</code></a> — returns the same instance as <code>getAuth</code>, with a live <code>currentUser</code></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">deleteUser(user)</code><span class="compat-sub"><span class="compat-behavior">Deletes the account from the user store AND signs the user out if they are current (fires <code>onAuthStateChanged(null)</code>), matching prod where deleting the signed-in user clears <code>auth.currentUser</code>. Real behavior: a subsequent <code>signInWithEmailAndPassword</code> for that identity throws <code>auth/user-not-found</code>; <code>reload(user)</code> after delete rejects with <code>auth/user-token-expired</code></span></span></span></summary>
421
- <div class="compat-evidence"><div class="compat-probe"><code>unit:fruit-aliases.test.ts</code> — user removed from the store, sign-out fired, re-sign-in throws <code>auth/user-not-found</code>; post-delete <code>reload</code> → <code>auth/user-token-expired</code>: <code>unit:upstream-auth-probes.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/auth/fruit-aliases.test.ts#L17" target="_blank" rel="noopener noreferrer"><code>unit:fruit-aliases.test.ts</code></a> — user removed from the store, sign-out fired, re-sign-in throws <code>auth/user-not-found</code>; post-delete <code>reload</code> → <code>auth/user-token-expired</code>: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/upstream-auth-probes.test.ts#L7" target="_blank" rel="noopener noreferrer"><code>unit:upstream-auth-probes.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">updateEmail(user, newEmail)</code><span class="compat-sub"><span class="compat-behavior">Changes the stored email (via the same path as <code>sandbox.updateUser</code>, rejecting <code>auth/email-already-in-use</code> / <code>auth/invalid-email</code>) and mutates the held <code>user</code> in place, so the next sign-in resolves against the new email. Leniency vs prod: the sandbox does NOT enforce <code>auth/requires-recent-login</code> and is not routed through <code>verifyBeforeUpdateEmail</code> (which the real SDK requires when email-enumeration protection is on)</span></span></span></summary>
425
- <div class="compat-evidence"><div class="compat-probe"><code>unit:fruit-aliases.test.ts</code> — stored email changes; re-sign-in works with the new email, fails with the old</div>
425
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/fruit-aliases.test.ts#L18" target="_blank" rel="noopener noreferrer"><code>unit:fruit-aliases.test.ts</code></a> — stored email changes; re-sign-in works with the new email, fails with the old</div>
426
426
  <div class="compat-note">email really changes; no requires-recent-login / verifyBeforeUpdateEmail enforcement</div></div>
427
427
  </details>
428
428
  <details class="compat-row" data-status="diverged">
429
429
  <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">updatePassword(user, newPassword)</code><span class="compat-sub"><span class="compat-behavior">Sets the stored password (validated for strength). The sandbox DOES store and verify passwords, so this is a real mutation: a subsequent <code>signInWithEmailAndPassword</code> with the new password succeeds and the old one throws <code>auth/wrong-password</code>. Leniency vs prod: no <code>auth/requires-recent-login</code> enforcement</span></span></span></summary>
430
- <div class="compat-evidence"><div class="compat-probe"><code>unit:fruit-aliases.test.ts</code> — new password signs in, old password throws <code>auth/wrong-password</code></div>
430
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/fruit-aliases.test.ts#L19" target="_blank" rel="noopener noreferrer"><code>unit:fruit-aliases.test.ts</code></a> — new password signs in, old password throws <code>auth/wrong-password</code></div>
431
431
  <div class="compat-note">password really changes + is verified; no requires-recent-login enforcement</div></div>
432
432
  </details>
433
433
  <details class="compat-row" data-status="ok">
434
434
  <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">reload(user)</code><span class="compat-sub"><span class="compat-behavior">Re-reads the stored record into the <code>user</code> object in place, so a change made out of band (e.g. <code>sandbox.updateUser</code>) is reflected on the held reference — matching prod's server refresh. If the identity is absent from the store (including after <code>deleteUser</code>), rejects with <code>auth/user-token-expired</code></span></span></span></summary>
435
- <div class="compat-evidence"><div class="compat-probe"><code>unit:fruit-aliases.test.ts</code> — an out-of-band <code>sandbox.updateUser</code> displayName change is visible on the held user after <code>reload</code>; missing-store / post-delete: <code>unit:upstream-auth-probes.test.ts</code></div></div>
435
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/fruit-aliases.test.ts#L20" target="_blank" rel="noopener noreferrer"><code>unit:fruit-aliases.test.ts</code></a> — an out-of-band <code>sandbox.updateUser</code> displayName change is visible on the held user after <code>reload</code>; missing-store / post-delete: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/upstream-auth-probes.test.ts#L7" target="_blank" rel="noopener noreferrer"><code>unit:upstream-auth-probes.test.ts</code></a></div></div>
436
436
  </details>
437
437
  <details class="compat-row" data-status="ok">
438
438
  <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">updateCurrentUser(auth, user)</code><span class="compat-sub"><span class="compat-behavior">Sets the sandbox's current user (pass <code>null</code> to sign out), firing <code>onAuthStateChanged</code> — <code>auth.currentUser</code> reflects the passed user afterward</span></span></span></summary>
439
- <div class="compat-evidence"><div class="compat-probe"><code>unit:fruit-aliases.test.ts</code> — <code>auth.currentUser</code> becomes the passed user; <code>null</code> signs out</div></div>
439
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/fruit-aliases.test.ts#L21" target="_blank" rel="noopener noreferrer"><code>unit:fruit-aliases.test.ts</code></a> — <code>auth.currentUser</code> becomes the passed user; <code>null</code> signs out</div></div>
440
440
  </details>
441
441
  <details class="compat-row" data-status="diverged">
442
442
  <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">useDeviceLanguage(auth)</code><span class="compat-sub"><span class="compat-behavior">Accepted no-op — the sandbox has no device locale to read, so there is no language to set; accepted so init code that calls it compiles + runs</span></span></span></summary>
443
- <div class="compat-evidence"><div class="compat-probe"><code>unit:fruit-aliases.test.ts</code> — resolves/returns without error</div>
443
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/fruit-aliases.test.ts#L22" target="_blank" rel="noopener noreferrer"><code>unit:fruit-aliases.test.ts</code></a> — resolves/returns without error</div>
444
444
  <div class="compat-note">no device locale in the sandbox</div></div>
445
445
  </details>
446
446
  </div>
@@ -448,124 +448,124 @@
448
448
  <div class="compat-list">
449
449
  <details class="compat-row" data-status="ok">
450
450
  <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">ActionCodeURL.parseLink(link) / parseActionCodeURL(link)</code><span class="compat-sub"><span class="compat-behavior">Parses an out-of-band action link. The <code>mode</code> query param maps to a NORMALIZED operation (<code>mode=resetPassword</code> -> <code>PASSWORD_RESET</code>, <code>mode=signIn</code> -> <code>EMAIL_SIGNIN</code>), <code>oobCode</code> surfaces as <code>code</code>, <code>lang</code> as <code>languageCode</code>, and <code>continueUrl</code> comes out URL-DECODED. A link missing <code>mode</code>, missing <code>oobCode</code>, carrying an unknown mode, or that is not a URL at all parses to <code>null</code> — the parse NEVER throws. <code>parseActionCodeURL</code> and <code>ActionCodeURL.parseLink</code> agree.</span></span></span></summary>
451
- <div class="compat-evidence"><div class="compat-probe">ORACLE-BACKED and project-independent: this is a PURE client-side parse (no network, no project, no mailbox), so the sandbox owes prod an exact match and there is no room for a divergence. Oracle: <code>auth-actioncodeurl-parse</code> against firebase-js-sdk 12.13.0. Replayed in <code>unit:oracle-conformance.test.ts</code>.</div></div>
451
+ <div class="compat-evidence"><div class="compat-probe">ORACLE-BACKED and project-independent: this is a PURE client-side parse (no network, no project, no mailbox), so the sandbox owes prod an exact match and there is no room for a divergence. Oracle: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/auth/auth-actioncodeurl-parse.json" target="_blank" rel="noopener noreferrer"><code>auth-actioncodeurl-parse</code></a> against firebase-js-sdk 12.13.0. Replayed in <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/oracle-conformance.test.ts#L563" target="_blank" rel="noopener noreferrer"><code>unit:oracle-conformance.test.ts</code></a>. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-email-link.test.ts#L15" target="_blank" rel="noopener noreferrer"><code>sandbox-email-link.test.ts</code></a>)</div></div>
452
452
  </details>
453
453
  <details class="compat-row" data-status="ok">
454
454
  <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">isSignInWithEmailLink(auth, link)</code><span class="compat-sub"><span class="compat-behavior">Pure predicate over the link string — no network. <code>true</code> only for a link whose mode is <code>signIn</code> AND which carries an <code>oobCode</code>; <code>false</code> for a password-reset link, for <code>mode=signIn</code> with no code, for garbage, and for the empty string. Never throws.</span></span></span></summary>
455
- <div class="compat-evidence"><div class="compat-probe">ORACLE-BACKED, project-independent (no server involved): <code>auth-issigninwithemaillink-predicate</code> captured all five cases against prod. Replayed in <code>unit:oracle-conformance.test.ts</code>.</div></div>
455
+ <div class="compat-evidence"><div class="compat-probe">ORACLE-BACKED, project-independent (no server involved): <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/auth/auth-issigninwithemaillink-predicate.json" target="_blank" rel="noopener noreferrer"><code>auth-issigninwithemaillink-predicate</code></a> captured all five cases against prod. Replayed in <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/oracle-conformance.test.ts#L599" target="_blank" rel="noopener noreferrer"><code>unit:oracle-conformance.test.ts</code></a>. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-email-link.test.ts#L22" target="_blank" rel="noopener noreferrer"><code>sandbox-email-link.test.ts</code></a>)</div></div>
456
456
  </details>
457
457
  <details class="compat-row" data-status="ok">
458
458
  <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">applyActionCode(auth, code)</code><span class="compat-sub"><span class="compat-behavior">Redeems an out-of-band code and performs its state change (<code>VERIFY_EMAIL</code> sets <code>emailVerified</code>, <code>VERIFY_AND_CHANGE_EMAIL</code> moves the account to the new address). Throws <code>auth/invalid-action-code</code> for a code the project never issued, for the empty string, and for a code already redeemed (single-use). A <code>PASSWORD_RESET</code> code is refused — <code>confirmPasswordReset</code> owns that one.</span></span></span></summary>
459
- <div class="compat-evidence"><div class="compat-probe">ORACLE-BACKED on the reject path: <code>auth-action-code-invalid</code> confirmed prod emits <code>auth/invalid-action-code</code> for both a bogus code and the empty string (this endpoint is NOT gated on the password provider, so it answered honestly). The APPLY path (a real code, redeemed) cannot be probed from a client — it needs a code from a real inbox — and is unit-backed end to end against the sandbox outbox in <code>unit:sandbox-email-link.test.ts</code>.</div></div>
459
+ <div class="compat-evidence"><div class="compat-probe">ORACLE-BACKED on the reject path: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/auth/auth-action-code-invalid.json" target="_blank" rel="noopener noreferrer"><code>auth-action-code-invalid</code></a> confirmed prod emits <code>auth/invalid-action-code</code> for both a bogus code and the empty string (this endpoint is NOT gated on the password provider, so it answered honestly). The APPLY path (a real code, redeemed) cannot be probed from a client — it needs a code from a real inbox — and is unit-backed end to end against the sandbox outbox in <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-email-link.test.ts#L16" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-email-link.test.ts</code></a>. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/oracle-conformance.test.ts#L609" target="_blank" rel="noopener noreferrer"><code>oracle-conformance.test.ts</code></a>)</div></div>
460
460
  </details>
461
461
  <details class="compat-row" data-status="ok">
462
462
  <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">sendSignInLinkToEmail(auth, email, settings)</code><span class="compat-sub"><span class="compat-behavior">ActionCodeSettings validation, enforced CLIENT-side before any request leaves the process: a missing or unparseable <code>url</code> throws <code>auth/invalid-continue-uri</code> (NOT <code>auth/missing-continue-uri</code>, despite the constant's name), and <code>handleCodeInApp</code> other than <code>true</code> throws <code>auth/argument-error</code>. On success the sandbox mints a single-use code and posts the message to the outbox.</span></span></span></summary>
463
- <div class="compat-evidence"><div class="compat-probe">ORACLE-BACKED on both client-side arms (project-independent — prod threw before any network call): <code>auth-sendsigninlinktoemail-settings-validation</code> captured <code>missingUrl: auth/invalid-continue-uri</code> and <code>handleCodeInAppFalse: auth/argument-error</code>. Replayed in <code>unit:oracle-conformance.test.ts</code>; the send path is unit-backed in <code>unit:sandbox-email-link.test.ts</code>.</div></div>
463
+ <div class="compat-evidence"><div class="compat-probe">ORACLE-BACKED on both client-side arms (project-independent — prod threw before any network call): <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/auth/auth-sendsigninlinktoemail-settings-validation.json" target="_blank" rel="noopener noreferrer"><code>auth-sendsigninlinktoemail-settings-validation</code></a> captured <code>missingUrl: auth/invalid-continue-uri</code> and <code>handleCodeInAppFalse: auth/argument-error</code>. Replayed in <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/oracle-conformance.test.ts#L624" target="_blank" rel="noopener noreferrer"><code>unit:oracle-conformance.test.ts</code></a>; the send path is unit-backed in <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-email-link.test.ts#L26" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-email-link.test.ts</code></a>.</div></div>
464
464
  </details>
465
465
  <details class="compat-row" data-status="ok">
466
466
  <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">signInWithEmailLink(auth, email, link)</code><span class="compat-sub"><span class="compat-behavior">Redeems the code in a sign-in link. A link carrying no <code>oobCode</code> throws <code>auth/argument-error</code> CLIENT-side (the SDK never reaches the server to ask about a code it cannot find in the link). A first-time sign-in for an address CREATES the account, <code>getAdditionalUserInfo(cred).isNewUser</code> is <code>true</code>, and the account arrives <code>emailVerified: true</code> — redeeming a code mailed to that address is proof of control. The code is single-use.</span></span></span></summary>
467
- <div class="compat-evidence"><div class="compat-probe">ORACLE-BACKED on the client-side reject (<code>auth-signinwithemaillink-invalid-link</code>: <code>noOobCode: auth/argument-error</code>). The REDEMPTION path could not be probed: the oracle project has email-link sign-in disabled, so the server arm answered <code>auth/operation-not-allowed</code>. That half is unit-backed end to end against the sandbox outbox (<code>unit:sandbox-email-link.test.ts</code> drives send -> read the outbox -> sign in).</div></div>
467
+ <div class="compat-evidence"><div class="compat-probe">ORACLE-BACKED on the client-side reject (<a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/auth/auth-signinwithemaillink-invalid-link.json" target="_blank" rel="noopener noreferrer"><code>auth-signinwithemaillink-invalid-link</code></a>: <code>noOobCode: auth/argument-error</code>). The REDEMPTION path could not be probed: the oracle project has email-link sign-in disabled, so the server arm answered <code>auth/operation-not-allowed</code>. That half is unit-backed end to end against the sandbox outbox (<a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-email-link.test.ts#L28" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-email-link.test.ts</code></a> drives send -> read the outbox -> sign in). (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/oracle-conformance.test.ts#L644" target="_blank" rel="noopener noreferrer"><code>oracle-conformance.test.ts</code></a>)</div></div>
468
468
  </details>
469
469
  <details class="compat-row" data-status="ok">
470
470
  <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">sendPasswordResetEmail(auth, email)</code><span class="compat-sub"><span class="compat-behavior">For an address NO account owns, prod RESOLVES silently — it does not throw <code>auth/user-not-found</code>. Email Enumeration Protection is on by default and refusing to leak account existence is the point. The sandbox matches: it resolves and mails nothing. A malformed address still throws <code>auth/invalid-email</code>. For a real account, the sandbox mails a reset code; <code>confirmPasswordReset</code> redeems it and the new password signs in while the old one throws <code>auth/wrong-password</code>.</span></span></span></summary>
471
- <div class="compat-evidence"><div class="compat-probe">ORACLE-BACKED on the enumeration-protection behavior — the fact most likely to be got wrong: <code>auth-sendpasswordresetemail-unknown-user</code> captured <code>resolvedForUnknownUser: true</code>, <code>unknownUserCode: null</code>, <code>malformedEmailCode: auth/invalid-email</code>. A shim that threw <code>auth/user-not-found</code> here would hand agent code an account oracle production deliberately removed. Reset round trip unit-backed in <code>unit:sandbox-email-link.test.ts</code>.</div></div>
471
+ <div class="compat-evidence"><div class="compat-probe">ORACLE-BACKED on the enumeration-protection behavior — the fact most likely to be got wrong: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/auth/auth-sendpasswordresetemail-unknown-user.json" target="_blank" rel="noopener noreferrer"><code>auth-sendpasswordresetemail-unknown-user</code></a> captured <code>resolvedForUnknownUser: true</code>, <code>unknownUserCode: null</code>, <code>malformedEmailCode: auth/invalid-email</code>. A shim that threw <code>auth/user-not-found</code> here would hand agent code an account oracle production deliberately removed. Reset round trip unit-backed in <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-email-link.test.ts#L25" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-email-link.test.ts</code></a>. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/oracle-conformance.test.ts#L656" target="_blank" rel="noopener noreferrer"><code>oracle-conformance.test.ts</code></a>)</div></div>
472
472
  </details>
473
473
  <details class="compat-row" data-status="ok">
474
474
  <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">sendEmailVerification(user)</code><span class="compat-sub"><span class="compat-behavior">Throws <code>auth/missing-email</code> for a user with no email on the account (an anonymous user). For a real account the mail goes out and NOTHING ELSE HAPPENS: <code>user.emailVerified</code> stays <code>false</code>. Verification happens when the code in the message is redeemed (<code>applyActionCode</code>), not when it is sent.</span></span></span></summary>
475
- <div class="compat-evidence"><div class="compat-probe">ORACLE-BACKED on the anonymous reject: <code>auth-sendemailverification-shape</code> captured <code>anonymousUserCode: auth/missing-email</code> against prod. The send-does-not-verify property is the one the whole flow turns on and is unit-backed (<code>unit:sandbox-email-link.test.ts</code> asserts <code>emailVerified</code> is still false after the send and true only after the code is applied) — it cannot be oracle-confirmed end to end because confirming it would require reading a real inbox.</div></div>
475
+ <div class="compat-evidence"><div class="compat-probe">ORACLE-BACKED on the anonymous reject: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/auth/auth-sendemailverification-shape.json" target="_blank" rel="noopener noreferrer"><code>auth-sendemailverification-shape</code></a> captured <code>anonymousUserCode: auth/missing-email</code> against prod. The send-does-not-verify property is the one the whole flow turns on and is unit-backed (<a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-email-link.test.ts#L24" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-email-link.test.ts</code></a> asserts <code>emailVerified</code> is still false after the send and true only after the code is applied) — it cannot be oracle-confirmed end to end because confirming it would require reading a real inbox. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/oracle-conformance.test.ts#L676" target="_blank" rel="noopener noreferrer"><code>oracle-conformance.test.ts</code></a>)</div></div>
476
476
  </details>
477
477
  <details class="compat-row" data-status="ok">
478
478
  <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">verifyBeforeUpdateEmail(user, newEmail)</code><span class="compat-sub"><span class="compat-behavior">Mails a code to the NEW address and returns. The account's email does NOT change until that code is redeemed — the single guarantee separating this API from a bare <code>updateEmail</code>: the user must prove control of the new address before it becomes theirs. On redemption the account moves and the new address arrives <code>emailVerified: true</code>. <code>checkActionCode</code> on the code reports <code>data.email</code> = the new address and <code>data.previousEmail</code> = the old one.</span></span></span></summary>
479
- <div class="compat-evidence"><div class="compat-probe">UNIT-BACKED, not oracle-backed, and the reason is recorded rather than hidden: the probe (<code>auth-verifybeforeupdateemail-shape</code>) ran against the real project and came back <code>auth/operation-not-allowed</code> on every arm, because the oracle project has the Email/Password provider DISABLED and the probe could not even create the user whose email it would change. The capture is committed showing exactly that. Behavior is proven end to end against the sandbox in <code>unit:sandbox-email-link.test.ts</code>.</div></div>
479
+ <div class="compat-evidence"><div class="compat-probe">UNIT-BACKED, not oracle-backed, and the reason is recorded rather than hidden: the probe (<a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/auth/auth-verifybeforeupdateemail-shape.json" target="_blank" rel="noopener noreferrer"><code>auth-verifybeforeupdateemail-shape</code></a>) ran against the real project and came back <code>auth/operation-not-allowed</code> on every arm, because the oracle project has the Email/Password provider DISABLED and the probe could not even create the user whose email it would change. The capture is committed showing exactly that. Behavior is proven end to end against the sandbox in <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-email-link.test.ts#L30" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-email-link.test.ts</code></a>. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/oracle-conformance.test.ts#L89" target="_blank" rel="noopener noreferrer"><code>oracle-conformance.test.ts</code></a>)</div></div>
480
480
  </details>
481
481
  <details class="compat-row" data-status="ok">
482
482
  <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">sandbox.takeAuthMail(auth) / sandbox.setAuthMailResolver(auth, resolver)</code><span class="compat-sub"><span class="compat-behavior">The sandbox IS the mail server. Every send posts a real message (operation, recipient, single-use code, and the full action link) to an outbox; <code>sandbox.takeAuthMail</code> reads it — the program's substitute for a human opening an inbox. The mailed link round-trips through the public <code>isSignInWithEmailLink</code> / <code>parseActionCodeURL</code> unchanged, and its code is the code the redeemer accepts, so the round trip production cannot complete without a human closes in-process. An installed <code>AuthMailResolver</code> is additionally notified per message; a resolver that THROWS does not fail the send that produced it.</span></span></span></summary>
483
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-email-link.test.ts</code> — sandbox-only driver (no <code>firebase/auth</code> counterpart; this is the seam that makes the family testable at all).</div></div>
483
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-email-link.test.ts#L102" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-email-link.test.ts</code></a> — sandbox-only driver (no <code>firebase/auth</code> counterpart; this is the seam that makes the family testable at all).</div></div>
484
484
  </details>
485
485
  <details class="compat-row" data-status="ok">
486
486
  <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">checkActionCode(auth, code) / verifyPasswordResetCode(auth, code) / confirmPasswordReset(auth, code, newPassword)</code><span class="compat-sub"><span class="compat-behavior"><code>checkActionCode</code> and <code>verifyPasswordResetCode</code> INSPECT a code without burning it — a check must not destroy the code the subsequent apply needs. <code>confirmPasswordReset</code> redeems it and sets the password, running the same strength check <code>createUserWithEmailAndPassword</code> runs; a weak new password throws <code>auth/weak-password</code> and does NOT burn the code (a typo must not destroy the user's one reset link). A code staged as expired throws <code>auth/expired-action-code</code>.</span></span></span></summary>
487
- <div class="compat-evidence"><div class="compat-probe">UNIT-BACKED, stated honestly: these three endpoints answered <code>auth/operation-not-allowed</code> in the oracle run (<code>auth-action-code-invalid</code>) because the oracle project's disabled password provider replies before the invalid-code contract can. That is a fact about the project's configuration, not about the API, so it is NOT asserted as evidence. The error codes used here are matched to the oracle-CAPTURED <code>AuthErrorCodes</code> map (<code>INVALID_OOB_CODE = auth/invalid-action-code</code>, <code>EXPIRED_OOB_CODE = auth/expired-action-code</code> — see auth#172). Behavior proven in <code>unit:sandbox-email-link.test.ts</code>.</div></div>
487
+ <div class="compat-evidence"><div class="compat-probe">UNIT-BACKED, stated honestly: these three endpoints answered <code>auth/operation-not-allowed</code> in the oracle run (<code>auth-action-code-invalid</code>) because the oracle project's disabled password provider replies before the invalid-code contract can. That is a fact about the project's configuration, not about the API, so it is NOT asserted as evidence. The error codes used here are matched to the oracle-CAPTURED <code>AuthErrorCodes</code> map (<code>INVALID_OOB_CODE = auth/invalid-action-code</code>, <code>EXPIRED_OOB_CODE = auth/expired-action-code</code> — see auth#172). Behavior proven in <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-email-link.test.ts#L17" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-email-link.test.ts</code></a>.</div></div>
488
488
  </details>
489
489
  <details class="compat-row" data-status="diverged">
490
490
  <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">sendSignInLinkToEmail</code><span class="compat-sub"><span class="compat-category"><code>auth/unauthorized-continue-uri</code> (domain allowlist)</span> · <span class="compat-behavior">Prod rejects a continue URL whose domain is not on the project's authorized-domains list. The sandbox has NO domain allowlist and does not invent one: it accepts any parseable continue URL. A continue URL that would be rejected in production is accepted here.</span></span></span></summary>
491
- <div class="compat-evidence"><div class="compat-probe">The sandbox has no project config to hold an authorized-domains list, so there is nothing to check against; inventing an allowlist would mean inventing a policy the user never set. The probe attempted the server arm (<code>auth-sendsigninlinktoemail-settings-validation</code>, <code>unauthorizedDomain</code>) but the oracle project has email-link sign-in disabled, so it answered <code>auth/operation-not-allowed</code> rather than <code>auth/unauthorized-continue-uri</code> — the divergence is declared from the documented contract, not from a capture we do not have.</div></div>
491
+ <div class="compat-evidence"><div class="compat-probe">The sandbox has no project config to hold an authorized-domains list, so there is nothing to check against; inventing an allowlist would mean inventing a policy the user never set. The probe attempted the server arm (<code>auth-sendsigninlinktoemail-settings-validation</code>, <code>unauthorizedDomain</code>) but the oracle project has email-link sign-in disabled, so it answered <code>auth/operation-not-allowed</code> rather than <code>auth/unauthorized-continue-uri</code> — the divergence is declared from the documented contract, not from a capture we do not have. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-email-link.test.ts#L26" target="_blank" rel="noopener noreferrer"><code>sandbox-email-link.test.ts</code></a>)</div></div>
492
492
  </details>
493
493
  </div>
494
494
  <h2 id="linkwithcredential--linkwithpopup--linkwithredirect--unlink--account-linking"><code>linkWithCredential</code> / <code>linkWithPopup</code> / <code>linkWithRedirect</code> / <code>unlink</code> — account linking</h2>
495
495
  <div class="compat-list">
496
496
  <details class="compat-row" data-status="ok">
497
497
  <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">linkWithCredential(user, credential)</code><span class="compat-sub"><span class="compat-behavior">The ANONYMOUS UPGRADE. Linking an email credential onto an anonymous account upgrades it IN PLACE: the uid is PRESERVED, <code>isAnonymous</code> flips to <code>false</code>, the email is set, and <code>providerData</code> gains the provider. Preserving the uid is what keeps the data the user created while anonymous theirs. Returns a <code>UserCredential</code> with <code>operationType: 'link'</code> and <code>getAdditionalUserInfo().isNewUser === false</code> (a link never creates an identity). The linked credential then works as a first-class <code>signInWithEmailAndPassword</code>.</span></span></span></summary>
498
- <div class="compat-evidence"><div class="compat-probe">UNIT-BACKED, not oracle-backed — stated plainly. The probe (<code>auth-link-email-credential-to-anonymous</code>) ran against the real project and returned <code>linkCode: auth/operation-not-allowed</code>: the oracle project has the Email/Password provider DISABLED, so no email credential can be minted there and the flow cannot be reached. The capture is committed showing exactly that rather than being dropped. Behavior proven in <code>unit:sandbox-linking-reauth.test.ts</code>, including the uid-preservation invariant.</div></div>
498
+ <div class="compat-evidence"><div class="compat-probe">UNIT-BACKED, not oracle-backed — stated plainly. The probe (<a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/auth/auth-link-email-credential-to-anonymous.json" target="_blank" rel="noopener noreferrer"><code>auth-link-email-credential-to-anonymous</code></a>) ran against the real project and returned <code>linkCode: auth/operation-not-allowed</code>: the oracle project has the Email/Password provider DISABLED, so no email credential can be minted there and the flow cannot be reached. The capture is committed showing exactly that rather than being dropped. Behavior proven in <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-linking-reauth.test.ts#L25" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-linking-reauth.test.ts</code></a>, including the uid-preservation invariant.</div></div>
499
499
  </details>
500
500
  <details class="compat-row" data-status="ok">
501
501
  <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">linkWithCredential</code><span class="compat-sub"><span class="compat-category">conflict codes</span> · <span class="compat-behavior"><code>auth/provider-already-linked</code> when the account already carries the provider (one identity per provider). <code>auth/email-already-in-use</code> when the email credential belongs to a DIFFERENT account — an address can back only one identity, so the link cannot be granted without stealing it.</span></span></span></summary>
502
- <div class="compat-evidence"><div class="compat-probe">UNIT-BACKED. The probe (<code>auth-link-conflicts</code>) ran and every arm returned <code>auth/operation-not-allowed</code> (same disabled Email/Password provider on the oracle project), so the conflict codes could not be observed against prod. They are matched instead to the oracle-CAPTURED <code>AuthErrorCodes</code> map (<code>PROVIDER_ALREADY_LINKED = auth/provider-already-linked</code>, <code>CREDENTIAL_ALREADY_IN_USE = auth/credential-already-in-use</code> — auth#172) and proven in <code>unit:sandbox-linking-reauth.test.ts</code>. NOTE: for an EMAIL credential the sandbox emits <code>auth/email-already-in-use</code>, the code prod uses for an address collision; <code>credential-already-in-use</code> remains the OAuth-credential case.</div></div>
502
+ <div class="compat-evidence"><div class="compat-probe">UNIT-BACKED. The probe (<a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/auth/auth-link-conflicts.json" target="_blank" rel="noopener noreferrer"><code>auth-link-conflicts</code></a>) ran and every arm returned <code>auth/operation-not-allowed</code> (same disabled Email/Password provider on the oracle project), so the conflict codes could not be observed against prod. They are matched instead to the oracle-CAPTURED <code>AuthErrorCodes</code> map (<code>PROVIDER_ALREADY_LINKED = auth/provider-already-linked</code>, <code>CREDENTIAL_ALREADY_IN_USE = auth/credential-already-in-use</code> — auth#172) and proven in <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-linking-reauth.test.ts#L25" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-linking-reauth.test.ts</code></a>. NOTE: for an EMAIL credential the sandbox emits <code>auth/email-already-in-use</code>, the code prod uses for an address collision; <code>credential-already-in-use</code> remains the OAuth-credential case.</div></div>
503
503
  </details>
504
504
  <details class="compat-row" data-status="ok">
505
505
  <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">unlink(user, providerId)</code><span class="compat-sub"><span class="compat-behavior">Detaches a provider and returns the updated user with a SHRUNKEN <code>providerData</code>. Unlinking a provider that was never linked throws <code>auth/no-such-provider</code>. Unlinking <code>'password'</code> takes the password with it, so <code>signInWithEmailAndPassword</code> for that account stops working. Unlinking the LAST provider does NOT re-anonymize the account — <code>isAnonymous</code> describes how an identity was born, not what it currently carries.</span></span></span></summary>
506
- <div class="compat-evidence"><div class="compat-probe">ORACLE-BACKED on the reject path — the ONE linking fact the oracle could reach, because it needs no email credential: <code>auth-unlink-provider</code> captured <code>noSuchProviderCode: auth/no-such-provider</code> against prod on an anonymous user. Replayed in <code>unit:oracle-conformance.test.ts</code>. The detach path is unit-backed (<code>unit:sandbox-linking-reauth.test.ts</code>).</div></div>
506
+ <div class="compat-evidence"><div class="compat-probe">ORACLE-BACKED on the reject path — the ONE linking fact the oracle could reach, because it needs no email credential: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/auth/auth-unlink-provider.json" target="_blank" rel="noopener noreferrer"><code>auth-unlink-provider</code></a> captured <code>noSuchProviderCode: auth/no-such-provider</code> against prod on an anonymous user. Replayed in <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/oracle-conformance.test.ts#L685" target="_blank" rel="noopener noreferrer"><code>unit:oracle-conformance.test.ts</code></a>. The detach path is unit-backed (<a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-linking-reauth.test.ts#L33" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-linking-reauth.test.ts</code></a>).</div></div>
507
507
  </details>
508
508
  <details class="compat-row" data-status="ok">
509
509
  <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">linkWithPopup(user, provider, resolver?) / linkWithRedirect(...)</code><span class="compat-sub"><span class="compat-behavior">Route through the SAME <code>AuthFlowResolver</code> seam as <code>signInWithPopup</code>, with <code>authType: 'link'</code> on the request so a host UI can present 'link your Google account' rather than 'sign in'. The resolved credential names the provider to attach; the sandbox performs the attach and the uid is preserved. A disabled provider throws <code>auth/operation-not-allowed</code> AHEAD of the resolver check — a code deliberately distinct from <code>auth/argument-error</code>, which keeps meaning 'enabled, but no resolver/mock wired'.</span></span></span></summary>
510
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-linking-reauth.test.ts</code> — asserts the resolver sees <code>authType: 'link'</code>, the uid is preserved, and the two error codes stay distinct. The OAuth arm cannot be oracle-probed at all (it needs a real IdP popup and a human), which is precisely why it goes through the resolver seam.</div></div>
510
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-linking-reauth.test.ts#L26" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-linking-reauth.test.ts</code></a> — asserts the resolver sees <code>authType: 'link'</code>, the uid is preserved, and the two error codes stay distinct. The OAuth arm cannot be oracle-probed at all (it needs a real IdP popup and a human), which is precisely why it goes through the resolver seam.</div></div>
511
511
  </details>
512
512
  </div>
513
513
  <h2 id="reauthenticatewithcredential--reauthenticatewithpopup--reauthenticatewithredirect--re-authentication"><code>reauthenticateWithCredential</code> / <code>reauthenticateWithPopup</code> / <code>reauthenticateWithRedirect</code> — re-authentication</h2>
514
514
  <div class="compat-list">
515
515
  <details class="compat-row" data-status="ok">
516
516
  <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">reauthenticateWithCredential(user, credential)</code><span class="compat-sub"><span class="compat-behavior">Really re-verifies: an email credential is checked against the stored password exactly as <code>signInWithEmailAndPassword</code> checks it. A wrong password throws <code>auth/wrong-password</code>. A credential belonging to a DIFFERENT account throws <code>auth/user-mismatch</code> — checked BEFORE the password compare, so it cannot leak whether the other account's password was right. On success a fresh ID token is minted (a new <code>authTime</code>), and the returned <code>UserCredential</code> carries <code>operationType: 'reauthenticate'</code>.</span></span></span></summary>
517
- <div class="compat-evidence"><div class="compat-probe">UNIT-BACKED, and the reason it is not oracle-backed is recorded rather than glossed: the probe (<code>auth-reauthenticate-with-credential</code>) ran against the real project and could not even create the two accounts it needs — <code>setupCode: auth/operation-not-allowed</code>, because the oracle project has the Email/Password provider DISABLED. The capture is committed showing that. <code>auth/user-mismatch</code> is matched to the oracle-captured <code>AuthErrorCodes</code> map (auth#172). Behavior proven in <code>unit:sandbox-linking-reauth.test.ts</code>.</div></div>
517
+ <div class="compat-evidence"><div class="compat-probe">UNIT-BACKED, and the reason it is not oracle-backed is recorded rather than glossed: the probe (<a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/auth/auth-reauthenticate-with-credential.json" target="_blank" rel="noopener noreferrer"><code>auth-reauthenticate-with-credential</code></a>) ran against the real project and could not even create the two accounts it needs — <code>setupCode: auth/operation-not-allowed</code>, because the oracle project has the Email/Password provider DISABLED. The capture is committed showing that. <code>auth/user-mismatch</code> is matched to the oracle-captured <code>AuthErrorCodes</code> map (auth#172). Behavior proven in <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-linking-reauth.test.ts#L27" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-linking-reauth.test.ts</code></a>.</div></div>
518
518
  </details>
519
519
  <details class="compat-row" data-status="diverged">
520
520
  <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">reauthenticate*</code><span class="compat-sub"><span class="compat-category"><code>auth/requires-recent-login</code> is NOT enforced</span> · <span class="compat-behavior">In production the POINT of re-authentication is the <code>auth/requires-recent-login</code> gate: <code>updateEmail</code> / <code>updatePassword</code> / <code>deleteUser</code> refuse to run on a session whose sign-in is older than a few minutes, and re-auth is how you clear it. The sandbox does NOT enforce that gate — those three mutations already run on a session of any age (a pre-existing documented divergence), so there is no gate here for re-auth to clear. What re-auth DOES do here is real but narrower: it genuinely re-verifies the credential, mints a fresh token, and returns <code>operationType: 'reauthenticate'</code>. Code that calls it runs unchanged against prod, where it also clears the gate.</span></span></span></summary>
521
- <div class="compat-evidence"><div class="compat-probe">Declared divergence, not a bug: inventing a recent-login gate would break every existing sandbox flow (which never re-authenticates) while proving nothing. <code>unit:sandbox-linking-reauth.test.ts</code> pins the behavior that IS provided (real credential re-verification + a fresh token).</div></div>
521
+ <div class="compat-evidence"><div class="compat-probe">Declared divergence, not a bug: inventing a recent-login gate would break every existing sandbox flow (which never re-authenticates) while proving nothing. <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-linking-reauth.test.ts#L27" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-linking-reauth.test.ts</code></a> pins the behavior that IS provided (real credential re-verification + a fresh token).</div></div>
522
522
  </details>
523
523
  <details class="compat-row" data-status="ok">
524
524
  <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">reauthenticateWithPopup(user, provider, resolver?) / reauthenticateWithRedirect(...)</code><span class="compat-sub"><span class="compat-behavior">Route through the shared resolver seam with <code>authType: 'reauth'</code>. The identity the resolver produces MUST be the user being re-authenticated — a resolver that hands back a different uid throws <code>auth/user-mismatch</code>. That check is the entire security content of the flow; without it 're-authentication' would accept anyone.</span></span></span></summary>
525
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-linking-reauth.test.ts</code> — asserts the resolver sees <code>authType: 'reauth'</code> and that an impostor identity is rejected with <code>auth/user-mismatch</code>.</div></div>
525
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-linking-reauth.test.ts#L28" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-linking-reauth.test.ts</code></a> — asserts the resolver sees <code>authType: 'reauth'</code> and that an impostor identity is rejected with <code>auth/user-mismatch</code>.</div></div>
526
526
  </details>
527
527
  </div>
528
528
  <h2 id="constants-credentials-and-inert-config-tokens">Constants, credentials, and inert config tokens</h2>
529
529
  <div class="compat-list">
530
530
  <details class="compat-row" data-status="ok">
531
531
  <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">getAdditionalUserInfo(userCredential)</code><span class="compat-sub"><span class="compat-behavior">Returns <code>{ isNewUser, profile, providerId }</code>. For a fresh anonymous sign-in prod reports <code>{ isNewUser: true, providerId: null, profile: {} }</code> — <code>providerId</code> is NULL, not <code>'anonymous'</code>, because anonymous is not a federated provider. <code>isNewUser</code> is <code>true</code> for <code>createUserWithEmailAndPassword</code>, a first-time email-link sign-in, and a custom-token sign-in that minted the account; <code>false</code> for a returning <code>signInWithEmailAndPassword</code> and for every <code>link</code> / <code>reauthenticate</code>.</span></span></span></summary>
532
- <div class="compat-evidence"><div class="compat-probe">ORACLE-BACKED on the anonymous shape: <code>auth-additional-user-info-shape</code> captured <code>{isNewUser: true, providerId: null, profile: {}}</code> against prod. Replayed in <code>unit:oracle-conformance.test.ts</code>. Email create/sign-in + custom-token mint/return isNewUser matrix (providerId null per AUTH-B2): <code>unit:upstream-auth-probes.test.ts</code>. Email-link + link/reauth arms: <code>unit:sandbox-email-link.test.ts</code>, <code>unit:sandbox-linking-reauth.test.ts</code>.</div></div>
532
+ <div class="compat-evidence"><div class="compat-probe">ORACLE-BACKED on the anonymous shape: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/auth/auth-additional-user-info-shape.json" target="_blank" rel="noopener noreferrer"><code>auth-additional-user-info-shape</code></a> captured <code>{isNewUser: true, providerId: null, profile: {}}</code> against prod. Replayed in <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/oracle-conformance.test.ts#L695" target="_blank" rel="noopener noreferrer"><code>unit:oracle-conformance.test.ts</code></a>. Email create/sign-in + custom-token mint/return isNewUser matrix (providerId null per AUTH-B2): <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/upstream-auth-probes.test.ts#L6" target="_blank" rel="noopener noreferrer"><code>unit:upstream-auth-probes.test.ts</code></a>. Email-link + link/reauth arms: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-email-link.test.ts#L20" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-email-link.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-linking-reauth.test.ts#L22" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-linking-reauth.test.ts</code></a>.</div></div>
533
533
  </details>
534
534
  <details class="compat-row" data-status="ok">
535
535
  <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">ProviderId / SignInMethod / OperationType / ActionCodeOperation / AuthErrorCodes</code><span class="compat-sub"><span class="compat-behavior">The constant maps, value for value. <code>OperationType.SIGN_IN === 'signIn'</code>, <code>SignInMethod.EMAIL_LINK === 'emailLink'</code>, <code>ProviderId.GOOGLE === 'google.com'</code>, <code>ActionCodeOperation.PASSWORD_RESET === 'PASSWORD_RESET'</code>, and the 106-entry <code>AuthErrorCodes</code> map (<code>INVALID_OOB_CODE === 'auth/invalid-action-code'</code>, <code>PROVIDER_ALREADY_LINKED === 'auth/provider-already-linked'</code>, <code>NO_SUCH_PROVIDER === 'auth/no-such-provider'</code>, <code>USER_MISMATCH === 'auth/user-mismatch'</code>, …).</span></span></span></summary>
536
- <div class="compat-evidence"><div class="compat-probe">ORACLE-BACKED, snapshotted straight from the shipped SDK: <code>auth-mechanical-surface-constants</code> against firebase-js-sdk 12.13.0, replayed value-for-value in <code>unit:oracle-conformance.test.ts</code>. This matters more than it looks: consumer code COMPARES against these constants, so a mirror that got a string wrong would turn every such comparison into a silent <code>false</code> — a worse failure than the export simply being absent, because it typechecks and runs. NOTE: the capture's <code>persistenceTypes</code> block is deliberately NOT asserted — the harness runs under Node, where firebase/auth stubs the browser-only persistence tokens to <code>type: 'NONE'</code> (it reports 'NONE' even for <code>browserLocalPersistence</code>, which is unambiguously 'LOCAL'); asserting it would be asserting a harness artifact. See auth#178.</div></div>
536
+ <div class="compat-evidence"><div class="compat-probe">ORACLE-BACKED, snapshotted straight from the shipped SDK: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/auth/auth-mechanical-surface-constants.json" target="_blank" rel="noopener noreferrer"><code>auth-mechanical-surface-constants</code></a> against firebase-js-sdk 12.13.0, replayed value-for-value in <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/oracle-conformance.test.ts#L711" target="_blank" rel="noopener noreferrer"><code>unit:oracle-conformance.test.ts</code></a>. This matters more than it looks: consumer code COMPARES against these constants, so a mirror that got a string wrong would turn every such comparison into a silent <code>false</code> — a worse failure than the export simply being absent, because it typechecks and runs. NOTE: the capture's <code>persistenceTypes</code> block is deliberately NOT asserted — the harness runs under Node, where firebase/auth stubs the browser-only persistence tokens to <code>type: 'NONE'</code> (it reports 'NONE' even for <code>browserLocalPersistence</code>, which is unambiguously 'LOCAL'); asserting it would be asserting a harness artifact. See auth#178.</div></div>
537
537
  </details>
538
538
  <details class="compat-row" data-status="diverged">
539
539
  <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">signInWithCustomToken(auth, customToken)</code><span class="compat-sub"><span class="compat-behavior">Throws <code>auth/invalid-custom-token</code> for a malformed token and for the empty string. The sandbox accepts a token in the two shapes it can honestly read: a JSON (optionally base64url) <code>{uid, claims}</code> payload — exactly what <code>admin.auth().createCustomToken</code> signs, so the pyric-admin mint and this redeem compose — or a real three-part JWT whose payload segment carries <code>uid</code>/<code>sub</code>. The SIGNATURE IS NOT VERIFIED: the sandbox has no service-account key. The identity is created if it does not exist, and the credential carries <code>providerId: null</code> (custom-token sign-in is not a federated provider).</span></span></span></summary>
540
- <div class="compat-evidence"><div class="compat-probe">ORACLE-BACKED on the reject path: <code>auth-signinwithcustomtoken-invalid</code> captured <code>auth/invalid-custom-token</code> for both a malformed token and the empty string, replayed in <code>unit:oracle-conformance.test.ts</code>. Diverged on the ACCEPT path, declared rather than hidden: prod verifies an RS256 signature against the project's service-account key and the sandbox has no key, so it reads the token's claims WITHOUT verifying them. A forged token that prod would reject is accepted here. The happy path cannot be oracle-probed from a Web SDK client at all (it needs an Admin-SDK-signed JWT).</div></div>
540
+ <div class="compat-evidence"><div class="compat-probe">ORACLE-BACKED on the reject path: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/auth/auth-signinwithcustomtoken-invalid.json" target="_blank" rel="noopener noreferrer"><code>auth-signinwithcustomtoken-invalid</code></a> captured <code>auth/invalid-custom-token</code> for both a malformed token and the empty string, replayed in <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/oracle-conformance.test.ts#L742" target="_blank" rel="noopener noreferrer"><code>unit:oracle-conformance.test.ts</code></a>. Diverged on the ACCEPT path, declared rather than hidden: prod verifies an RS256 signature against the project's service-account key and the sandbox has no key, so it reads the token's claims WITHOUT verifying them. A forged token that prod would reject is accepted here. The happy path cannot be oracle-probed from a Web SDK client at all (it needs an Admin-SDK-signed JWT).</div></div>
541
541
  </details>
542
542
  <details class="compat-row" data-status="ok">
543
543
  <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">validatePassword(auth, password)</code><span class="compat-sub"><span class="compat-behavior">Returns a <code>PasswordValidationStatus</code> against the project's password policy, WITHOUT attempting a sign-up (so a UI can show live strength feedback). The sandbox's policy is <code>minPasswordLength: 6</code>, <code>maxPasswordLength: 4096</code>, <code>enforcementState: 'ENFORCE'</code>, with every character-class requirement UNSET — so a password this function calls valid is exactly one <code>createUserWithEmailAndPassword</code> will accept. The character-class fields are <code>undefined</code>, not <code>false</code>: upstream distinguishes 'not required' from 'required and unmet', and reporting <code>false</code> would claim the password failed a rule the project never had.</span></span></span></summary>
544
- <div class="compat-evidence"><div class="compat-probe">ORACLE-BACKED: <code>auth-validatepassword-status-shape</code> captured prod's live policy (minPasswordLength 6, maxPasswordLength 4096, ENFORCE, character classes unset) and the status shape for a weak and a strong password. Replayed in <code>unit:oracle-conformance.test.ts</code>. The 6-char minimum agrees with the separately oracle-pinned <code>auth/weak-password</code> threshold on the create path, so the two surfaces draw the same line here exactly as they do in prod.</div></div>
544
+ <div class="compat-evidence"><div class="compat-probe">ORACLE-BACKED: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/auth/auth-validatepassword-status-shape.json" target="_blank" rel="noopener noreferrer"><code>auth-validatepassword-status-shape</code></a> captured prod's live policy (minPasswordLength 6, maxPasswordLength 4096, ENFORCE, character classes unset) and the status shape for a weak and a strong password. Replayed in <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/oracle-conformance.test.ts#L753" target="_blank" rel="noopener noreferrer"><code>unit:oracle-conformance.test.ts</code></a>. The 6-char minimum agrees with the separately oracle-pinned <code>auth/weak-password</code> threshold on the create path, so the two surfaces draw the same line here exactly as they do in prod.</div></div>
545
545
  </details>
546
546
  <details class="compat-row" data-status="unsupported">
547
547
  <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">fetchSignInMethodsForEmail(auth, email)</code><span class="compat-sub"><span class="compat-behavior">Not implemented; disposition <code>auth.email-enumeration</code> owns the reviewed availability and rationale.</span></span></span></summary>
548
- <div class="compat-evidence"><div class="compat-probe">disposition:auth.email-enumeration; the <code>auth-fetchsigninmethodsforemail-deprecated</code> capture is retained but inconclusive because the oracle project's Email/Password provider is disabled.</div></div>
548
+ <div class="compat-evidence"><div class="compat-probe">disposition:auth.email-enumeration; the <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/auth/auth-fetchsigninmethodsforemail-deprecated.json" target="_blank" rel="noopener noreferrer"><code>auth-fetchsigninmethodsforemail-deprecated</code></a> capture is retained but inconclusive because the oracle project's Email/Password provider is disabled.</div></div>
549
549
  </details>
550
550
  <details class="compat-row" data-status="diverged">
551
551
  <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">inMemoryPersistence / browserSessionPersistence / browserLocalPersistence / indexedDBLocalPersistence / browserCookiePersistence / browserPopupRedirectResolver / debugErrorMap / prodErrorMap</code><span class="compat-sub"><span class="compat-behavior">Inert configuration tokens, accepted so the idiomatic <code>initializeAuth(app, { persistence: indexedDBLocalPersistence })</code> and <code>setPersistence(auth, browserCookiePersistence)</code> compile, run, and behave identically. The <code>.type</code> discriminant matches upstream exactly (<code>NONE</code> / <code>SESSION</code> / <code>LOCAL</code> / <code>LOCAL</code> / <code>COOKIE</code>) because consumer code branches on it. <code>browserPopupRedirectResolver</code> is accepted and ignored — the sandbox has its own first-class pluggable equivalent (<code>sandbox.setAuthFlowResolver</code>). <code>prodErrorMap</code> is accepted and DELIBERATELY not honored: installing it upstream strips error messages, and doing that in a sandbox whose purpose is to tell a developer what went wrong would be actively hostile.</span></span></span></summary>
552
- <div class="compat-evidence"><div class="compat-probe">In an in-memory sandbox the persistence CHOICE has no observable consequence, so accepting the token and recording the mode is the honest behavior — the same inert-token pattern <code>pyric/firestore</code> uses for its cache-factory tokens. <code>unit:types.test.ts</code> + <code>unit:sandbox-email-link.test.ts</code> cover the exports. Oracle caveat recorded on auth#172: the <code>persistenceTypes</code> block of <code>auth-mechanical-surface-constants</code> reports <code>NONE</code> for every token including <code>browserLocalPersistence</code> — a Node-build stub artifact, not the browser contract, so the <code>.type</code> values here follow the documented <code>Persistence.type</code> union instead.</div></div>
552
+ <div class="compat-evidence"><div class="compat-probe">In an in-memory sandbox the persistence CHOICE has no observable consequence, so accepting the token and recording the mode is the honest behavior — the same inert-token pattern <code>pyric/firestore</code> uses for its cache-factory tokens. <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/types.test.ts#L1" target="_blank" rel="noopener noreferrer"><code>unit:types.test.ts</code></a> + <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-email-link.test.ts#L1" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-email-link.test.ts</code></a> cover the exports. Oracle caveat recorded on auth#172: the <code>persistenceTypes</code> block of <code>auth-mechanical-surface-constants</code> reports <code>NONE</code> for every token including <code>browserLocalPersistence</code> — a Node-build stub artifact, not the browser contract, so the <code>.type</code> values here follow the documented <code>Persistence.type</code> union instead.</div></div>
553
553
  </details>
554
554
  <details class="compat-row" data-status="diverged">
555
555
  <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">revokeAccessToken(auth, token)</code><span class="compat-sub"><span class="compat-behavior">Accepted no-op. In production this reaches OUTSIDE Firebase entirely — it tells the identity provider (in practice Apple) to revoke an OAuth access token, which is a call landing on Apple's servers. There is no external IdP behind a sandbox sign-in, so there is no token out there to revoke and nothing this call could truthfully do. It resolves (so the account-deletion flow Apple requires an app to ship runs end to end against the sandbox) and changes no sandbox state (because claiming otherwise would be a lie).</span></span></span></summary>
556
- <div class="compat-evidence"><div class="compat-probe"><code>unit:types.test.ts</code> — the export resolves. Deliberately not oracle-probed: a successful probe would revoke a real token at a real IdP, which is a side effect on someone else's system that a conformance run has no business causing.</div></div>
556
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/types.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:types.test.ts</code></a> — the export resolves. Deliberately not oracle-probed: a successful probe would revoke a real token at a real IdP, which is a side effect on someone else's system that a conformance run has no business causing.</div></div>
557
557
  </details>
558
558
  <details class="compat-row" data-status="ok">
559
559
  <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">AuthCredential / EmailAuthCredential / OAuthCredential</code><span class="compat-sub"><span class="compat-behavior">Real classes, not markers. <code>EmailAuthProvider.credential(email, password)</code> returns an <code>EmailAuthCredential</code> that CARRIES THE SECRET — which is what makes <code>linkWithCredential</code>, <code>reauthenticateWithCredential</code>, and <code>signInWithCredential</code> decidable in the sandbox with no resolver and no mock (the backend already stores and verifies passwords). <code>credentialWithLink</code> carries the link instead, and <code>signInMethod</code> discriminates (<code>'password'</code> vs <code>'emailLink'</code>). The backing secret is non-enumerable, so a spread or <code>Object.keys</code> walk does not pick it up; <code>toJSON()</code> DOES carry it, matching upstream (whose <code>fromJSON</code> needs it to round-trip). <code>OAuthCredential</code> carries the IdP tokens, which the sandbox cannot verify — those flows keep going through the resolver seam.</span></span></span></summary>
560
- <div class="compat-evidence"><div class="compat-probe"><code>unit:sandbox-linking-reauth.test.ts</code> — pins the secret-carrying behavior, the enumerable/toJSON split (so nobody 'hardens' it into a divergence later), and that a real email credential now signs in via <code>signInWithCredential</code> instead of throwing the sandbox-only <code>auth/no-mock-configured</code> it used to.</div></div>
560
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-linking-reauth.test.ts#L323" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-linking-reauth.test.ts</code></a> — pins the secret-carrying behavior, the enumerable/toJSON split (so nobody 'hardens' it into a divergence later), and that a real email credential now signs in via <code>signInWithCredential</code> instead of throwing the sandbox-only <code>auth/no-mock-configured</code> it used to.</div></div>
561
561
  </details>
562
562
  <details class="compat-row" data-status="ok">
563
563
  <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">TwitterAuthProvider / SAMLAuthProvider</code><span class="compat-sub"><span class="compat-behavior">Provider marker classes. <code>TwitterAuthProvider.credential(token, secret)</code> takes a token AND a secret — Twitter is the one OAuth 1.0a provider in the set, where the OAuth 2.0 providers take a single access token. <code>SAMLAuthProvider</code>'s constructor ENFORCES the <code>saml.</code> providerId prefix (throwing <code>auth/argument-error</code> otherwise), because that id is what routes an assertion to the right configured IdP and a typo there would silently target nothing. SAML has no client-constructible credential, so the class has no <code>credential()</code> factory.</span></span></span></summary>
564
- <div class="compat-evidence"><div class="compat-probe"><code>unit:types.test.ts</code> — construction, PROVIDER_ID values, and the SAML prefix guard.</div></div>
564
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/types.test.ts#L1" target="_blank" rel="noopener noreferrer"><code>unit:types.test.ts</code></a> — construction, PROVIDER_ID values, and the SAML prefix guard.</div></div>
565
565
  </details>
566
566
  <details class="compat-row" data-status="ok">
567
567
  <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">User.providerData</code><span class="compat-sub"><span class="compat-behavior">One entry PER LINKED PROVIDER, read from the identity's stored record. Previously the sandbox synthesized a single <code>{providerId: 'password'}</code> entry for EVERY non-anonymous user, so a Google popup sign-in reported its provider as <code>'password'</code> and a linked account could never show more than one. Consumer code branches on this array (that is what it is for — 'is this account linked to Google?'), so the synthesized version was actively misleading. Empty for anonymous users; after <code>unlink</code> of the last provider it is genuinely empty (it does not resurrect the removed provider).</span></span></span></summary>
568
- <div class="compat-evidence"><div class="compat-probe">Was a BUG until this climb, and worth naming as one: the array was fabricated, not read. Now fixed and locked. <code>unit:sandbox-linking-reauth.test.ts</code> now pins that a Google link surfaces <code>google.com</code> in <code>providerData</code>, that <code>unlink</code> shrinks it, and that unlinking the last provider leaves it empty rather than falling back to a synthesized <code>'password'</code> entry.</div></div>
568
+ <div class="compat-evidence"><div class="compat-probe">Was a BUG until this climb, and worth naming as one: the array was fabricated, not read. Now fixed and locked. <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-linking-reauth.test.ts#L54" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-linking-reauth.test.ts</code></a> now pins that a Google link surfaces <code>google.com</code> in <code>providerData</code>, that <code>unlink</code> shrinks it, and that unlinking the last provider leaves it empty rather than falling back to a synthesized <code>'password'</code> entry.</div></div>
569
569
  </details>
570
570
  </div>
571
571
  <h2 id="current-gaps">Current gaps</h2>
@@ -573,11 +573,11 @@
573
573
  <p>Known differences between Pyric and production Firebase. Each remains tracked as a non-conforming row.</p>
574
574
  <div class="compat-list">
575
575
  <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">signInAnonymously(auth)</code><span class="compat-sub"><span class="compat-behavior">Auto-generates a uid for fresh sign-ins (sandbox format: <code>anonymous-{N}</code>)</span></span></span></summary>
576
- <div class="compat-evidence"><div class="compat-note"><code>unit:sandbox-anonymous.test.ts</code> — prod uids are 28-char base64-ish; sandbox uses a readable counter for debuggability</div></div></details>
576
+ <div class="compat-evidence"><div class="compat-note"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-anonymous.test.ts#L7" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-anonymous.test.ts</code></a> — prod uids are 28-char base64-ish; sandbox uses a readable counter for debuggability</div></div></details>
577
577
  <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">signInAnonymously(auth)</code><span class="compat-sub"><span class="compat-behavior">Anonymous users persist across page reload via configured <code>Persistence</code> (prod only — sandbox has no persistence layer)</span></span></span></summary>
578
578
  <div class="compat-evidence"><div class="compat-note">divergence: sandbox memory only; within one tab the user persists, across reload they don't</div></div></details>
579
579
  <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">signInWithEmailAndPassword(auth, email, password)</code><span class="compat-sub"><span class="compat-behavior">An EMPTY password throws <code>auth/missing-password</code> (message "A non-empty password must be provided"), fired before the user-DB lookup so it can't be used to enumerate seeded emails. Upstream maps the <code>MISSING_PASSWORD</code> server error (<code>core/errors.ts:92,282,563</code>). ⚠ best-known semantics — message text not yet captured against a live project (STOP-flagged for an oracle pass; the <code>.code</code> is the load-bearing part).</span></span></span></summary>
580
- <div class="compat-evidence"><div class="compat-note"><code>unit:sandbox-cluster-b9-b12.test.ts</code> (locks AUTH-B11)</div></div></details>
580
+ <div class="compat-evidence"><div class="compat-note"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-cluster-b9-b12.test.ts#L18" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-cluster-b9-b12.test.ts</code></a> (locks AUTH-B11)</div></div></details>
581
581
  <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">signOut(auth)</code><span class="compat-sub"><span class="compat-behavior">Clears the active session's persistence in prod; sandbox has no persistence</span></span></span></summary>
582
582
  <div class="compat-evidence"><div class="compat-note">divergence: same memory-only constraint as the anonymous persistence row</div></div></details>
583
583
  <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">setPersistence(auth, persistence)</code><span class="compat-sub"><span class="compat-behavior">Actually changes where the auth state is persisted</span></span></span></summary>
@@ -585,25 +585,25 @@
585
585
  <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">signInWithPopup(auth, provider)` / `signInWithCredential(auth, credential)</code><span class="compat-sub"><span class="compat-behavior">Opens a popup window in prod</span></span></span></summary>
586
586
  <div class="compat-evidence"><div class="compat-note">divergence: sandbox skips the popup; mock pre-stage replaces the popup result</div></div></details>
587
587
  <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">beforeAuthStateChanged(auth, callback, onAbort?)</code><span class="compat-sub"><span class="compat-behavior"><code>sandbox.setUser</code> (the sandbox-only test driver) BYPASSES the gate entirely — it is a raw identity force with no prod analog (same bypass it already has for provider enforcement / <code>signInProvider</code> tracking), so no registered <code>beforeAuthStateChanged</code> callback runs and none can block it.</span></span></span></summary>
588
- <div class="compat-evidence"><div class="compat-note"><code>unit:sandbox-before-auth-state-changed.test.ts</code> ("sandbox.setUser test driver bypasses the gate")</div></div></details>
588
+ <div class="compat-evidence"><div class="compat-note"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-before-auth-state-changed.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-before-auth-state-changed.test.ts</code></a> ("sandbox.setUser test driver bypasses the gate")</div></div></details>
589
589
  <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">beforeAuthStateChanged(auth, callback, onAbort?)</code><span class="compat-sub"><span class="compat-behavior">Served-worker path (SharedWorker-backed auth in <code>@pyric/cli</code>): the worker owns the shared user pool and commits transitions on its own side of the port, so a page-local <code>beforeAuthStateChanged</code> registration can't actually gate a worker-driven sign-in. Rather than silently accept a callback that could never run, registering THROWS immediately (<code>auth/operation-not-supported-in-this-environment</code>) — same defensive pattern as <code>signInWithCredential</code> over the worker.</span></span></span></summary>
590
590
  <div class="compat-evidence"><div class="compat-note"><code>packages/cli/src/serve/worker/client.ts</code> (<code>beforeAuthStateChanged</code> throws <code>makeUnsupported</code>)</div></div></details>
591
591
  <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">updateEmail(user, newEmail)</code><span class="compat-sub"><span class="compat-behavior">Changes the stored email (via the same path as <code>sandbox.updateUser</code>, rejecting <code>auth/email-already-in-use</code> / <code>auth/invalid-email</code>) and mutates the held <code>user</code> in place, so the next sign-in resolves against the new email. Leniency vs prod: the sandbox does NOT enforce <code>auth/requires-recent-login</code> and is not routed through <code>verifyBeforeUpdateEmail</code> (which the real SDK requires when email-enumeration protection is on)</span></span></span></summary>
592
- <div class="compat-evidence"><div class="compat-note"><code>unit:fruit-aliases.test.ts</code> — stored email changes; re-sign-in works with the new email, fails with the old</div></div></details>
592
+ <div class="compat-evidence"><div class="compat-note"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/fruit-aliases.test.ts#L18" target="_blank" rel="noopener noreferrer"><code>unit:fruit-aliases.test.ts</code></a> — stored email changes; re-sign-in works with the new email, fails with the old</div></div></details>
593
593
  <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">updatePassword(user, newPassword)</code><span class="compat-sub"><span class="compat-behavior">Sets the stored password (validated for strength). The sandbox DOES store and verify passwords, so this is a real mutation: a subsequent <code>signInWithEmailAndPassword</code> with the new password succeeds and the old one throws <code>auth/wrong-password</code>. Leniency vs prod: no <code>auth/requires-recent-login</code> enforcement</span></span></span></summary>
594
- <div class="compat-evidence"><div class="compat-note"><code>unit:fruit-aliases.test.ts</code> — new password signs in, old password throws <code>auth/wrong-password</code></div></div></details>
594
+ <div class="compat-evidence"><div class="compat-note"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/fruit-aliases.test.ts#L19" target="_blank" rel="noopener noreferrer"><code>unit:fruit-aliases.test.ts</code></a> — new password signs in, old password throws <code>auth/wrong-password</code></div></div></details>
595
595
  <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">useDeviceLanguage(auth)</code><span class="compat-sub"><span class="compat-behavior">Accepted no-op — the sandbox has no device locale to read, so there is no language to set; accepted so init code that calls it compiles + runs</span></span></span></summary>
596
- <div class="compat-evidence"><div class="compat-note"><code>unit:fruit-aliases.test.ts</code> — resolves/returns without error</div></div></details>
596
+ <div class="compat-evidence"><div class="compat-note"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/fruit-aliases.test.ts#L22" target="_blank" rel="noopener noreferrer"><code>unit:fruit-aliases.test.ts</code></a> — resolves/returns without error</div></div></details>
597
597
  <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">sendSignInLinkToEmail</code><span class="compat-sub"><span class="compat-behavior">Prod rejects a continue URL whose domain is not on the project's authorized-domains list. The sandbox has NO domain allowlist and does not invent one: it accepts any parseable continue URL. A continue URL that would be rejected in production is accepted here.</span></span></span></summary>
598
- <div class="compat-evidence"><div class="compat-note">The sandbox has no project config to hold an authorized-domains list, so there is nothing to check against; inventing an allowlist would mean inventing a policy the user never set. The probe attempted the server arm (<code>auth-sendsigninlinktoemail-settings-validation</code>, <code>unauthorizedDomain</code>) but the oracle project has email-link sign-in disabled, so it answered <code>auth/operation-not-allowed</code> rather than <code>auth/unauthorized-continue-uri</code> — the divergence is declared from the documented contract, not from a capture we do not have.</div></div></details>
598
+ <div class="compat-evidence"><div class="compat-note">The sandbox has no project config to hold an authorized-domains list, so there is nothing to check against; inventing an allowlist would mean inventing a policy the user never set. The probe attempted the server arm (<code>auth-sendsigninlinktoemail-settings-validation</code>, <code>unauthorizedDomain</code>) but the oracle project has email-link sign-in disabled, so it answered <code>auth/operation-not-allowed</code> rather than <code>auth/unauthorized-continue-uri</code> — the divergence is declared from the documented contract, not from a capture we do not have. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-email-link.test.ts#L26" target="_blank" rel="noopener noreferrer"><code>sandbox-email-link.test.ts</code></a>)</div></div></details>
599
599
  <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">reauthenticate*</code><span class="compat-sub"><span class="compat-behavior">In production the POINT of re-authentication is the <code>auth/requires-recent-login</code> gate: <code>updateEmail</code> / <code>updatePassword</code> / <code>deleteUser</code> refuse to run on a session whose sign-in is older than a few minutes, and re-auth is how you clear it. The sandbox does NOT enforce that gate — those three mutations already run on a session of any age (a pre-existing documented divergence), so there is no gate here for re-auth to clear. What re-auth DOES do here is real but narrower: it genuinely re-verifies the credential, mints a fresh token, and returns <code>operationType: 'reauthenticate'</code>. Code that calls it runs unchanged against prod, where it also clears the gate.</span></span></span></summary>
600
- <div class="compat-evidence"><div class="compat-note">Declared divergence, not a bug: inventing a recent-login gate would break every existing sandbox flow (which never re-authenticates) while proving nothing. <code>unit:sandbox-linking-reauth.test.ts</code> pins the behavior that IS provided (real credential re-verification + a fresh token).</div></div></details>
600
+ <div class="compat-evidence"><div class="compat-note">Declared divergence, not a bug: inventing a recent-login gate would break every existing sandbox flow (which never re-authenticates) while proving nothing. <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-linking-reauth.test.ts#L27" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-linking-reauth.test.ts</code></a> pins the behavior that IS provided (real credential re-verification + a fresh token).</div></div></details>
601
601
  <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">signInWithCustomToken(auth, customToken)</code><span class="compat-sub"><span class="compat-behavior">Throws <code>auth/invalid-custom-token</code> for a malformed token and for the empty string. The sandbox accepts a token in the two shapes it can honestly read: a JSON (optionally base64url) <code>{uid, claims}</code> payload — exactly what <code>admin.auth().createCustomToken</code> signs, so the pyric-admin mint and this redeem compose — or a real three-part JWT whose payload segment carries <code>uid</code>/<code>sub</code>. The SIGNATURE IS NOT VERIFIED: the sandbox has no service-account key. The identity is created if it does not exist, and the credential carries <code>providerId: null</code> (custom-token sign-in is not a federated provider).</span></span></span></summary>
602
- <div class="compat-evidence"><div class="compat-note">ORACLE-BACKED on the reject path: <code>auth-signinwithcustomtoken-invalid</code> captured <code>auth/invalid-custom-token</code> for both a malformed token and the empty string, replayed in <code>unit:oracle-conformance.test.ts</code>. Diverged on the ACCEPT path, declared rather than hidden: prod verifies an RS256 signature against the project's service-account key and the sandbox has no key, so it reads the token's claims WITHOUT verifying them. A forged token that prod would reject is accepted here. The happy path cannot be oracle-probed from a Web SDK client at all (it needs an Admin-SDK-signed JWT).</div></div></details>
602
+ <div class="compat-evidence"><div class="compat-note">ORACLE-BACKED on the reject path: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/auth/auth-signinwithcustomtoken-invalid.json" target="_blank" rel="noopener noreferrer"><code>auth-signinwithcustomtoken-invalid</code></a> captured <code>auth/invalid-custom-token</code> for both a malformed token and the empty string, replayed in <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/oracle-conformance.test.ts#L742" target="_blank" rel="noopener noreferrer"><code>unit:oracle-conformance.test.ts</code></a>. Diverged on the ACCEPT path, declared rather than hidden: prod verifies an RS256 signature against the project's service-account key and the sandbox has no key, so it reads the token's claims WITHOUT verifying them. A forged token that prod would reject is accepted here. The happy path cannot be oracle-probed from a Web SDK client at all (it needs an Admin-SDK-signed JWT).</div></div></details>
603
603
  <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">inMemoryPersistence / browserSessionPersistence / browserLocalPersistence / indexedDBLocalPersistence / browserCookiePersistence / browserPopupRedirectResolver / debugErrorMap / prodErrorMap</code><span class="compat-sub"><span class="compat-behavior">Inert configuration tokens, accepted so the idiomatic <code>initializeAuth(app, { persistence: indexedDBLocalPersistence })</code> and <code>setPersistence(auth, browserCookiePersistence)</code> compile, run, and behave identically. The <code>.type</code> discriminant matches upstream exactly (<code>NONE</code> / <code>SESSION</code> / <code>LOCAL</code> / <code>LOCAL</code> / <code>COOKIE</code>) because consumer code branches on it. <code>browserPopupRedirectResolver</code> is accepted and ignored — the sandbox has its own first-class pluggable equivalent (<code>sandbox.setAuthFlowResolver</code>). <code>prodErrorMap</code> is accepted and DELIBERATELY not honored: installing it upstream strips error messages, and doing that in a sandbox whose purpose is to tell a developer what went wrong would be actively hostile.</span></span></span></summary>
604
- <div class="compat-evidence"><div class="compat-note">In an in-memory sandbox the persistence CHOICE has no observable consequence, so accepting the token and recording the mode is the honest behavior — the same inert-token pattern <code>pyric/firestore</code> uses for its cache-factory tokens. <code>unit:types.test.ts</code> + <code>unit:sandbox-email-link.test.ts</code> cover the exports. Oracle caveat recorded on auth#172: the <code>persistenceTypes</code> block of <code>auth-mechanical-surface-constants</code> reports <code>NONE</code> for every token including <code>browserLocalPersistence</code> — a Node-build stub artifact, not the browser contract, so the <code>.type</code> values here follow the documented <code>Persistence.type</code> union instead.</div></div></details>
604
+ <div class="compat-evidence"><div class="compat-note">In an in-memory sandbox the persistence CHOICE has no observable consequence, so accepting the token and recording the mode is the honest behavior — the same inert-token pattern <code>pyric/firestore</code> uses for its cache-factory tokens. <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/types.test.ts#L1" target="_blank" rel="noopener noreferrer"><code>unit:types.test.ts</code></a> + <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/sandbox-email-link.test.ts#L1" target="_blank" rel="noopener noreferrer"><code>unit:sandbox-email-link.test.ts</code></a> cover the exports. Oracle caveat recorded on auth#172: the <code>persistenceTypes</code> block of <code>auth-mechanical-surface-constants</code> reports <code>NONE</code> for every token including <code>browserLocalPersistence</code> — a Node-build stub artifact, not the browser contract, so the <code>.type</code> values here follow the documented <code>Persistence.type</code> union instead.</div></div></details>
605
605
  <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">revokeAccessToken(auth, token)</code><span class="compat-sub"><span class="compat-behavior">Accepted no-op. In production this reaches OUTSIDE Firebase entirely — it tells the identity provider (in practice Apple) to revoke an OAuth access token, which is a call landing on Apple's servers. There is no external IdP behind a sandbox sign-in, so there is no token out there to revoke and nothing this call could truthfully do. It resolves (so the account-deletion flow Apple requires an app to ship runs end to end against the sandbox) and changes no sandbox state (because claiming otherwise would be a lie).</span></span></span></summary>
606
- <div class="compat-evidence"><div class="compat-note"><code>unit:types.test.ts</code> — the export resolves. Deliberately not oracle-probed: a successful probe would revoke a real token at a real IdP, which is a side effect on someone else's system that a conformance run has no business causing.</div></div></details>
606
+ <div class="compat-evidence"><div class="compat-note"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/auth/types.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:types.test.ts</code></a> — the export resolves. Deliberately not oracle-probed: a successful probe would revoke a real token at a real IdP, which is a side effect on someone else's system that a conformance run has no business causing.</div></div></details>
607
607
  </div>
608
608
  <h3 id="not-implemented-yet">Not implemented yet</h3>
609
609
  <p>Tracked behavior that is not implemented in the current contract.</p>
@@ -617,7 +617,7 @@
617
617
  <details class="compat-row" data-status="unsupported"><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">User` methods</code><span class="compat-sub"><span class="compat-behavior"><code>user.reload()</code> / <code>user.delete()</code> / <code>user.toJSON()</code> / <code>user.refreshToken</code> / <code>user.tenantId</code></span></span></span></summary>
618
618
  <div class="compat-evidence"><div class="compat-note">not modeled by the sandbox; retained as an explicit unsupported behavior row rather than synthesized (AUTH-GAP)</div></div></details>
619
619
  <details class="compat-row" data-status="unsupported"><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">fetchSignInMethodsForEmail(auth, email)</code><span class="compat-sub"><span class="compat-behavior">Not implemented; disposition <code>auth.email-enumeration</code> owns the reviewed availability and rationale.</span></span></span></summary>
620
- <div class="compat-evidence"><div class="compat-note">disposition:auth.email-enumeration; the <code>auth-fetchsigninmethodsforemail-deprecated</code> capture is retained but inconclusive because the oracle project's Email/Password provider is disabled.</div></div></details>
620
+ <div class="compat-evidence"><div class="compat-note">disposition:auth.email-enumeration; the <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/auth/auth-fetchsigninmethodsforemail-deprecated.json" target="_blank" rel="noopener noreferrer"><code>auth-fetchsigninmethodsforemail-deprecated</code></a> capture is retained but inconclusive because the oracle project's Email/Password provider is disabled.</div></div></details>
621
621
  </div>
622
622
  <h3 id="unverified">Unverified</h3>
623
623
  <p>Tracked behavior whose available evidence does not yet establish the production result.</p>