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

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 (378) hide show
  1. package/README.md +13 -9
  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/cli/database-rules.d.ts.map +1 -1
  7. package/dist/cli/database-rules.js +6 -4
  8. package/dist/cli/database-rules.js.map +1 -1
  9. package/dist/cli/dev-runner.d.ts +9 -0
  10. package/dist/cli/dev-runner.d.ts.map +1 -1
  11. package/dist/cli/dev-runner.js +18 -0
  12. package/dist/cli/dev-runner.js.map +1 -1
  13. package/dist/cli/serve.d.ts.map +1 -1
  14. package/dist/cli/serve.js +61 -6
  15. package/dist/cli/serve.js.map +1 -1
  16. package/dist/cli/verify.js +2 -2
  17. package/dist/cli/verify.js.map +1 -1
  18. package/dist/conformance/.generated/can-i-use-browser.d.ts.map +1 -1
  19. package/dist/conformance/.generated/can-i-use-browser.js +1 -1
  20. package/dist/conformance/.generated/can-i-use-browser.js.map +1 -1
  21. package/dist/conformance/.generated/can-i-use.d.ts.map +1 -1
  22. package/dist/conformance/.generated/can-i-use.js +1 -1
  23. package/dist/conformance/.generated/can-i-use.js.map +1 -1
  24. package/dist/conformance/.generated/conformance-docs.d.ts.map +1 -1
  25. package/dist/conformance/.generated/conformance-docs.js +1 -1
  26. package/dist/conformance/.generated/conformance-docs.js.map +1 -1
  27. package/dist/next/client-aliases.d.ts +11 -0
  28. package/dist/next/client-aliases.d.ts.map +1 -0
  29. package/dist/next/client-aliases.js +38 -0
  30. package/dist/next/client-aliases.js.map +1 -0
  31. package/dist/next/dev-rewrites.d.ts +9 -0
  32. package/dist/next/dev-rewrites.d.ts.map +1 -0
  33. package/dist/next/dev-rewrites.js +53 -0
  34. package/dist/next/dev-rewrites.js.map +1 -0
  35. package/dist/next/guard.d.ts +15 -0
  36. package/dist/next/guard.d.ts.map +1 -0
  37. package/dist/next/guard.js +52 -0
  38. package/dist/next/guard.js.map +1 -0
  39. package/dist/next/index.d.ts +12 -0
  40. package/dist/next/index.d.ts.map +1 -0
  41. package/dist/next/index.js +11 -0
  42. package/dist/next/index.js.map +1 -0
  43. package/dist/next/runtime-env.d.ts +7 -0
  44. package/dist/next/runtime-env.d.ts.map +1 -0
  45. package/dist/next/runtime-env.js +13 -0
  46. package/dist/next/runtime-env.js.map +1 -0
  47. package/dist/next/server-external-packages.d.ts +10 -0
  48. package/dist/next/server-external-packages.d.ts.map +1 -0
  49. package/dist/next/server-external-packages.js +34 -0
  50. package/dist/next/server-external-packages.js.map +1 -0
  51. package/dist/next/target-url.d.ts +11 -0
  52. package/dist/next/target-url.d.ts.map +1 -0
  53. package/dist/next/target-url.js +36 -0
  54. package/dist/next/target-url.js.map +1 -0
  55. package/dist/next/turbopack-config.d.ts +9 -0
  56. package/dist/next/turbopack-config.d.ts.map +1 -0
  57. package/dist/next/turbopack-config.js +39 -0
  58. package/dist/next/turbopack-config.js.map +1 -0
  59. package/dist/next/types.d.ts +40 -0
  60. package/dist/next/types.d.ts.map +1 -0
  61. package/dist/next/types.js +5 -0
  62. package/dist/next/types.js.map +1 -0
  63. package/dist/next/webpack-config.d.ts +11 -0
  64. package/dist/next/webpack-config.d.ts.map +1 -0
  65. package/dist/next/webpack-config.js +51 -0
  66. package/dist/next/webpack-config.js.map +1 -0
  67. package/dist/next/with-pyric.d.ts +22 -0
  68. package/dist/next/with-pyric.d.ts.map +1 -0
  69. package/dist/next/with-pyric.js +49 -0
  70. package/dist/next/with-pyric.js.map +1 -0
  71. package/dist/register/app-bridge.d.ts +9 -0
  72. package/dist/register/app-bridge.d.ts.map +1 -0
  73. package/dist/register/app-bridge.js +56 -0
  74. package/dist/register/app-bridge.js.map +1 -0
  75. package/dist/register/hooks.d.ts.map +1 -1
  76. package/dist/register/hooks.js +6 -1
  77. package/dist/register/hooks.js.map +1 -1
  78. package/dist/register/index.js +1 -1
  79. package/dist/register/index.js.map +1 -1
  80. package/dist/register/mapping.d.ts +3 -1
  81. package/dist/register/mapping.d.ts.map +1 -1
  82. package/dist/register/mapping.js +41 -5
  83. package/dist/register/mapping.js.map +1 -1
  84. package/dist/rtdb/rules-json.d.ts +12 -0
  85. package/dist/rtdb/rules-json.d.ts.map +1 -1
  86. package/dist/rtdb/rules-json.js +98 -0
  87. package/dist/rtdb/rules-json.js.map +1 -1
  88. package/dist/serve/bundler.d.ts.map +1 -1
  89. package/dist/serve/bundler.js +2 -0
  90. package/dist/serve/bundler.js.map +1 -1
  91. package/dist/serve/discovery.d.ts +4 -4
  92. package/dist/serve/discovery.d.ts.map +1 -1
  93. package/dist/serve/discovery.js +59 -38
  94. package/dist/serve/discovery.js.map +1 -1
  95. package/dist/serve/entries/app-ai-passthrough.d.ts +8 -0
  96. package/dist/serve/entries/app-ai-passthrough.d.ts.map +1 -0
  97. package/dist/serve/entries/app-ai-passthrough.js +59 -0
  98. package/dist/serve/entries/app-ai-passthrough.js.map +1 -0
  99. package/dist/serve/entries/auth-helper-core.js +9 -7
  100. package/dist/serve/entries/auth-helper-core.js.map +1 -1
  101. package/dist/serve/entries/auth.d.ts +1 -0
  102. package/dist/serve/entries/auth.d.ts.map +1 -1
  103. package/dist/serve/entries/auth.js +1 -0
  104. package/dist/serve/entries/auth.js.map +1 -1
  105. package/dist/serve/entries/bridge-url.d.ts.map +1 -1
  106. package/dist/serve/entries/bridge-url.js +7 -2
  107. package/dist/serve/entries/bridge-url.js.map +1 -1
  108. package/dist/serve/entries/database.d.ts +1 -0
  109. package/dist/serve/entries/database.d.ts.map +1 -1
  110. package/dist/serve/entries/database.js +1 -0
  111. package/dist/serve/entries/database.js.map +1 -1
  112. package/dist/serve/entries/firestore.d.ts +1 -0
  113. package/dist/serve/entries/firestore.d.ts.map +1 -1
  114. package/dist/serve/entries/firestore.js +1 -0
  115. package/dist/serve/entries/firestore.js.map +1 -1
  116. package/dist/serve/entries/init.d.ts.map +1 -1
  117. package/dist/serve/entries/init.js +4 -2
  118. package/dist/serve/entries/init.js.map +1 -1
  119. package/dist/serve/entries/runtime.js +23 -0
  120. package/dist/serve/entries/runtime.js.map +1 -1
  121. package/dist/serve/entries/storage.d.ts +2 -0
  122. package/dist/serve/entries/storage.d.ts.map +1 -1
  123. package/dist/serve/entries/storage.js +9 -2
  124. package/dist/serve/entries/storage.js.map +1 -1
  125. package/dist/serve/html-injection.d.ts +3 -1
  126. package/dist/serve/html-injection.d.ts.map +1 -1
  127. package/dist/serve/html-injection.js +27 -1
  128. package/dist/serve/html-injection.js.map +1 -1
  129. package/dist/serve/rules.js +2 -2
  130. package/dist/serve/rules.js.map +1 -1
  131. package/dist/serve/runtime/chip-config.d.ts +1 -1
  132. package/dist/serve/runtime/chip-config.d.ts.map +1 -1
  133. package/dist/serve/runtime/chip-config.js +16 -8
  134. package/dist/serve/runtime/chip-config.js.map +1 -1
  135. package/dist/serve/sandbox-session.d.ts +1 -0
  136. package/dist/serve/sandbox-session.d.ts.map +1 -1
  137. package/dist/serve/sandbox-session.js +30 -2
  138. package/dist/serve/sandbox-session.js.map +1 -1
  139. package/dist/serve/site-ui/_astro/_studio_.B6b_G1ev.css +1 -0
  140. package/dist/serve/site-ui/_astro/app.bnmVTkIY.js +11 -0
  141. package/dist/serve/site-ui/_astro/app.t6u-adB_.js +1 -0
  142. package/dist/serve/site-ui/_astro/{chess-showcase.CnoxIle5.js → chess-showcase.kC8dXLKY.js} +1 -1
  143. package/dist/serve/site-ui/_astro/{example-runtime.R1d96L6-.js → example-runtime.D79PnoSk.js} +1 -1
  144. package/dist/serve/site-ui/_astro/index.DzVde04M.js +1 -0
  145. package/dist/serve/site-ui/_astro/reads.BYBRLbsE.js +1 -0
  146. package/dist/serve/site-ui/_astro/sandbox-controls.C9Aaazt8.js +1252 -0
  147. package/dist/serve/site-ui/_astro/{seed.EyI3VYEu.js → seed.DANAIyRw.js} +1 -1
  148. package/dist/serve/site-ui/auth/index.html +2 -2
  149. package/dist/serve/site-ui/docs/_rhythm/index.html +1 -1
  150. package/dist/serve/site-ui/docs/agent/set-up-your-agent/index.html +1 -1
  151. package/dist/serve/site-ui/docs/agent/watch-and-review/index.html +1 -1
  152. package/dist/serve/site-ui/docs/agent/work-with-an-agent/index.html +1 -1
  153. package/dist/serve/site-ui/docs/ai-compat/index.html +85 -85
  154. package/dist/serve/site-ui/docs/ai-compat.md +84 -84
  155. package/dist/serve/site-ui/docs/api-reference/index.html +3 -1
  156. package/dist/serve/site-ui/docs/api-reference.md +2 -0
  157. package/dist/serve/site-ui/docs/app-compat/index.html +28 -28
  158. package/dist/serve/site-ui/docs/app-compat.md +27 -27
  159. package/dist/serve/site-ui/docs/auth-compat/index.html +122 -122
  160. package/dist/serve/site-ui/docs/auth-compat.md +121 -121
  161. package/dist/serve/site-ui/docs/build/ai-logic/index.html +1 -1
  162. package/dist/serve/site-ui/docs/build/authentication/index.html +1 -1
  163. package/dist/serve/site-ui/docs/build/cloud-firestore/index.html +1 -1
  164. package/dist/serve/site-ui/docs/build/cloud-messaging/index.html +1 -1
  165. package/dist/serve/site-ui/docs/build/cloud-storage/index.html +1 -1
  166. package/dist/serve/site-ui/docs/build/realtime-database/index.html +1 -1
  167. package/dist/serve/site-ui/docs/conformance-scores/index.html +4 -4
  168. package/dist/serve/site-ui/docs/conformance-scores.md +3 -3
  169. package/dist/serve/site-ui/docs/create-pyric-reference-api/index.html +6 -6
  170. package/dist/serve/site-ui/docs/create-pyric-reference-api.md +5 -5
  171. package/dist/serve/site-ui/docs/database-compat/index.html +200 -448
  172. package/dist/serve/site-ui/docs/database-compat.md +198 -461
  173. package/dist/serve/site-ui/docs/examples/chess/index.html +2 -2
  174. package/dist/serve/site-ui/docs/examples/index.html +1 -1
  175. package/dist/serve/site-ui/docs/firestore-compat/index.html +186 -195
  176. package/dist/serve/site-ui/docs/firestore-compat.md +184 -195
  177. package/dist/serve/site-ui/docs/functions-rtdb-compat/index.html +15 -15
  178. package/dist/serve/site-ui/docs/functions-rtdb-compat.md +14 -14
  179. package/dist/serve/site-ui/docs/get-started/how-the-swap-works/index.html +1 -1
  180. package/dist/serve/site-ui/docs/get-started/nextjs/index.html +57 -0
  181. package/dist/serve/site-ui/docs/get-started/nextjs.md +86 -0
  182. package/dist/serve/site-ui/docs/get-started/start-building/index.html +25 -8
  183. package/dist/serve/site-ui/docs/get-started/start-building.md +34 -5
  184. package/dist/serve/site-ui/docs/get-started/vite/index.html +26 -0
  185. package/dist/serve/site-ui/docs/get-started/vite.md +45 -0
  186. package/dist/serve/site-ui/docs/index.json +668 -103
  187. package/dist/serve/site-ui/docs/messaging-compat/index.html +56 -56
  188. package/dist/serve/site-ui/docs/messaging-compat.md +55 -55
  189. package/dist/serve/site-ui/docs/observe/resolve-runtime-status/index.html +1 -1
  190. package/dist/serve/site-ui/docs/observe/see-whats-happening/index.html +1 -1
  191. package/dist/serve/site-ui/docs/observe/shape-your-data/index.html +1 -1
  192. package/dist/serve/site-ui/docs/overview/index.html +1 -1
  193. package/dist/serve/site-ui/docs/pyric-admin-app-reference-api/index.html +39 -13
  194. package/dist/serve/site-ui/docs/pyric-admin-app-reference-api.md +28 -2
  195. package/dist/serve/site-ui/docs/pyric-admin-auth-reference-api/index.html +1 -1
  196. package/dist/serve/site-ui/docs/pyric-admin-database-reference-api/index.html +38 -6
  197. package/dist/serve/site-ui/docs/pyric-admin-database-reference-api.md +43 -0
  198. package/dist/serve/site-ui/docs/pyric-admin-firestore-reference-api/index.html +9 -1
  199. package/dist/serve/site-ui/docs/pyric-admin-firestore-reference-api.md +8 -0
  200. package/dist/serve/site-ui/docs/pyric-admin-messaging-reference-api/index.html +1 -1
  201. package/dist/serve/site-ui/docs/pyric-admin-storage-reference-api/index.html +27 -4
  202. package/dist/serve/site-ui/docs/pyric-admin-storage-reference-api.md +23 -0
  203. package/dist/serve/site-ui/docs/pyric-ai-reference-api/index.html +1 -1
  204. package/dist/serve/site-ui/docs/pyric-ai-scripting-reference-api/index.html +1 -1
  205. package/dist/serve/site-ui/docs/pyric-app-reference-api/index.html +1 -1
  206. package/dist/serve/site-ui/docs/pyric-auth-reference-api/index.html +1 -1
  207. package/dist/serve/site-ui/docs/pyric-cli-assurance-browser-reference-api/index.html +1 -1
  208. package/dist/serve/site-ui/docs/pyric-cli-assurance-reference-api/index.html +1 -1
  209. package/dist/serve/site-ui/docs/pyric-cli-bridge-client-reference-api/index.html +1 -1
  210. package/dist/serve/site-ui/docs/pyric-cli-bridge-reference-api/index.html +1 -1
  211. package/dist/serve/site-ui/docs/pyric-cli-conformance-browser-reference-api/index.html +1 -1
  212. package/dist/serve/site-ui/docs/pyric-cli-conformance-docs-reference-api/index.html +1 -1
  213. package/dist/serve/site-ui/docs/pyric-cli-conformance-reference-api/index.html +1 -1
  214. package/dist/serve/site-ui/docs/pyric-cli-credentials-node-reference-api/index.html +1 -1
  215. package/dist/serve/site-ui/docs/pyric-cli-discover-reference-api/index.html +1 -1
  216. package/dist/serve/site-ui/docs/pyric-cli-next-reference-api/index.html +123 -0
  217. package/dist/serve/site-ui/docs/pyric-cli-next-reference-api.md +99 -0
  218. package/dist/serve/site-ui/docs/pyric-cli-register-app-bridge-reference-api/index.html +127 -0
  219. package/dist/serve/site-ui/docs/pyric-cli-register-app-bridge-reference-api.md +174 -0
  220. package/dist/serve/site-ui/docs/pyric-cli-register-reference-api/index.html +1 -1
  221. package/dist/serve/site-ui/docs/pyric-cli-remote-reference-api/index.html +1 -1
  222. package/dist/serve/site-ui/docs/pyric-cli-serve-worker-reference-api/index.html +570 -326
  223. package/dist/serve/site-ui/docs/pyric-cli-serve-worker-reference-api.md +198 -6
  224. package/dist/serve/site-ui/docs/pyric-cli-verify-reference-api/index.html +1 -1
  225. package/dist/serve/site-ui/docs/pyric-cli-vite-reference-api/index.html +1 -1
  226. package/dist/serve/site-ui/docs/pyric-database-reference-api/index.html +1 -1
  227. package/dist/serve/site-ui/docs/pyric-firestore-reference-api/index.html +2971 -627
  228. package/dist/serve/site-ui/docs/pyric-firestore-reference-api.md +2873 -517
  229. package/dist/serve/site-ui/docs/pyric-messaging-reference-api/index.html +1 -1
  230. package/dist/serve/site-ui/docs/pyric-messaging-sw-reference-api/index.html +1 -1
  231. package/dist/serve/site-ui/docs/pyric-rules-reference-api/index.html +43 -3
  232. package/dist/serve/site-ui/docs/pyric-rules-reference-api.md +8 -0
  233. package/dist/serve/site-ui/docs/pyric-sandbox-database-reference-api/index.html +26 -2
  234. package/dist/serve/site-ui/docs/pyric-sandbox-database-reference-api.md +24 -0
  235. package/dist/serve/site-ui/docs/pyric-sandbox-firestore-reference-api/index.html +1 -1
  236. package/dist/serve/site-ui/docs/pyric-sandbox-reference-api/index.html +77 -9
  237. package/dist/serve/site-ui/docs/pyric-sandbox-reference-api.md +28 -13
  238. package/dist/serve/site-ui/docs/pyric-storage-reference-api/index.html +240 -63
  239. package/dist/serve/site-ui/docs/pyric-storage-reference-api.md +164 -5
  240. package/dist/serve/site-ui/docs/pyric-ui-agents-reference-api/index.html +1 -1
  241. package/dist/serve/site-ui/docs/pyric-ui-auth-hooks-reference-api/index.html +1 -1
  242. package/dist/serve/site-ui/docs/pyric-ui-auth-reference-api/index.html +1 -1
  243. package/dist/serve/site-ui/docs/pyric-ui-events-hooks-reference-api/index.html +1 -1
  244. package/dist/serve/site-ui/docs/pyric-ui-events-reference-api/index.html +1 -1
  245. package/dist/serve/site-ui/docs/pyric-ui-firestore-hooks-reference-api/index.html +1 -1
  246. package/dist/serve/site-ui/docs/pyric-ui-firestore-reference-api/index.html +1 -1
  247. package/dist/serve/site-ui/docs/pyric-ui-primitives-reference-api/index.html +1 -1
  248. package/dist/serve/site-ui/docs/pyric-ui-rtdb-reference-api/index.html +1 -1
  249. package/dist/serve/site-ui/docs/pyric-ui-rules-hooks-reference-api/index.html +1 -1
  250. package/dist/serve/site-ui/docs/pyric-ui-rules-reference-api/index.html +1 -1
  251. package/dist/serve/site-ui/docs/pyric-ui-storage-hooks-reference-api/index.html +1 -1
  252. package/dist/serve/site-ui/docs/pyric-ui-storage-reference-api/index.html +2 -1
  253. package/dist/serve/site-ui/docs/pyric-ui-storage-reference-api.md +1 -0
  254. package/dist/serve/site-ui/docs/pyric-ui-traffic-hooks-reference-api/index.html +1 -1
  255. package/dist/serve/site-ui/docs/pyric-ui-traffic-reference-api/index.html +1 -1
  256. package/dist/serve/site-ui/docs/rules-compat/index.html +87 -79
  257. package/dist/serve/site-ui/docs/rules-compat.md +86 -78
  258. package/dist/serve/site-ui/docs/secure/audit-your-rules/index.html +1 -1
  259. package/dist/serve/site-ui/docs/secure/firestore-rules-limits/index.html +1 -1
  260. package/dist/serve/site-ui/docs/secure/read-a-denial/index.html +1 -1
  261. package/dist/serve/site-ui/docs/secure/rtdb-rules-in-typescript/index.html +1 -1
  262. package/dist/serve/site-ui/docs/secure/rules-standard-library/index.html +1 -1
  263. package/dist/serve/site-ui/docs/secure/secure-it-with-rules/index.html +1 -1
  264. package/dist/serve/site-ui/docs/secure/simulate-and-lint/index.html +1 -1
  265. package/dist/serve/site-ui/docs/secure/write-a-rules-test-suite/index.html +1 -1
  266. package/dist/serve/site-ui/docs/ship/ship-to-production/index.html +1 -1
  267. package/dist/serve/site-ui/docs/ship/test-in-node/index.html +1 -1
  268. package/dist/serve/site-ui/docs/storage-compat/index.html +119 -125
  269. package/dist/serve/site-ui/docs/storage-compat.md +118 -124
  270. package/dist/serve/site-ui/docs/trust/how-we-know-it-matches-firebase/index.html +1 -1
  271. package/dist/serve/site-ui/docs/trust/rules-standard-library/index.html +1 -1
  272. package/dist/serve/site-ui/docs/trust/storage-rules-stdlib-research/index.html +1 -1
  273. package/dist/serve/site-ui/docs/trust/versioning-and-compatibility/index.html +1 -1
  274. package/dist/serve/site-ui/docs/tutorial/index.html +1 -1
  275. package/dist/serve/site-ui/examples/chess/index.html +1 -1
  276. package/dist/serve/site-ui/examples/firestore-first-write/index.html +1 -1
  277. package/dist/serve/site-ui/firestore/index.html +2 -2
  278. package/dist/serve/site-ui/index.html +2 -2
  279. package/dist/serve/site-ui/llms.txt +6 -2
  280. package/dist/serve/site-ui/rtdb/index.html +2 -2
  281. package/dist/serve/site-ui/settings/index.html +2 -2
  282. package/dist/serve/site-ui/storage/index.html +2 -2
  283. package/dist/serve/site-ui/traffic/index.html +2 -2
  284. package/dist/serve/vite-ai-config.d.ts +4 -1
  285. package/dist/serve/vite-ai-config.d.ts.map +1 -1
  286. package/dist/serve/vite-ai-config.js +83 -19
  287. package/dist/serve/vite-ai-config.js.map +1 -1
  288. package/dist/serve/vite-generation-rules-watch.d.ts.map +1 -1
  289. package/dist/serve/vite-generation-rules-watch.js +61 -14
  290. package/dist/serve/vite-generation-rules-watch.js.map +1 -1
  291. package/dist/serve/vite-module-swap.d.ts +4 -1
  292. package/dist/serve/vite-module-swap.d.ts.map +1 -1
  293. package/dist/serve/vite-module-swap.js +106 -21
  294. package/dist/serve/vite-module-swap.js.map +1 -1
  295. package/dist/serve/vite-plugin.d.ts.map +1 -1
  296. package/dist/serve/vite-plugin.js +45 -5
  297. package/dist/serve/vite-plugin.js.map +1 -1
  298. package/dist/serve/vite-sandbox-generation.js +1 -1
  299. package/dist/serve/vite-sandbox-generation.js.map +1 -1
  300. package/dist/serve/worker/client/resumable.d.ts +30 -0
  301. package/dist/serve/worker/client/resumable.d.ts.map +1 -0
  302. package/dist/serve/worker/client/resumable.js +416 -0
  303. package/dist/serve/worker/client/resumable.js.map +1 -0
  304. package/dist/serve/worker/client/rtdb-references.d.ts.map +1 -1
  305. package/dist/serve/worker/client/rtdb-references.js +4 -1
  306. package/dist/serve/worker/client/rtdb-references.js.map +1 -1
  307. package/dist/serve/worker/client/storage.d.ts +16 -2
  308. package/dist/serve/worker/client/storage.d.ts.map +1 -1
  309. package/dist/serve/worker/client/storage.js +57 -6
  310. package/dist/serve/worker/client/storage.js.map +1 -1
  311. package/dist/serve/worker/host/rules.d.ts.map +1 -1
  312. package/dist/serve/worker/host/rules.js +2 -1
  313. package/dist/serve/worker/host/rules.js.map +1 -1
  314. package/dist/serve/worker/index.d.ts +1 -1
  315. package/dist/serve/worker/index.d.ts.map +1 -1
  316. package/dist/serve/worker/index.js +1 -1
  317. package/dist/serve/worker/index.js.map +1 -1
  318. package/dist/serve/worker/serve-init.d.ts.map +1 -1
  319. package/dist/serve/worker/serve-init.js +31 -0
  320. package/dist/serve/worker/serve-init.js.map +1 -1
  321. package/package.json +15 -4
  322. package/src/assurance/.generated/conformance-verdicts.ts +80 -72
  323. package/src/cli/cli.test.ts +14 -0
  324. package/src/cli/database-rules.ts +6 -4
  325. package/src/cli/dev-runner.ts +21 -0
  326. package/src/cli/serve.ts +62 -5
  327. package/src/cli/verify.ts +2 -2
  328. package/src/conformance/.generated/can-i-use-browser.ts +1 -1
  329. package/src/conformance/.generated/can-i-use.ts +1 -1
  330. package/src/conformance/.generated/conformance-docs.ts +1 -1
  331. package/src/next/client-aliases.ts +39 -0
  332. package/src/next/dev-rewrites.ts +77 -0
  333. package/src/next/guard.ts +61 -0
  334. package/src/next/index.ts +16 -0
  335. package/src/next/runtime-env.ts +18 -0
  336. package/src/next/server-external-packages.ts +43 -0
  337. package/src/next/target-url.ts +46 -0
  338. package/src/next/turbopack-config.ts +54 -0
  339. package/src/next/types.ts +47 -0
  340. package/src/next/webpack-config.ts +71 -0
  341. package/src/next/with-pyric.ts +58 -0
  342. package/src/register/app-bridge.ts +79 -0
  343. package/src/register/hooks.ts +6 -1
  344. package/src/register/index.ts +1 -1
  345. package/src/register/mapping.ts +50 -4
  346. package/src/rtdb/rules-json.ts +110 -0
  347. package/src/serve/bundler.ts +2 -0
  348. package/src/serve/discovery.ts +65 -44
  349. package/src/serve/entries/app-ai-passthrough.ts +83 -0
  350. package/src/serve/entries/auth-helper-core.ts +9 -7
  351. package/src/serve/entries/auth.ts +1 -0
  352. package/src/serve/entries/bridge-url.ts +9 -2
  353. package/src/serve/entries/database.ts +1 -0
  354. package/src/serve/entries/firestore.ts +1 -0
  355. package/src/serve/entries/init.ts +5 -2
  356. package/src/serve/entries/runtime.ts +25 -0
  357. package/src/serve/entries/storage.ts +12 -1
  358. package/src/serve/html-injection.ts +26 -1
  359. package/src/serve/rules.ts +2 -2
  360. package/src/serve/runtime/chip-config.ts +19 -6
  361. package/src/serve/sandbox-session.ts +31 -2
  362. package/src/serve/vite-ai-config.ts +86 -19
  363. package/src/serve/vite-generation-rules-watch.ts +67 -15
  364. package/src/serve/vite-module-swap.ts +121 -21
  365. package/src/serve/vite-plugin.ts +44 -6
  366. package/src/serve/vite-sandbox-generation.ts +1 -1
  367. package/src/serve/worker/client/resumable.ts +506 -0
  368. package/src/serve/worker/client/rtdb-references.ts +4 -1
  369. package/src/serve/worker/client/storage.ts +77 -7
  370. package/src/serve/worker/host/rules.ts +2 -1
  371. package/src/serve/worker/index.ts +4 -0
  372. package/src/serve/worker/serve-init.ts +30 -0
  373. package/dist/serve/site-ui/_astro/_studio_.BtYpqe1A.css +0 -1
  374. package/dist/serve/site-ui/_astro/app.4HeLP_FA.js +0 -109
  375. package/dist/serve/site-ui/_astro/app.BgSnCR5G.js +0 -1
  376. package/dist/serve/site-ui/_astro/index.BJiOemMX.js +0 -1
  377. package/dist/serve/site-ui/_astro/reads.BrKNdKAa.js +0 -1
  378. package/dist/serve/site-ui/_astro/sandbox-controls.BTb3EbQp.js +0 -1153
@@ -4,13 +4,13 @@
4
4
 
5
5
  <div class="compat-stat">
6
6
  <p class="compat-stat-figure">
7
- <span class="compat-stat-pct">62.9%</span>
7
+ <span class="compat-stat-pct">77.1%</span>
8
8
  <span class="compat-stat-label">of the public API supported</span>
9
9
  </p>
10
10
  <div class="compat-meters">
11
- <span class="compat-meter-track"><span class="compat-meter-fill" style="width: 62.9%"></span></span>
11
+ <span class="compat-meter-track"><span class="compat-meter-fill" style="width: 77.1%"></span></span>
12
12
  </div>
13
- <p class="compat-stat-denom">22 of 35 public API</p>
13
+ <p class="compat-stat-denom">27 of 35 public API</p>
14
14
  </div>
15
15
 
16
16
 
@@ -27,55 +27,55 @@
27
27
  <div class="compat-list">
28
28
  <details class="compat-row" data-status="ok">
29
29
  <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">getStorage(app, bucketUrl?)<code> / </code>getStorageSandbox(target, options?)</code><span class="compat-sub"><span class="compat-behavior"><code>getStorageSandbox(ctx)</code> returns a tagged sandbox-target handle (frozen identity)</span></span></span></summary>
30
- <div class="compat-evidence"><div class="compat-probe"><code>unit:service.test.ts</code></div></div>
30
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/service.test.ts#L15" target="_blank" rel="noopener noreferrer"><code>unit:service.test.ts</code></a></div></div>
31
31
  </details>
32
32
  <details class="compat-row" data-status="ok">
33
33
  <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">getStorage(app, bucketUrl?)<code> / </code>getStorageSandbox(target, options?)</code><span class="compat-sub"><span class="compat-behavior"><code>getStorageSandbox(sandbox)</code> wraps a bare Sandbox with an anonymous context (<code>auth: null</code>)</span></span></span></summary>
34
- <div class="compat-evidence"><div class="compat-probe"><code>unit:service.test.ts</code></div></div>
34
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/service.test.ts#L15" target="_blank" rel="noopener noreferrer"><code>unit:service.test.ts</code></a></div></div>
35
35
  </details>
36
36
  <details class="compat-row" data-status="ok">
37
37
  <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">getStorage(app, bucketUrl?)<code> / </code>getStorageSandbox(target, options?)</code><span class="compat-sub"><span class="compat-behavior"><code>getStorage(app)</code> returns the sandbox handle selected by package resolution</span></span></span></summary>
38
- <div class="compat-evidence"><div class="compat-probe"><code>entry-path:storage</code> runs the canonical <code>initializeApp</code> → <code>getStorage(app)</code> → <code>ref</code> → <code>uploadBytes</code> flow</div></div>
38
+ <div class="compat-evidence"><div class="compat-probe"><code>entry-path:storage</code> runs the canonical <code>initializeApp</code> → <code>getStorage(app)</code> → <code>ref</code> → <code>uploadBytes</code> flow (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/entry-path/storage.ts#L6" target="_blank" rel="noopener noreferrer"><code>storage.ts</code></a>)</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">getStorage(app, bucketUrl?)<code> / </code>getStorageSandbox(target, options?)</code><span class="compat-sub"><span class="compat-behavior">Two <code>getStorageSandbox(ctx)</code> calls on the same context return the SAME wrapper (identity-stable)</span></span></span></summary>
42
- <div class="compat-evidence"><div class="compat-probe"><code>unit:service.test.ts</code> ("returns the same handle for repeated calls on the same context")</div></div>
42
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/service.test.ts#L15" target="_blank" rel="noopener noreferrer"><code>unit:service.test.ts</code></a> ("returns the same handle for repeated calls on the same context")</div></div>
43
43
  </details>
44
44
  <details class="compat-row" data-status="ok">
45
45
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">getStorage(app, bucketUrl?)<code> / </code>getStorageSandbox(target, options?)</code><span class="compat-sub"><span class="compat-behavior">Two <code>getStorageSandbox(sandbox)</code> calls on a bare <code>Sandbox</code> return the SAME wrapper (identity-stable)</span></span></span></summary>
46
- <div class="compat-evidence"><div class="compat-probe">ST-B3 fixed: <code>withAuth(null)</code> mints a fresh context per call, so the per-context cache missed and bare-Sandbox calls returned different handles. A <code>Sandbox</code>-keyed cache makes the convenience path stable, matching the docstring. Probe: <code>unit:service.test.ts</code> ("ST-B3: returns the same handle for repeated bare-Sandbox calls").</div></div>
46
+ <div class="compat-evidence"><div class="compat-probe">ST-B3 fixed: <code>withAuth(null)</code> mints a fresh context per call, so the per-context cache missed and bare-Sandbox calls returned different handles. A <code>Sandbox</code>-keyed cache makes the convenience path stable, matching the docstring. Probe: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/service.test.ts#L15" target="_blank" rel="noopener noreferrer"><code>unit:service.test.ts</code></a> ("ST-B3: returns the same handle for repeated bare-Sandbox calls").</div></div>
47
47
  </details>
48
48
  <details class="compat-row" data-status="ok">
49
49
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">getStorage(app, bucketUrl?)<code> / </code>getStorageSandbox(target, options?)</code><span class="compat-sub"><span class="compat-behavior">Two different <code>SandboxContext</code>s on the same <code>Sandbox</code> get DIFFERENT handles but share the underlying <code>StorageService</code> (IDB)</span></span></span></summary>
50
- <div class="compat-evidence"><div class="compat-probe"><code>unit:service.test.ts</code> ("shares the underlying StorageService across contexts on the same sandbox")</div></div>
50
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/service.test.ts#L4" target="_blank" rel="noopener noreferrer"><code>unit:service.test.ts</code></a> ("shares the underlying StorageService across contexts on the same sandbox")</div></div>
51
51
  </details>
52
52
  <details class="compat-row" data-status="ok">
53
53
  <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">getStorage(app, bucketUrl?)<code> / </code>getStorageSandbox(target, options?)</code><span class="compat-sub"><span class="compat-behavior"><code>options.bucket</code> round-trips on metadata records; v1 has a single implicit bucket but the field is preserved</span></span></span></summary>
54
- <div class="compat-evidence"><div class="compat-probe"><code>unit:service.test.ts</code> ("records the bucket value on the handle")</div></div>
54
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/service.test.ts#L4" target="_blank" rel="noopener noreferrer"><code>unit:service.test.ts</code></a> ("records the bucket value on the handle")</div></div>
55
55
  </details>
56
56
  <details class="compat-row" data-status="ok">
57
57
  <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">getStorage(app, bucketUrl?)<code> / </code>getStorageSandbox(target, options?)</code><span class="compat-sub"><span class="compat-behavior"><code>options.dbName</code> honored on the FIRST call per <code>Sandbox</code>; second-call overrides ignored</span></span></span></summary>
58
- <div class="compat-evidence"><div class="compat-probe"><code>unit:service.test.ts</code> ("dbName only takes effect on the sandbox's first getStorage call")</div></div>
58
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/service.test.ts#L4" target="_blank" rel="noopener noreferrer"><code>unit:service.test.ts</code></a> ("dbName only takes effect on the sandbox's first getStorage call")</div></div>
59
59
  </details>
60
60
  <details class="compat-row" data-status="ok">
61
61
  <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">getStorage(app, bucketUrl?)<code> / </code>getStorageSandbox(target, options?)</code><span class="compat-sub"><span class="compat-behavior"><code>options.rules</code> parsed eagerly — malformed rules throw <code>SyntaxError</code> at config time</span></span></span></summary>
62
- <div class="compat-evidence"><div class="compat-probe"><code>unit:storage/sandbox/rules.test.ts</code> (parse errors propagate from <code>parseStorageRules</code>)</div></div>
62
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/sandbox/rules.test.ts#L1" target="_blank" rel="noopener noreferrer"><code>unit:storage/sandbox/rules.test.ts</code></a> (parse errors propagate from <code>parseStorageRules</code>)</div></div>
63
63
  </details>
64
64
  <details class="compat-row" data-status="ok">
65
65
  <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">getStorage(app, bucketUrl?)<code> / </code>getStorageSandbox(target, options?)</code><span class="compat-sub"><span class="compat-behavior">The <code>TARGET_SYMBOL</code> brand keeps each handle bound to its owning sandbox service and identity</span></span></span></summary>
66
- <div class="compat-evidence"><div class="compat-probe"><code>unit:service.test.ts</code> (distinct contexts share one service while retaining distinct handles)</div></div>
66
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/service.test.ts#L4" target="_blank" rel="noopener noreferrer"><code>unit:service.test.ts</code></a> (distinct contexts share one service while retaining distinct handles)</div></div>
67
67
  </details>
68
68
  <details class="compat-row" data-status="ok">
69
69
  <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">getStorage(app, bucketUrl?)<code> / </code>getStorageSandbox(target, options?)</code><span class="compat-sub"><span class="compat-behavior">Unrecognized handle (not produced by a factory) → <code>TypeError</code> "not a FirebaseStorage handle"</span></span></span></summary>
70
- <div class="compat-evidence"><div class="compat-probe"><code>unit:service.test.ts</code> ("rejects an object that was not produced by a factory")</div></div>
70
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/service.test.ts#L4" target="_blank" rel="noopener noreferrer"><code>unit:service.test.ts</code></a> ("rejects an object that was not produced by a factory")</div></div>
71
71
  </details>
72
72
  <details class="compat-row" data-status="diverged">
73
73
  <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">getStorage(app, bucketUrl?)<code> / </code>getStorageSandbox(target, options?)</code><span class="compat-sub"><span class="compat-behavior"><code>getStorage(app, bucketUrl?)</code> accepts Firebase's bucket argument; the sandbox remains bound to its configured single bucket</span></span></span></summary>
74
- <div class="compat-evidence"><div class="compat-probe">Package resolution owns production selection. The sandbox accepts the canonical argument but does not model production multi-bucket routing; <code>unit:service.test.ts</code> pins the configured <code>pyric-default</code> bucket.</div></div>
74
+ <div class="compat-evidence"><div class="compat-probe">Package resolution owns production selection. The sandbox accepts the canonical argument but does not model production multi-bucket routing; <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/service.test.ts#L4" target="_blank" rel="noopener noreferrer"><code>unit:service.test.ts</code></a> pins the configured <code>pyric-default</code> bucket.</div></div>
75
75
  </details>
76
76
  <details class="compat-row" data-status="ok">
77
77
  <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">getStorage(app, bucketUrl?)<code> / </code>getStorageSandbox(target, options?)</code><span class="compat-sub"><span class="compat-behavior">The served <code>firebase/storage</code> entry accepts bare <code>getStorage()</code> and returns the page's shared sandbox handle</span></span></span></summary>
78
- <div class="compat-evidence"><div class="compat-probe">The canonical served entry supplies the page sandbox when the app argument is omitted; the entry-path and bundler suites execute the public package shape.</div></div>
78
+ <div class="compat-evidence"><div class="compat-probe">The canonical served entry supplies the page sandbox when the app argument is omitted; the entry-path and bundler suites execute the public package shape. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/entry-path/storage.ts#L6" target="_blank" rel="noopener noreferrer"><code>storage.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/bundler.test.ts#L448" target="_blank" rel="noopener noreferrer"><code>bundler.test.ts</code></a>)</div></div>
79
79
  </details>
80
80
  </div>
81
81
 
@@ -84,47 +84,47 @@
84
84
  <div class="compat-list">
85
85
  <details class="compat-row" data-status="ok">
86
86
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">ref(storage[, path])<code> / </code>ref(parent, path)</code><span class="compat-sub"><span class="compat-behavior"><code>ref(storage)</code> returns the root ref — <code>fullPath === ''</code>, <code>name === ''</code>, <code>parent === null</code>, <code>root === self</code></span></span></span></summary>
87
- <div class="compat-evidence"><div class="compat-probe"><code>unit:reference.test.ts</code> ("root reference has empty fullPath, null parent, and equal root")</div></div>
87
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/reference.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:reference.test.ts</code></a> ("root reference has empty fullPath, null parent, and equal root")</div></div>
88
88
  </details>
89
89
  <details class="compat-row" data-status="ok">
90
90
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">ref(storage[, path])<code> / </code>ref(parent, path)</code><span class="compat-sub"><span class="compat-behavior"><code>ref(storage, 'sessions/s1.json')</code> populates <code>fullPath</code>, <code>name</code> (last segment), <code>parent</code> (path without last segment)</span></span></span></summary>
91
- <div class="compat-evidence"><div class="compat-probe"><code>unit:reference.test.ts</code> ("ref(storage, path) populates fullPath and name from the last segment")</div></div>
91
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/reference.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:reference.test.ts</code></a> ("ref(storage, path) populates fullPath and name from the last segment")</div></div>
92
92
  </details>
93
93
  <details class="compat-row" data-status="ok">
94
94
  <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">ref(storage[, path])<code> / </code>ref(parent, path)</code><span class="compat-sub"><span class="compat-behavior">Path normalization: leading slashes stripped (<code>/sessions/s1</code> → <code>sessions/s1</code>)</span></span></span></summary>
95
- <div class="compat-evidence"><div class="compat-probe"><code>unit:reference.test.ts</code> ("normalizes leading/trailing/double slashes")</div></div>
95
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/reference.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:reference.test.ts</code></a> ("normalizes leading/trailing/double slashes")</div></div>
96
96
  </details>
97
97
  <details class="compat-row" data-status="ok">
98
98
  <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">ref(storage[, path])<code> / </code>ref(parent, path)</code><span class="compat-sub"><span class="compat-behavior">Path normalization: trailing slashes stripped</span></span></span></summary>
99
- <div class="compat-evidence"><div class="compat-probe"><code>unit:reference.test.ts</code></div></div>
99
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/reference.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:reference.test.ts</code></a></div></div>
100
100
  </details>
101
101
  <details class="compat-row" data-status="ok">
102
102
  <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">ref(storage[, path])<code> / </code>ref(parent, path)</code><span class="compat-sub"><span class="compat-behavior">Path normalization: repeated internal slashes collapsed (<code>a//b</code> → <code>a/b</code>)</span></span></span></summary>
103
- <div class="compat-evidence"><div class="compat-probe"><code>unit:reference.test.ts</code></div></div>
103
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/reference.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:reference.test.ts</code></a></div></div>
104
104
  </details>
105
105
  <details class="compat-row" data-status="ok">
106
106
  <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">ref(storage[, path])<code> / </code>ref(parent, path)</code><span class="compat-sub"><span class="compat-behavior"><code>ref(parent, child)</code> joins relative to parent's <code>fullPath</code></span></span></span></summary>
107
- <div class="compat-evidence"><div class="compat-probe"><code>unit:reference.test.ts</code> ("ref(parent, child) joins relative to the parent")</div></div>
107
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/reference.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:reference.test.ts</code></a> ("ref(parent, child) joins relative to the parent")</div></div>
108
108
  </details>
109
109
  <details class="compat-row" data-status="ok">
110
110
  <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">ref(storage[, path])<code> / </code>ref(parent, path)</code><span class="compat-sub"><span class="compat-behavior"><code>parent</code> chain walks back to root (each <code>.parent</code> strips one segment until empty, then <code>null</code>)</span></span></span></summary>
111
- <div class="compat-evidence"><div class="compat-probe"><code>unit:reference.test.ts</code> ("parent traversal walks back to root")</div></div>
111
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/reference.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:reference.test.ts</code></a> ("parent traversal walks back to root")</div></div>
112
112
  </details>
113
113
  <details class="compat-row" data-status="ok">
114
114
  <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">ref(storage[, path])<code> / </code>ref(parent, path)</code><span class="compat-sub"><span class="compat-behavior"><code>root</code> accessor returns the bucket-root ref regardless of starting depth</span></span></span></summary>
115
- <div class="compat-evidence"><div class="compat-probe"><code>unit:reference.test.ts</code></div></div>
115
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/reference.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:reference.test.ts</code></a></div></div>
116
116
  </details>
117
117
  <details class="compat-row" data-status="ok">
118
118
  <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">ref(storage[, path])<code> / </code>ref(parent, path)</code><span class="compat-sub"><span class="compat-behavior"><code>toString()</code> returns <code>gs://&lt;bucket&gt;/&lt;fullPath&gt;</code></span></span></span></summary>
119
- <div class="compat-evidence"><div class="compat-probe"><code>unit:reference.test.ts</code> ("toString returns gs://bucket/path")</div></div>
119
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/reference.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:reference.test.ts</code></a> ("toString returns gs://bucket/path")</div></div>
120
120
  </details>
121
121
  <details class="compat-row" data-status="ok">
122
122
  <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">ref(storage[, path])<code> / </code>ref(parent, path)</code><span class="compat-sub"><span class="compat-behavior">Reference identity: two <code>ref(s, 'a/b')</code> calls are equal-by-<code>toString</code> but NOT <code>===</code> (value objects, not interned)</span></span></span></summary>
123
- <div class="compat-evidence"><div class="compat-probe">(implicit in <code>unit:reference.test.ts</code> parent-chain test — each <code>.parent</code> returns a fresh object)</div></div>
123
+ <div class="compat-evidence"><div class="compat-probe">(implicit in <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/reference.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:reference.test.ts</code></a> parent-chain test — each <code>.parent</code> returns a fresh object)</div></div>
124
124
  </details>
125
125
  <details class="compat-row" data-status="ok">
126
126
  <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">ref(storage[, path])<code> / </code>ref(parent, path)</code><span class="compat-sub"><span class="compat-behavior">References are mirror-owned value objects; <code>parent</code> and <code>root</code> preserve the same storage handle and path semantics</span></span></span></summary>
127
- <div class="compat-evidence"><div class="compat-probe"><code>unit:reference.test.ts</code> pins parent traversal, root identity, bucket, and path behavior through the public reference interface.</div></div>
127
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/reference.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:reference.test.ts</code></a> pins parent traversal, root identity, bucket, and path behavior through the public reference interface.</div></div>
128
128
  </details>
129
129
  </div>
130
130
 
@@ -133,64 +133,64 @@
133
133
  <div class="compat-list">
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">uploadBytes(ref, data, metadata?)</code><span class="compat-sub"><span class="compat-behavior">Accepts <code>Blob</code> payload; returns <code>UploadResult</code> with populated <code>metadata</code></span></span></span></summary>
136
- <div class="compat-evidence"><div class="compat-probe"><code>unit:reference.test.ts</code> ("accepts a Blob and round-trips through getBlob")</div></div>
136
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/reference.test.ts#L9" target="_blank" rel="noopener noreferrer"><code>unit:reference.test.ts</code></a> ("accepts a Blob and round-trips through getBlob")</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">uploadBytes(ref, data, metadata?)</code><span class="compat-sub"><span class="compat-behavior">Accepts <code>Uint8Array</code> payload</span></span></span></summary>
140
- <div class="compat-evidence"><div class="compat-probe"><code>unit:reference.test.ts</code> ("accepts a Uint8Array")</div></div>
140
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/reference.test.ts#L9" target="_blank" rel="noopener noreferrer"><code>unit:reference.test.ts</code></a> ("accepts a Uint8Array")</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">uploadBytes(ref, data, metadata?)</code><span class="compat-sub"><span class="compat-behavior">Accepts <code>ArrayBuffer</code> payload</span></span></span></summary>
144
- <div class="compat-evidence"><div class="compat-probe"><code>unit:reference.test.ts</code> ("accepts an ArrayBuffer")</div></div>
144
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/reference.test.ts#L9" target="_blank" rel="noopener noreferrer"><code>unit:reference.test.ts</code></a> ("accepts an ArrayBuffer")</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">uploadBytes(ref, data, metadata?)</code><span class="compat-sub"><span class="compat-behavior">ContentType precedence: caller's <code>metadata.contentType</code> &gt; <code>Blob.type</code> &gt; <code>application/octet-stream</code></span></span></span></summary>
148
- <div class="compat-evidence"><div class="compat-probe"><code>unit:reference.test.ts</code> ("metadata.contentType overrides the Blob's intrinsic type" + "falls back to application/octet-stream when no type is supplied")</div></div>
148
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/reference.test.ts#L9" target="_blank" rel="noopener noreferrer"><code>unit:reference.test.ts</code></a> ("metadata.contentType overrides the Blob's intrinsic type" + "falls back to application/octet-stream when no type is supplied")</div></div>
149
149
  </details>
150
150
  <details class="compat-row" data-status="ok">
151
151
  <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">uploadBytes(ref, data, metadata?)</code><span class="compat-sub"><span class="compat-behavior"><code>Blob.type === ''</code> (no intrinsic type) falls through to <code>application/octet-stream</code>, NOT to <code>''</code></span></span></span></summary>
152
- <div class="compat-evidence"><div class="compat-probe"><code>unit:reference.test.ts</code> ("falls back to application/octet-stream when no type is supplied")</div></div>
152
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/reference.test.ts#L9" target="_blank" rel="noopener noreferrer"><code>unit:reference.test.ts</code></a> ("falls back to application/octet-stream when no type is supplied")</div></div>
153
153
  </details>
154
154
  <details class="compat-row" data-status="ok">
155
155
  <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">uploadBytes(ref, data, metadata?)</code><span class="compat-sub"><span class="compat-behavior"><code>customMetadata</code> round-trips through the upload pipeline</span></span></span></summary>
156
- <div class="compat-evidence"><div class="compat-probe"><code>unit:reference.test.ts</code> ("round-trips customMetadata") + <code>unit:metadata.test.ts</code></div></div>
156
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/reference.test.ts#L9" target="_blank" rel="noopener noreferrer"><code>unit:reference.test.ts</code></a> ("round-trips customMetadata") + <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/metadata.test.ts#L5" target="_blank" rel="noopener noreferrer"><code>unit:metadata.test.ts</code></a></div></div>
157
157
  </details>
158
158
  <details class="compat-row" data-status="ok">
159
159
  <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">uploadBytes(ref, data, metadata?)</code><span class="compat-sub"><span class="compat-behavior">Empty <code>Blob.type</code> rewrap: when caller hint differs from <code>Blob.type</code>, the blob is re-wrapped with the caller's type (same bytes)</span></span></span></summary>
160
- <div class="compat-evidence"><div class="compat-probe">implicit in <code>unit:reference.test.ts</code> ("metadata.contentType overrides the Blob's intrinsic type")</div></div>
160
+ <div class="compat-evidence"><div class="compat-probe">implicit in <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/reference.test.ts#L9" target="_blank" rel="noopener noreferrer"><code>unit:reference.test.ts</code></a> ("metadata.contentType overrides the Blob's intrinsic type")</div></div>
161
161
  </details>
162
162
  <details class="compat-row" data-status="ok">
163
163
  <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">uploadBytes(ref, data, metadata?)</code><span class="compat-sub"><span class="compat-behavior">Throws <code>storage/invalid-root-operation</code> when called on the root reference</span></span></span></summary>
164
- <div class="compat-evidence"><div class="compat-probe"><code>unit:reference.test.ts</code> ("throws on root reference")</div></div>
164
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/reference.test.ts#L9" target="_blank" rel="noopener noreferrer"><code>unit:reference.test.ts</code></a> ("throws on root reference")</div></div>
165
165
  </details>
166
166
  <details class="compat-row" data-status="ok">
167
167
  <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">uploadBytes(ref, data, metadata?)</code><span class="compat-sub"><span class="compat-behavior">Returned <code>metadata.fullPath</code> matches the ref's <code>fullPath</code></span></span></span></summary>
168
- <div class="compat-evidence"><div class="compat-probe"><code>unit:reference.test.ts</code></div></div>
168
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/reference.test.ts#L9" target="_blank" rel="noopener noreferrer"><code>unit:reference.test.ts</code></a></div></div>
169
169
  </details>
170
170
  <details class="compat-row" data-status="ok">
171
171
  <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">uploadBytes(ref, data, metadata?)</code><span class="compat-sub"><span class="compat-behavior">Returned <code>metadata.size</code> matches the input blob's byte length</span></span></span></summary>
172
- <div class="compat-evidence"><div class="compat-probe"><code>unit:reference.test.ts</code></div></div>
172
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/reference.test.ts#L9" target="_blank" rel="noopener noreferrer"><code>unit:reference.test.ts</code></a></div></div>
173
173
  </details>
174
174
  <details class="compat-row" data-status="ok">
175
175
  <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">uploadBytes(ref, data, metadata?)</code><span class="compat-sub"><span class="compat-behavior">Returned <code>metadata.bucket</code> matches the storage handle's bucket</span></span></span></summary>
176
- <div class="compat-evidence"><div class="compat-probe"><code>unit:reference.test.ts</code></div></div>
176
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/reference.test.ts#L9" target="_blank" rel="noopener noreferrer"><code>unit:reference.test.ts</code></a></div></div>
177
177
  </details>
178
178
  <details class="compat-row" data-status="ok">
179
179
  <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">uploadBytes(ref, data, metadata?)</code><span class="compat-sub"><span class="compat-behavior">Replaces any existing object at the path (overwrite, not append)</span></span></span></summary>
180
- <div class="compat-evidence"><div class="compat-probe"><code>unit:upstream-storage-probes.test.ts</code> ("second uploadBytes at the same path replaces bytes and metadata")</div></div>
180
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/upstream-storage-probes.test.ts#L19" target="_blank" rel="noopener noreferrer"><code>unit:upstream-storage-probes.test.ts</code></a> ("second uploadBytes at the same path replaces bytes and metadata")</div></div>
181
181
  </details>
182
182
  <details class="compat-row" data-status="ok">
183
183
  <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">uploadBytes(ref, data, metadata?)</code><span class="compat-sub"><span class="compat-behavior">Prod: round-trips uploaded bytes through <code>getDownloadURL</code> + fetch (byte-for-byte equality)</span></span></span></summary>
184
- <div class="compat-evidence"><div class="compat-probe">oracle: <code>packages/conformance/observations/storage/storage-upload-bytes-roundtrip.json</code> (against blockingfun, fb-js-sdk 12.13.0: 6-byte payload → uploadBytes → getDownloadURL → HTTPS fetch → <code>bytesMatch: true</code>, <code>urlIsHttps: true</code>, <code>bodyLen === payloadLen === 6</code>). This row records the production answer key; row #51 compares the sandbox's page-local URL behavior against it.</div>
184
+ <div class="compat-evidence"><div class="compat-probe">oracle: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/storage/storage-upload-bytes-roundtrip.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/storage/storage-upload-bytes-roundtrip.json</code></a> (against blockingfun, fb-js-sdk 12.13.0: 6-byte payload → uploadBytes → getDownloadURL → HTTPS fetch → <code>bytesMatch: true</code>, <code>urlIsHttps: true</code>, <code>bodyLen === payloadLen === 6</code>). This row records the production answer key; row #51 compares the sandbox's page-local URL behavior against it.</div>
185
185
  <div class="compat-note">(prod-only)</div></div>
186
186
  </details>
187
187
  <details class="compat-row" data-status="ok">
188
188
  <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">uploadBytes(ref, data, metadata?)</code><span class="compat-sub"><span class="compat-behavior">Returned <code>metadata.contentType</code> matches what the caller hinted (when set)</span></span></span></summary>
189
- <div class="compat-evidence"><div class="compat-probe"><code>unit:reference.test.ts</code> + oracle: <code>packages/conformance/observations/storage/storage-upload-then-getmetadata.json</code> (<code>contentType: 'application/octet-stream'</code> round-trip against blockingfun, fb-js-sdk 12.13.0; <code>contentTypeMatches: true</code>)</div></div>
189
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/reference.test.ts#L9" target="_blank" rel="noopener noreferrer"><code>unit:reference.test.ts</code></a> + oracle: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/storage/storage-upload-then-getmetadata.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/storage/storage-upload-then-getmetadata.json</code></a> (<code>contentType: 'application/octet-stream'</code> round-trip against blockingfun, fb-js-sdk 12.13.0; <code>contentTypeMatches: true</code>)</div></div>
190
190
  </details>
191
191
  <details class="compat-row" data-status="ok">
192
192
  <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">uploadBytes(ref, data, metadata?)</code><span class="compat-sub"><span class="compat-behavior">Returned <code>metadata.generation</code> / <code>metageneration</code> are stringified counters (<code>'1'</code> after fresh upload)</span></span></span></summary>
193
- <div class="compat-evidence"><div class="compat-probe"><code>unit:metadata.test.ts</code></div></div>
193
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/metadata.test.ts#L5" target="_blank" rel="noopener noreferrer"><code>unit:metadata.test.ts</code></a></div></div>
194
194
  </details>
195
195
  </div>
196
196
 
@@ -199,57 +199,56 @@
199
199
  <div class="compat-list">
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">uploadString(ref, value, format?, metadata?)</code><span class="compat-sub"><span class="compat-behavior"><code>format='raw'</code> (default): UTF-8 encodes the string; <code>contentType</code> defaults to <code>text/plain;charset=utf-8</code></span></span></span></summary>
202
- <div class="compat-evidence"><div class="compat-probe"><code>unit:reference.test.ts</code> ("raw format encodes UTF-8 and defaults contentType to text/plain")</div></div>
202
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/reference.test.ts#L11" target="_blank" rel="noopener noreferrer"><code>unit:reference.test.ts</code></a> ("raw format encodes UTF-8 and defaults contentType to text/plain")</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">uploadString(ref, value, format?, metadata?)</code><span class="compat-sub"><span class="compat-behavior"><code>format='base64'</code>: decodes payload bytes from standard base64</span></span></span></summary>
206
- <div class="compat-evidence"><div class="compat-probe"><code>unit:reference.test.ts</code> ("base64 format decodes payload bytes")</div></div>
206
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/reference.test.ts#L11" target="_blank" rel="noopener noreferrer"><code>unit:reference.test.ts</code></a> ("base64 format decodes payload bytes")</div></div>
207
207
  </details>
208
208
  <details class="compat-row" data-status="diverged">
209
209
  <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">uploadString(ref, value, format?, metadata?)</code><span class="compat-sub"><span class="compat-behavior">Sandbox: <code>format='base64url'</code> (or any unknown format) rejected with <code>storage/invalid-format</code> naming the bad format. Prod: <code>base64url</code> is ACCEPTED (upload succeeds); a genuinely-unrecognized format throws <code>storage/unknown</code></span></span></span></summary>
210
- <div class="compat-evidence"><div class="compat-probe">divergence, both halves oracle-locked by <code>packages/conformance/observations/storage/storage-uploadstring-unknown-format.json</code>: prod accepts <code>base64url</code> (<code>base64urlOk: true</code>) and throws <code>storage/unknown</code> for an unrecognized format — not <code>storage/invalid-format</code>. The v1 sandbox ships only <code>raw</code>/<code>base64</code>/<code>data_url</code> (matches <code>StringFormat</code>) and throws <code>storage/invalid-format</code> for anything else (ST-B3 replaced the old mis-parse-as-data_url behavior). Both sides pinned in <code>oracle-conformance.test.ts</code>; sandbox code path documented in <code>upload.ts</code>'s <code>decodeString</code>. Implementing base64url decoding is still one line in <code>decodeString</code>.</div></div>
210
+ <div class="compat-evidence"><div class="compat-probe">divergence, both halves oracle-locked by <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/storage/storage-uploadstring-unknown-format.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/storage/storage-uploadstring-unknown-format.json</code></a>: prod accepts <code>base64url</code> (<code>base64urlOk: true</code>) and throws <code>storage/unknown</code> for an unrecognized format — not <code>storage/invalid-format</code>. The v1 sandbox ships only <code>raw</code>/<code>base64</code>/<code>data_url</code> (matches <code>StringFormat</code>) and throws <code>storage/invalid-format</code> for anything else (ST-B3 replaced the old mis-parse-as-data_url behavior). Both sides pinned in <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/oracle-conformance.test.ts#L129" target="_blank" rel="noopener noreferrer"><code>oracle-conformance.test.ts</code></a>; sandbox code path documented in <code>upload.ts</code>'s <code>decodeString</code>. Implementing base64url decoding is still one line in <code>decodeString</code>. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/error-codes.test.ts#L25" target="_blank" rel="noopener noreferrer"><code>error-codes.test.ts</code></a>)</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">uploadString(ref, value, format?, metadata?)</code><span class="compat-sub"><span class="compat-behavior"><code>format='data_url'</code>: parses <code>data:&lt;mime&gt;;base64,&lt;payload&gt;</code>, infers <code>contentType</code> from prefix</span></span></span></summary>
214
- <div class="compat-evidence"><div class="compat-probe"><code>unit:reference.test.ts</code> ("data_url format infers contentType from the prefix")</div></div>
214
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/reference.test.ts#L11" target="_blank" rel="noopener noreferrer"><code>unit:reference.test.ts</code></a> ("data_url format infers contentType from the prefix")</div></div>
215
215
  </details>
216
216
  <details class="compat-row" data-status="ok">
217
217
  <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">uploadString(ref, value, format?, metadata?)</code><span class="compat-sub"><span class="compat-behavior"><code>format='data_url'</code> with non-base64 payload: percent-decodes the body</span></span></span></summary>
218
- <div class="compat-evidence"><div class="compat-probe"><code>unit:upstream-storage-probes.test.ts</code> ("non-base64 data_url percent-decodes the body"; malformed <code>%%0</code> → <code>storage/invalid-format</code>)</div></div>
218
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/upstream-storage-probes.test.ts#L20" target="_blank" rel="noopener noreferrer"><code>unit:upstream-storage-probes.test.ts</code></a> ("non-base64 data_url percent-decodes the body"; malformed <code>%%0</code> → <code>storage/invalid-format</code>)</div></div>
219
219
  </details>
220
220
  <details class="compat-row" data-status="ok">
221
221
  <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">uploadString(ref, value, format?, metadata?)</code><span class="compat-sub"><span class="compat-behavior">Caller's <code>metadata.contentType</code> beats data_url inference</span></span></span></summary>
222
- <div class="compat-evidence"><div class="compat-probe"><code>unit:reference.test.ts</code> ("caller metadata.contentType beats data_url inference")</div></div>
222
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/reference.test.ts#L11" target="_blank" rel="noopener noreferrer"><code>unit:reference.test.ts</code></a> ("caller metadata.contentType beats data_url inference")</div></div>
223
223
  </details>
224
224
  <details class="compat-row" data-status="ok">
225
225
  <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">uploadString(ref, value, format?, metadata?)</code><span class="compat-sub"><span class="compat-behavior">Malformed <code>data_url</code> (no comma / doesn't start with <code>data:</code>) throws <code>TypeError</code> with "data_url format" message</span></span></span></summary>
226
- <div class="compat-evidence"><div class="compat-probe"><code>unit:reference.test.ts</code> ("throws on malformed data_url")</div></div>
226
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/reference.test.ts#L11" target="_blank" rel="noopener noreferrer"><code>unit:reference.test.ts</code></a> ("throws on malformed data_url")</div></div>
227
227
  </details>
228
228
  <details class="compat-row" data-status="ok">
229
- <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">uploadString(ref, value, format?, metadata?)</code><span class="compat-sub"><span class="compat-behavior">Prod: <code>uploadString(ref, value, 'base64')</code> round-trips via <code>getDownloadURL</code> + fetch</span></span></span></summary>
230
- <div class="compat-evidence"><div class="compat-probe">oracle: <code>packages/conformance/observations/storage/storage-uploadstring-base64-roundtrip.json</code> (<code>'aGVsbG8='</code> → <code>'hello'</code> against blockingfun, fb-js-sdk 12.13.0; <code>textMatches: true</code>)</div>
231
- <div class="compat-note">(prod-only)</div></div>
229
+ <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">uploadString(ref, value, format?, metadata?)</code><span class="compat-sub"><span class="compat-behavior"><code>uploadString(ref, value, 'base64')</code> round-trips via <code>getDownloadURL</code> + fetch across in-page sandbox and SharedWorker mode</span></span></span></summary>
230
+ <div class="compat-evidence"><div class="compat-probe">oracle: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/storage/storage-uploadstring-base64-roundtrip.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/storage/storage-uploadstring-base64-roundtrip.json</code></a> (<code>'aGVsbG8='</code> → <code>'hello'</code> against blockingfun, fb-js-sdk 12.13.0; <code>textMatches: true</code>). Sandbox oracle replay matches production behavior in-page, and client↔host integration proves SharedWorker mode decodes and transfers string payloads faithfully over the worker port. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/oracle-conformance.test.ts#L192" target="_blank" rel="noopener noreferrer"><code>oracle-conformance.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/worker/integration.test.ts#L252" target="_blank" rel="noopener noreferrer"><code>integration.test.ts</code></a>)</div></div>
232
231
  </details>
233
232
  </div>
234
233
 
235
234
  ## `uploadBytesResumable(ref, data, metadata?)` — resumable upload + task observers
236
235
 
237
236
  <div class="compat-list">
238
- <details class="compat-row" data-status="unsupported">
239
- <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">uploadBytesResumable(ref, data, metadata?)</code><span class="compat-sub"><span class="compat-behavior">Exported by <code>firebase/storage</code>; returns an <code>UploadTask</code> with <code>pause()</code> / <code>resume()</code> / <code>cancel()</code></span></span></span></summary>
240
- <div class="compat-evidence"><div class="compat-probe">No <code>pyric/storage</code> implementation or behavioral probe exists; availability is classified by the owning surface contract.</div></div>
237
+ <details class="compat-row" data-status="diverged">
238
+ <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">uploadBytesResumable(ref, data, metadata?)</code><span class="compat-sub"><span class="compat-behavior">Exported by <code>firebase/storage</code>; returns an <code>UploadTask</code> with <code>pause()</code> / <code>resume()</code> / <code>cancel()</code></span></span></span></summary>
239
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/resumable-upload.test.ts#L14" target="_blank" rel="noopener noreferrer"><code>unit:resumable-upload.test.ts</code></a> confirms <code>uploadBytesResumable(ref, data)</code> returns an <code>UploadTask</code> supporting <code>pause()</code>, <code>resume()</code>, and <code>cancel()</code> over synthetic microtask steps.</div></div>
241
240
  </details>
242
- <details class="compat-row" data-status="unsupported">
243
- <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">uploadBytesResumable(ref, data, metadata?)</code><span class="compat-sub"><span class="compat-behavior"><code>task.on('state_changed', next, error, complete)</code> fires <code>next</code> with <code>{bytesTransferred, totalBytes, state}</code> snapshots</span></span></span></summary>
244
- <div class="compat-evidence"><div class="compat-probe">not implemented</div></div>
241
+ <details class="compat-row" data-status="diverged">
242
+ <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">uploadBytesResumable(ref, data, metadata?)</code><span class="compat-sub"><span class="compat-behavior"><code>task.on('state_changed', next, error, complete)</code> fires <code>next</code> with <code>{bytesTransferred, totalBytes, state}</code> snapshots</span></span></span></summary>
243
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/resumable-upload.test.ts#L14" target="_blank" rel="noopener noreferrer"><code>unit:resumable-upload.test.ts</code></a> confirms synthetic progress snapshots are emitted during task execution without requiring network transfer.</div></div>
245
244
  </details>
246
- <details class="compat-row" data-status="unsupported">
247
- <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">uploadBytesResumable(ref, data, metadata?)</code><span class="compat-sub"><span class="compat-behavior"><code>task.pause()</code> flips <code>state</code> to <code>'paused'</code>; <code>task.resume()</code> continues</span></span></span></summary>
248
- <div class="compat-evidence"><div class="compat-probe">not implemented</div></div>
245
+ <details class="compat-row" data-status="diverged">
246
+ <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">uploadBytesResumable(ref, data, metadata?)</code><span class="compat-sub"><span class="compat-behavior"><code>task.pause()</code> flips <code>state</code> to <code>'paused'</code>; <code>task.resume()</code> continues</span></span></span></summary>
247
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/resumable-upload.test.ts#L14" target="_blank" rel="noopener noreferrer"><code>unit:resumable-upload.test.ts</code></a> confirms calling <code>task.pause()</code> and <code>task.resume()</code> transitions through <code>'paused'</code> and <code>'running'</code> states deterministically.</div></div>
249
248
  </details>
250
- <details class="compat-row" data-status="unsupported">
251
- <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">uploadBytesResumable(ref, data, metadata?)</code><span class="compat-sub"><span class="compat-behavior"><code>task.cancel()</code> rejects the upload with <code>storage/canceled</code></span></span></span></summary>
252
- <div class="compat-evidence"><div class="compat-probe">not implemented</div></div>
249
+ <details class="compat-row" data-status="diverged">
250
+ <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">uploadBytesResumable(ref, data, metadata?)</code><span class="compat-sub"><span class="compat-behavior"><code>task.cancel()</code> rejects the upload with <code>storage/canceled</code></span></span></span></summary>
251
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/resumable-upload.test.ts#L14" target="_blank" rel="noopener noreferrer"><code>unit:resumable-upload.test.ts</code></a> confirms cancellation rejects with <code>storage/canceled</code> and leaves no falsely completed object in storage.</div></div>
253
252
  </details>
254
253
  </div>
255
254
 
@@ -258,11 +257,11 @@
258
257
  <div class="compat-list">
259
258
  <details class="compat-row" data-status="diverged">
260
259
  <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">getDownloadURL(ref)</code><span class="compat-sub"><span class="compat-behavior">Exported by <code>firebase/storage</code>; returns a token-signed HTTPS URL that fetches the blob</span></span></span></summary>
261
- <div class="compat-evidence"><div class="compat-probe">Implemented with a two-sided pin. Production observation <code>storage-upload-bytes-roundtrip</code> records <code>urlIsHttps: true</code> and a byte-identical fetch. The sandbox oracle replay now calls the same public <code>getDownloadURL</code> + <code>fetch</code> path and proves byte-identical content, while explicitly asserting its URL starts with <code>blob:</code>. The client↔host integration proves SharedWorker mode creates that URL in the calling page after the rules-checked Blob crosses the port. The remaining divergence is URL identity and lifetime: the sandbox URL is a page-local snapshot, not token-signed HTTPS and not shareable outside that page.</div></div>
260
+ <div class="compat-evidence"><div class="compat-probe">Implemented with a two-sided pin. Production observation <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/storage/storage-upload-bytes-roundtrip.json" target="_blank" rel="noopener noreferrer"><code>storage-upload-bytes-roundtrip</code></a> records <code>urlIsHttps: true</code> and a byte-identical fetch. The sandbox oracle replay now calls the same public <code>getDownloadURL</code> + <code>fetch</code> path and proves byte-identical content, while explicitly asserting its URL starts with <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/oracle-conformance.test.ts#L170" target="_blank" rel="noopener noreferrer"><code>blob:</code></a>. The client↔host integration proves SharedWorker mode creates that URL in the calling page after the rules-checked Blob crosses the port. The remaining divergence is URL identity and lifetime: the sandbox URL is a page-local snapshot, not token-signed HTTPS and not shareable outside that page. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/worker/integration.test.ts#L237" target="_blank" rel="noopener noreferrer"><code>integration.test.ts</code></a>)</div></div>
262
261
  </details>
263
262
  <details class="compat-row" data-status="ok">
264
263
  <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">getDownloadURL(ref)</code><span class="compat-sub"><span class="compat-behavior">Throws <code>storage/object-not-found</code> for missing objects</span></span></span></summary>
265
- <div class="compat-evidence"><div class="compat-probe">Production observation <code>storage-delete-then-get-throws</code> records <code>getDownloadURL</code> throwing <code>storage/object-not-found</code> after deletion. The sandbox oracle replay now invokes <code>getDownloadURL</code> itself and matches that code; the public error-code suite also pins the never-existing-object case.</div></div>
264
+ <div class="compat-evidence"><div class="compat-probe">Production observation <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/storage/storage-delete-then-get-throws.json" target="_blank" rel="noopener noreferrer"><code>storage-delete-then-get-throws</code></a> records <code>getDownloadURL</code> throwing <code>storage/object-not-found</code> after deletion. The sandbox oracle replay now invokes <code>getDownloadURL</code> itself and matches that code; the public error-code suite also pins the never-existing-object case. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/error-codes.test.ts#L28" target="_blank" rel="noopener noreferrer"><code>error-codes.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/oracle-conformance.test.ts#L110" target="_blank" rel="noopener noreferrer"><code>oracle-conformance.test.ts</code></a>)</div></div>
266
265
  </details>
267
266
  </div>
268
267
 
@@ -271,23 +270,23 @@
271
270
  <div class="compat-list">
272
271
  <details class="compat-row" data-status="ok">
273
272
  <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">getBytes(ref, maxDownloadSize?)</code><span class="compat-sub"><span class="compat-behavior">Returns the blob's contents as an <code>ArrayBuffer</code></span></span></span></summary>
274
- <div class="compat-evidence"><div class="compat-probe"><code>unit:reference.test.ts</code> ("accepts a Uint8Array" round-trip via <code>getBytes</code>)</div></div>
273
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/reference.test.ts#L12" target="_blank" rel="noopener noreferrer"><code>unit:reference.test.ts</code></a> ("accepts a Uint8Array" round-trip via <code>getBytes</code>)</div></div>
275
274
  </details>
276
275
  <details class="compat-row" data-status="ok">
277
276
  <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">getBytes(ref, maxDownloadSize?)</code><span class="compat-sub"><span class="compat-behavior">Throws <code>storage/object-not-found</code> when no object exists at the path</span></span></span></summary>
278
- <div class="compat-evidence"><div class="compat-probe"><code>unit:reference.test.ts</code> ("throws storage/object-not-found for missing paths") + oracle: <code>packages/conformance/observations/storage/storage-delete-then-get-throws.json</code> (against blockingfun, fb-js-sdk 12.13.0: upload → delete → <code>getDownloadURL</code> on the deleted ref throws <code>FirebaseError</code> with <code>code: 'storage/object-not-found'</code>)</div></div>
277
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/reference.test.ts#L12" target="_blank" rel="noopener noreferrer"><code>unit:reference.test.ts</code></a> ("throws storage/object-not-found for missing paths") + oracle: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/storage/storage-delete-then-get-throws.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/storage/storage-delete-then-get-throws.json</code></a> (against blockingfun, fb-js-sdk 12.13.0: upload → delete → <code>getDownloadURL</code> on the deleted ref throws <code>FirebaseError</code> with <code>code: 'storage/object-not-found'</code>)</div></div>
279
278
  </details>
280
279
  <details class="compat-row" data-status="ok">
281
280
  <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">getBytes(ref, maxDownloadSize?)</code><span class="compat-sub"><span class="compat-behavior">When the object exceeds <code>maxDownloadSize</code>, returns a truncated prefix of that byte length (does not throw)</span></span></span></summary>
282
- <div class="compat-evidence"><div class="compat-probe"><code>unit:upstream-storage-probes.test.ts</code> ("getBytes / getBlob return a truncated prefix when the object exceeds the cap"). Matches upstream <code>getBytesInternal</code> / <code>getBlobInternal</code> post-fetch slice (GCS may ignore Range on small files). Prior COMPAT claim that the cap throws was wrong.</div></div>
281
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/upstream-storage-probes.test.ts#L15" target="_blank" rel="noopener noreferrer"><code>unit:upstream-storage-probes.test.ts</code></a> ("getBytes / getBlob return a truncated prefix when the object exceeds the cap"). Matches upstream <code>getBytesInternal</code> / <code>getBlobInternal</code> post-fetch slice (GCS may ignore Range on small files). Prior COMPAT claim that the cap throws was wrong.</div></div>
283
282
  </details>
284
283
  <details class="compat-row" data-status="ok">
285
284
  <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">getBytes(ref, maxDownloadSize?)</code><span class="compat-sub"><span class="compat-behavior">Just-under-cap reads succeed and return the full byte length</span></span></span></summary>
286
- <div class="compat-evidence"><div class="compat-probe"><code>unit:upstream-storage-probes.test.ts</code> ("just-under-cap reads return the full object") + <code>unit:reference.test.ts</code> ("honors maxDownloadSizeBytes when the blob is too large")</div></div>
285
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/upstream-storage-probes.test.ts#L15" target="_blank" rel="noopener noreferrer"><code>unit:upstream-storage-probes.test.ts</code></a> ("just-under-cap reads return the full object") + <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/reference.test.ts#L12" target="_blank" rel="noopener noreferrer"><code>unit:reference.test.ts</code></a> ("honors maxDownloadSizeBytes when the blob is too large")</div></div>
287
286
  </details>
288
287
  <details class="compat-row" data-status="ok">
289
288
  <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">getBytes(ref, maxDownloadSize?)</code><span class="compat-sub"><span class="compat-behavior">Throws <code>storage/invalid-root-operation</code> when called on the root reference</span></span></span></summary>
290
- <div class="compat-evidence"><div class="compat-probe"><code>unit:reference.test.ts</code> ("throws invalid-root-operation on root reads")</div></div>
289
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/reference.test.ts#L12" target="_blank" rel="noopener noreferrer"><code>unit:reference.test.ts</code></a> ("throws invalid-root-operation on root reads")</div></div>
291
290
  </details>
292
291
  </div>
293
292
 
@@ -296,15 +295,15 @@
296
295
  <div class="compat-list">
297
296
  <details class="compat-row" data-status="ok">
298
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">getBlob(ref, maxDownloadSize?)</code><span class="compat-sub"><span class="compat-behavior">Returns the stored bytes wrapped as a <code>Blob</code> (with <code>.type</code> from metadata)</span></span></span></summary>
299
- <div class="compat-evidence"><div class="compat-probe"><code>unit:reference.test.ts</code> ("accepts a Blob and round-trips through getBlob")</div></div>
298
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/reference.test.ts#L12" target="_blank" rel="noopener noreferrer"><code>unit:reference.test.ts</code></a> ("accepts a Blob and round-trips through getBlob")</div></div>
300
299
  </details>
301
300
  <details class="compat-row" data-status="ok">
302
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">getBlob(ref, maxDownloadSize?)</code><span class="compat-sub"><span class="compat-behavior">Throws <code>storage/object-not-found</code> for missing paths</span></span></span></summary>
303
- <div class="compat-evidence"><div class="compat-probe"><code>unit:reference.test.ts</code> ("throws storage/object-not-found for missing paths")</div></div>
302
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/reference.test.ts#L12" target="_blank" rel="noopener noreferrer"><code>unit:reference.test.ts</code></a> ("throws storage/object-not-found for missing paths")</div></div>
304
303
  </details>
305
304
  <details class="compat-row" data-status="ok">
306
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">getBlob(ref, maxDownloadSize?)</code><span class="compat-sub"><span class="compat-behavior">Honors <code>maxDownloadSize</code> same as <code>getBytes</code></span></span></span></summary>
307
- <div class="compat-evidence"><div class="compat-probe"><code>unit:upstream-storage-probes.test.ts</code> ("getBytes / getBlob return a truncated prefix when the object exceeds the cap"; shared <code>fetchBlob</code> helper in <code>download.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/storage/upstream-storage-probes.test.ts#L14" target="_blank" rel="noopener noreferrer"><code>unit:upstream-storage-probes.test.ts</code></a> ("getBytes / getBlob return a truncated prefix when the object exceeds the cap"; shared <code>fetchBlob</code> helper in <code>download.ts</code>)</div></div>
308
307
  </details>
309
308
  <details class="compat-row" data-status="ok">
310
309
  <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">getBlob(ref, maxDownloadSize?)</code><span class="compat-sub"><span class="compat-behavior">Root-ref read throws <code>storage/invalid-root-operation</code></span></span></span></summary>
@@ -326,19 +325,19 @@
326
325
  <div class="compat-list">
327
326
  <details class="compat-row" data-status="ok">
328
327
  <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">deleteObject(ref)</code><span class="compat-sub"><span class="compat-behavior">Removes both the blob AND the metadata atomically (post-delete <code>getBlob</code> throws <code>object-not-found</code>)</span></span></span></summary>
329
- <div class="compat-evidence"><div class="compat-probe"><code>unit:reference.test.ts</code> ("removes both blob and metadata")</div></div>
328
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/reference.test.ts#L14" target="_blank" rel="noopener noreferrer"><code>unit:reference.test.ts</code></a> ("removes both blob and metadata")</div></div>
330
329
  </details>
331
330
  <details class="compat-row" data-status="diverged">
332
331
  <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">deleteObject(ref)</code><span class="compat-sub"><span class="compat-behavior">Sandbox: no-op on missing path (does NOT throw)</span></span></span></summary>
333
- <div class="compat-evidence"><div class="compat-probe">divergence: sandbox is no-op via <code>persistence.ts</code>'s <code>delete</code>. Prod's <code>deleteObject</code> on a missing path throws <code>storage/object-not-found</code>. Oracle-locked: <code>packages/conformance/observations/storage/storage-delete-missing-throws.json</code> (<code>code: 'storage/object-not-found'</code>, <code>name: 'FirebaseError'</code> against blockingfun, fb-js-sdk 12.13.0). Both sides pinned in <code>oracle-conformance.test.ts</code>; documented in <code>download.ts</code>.</div></div>
332
+ <div class="compat-evidence"><div class="compat-probe">divergence: sandbox is no-op via <code>persistence.ts</code>'s <code>delete</code>. Prod's <code>deleteObject</code> on a missing path throws <code>storage/object-not-found</code>. Oracle-locked: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/storage/storage-delete-missing-throws.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/storage/storage-delete-missing-throws.json</code></a> (<code>code: 'storage/object-not-found'</code>, <code>name: 'FirebaseError'</code> against blockingfun, fb-js-sdk 12.13.0). Both sides pinned in <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/oracle-conformance.test.ts#L91" target="_blank" rel="noopener noreferrer"><code>oracle-conformance.test.ts</code></a>; documented in <code>download.ts</code>.</div></div>
334
333
  </details>
335
334
  <details class="compat-row" data-status="ok">
336
335
  <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">deleteObject(ref)</code><span class="compat-sub"><span class="compat-behavior">Throws <code>storage/invalid-root-operation</code> on the root reference</span></span></span></summary>
337
- <div class="compat-evidence"><div class="compat-probe"><code>unit:reference.test.ts</code> ("throws invalid-root-operation on root")</div></div>
336
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/reference.test.ts#L14" target="_blank" rel="noopener noreferrer"><code>unit:reference.test.ts</code></a> ("throws invalid-root-operation on root")</div></div>
338
337
  </details>
339
338
  <details class="compat-row" data-status="ok">
340
339
  <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">deleteObject(ref)</code><span class="compat-sub"><span class="compat-behavior">Prod: a successful <code>deleteObject</code> followed by <code>getDownloadURL</code> on the same ref throws <code>storage/object-not-found</code></span></span></span></summary>
341
- <div class="compat-evidence"><div class="compat-probe">oracle: <code>packages/conformance/observations/storage/storage-delete-then-get-throws.json</code> (against blockingfun, fb-js-sdk 12.13.0: upload + delete succeed, then <code>getDownloadURL</code> throws <code>code: 'storage/object-not-found'</code>, message <code>"Firebase Storage: Object '…' does not exist."</code>, <code>isFirebaseError: true</code>)</div>
340
+ <div class="compat-evidence"><div class="compat-probe">oracle: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/storage/storage-delete-then-get-throws.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/storage/storage-delete-then-get-throws.json</code></a> (against blockingfun, fb-js-sdk 12.13.0: upload + delete succeed, then <code>getDownloadURL</code> throws <code>code: 'storage/object-not-found'</code>, message <code>"Firebase Storage: Object '…' does not exist."</code>, <code>isFirebaseError: true</code>)</div>
342
341
  <div class="compat-note">(prod-only)</div></div>
343
342
  </details>
344
343
  <details class="compat-row" data-status="ok">
@@ -352,47 +351,47 @@
352
351
  <div class="compat-list">
353
352
  <details class="compat-row" data-status="ok">
354
353
  <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">listAll(ref)</code><span class="compat-sub"><span class="compat-behavior">Returns <code>ListResult</code> with <code>items</code> (direct child files) + <code>prefixes</code> (sub-folder refs) + <code>nextPageToken: undefined</code></span></span></span></summary>
355
- <div class="compat-evidence"><div class="compat-probe"><code>unit:list.test.ts</code></div></div>
354
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/list.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:list.test.ts</code></a></div></div>
356
355
  </details>
357
356
  <details class="compat-row" data-status="ok">
358
357
  <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">listAll(ref)</code><span class="compat-sub"><span class="compat-behavior">Empty bucket → both arrays empty, <code>nextPageToken: undefined</code></span></span></span></summary>
359
- <div class="compat-evidence"><div class="compat-probe"><code>unit:list.test.ts</code> ("returns empty arrays on an empty bucket")</div></div>
358
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/list.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:list.test.ts</code></a> ("returns empty arrays on an empty bucket")</div></div>
360
359
  </details>
361
360
  <details class="compat-row" data-status="ok">
362
361
  <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">listAll(ref)</code><span class="compat-sub"><span class="compat-behavior">Direct children only — does NOT recurse into grandchildren as items</span></span></span></summary>
363
- <div class="compat-evidence"><div class="compat-probe"><code>unit:list.test.ts</code> ("does not recurse into grandchildren as items")</div></div>
362
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/list.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:list.test.ts</code></a> ("does not recurse into grandchildren as items")</div></div>
364
363
  </details>
365
364
  <details class="compat-row" data-status="ok">
366
365
  <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">listAll(ref)</code><span class="compat-sub"><span class="compat-behavior">Sub-folders surface as <code>prefixes</code> and are deduplicated (many files under one folder → ONE prefix entry)</span></span></span></summary>
367
- <div class="compat-evidence"><div class="compat-probe"><code>unit:list.test.ts</code> ("promotes sub-folders into prefixes (deduplicated)")</div></div>
366
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/list.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:list.test.ts</code></a> ("promotes sub-folders into prefixes (deduplicated)")</div></div>
368
367
  </details>
369
368
  <details class="compat-row" data-status="ok">
370
369
  <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">listAll(ref)</code><span class="compat-sub"><span class="compat-behavior"><code>items</code> sorted by path (IDB key order, lexicographic)</span></span></span></summary>
371
- <div class="compat-evidence"><div class="compat-probe"><code>unit:list.test.ts</code> ("lists direct children of a folder")</div></div>
370
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/list.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:list.test.ts</code></a> ("lists direct children of a folder")</div></div>
372
371
  </details>
373
372
  <details class="compat-row" data-status="ok">
374
373
  <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">listAll(ref)</code><span class="compat-sub"><span class="compat-behavior"><code>prefixes</code> sorted lexicographically by <code>fullPath</code> (for determinism)</span></span></span></summary>
375
- <div class="compat-evidence"><div class="compat-probe"><code>unit:list.test.ts</code> (root-scan example asserts <code>configs</code> &lt; <code>sessions</code>)</div></div>
374
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/list.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:list.test.ts</code></a> (root-scan example asserts <code>configs</code> &lt; <code>sessions</code>)</div></div>
376
375
  </details>
377
376
  <details class="compat-row" data-status="ok">
378
377
  <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">listAll(ref)</code><span class="compat-sub"><span class="compat-behavior">The scanned ref itself is NEVER included in <code>items</code> (even when an object exists at the exact prefix path)</span></span></span></summary>
379
- <div class="compat-evidence"><div class="compat-probe"><code>unit:list.test.ts</code> ("does not include the scanned ref itself")</div></div>
378
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/list.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:list.test.ts</code></a> ("does not include the scanned ref itself")</div></div>
380
379
  </details>
381
380
  <details class="compat-row" data-status="ok">
382
381
  <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">listAll(ref)</code><span class="compat-sub"><span class="compat-behavior"><code>listAll(ref(storage))</code> (root) scans the entire bucket</span></span></span></summary>
383
- <div class="compat-evidence"><div class="compat-probe"><code>unit:list.test.ts</code> ("listAll on the root scans the entire bucket")</div></div>
382
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/list.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:list.test.ts</code></a> ("listAll on the root scans the entire bucket")</div></div>
384
383
  </details>
385
384
  <details class="compat-row" data-status="ok">
386
385
  <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">listAll(ref)</code><span class="compat-sub"><span class="compat-behavior">Items expose the full <code>StorageReference</code> shape (storage, bucket, name, parent)</span></span></span></summary>
387
- <div class="compat-evidence"><div class="compat-probe"><code>unit:list.test.ts</code> ("items expose the StorageReference shape")</div></div>
386
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/list.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:list.test.ts</code></a> ("items expose the StorageReference shape")</div></div>
388
387
  </details>
389
388
  <details class="compat-row" data-status="ok">
390
389
  <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">listAll(ref)</code><span class="compat-sub"><span class="compat-behavior">Prod: items + prefixes shape matches sandbox after <code>N</code> uploads under a directory</span></span></span></summary>
391
- <div class="compat-evidence"><div class="compat-probe">oracle: <code>packages/conformance/observations/storage/storage-listall-shape.json</code> (against blockingfun, fb-js-sdk 12.13.0: 3 direct children + 1 grandchild → <code>items</code> has all 3 direct children sorted, <code>prefixes</code> has the single sub-folder, <code>itemCount: 3</code>, <code>prefixCount: 1</code>, <code>threeDirectChildren: true</code>, <code>oneSubPrefix: true</code>)</div></div>
390
+ <div class="compat-evidence"><div class="compat-probe">oracle: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/storage/storage-listall-shape.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/storage/storage-listall-shape.json</code></a> (against blockingfun, fb-js-sdk 12.13.0: 3 direct children + 1 grandchild → <code>items</code> has all 3 direct children sorted, <code>prefixes</code> has the single sub-folder, <code>itemCount: 3</code>, <code>prefixCount: 1</code>, <code>threeDirectChildren: true</code>, <code>oneSubPrefix: true</code>)</div></div>
392
391
  </details>
393
392
  <details class="compat-row" data-status="ok">
394
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">listAll(ref)</code><span class="compat-sub"><span class="compat-behavior"><code>listAll</code> enforces rules: <code>read</code> permission on the scanned prefix path governs list (Firebase: <code>read</code> covers download AND list), denied prefix → <code>storage/unauthorized</code></span></span></span></summary>
395
- <div class="compat-evidence"><div class="compat-probe">ST-B2 fixed: <code>list.ts</code> now calls <code>enforceRules</code> with <code>method: 'read'</code> on the listed prefix (was a silent bypass — a denied tree was still fully enumerable). With no rules configured the check is a no-op. Probe: <code>unit:list-rules.test.ts</code> ("denies an anonymous listAll of a tree the rules protect" / "allows an authed listAll"). Note: a <code>read</code> rule scoped to <code>match /sessions/{id}</code> does NOT grant list on <code>/sessions</code> — the folder needs its own read rule, matching prod; the session-archive demo ruleset adds <code>match /sessions { allow read }</code>.</div></div>
394
+ <div class="compat-evidence"><div class="compat-probe">ST-B2 fixed: <code>list.ts</code> now calls <code>enforceRules</code> with <code>method: 'read'</code> on the listed prefix (was a silent bypass — a denied tree was still fully enumerable). With no rules configured the check is a no-op. Probe: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/list-rules.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:list-rules.test.ts</code></a> ("denies an anonymous listAll of a tree the rules protect" / "allows an authed listAll"). Note: a <code>read</code> rule scoped to <code>match /sessions/{id}</code> does NOT grant list on <code>/sessions</code> — the folder needs its own read rule, matching prod; the session-archive demo ruleset adds <code>match /sessions { allow read }</code>.</div></div>
396
395
  </details>
397
396
  </div>
398
397
 
@@ -410,31 +409,31 @@
410
409
  <div class="compat-list">
411
410
  <details class="compat-row" data-status="ok">
412
411
  <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">getMetadata(ref)<code> / </code>updateMetadata(ref, metadata)</code><span class="compat-sub"><span class="compat-behavior"><code>getMetadata(ref)</code> returns the same <code>FullMetadata</code> shape <code>uploadBytes</code> produced</span></span></span></summary>
413
- <div class="compat-evidence"><div class="compat-probe"><code>unit:metadata.test.ts</code> ("returns the FullMetadata uploadBytes wrote")</div></div>
412
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/metadata.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:metadata.test.ts</code></a> ("returns the FullMetadata uploadBytes wrote")</div></div>
414
413
  </details>
415
414
  <details class="compat-row" data-status="ok">
416
415
  <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">getMetadata(ref)<code> / </code>updateMetadata(ref, metadata)</code><span class="compat-sub"><span class="compat-behavior"><code>getMetadata(ref)</code> throws <code>storage/object-not-found</code> for missing paths</span></span></span></summary>
417
- <div class="compat-evidence"><div class="compat-probe"><code>unit:metadata.test.ts</code> ("throws object-not-found for missing paths")</div></div>
416
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/metadata.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:metadata.test.ts</code></a> ("throws object-not-found for missing paths")</div></div>
418
417
  </details>
419
418
  <details class="compat-row" data-status="ok">
420
419
  <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">getMetadata(ref)<code> / </code>updateMetadata(ref, metadata)</code><span class="compat-sub"><span class="compat-behavior"><code>getMetadata(ref)</code> throws <code>storage/invalid-root-operation</code> on the root</span></span></span></summary>
421
- <div class="compat-evidence"><div class="compat-probe"><code>unit:metadata.test.ts</code> ("throws invalid-root-operation on the root reference")</div></div>
420
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/metadata.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:metadata.test.ts</code></a> ("throws invalid-root-operation on the root reference")</div></div>
422
421
  </details>
423
422
  <details class="compat-row" data-status="ok">
424
423
  <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">getMetadata(ref)<code> / </code>updateMetadata(ref, metadata)</code><span class="compat-sub"><span class="compat-behavior"><code>updateMetadata(ref, patch)</code> replaces the listed client-settable fields wholesale (per Firebase semantics)</span></span></span></summary>
425
- <div class="compat-evidence"><div class="compat-probe"><code>unit:metadata.test.ts</code> ("replaces settable fields, bumps metageneration, refreshes updated")</div></div>
424
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/metadata.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:metadata.test.ts</code></a> ("replaces settable fields, bumps metageneration, refreshes updated")</div></div>
426
425
  </details>
427
426
  <details class="compat-row" data-status="ok">
428
427
  <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">getMetadata(ref)<code> / </code>updateMetadata(ref, metadata)</code><span class="compat-sub"><span class="compat-behavior"><code>updateMetadata</code> bumps <code>metageneration</code> by 1 on each call</span></span></span></summary>
429
- <div class="compat-evidence"><div class="compat-probe"><code>unit:metadata.test.ts</code></div></div>
428
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/metadata.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:metadata.test.ts</code></a></div></div>
430
429
  </details>
431
430
  <details class="compat-row" data-status="ok">
432
431
  <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">getMetadata(ref)<code> / </code>updateMetadata(ref, metadata)</code><span class="compat-sub"><span class="compat-behavior"><code>updateMetadata</code> refreshes <code>updated</code> to the call moment; <code>timeCreated</code> and <code>generation</code> stay pinned</span></span></span></summary>
433
- <div class="compat-evidence"><div class="compat-probe"><code>unit:metadata.test.ts</code></div></div>
432
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/metadata.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:metadata.test.ts</code></a></div></div>
434
433
  </details>
435
434
  <details class="compat-row" data-status="ok">
436
435
  <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">getMetadata(ref)<code> / </code>updateMetadata(ref, metadata)</code><span class="compat-sub"><span class="compat-behavior"><code>updateMetadata</code> preserves the blob bytes (only metadata changes)</span></span></span></summary>
437
- <div class="compat-evidence"><div class="compat-probe"><code>unit:metadata.test.ts</code> ("leaves the blob content untouched")</div></div>
436
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/metadata.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:metadata.test.ts</code></a> ("leaves the blob content untouched")</div></div>
438
437
  </details>
439
438
  <details class="compat-row" data-status="diverged">
440
439
  <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">getMetadata(ref)<code> / </code>updateMetadata(ref, metadata)</code><span class="compat-sub"><span class="compat-behavior"><code>updateMetadata</code> with <code>undefined</code> field values preserves the prior value (does NOT clear it)</span></span></span></summary>
@@ -442,25 +441,25 @@
442
441
  </details>
443
442
  <details class="compat-row" data-status="ok">
444
443
  <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">getMetadata(ref)<code> / </code>updateMetadata(ref, metadata)</code><span class="compat-sub"><span class="compat-behavior"><code>updateMetadata</code> throws <code>storage/object-not-found</code> for missing paths</span></span></span></summary>
445
- <div class="compat-evidence"><div class="compat-probe"><code>unit:metadata.test.ts</code> ("throws object-not-found when the path is missing")</div></div>
444
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/metadata.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:metadata.test.ts</code></a> ("throws object-not-found when the path is missing")</div></div>
446
445
  </details>
447
446
  <details class="compat-row" data-status="ok">
448
447
  <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">getMetadata(ref)<code> / </code>updateMetadata(ref, metadata)</code><span class="compat-sub"><span class="compat-behavior"><code>updateMetadata</code> throws <code>storage/invalid-root-operation</code> on the root</span></span></span></summary>
449
- <div class="compat-evidence"><div class="compat-probe"><code>unit:metadata.test.ts</code> ("throws invalid-root-operation on the root reference")</div></div>
448
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/metadata.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>unit:metadata.test.ts</code></a> ("throws invalid-root-operation on the root reference")</div></div>
450
449
  </details>
451
450
  <details class="compat-row" data-status="ok">
452
451
  <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">getMetadata(ref)<code> / </code>updateMetadata(ref, metadata)</code><span class="compat-sub"><span class="compat-behavior">Prod: <code>getMetadata</code> after <code>uploadBytes</code> returns <code>contentType</code> and <code>size</code> matching what was uploaded</span></span></span></summary>
453
- <div class="compat-evidence"><div class="compat-probe">oracle: <code>packages/conformance/observations/storage/storage-upload-then-getmetadata.json</code> (against blockingfun, fb-js-sdk 12.13.0: upload 128-byte payload with <code>contentType: 'application/octet-stream'</code>, getMetadata returns <code>metadataSize: 128</code>, <code>metadataContentType: 'application/octet-stream'</code>, <code>metadataBucket: 'blockingfun.firebasestorage.app'</code>, <code>metadataMetageneration: '1'</code>, <code>fullPathMatches: true</code>)</div>
452
+ <div class="compat-evidence"><div class="compat-probe">oracle: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/storage/storage-upload-then-getmetadata.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/storage/storage-upload-then-getmetadata.json</code></a> (against blockingfun, fb-js-sdk 12.13.0: upload 128-byte payload with <code>contentType: 'application/octet-stream'</code>, getMetadata returns <code>metadataSize: 128</code>, <code>metadataContentType: 'application/octet-stream'</code>, <code>metadataBucket: 'blockingfun.firebasestorage.app'</code>, <code>metadataMetageneration: '1'</code>, <code>fullPathMatches: true</code>)</div>
454
453
  <div class="compat-note">(prod-only)</div></div>
455
454
  </details>
456
455
  <details class="compat-row" data-status="ok">
457
456
  <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">getMetadata(ref)<code> / </code>updateMetadata(ref, metadata)</code><span class="compat-sub"><span class="compat-behavior">Prod: <code>updateMetadata({customMetadata: {...}})</code> round-trips through a follow-up <code>getMetadata</code></span></span></span></summary>
458
- <div class="compat-evidence"><div class="compat-probe">oracle: <code>packages/conformance/observations/storage/storage-update-metadata-roundtrip.json</code> (against blockingfun, fb-js-sdk 12.13.0: post-update <code>getMetadata</code> returns the exact <code>customMetadata</code> object, <code>metageneration</code> bumps <code>'1'</code> → <code>'2'</code>, <code>customSurvived: true</code>, <code>metagenerationBumped: true</code>)</div>
457
+ <div class="compat-evidence"><div class="compat-probe">oracle: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/storage/storage-update-metadata-roundtrip.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/storage/storage-update-metadata-roundtrip.json</code></a> (against blockingfun, fb-js-sdk 12.13.0: post-update <code>getMetadata</code> returns the exact <code>customMetadata</code> object, <code>metageneration</code> bumps <code>'1'</code> → <code>'2'</code>, <code>customSurvived: true</code>, <code>metagenerationBumped: true</code>)</div>
459
458
  <div class="compat-note">(prod-only)</div></div>
460
459
  </details>
461
460
  <details class="compat-row" data-status="diverged">
462
461
  <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">getMetadata(ref)<code> / </code>updateMetadata(ref, metadata)</code><span class="compat-sub"><span class="compat-behavior"><code>FullMetadata.md5Hash</code> populated on uploads</span></span></span></summary>
463
- <div class="compat-evidence"><div class="compat-probe">divergence: sandbox does NOT compute <code>md5Hash</code>. Oracle-locked: <code>packages/conformance/observations/storage/storage-upload-then-getmetadata.json</code> confirms prod sets <code>md5Hash</code> (<code>hasMd5Hash: true</code> after a vanilla <code>uploadBytes</code>). Both sides pinned in <code>oracle-conformance.test.ts</code>. Aligning the sandbox is a one-spot fix in <code>upload.ts</code>'s <code>buildStoredMetadata</code>.</div></div>
462
+ <div class="compat-evidence"><div class="compat-probe">divergence: sandbox does NOT compute <code>md5Hash</code>. Oracle-locked: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/storage/storage-upload-then-getmetadata.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/storage/storage-upload-then-getmetadata.json</code></a> confirms prod sets <code>md5Hash</code> (<code>hasMd5Hash: true</code> after a vanilla <code>uploadBytes</code>). Both sides pinned in <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/oracle-conformance.test.ts#L207" target="_blank" rel="noopener noreferrer"><code>oracle-conformance.test.ts</code></a>. Aligning the sandbox is a one-spot fix in <code>upload.ts</code>'s <code>buildStoredMetadata</code>.</div></div>
464
463
  </details>
465
464
  <details class="compat-row" data-status="unsupported">
466
465
  <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">getMetadata(ref)<code> / </code>updateMetadata(ref, metadata)</code><span class="compat-sub"><span class="compat-behavior"><code>FullMetadata.ref</code> lazy population (prod populates lazily)</span></span></span></summary>
@@ -487,23 +486,23 @@ API) moved to the native `storage-rules` surface (`docs/rules/COMPAT.md`).
487
486
  <div class="compat-list">
488
487
  <details class="compat-row" data-status="ok">
489
488
  <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">Rules enforcement</code><span class="compat-sub"><span class="compat-behavior">Op-level enforcement: <code>uploadBytes</code> against a denied path throws <code>storage/unauthorized</code> on sandbox / <code>storage/unauthorized</code> on prod, <code>.code</code> exposed on both</span></span></span></summary>
490
- <div class="compat-evidence"><div class="compat-probe">ST-B1 fixed: sandbox now throws a <code>StorageError</code> (see <code>src/storage/errors.ts</code>) whose <code>.code === 'storage/unauthorized'</code> — matching prod's <code>FirebaseError.code</code>. Probe: <code>unit:error-codes.test.ts</code> ("unauthorized when rules deny the operation"). Residual divergence (documented, not a <code>.code</code> gap): the sandbox <code>StorageError.name</code> is <code>'StorageError'</code> (plain <code>Error</code> subclass, same shape as Firestore's <code>SandboxError</code>) where prod reports <code>name: 'FirebaseError'</code> / <code>isFirebaseError: true</code>, and the message wording differs (sandbox embeds the matched-rule reason chain). Oracle-locked: <code>packages/conformance/observations/storage/storage-rules-denied-error-code.json</code> (against blockingfun, fb-js-sdk 12.13.0: <code>code: 'storage/unauthorized'</code>, message <code>"Firebase Storage: User does not have permission to access '&lt;path&gt;'."</code>, <code>name: 'FirebaseError'</code>, <code>isFirebaseError: true</code>).</div></div>
489
+ <div class="compat-evidence"><div class="compat-probe">ST-B1 fixed: sandbox now throws a <code>StorageError</code> (see <code>src/storage/errors.ts</code>) whose <code>.code === 'storage/unauthorized'</code> — matching prod's <code>FirebaseError.code</code>. Probe: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/error-codes.test.ts#L15" target="_blank" rel="noopener noreferrer"><code>unit:error-codes.test.ts</code></a> ("unauthorized when rules deny the operation"). Residual divergence (documented, not a <code>.code</code> gap): the sandbox <code>StorageError.name</code> is <code>'StorageError'</code> (plain <code>Error</code> subclass, same shape as Firestore's <code>SandboxError</code>) where prod reports <code>name: 'FirebaseError'</code> / <code>isFirebaseError: true</code>, and the message wording differs (sandbox embeds the matched-rule reason chain). Oracle-locked: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/storage/storage-rules-denied-error-code.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/storage/storage-rules-denied-error-code.json</code></a> (against blockingfun, fb-js-sdk 12.13.0: <code>code: 'storage/unauthorized'</code>, message <code>"Firebase Storage: User does not have permission to access '&lt;path&gt;'."</code>, <code>name: 'FirebaseError'</code>, <code>isFirebaseError: true</code>).</div></div>
491
490
  </details>
492
491
  <details class="compat-row" data-status="ok">
493
492
  <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">Rules enforcement</code><span class="compat-sub"><span class="compat-behavior"><code>getMetadata</code> against a denied path throws <code>storage/unauthorized</code></span></span></span></summary>
494
- <div class="compat-evidence"><div class="compat-probe"><code>unit:storage/enforce.test.ts</code> (operation-integration section)</div></div>
493
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/enforce.test.ts#L4" target="_blank" rel="noopener noreferrer"><code>unit:storage/enforce.test.ts</code></a> (operation-integration section)</div></div>
495
494
  </details>
496
495
  <details class="compat-row" data-status="ok">
497
496
  <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">Rules enforcement</code><span class="compat-sub"><span class="compat-behavior"><code>updateMetadata</code> against a denied path throws <code>storage/unauthorized</code></span></span></span></summary>
498
- <div class="compat-evidence"><div class="compat-probe"><code>unit:storage/enforce.test.ts</code></div></div>
497
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/enforce.test.ts#L4" target="_blank" rel="noopener noreferrer"><code>unit:storage/enforce.test.ts</code></a></div></div>
499
498
  </details>
500
499
  <details class="compat-row" data-status="ok">
501
500
  <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">Rules enforcement</code><span class="compat-sub"><span class="compat-behavior"><code>deleteObject</code> against a denied path throws <code>storage/unauthorized</code></span></span></span></summary>
502
- <div class="compat-evidence"><div class="compat-probe"><code>unit:storage/enforce.test.ts</code></div></div>
501
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/enforce.test.ts#L4" target="_blank" rel="noopener noreferrer"><code>unit:storage/enforce.test.ts</code></a></div></div>
503
502
  </details>
504
503
  <details class="compat-row" data-status="ok">
505
504
  <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">Rules enforcement</code><span class="compat-sub"><span class="compat-behavior">Ordinary SDK object paths are evaluated under Firebase Storage's canonical <code>/b/{bucket}/o/{object}</code> rules namespace while metadata preserves the ordinary object path</span></span></span></summary>
506
- <div class="compat-evidence"><div class="compat-probe"><code>unit:storage/enforce.test.ts</code> ("maps ordinary SDK object paths into the canonical bucket rules namespace"). Production's canonical <code>/b/{bucket}/o/...</code> namespace is independently captured on the <code>storage-rules</code> surface; this adapter mapping seam is unit-backed rather than presented as a production observation.</div></div>
505
+ <div class="compat-evidence"><div class="compat-probe"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/enforce.test.ts#L4" target="_blank" rel="noopener noreferrer"><code>unit:storage/enforce.test.ts</code></a> ("maps ordinary SDK object paths into the canonical bucket rules namespace"). Production's canonical <code>/b/{bucket}/o/...</code> namespace is independently captured on the <code>storage-rules</code> surface; this adapter mapping seam is unit-backed rather than presented as a production observation.</div></div>
507
506
  </details>
508
507
  </div>
509
508
 
@@ -515,17 +514,25 @@ Known differences between Pyric and production Firebase. Each remains tracked as
515
514
 
516
515
  <div class="compat-list">
517
516
  <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">getStorage(app, bucketUrl?)` / `getStorageSandbox(target, options?)</code><span class="compat-sub"><span class="compat-behavior"><code>getStorage(app, bucketUrl?)</code> accepts Firebase's bucket argument; the sandbox remains bound to its configured single bucket</span></span></span></summary>
518
- <div class="compat-evidence"><div class="compat-note">Package resolution owns production selection. The sandbox accepts the canonical argument but does not model production multi-bucket routing; <code>unit:service.test.ts</code> pins the configured <code>pyric-default</code> bucket.</div></div></details>
517
+ <div class="compat-evidence"><div class="compat-note">Package resolution owns production selection. The sandbox accepts the canonical argument but does not model production multi-bucket routing; <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/service.test.ts#L4" target="_blank" rel="noopener noreferrer"><code>unit:service.test.ts</code></a> pins the configured <code>pyric-default</code> bucket.</div></div></details>
519
518
  <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">uploadString(ref, value, format?, metadata?)</code><span class="compat-sub"><span class="compat-behavior">Sandbox: <code>format='base64url'</code> (or any unknown format) rejected with <code>storage/invalid-format</code> naming the bad format. Prod: <code>base64url</code> is ACCEPTED (upload succeeds); a genuinely-unrecognized format throws <code>storage/unknown</code></span></span></span></summary>
520
- <div class="compat-evidence"><div class="compat-note">divergence, both halves oracle-locked by <code>packages/conformance/observations/storage/storage-uploadstring-unknown-format.json</code>: prod accepts <code>base64url</code> (<code>base64urlOk: true</code>) and throws <code>storage/unknown</code> for an unrecognized format — not <code>storage/invalid-format</code>. The v1 sandbox ships only <code>raw</code>/<code>base64</code>/<code>data_url</code> (matches <code>StringFormat</code>) and throws <code>storage/invalid-format</code> for anything else (ST-B3 replaced the old mis-parse-as-data_url behavior). Both sides pinned in <code>oracle-conformance.test.ts</code>; sandbox code path documented in <code>upload.ts</code>'s <code>decodeString</code>. Implementing base64url decoding is still one line in <code>decodeString</code>.</div></div></details>
519
+ <div class="compat-evidence"><div class="compat-note">divergence, both halves oracle-locked by <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/storage/storage-uploadstring-unknown-format.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/storage/storage-uploadstring-unknown-format.json</code></a>: prod accepts <code>base64url</code> (<code>base64urlOk: true</code>) and throws <code>storage/unknown</code> for an unrecognized format — not <code>storage/invalid-format</code>. The v1 sandbox ships only <code>raw</code>/<code>base64</code>/<code>data_url</code> (matches <code>StringFormat</code>) and throws <code>storage/invalid-format</code> for anything else (ST-B3 replaced the old mis-parse-as-data_url behavior). Both sides pinned in <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/oracle-conformance.test.ts#L129" target="_blank" rel="noopener noreferrer"><code>oracle-conformance.test.ts</code></a>; sandbox code path documented in <code>upload.ts</code>'s <code>decodeString</code>. Implementing base64url decoding is still one line in <code>decodeString</code>. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/error-codes.test.ts#L25" target="_blank" rel="noopener noreferrer"><code>error-codes.test.ts</code></a>)</div></div></details>
520
+ <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">uploadBytesResumable(ref, data, metadata?)</code><span class="compat-sub"><span class="compat-behavior">Exported by <code>firebase/storage</code>; returns an <code>UploadTask</code> with <code>pause()</code> / <code>resume()</code> / <code>cancel()</code></span></span></span></summary>
521
+ <div class="compat-evidence"><div class="compat-note"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/resumable-upload.test.ts#L14" target="_blank" rel="noopener noreferrer"><code>unit:resumable-upload.test.ts</code></a> confirms <code>uploadBytesResumable(ref, data)</code> returns an <code>UploadTask</code> supporting <code>pause()</code>, <code>resume()</code>, and <code>cancel()</code> over synthetic microtask steps.</div></div></details>
522
+ <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">uploadBytesResumable(ref, data, metadata?)</code><span class="compat-sub"><span class="compat-behavior"><code>task.on('state_changed', next, error, complete)</code> fires <code>next</code> with <code>{bytesTransferred, totalBytes, state}</code> snapshots</span></span></span></summary>
523
+ <div class="compat-evidence"><div class="compat-note"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/resumable-upload.test.ts#L14" target="_blank" rel="noopener noreferrer"><code>unit:resumable-upload.test.ts</code></a> confirms synthetic progress snapshots are emitted during task execution without requiring network transfer.</div></div></details>
524
+ <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">uploadBytesResumable(ref, data, metadata?)</code><span class="compat-sub"><span class="compat-behavior"><code>task.pause()</code> flips <code>state</code> to <code>'paused'</code>; <code>task.resume()</code> continues</span></span></span></summary>
525
+ <div class="compat-evidence"><div class="compat-note"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/resumable-upload.test.ts#L14" target="_blank" rel="noopener noreferrer"><code>unit:resumable-upload.test.ts</code></a> confirms calling <code>task.pause()</code> and <code>task.resume()</code> transitions through <code>'paused'</code> and <code>'running'</code> states deterministically.</div></div></details>
526
+ <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">uploadBytesResumable(ref, data, metadata?)</code><span class="compat-sub"><span class="compat-behavior"><code>task.cancel()</code> rejects the upload with <code>storage/canceled</code></span></span></span></summary>
527
+ <div class="compat-evidence"><div class="compat-note"><a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/resumable-upload.test.ts#L14" target="_blank" rel="noopener noreferrer"><code>unit:resumable-upload.test.ts</code></a> confirms cancellation rejects with <code>storage/canceled</code> and leaves no falsely completed object in storage.</div></div></details>
521
528
  <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">getDownloadURL(ref)</code><span class="compat-sub"><span class="compat-behavior">Exported by <code>firebase/storage</code>; returns a token-signed HTTPS URL that fetches the blob</span></span></span></summary>
522
- <div class="compat-evidence"><div class="compat-note">Implemented with a two-sided pin. Production observation <code>storage-upload-bytes-roundtrip</code> records <code>urlIsHttps: true</code> and a byte-identical fetch. The sandbox oracle replay now calls the same public <code>getDownloadURL</code> + <code>fetch</code> path and proves byte-identical content, while explicitly asserting its URL starts with <code>blob:</code>. The client↔host integration proves SharedWorker mode creates that URL in the calling page after the rules-checked Blob crosses the port. The remaining divergence is URL identity and lifetime: the sandbox URL is a page-local snapshot, not token-signed HTTPS and not shareable outside that page.</div></div></details>
529
+ <div class="compat-evidence"><div class="compat-note">Implemented with a two-sided pin. Production observation <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/storage/storage-upload-bytes-roundtrip.json" target="_blank" rel="noopener noreferrer"><code>storage-upload-bytes-roundtrip</code></a> records <code>urlIsHttps: true</code> and a byte-identical fetch. The sandbox oracle replay now calls the same public <code>getDownloadURL</code> + <code>fetch</code> path and proves byte-identical content, while explicitly asserting its URL starts with <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/oracle-conformance.test.ts#L170" target="_blank" rel="noopener noreferrer"><code>blob:</code></a>. The client↔host integration proves SharedWorker mode creates that URL in the calling page after the rules-checked Blob crosses the port. The remaining divergence is URL identity and lifetime: the sandbox URL is a page-local snapshot, not token-signed HTTPS and not shareable outside that page. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/worker/integration.test.ts#L237" target="_blank" rel="noopener noreferrer"><code>integration.test.ts</code></a>)</div></div></details>
523
530
  <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">deleteObject(ref)</code><span class="compat-sub"><span class="compat-behavior">Sandbox: no-op on missing path (does NOT throw)</span></span></span></summary>
524
- <div class="compat-evidence"><div class="compat-note">divergence: sandbox is no-op via <code>persistence.ts</code>'s <code>delete</code>. Prod's <code>deleteObject</code> on a missing path throws <code>storage/object-not-found</code>. Oracle-locked: <code>packages/conformance/observations/storage/storage-delete-missing-throws.json</code> (<code>code: 'storage/object-not-found'</code>, <code>name: 'FirebaseError'</code> against blockingfun, fb-js-sdk 12.13.0). Both sides pinned in <code>oracle-conformance.test.ts</code>; documented in <code>download.ts</code>.</div></div></details>
531
+ <div class="compat-evidence"><div class="compat-note">divergence: sandbox is no-op via <code>persistence.ts</code>'s <code>delete</code>. Prod's <code>deleteObject</code> on a missing path throws <code>storage/object-not-found</code>. Oracle-locked: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/storage/storage-delete-missing-throws.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/storage/storage-delete-missing-throws.json</code></a> (<code>code: 'storage/object-not-found'</code>, <code>name: 'FirebaseError'</code> against blockingfun, fb-js-sdk 12.13.0). Both sides pinned in <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/oracle-conformance.test.ts#L91" target="_blank" rel="noopener noreferrer"><code>oracle-conformance.test.ts</code></a>; documented in <code>download.ts</code>.</div></div></details>
525
532
  <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">getMetadata(ref)` / `updateMetadata(ref, metadata)</code><span class="compat-sub"><span class="compat-behavior"><code>updateMetadata</code> with <code>undefined</code> field values preserves the prior value (does NOT clear it)</span></span></span></summary>
526
533
  <div class="compat-evidence"><div class="compat-note">divergence: prod accepts <code>null</code> to explicitly clear a field. Sandbox doesn't model <code>null</code>-clear (per <code>metadata.ts</code> doc comment). Documented; not probe-locked.</div></div></details>
527
534
  <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">getMetadata(ref)` / `updateMetadata(ref, metadata)</code><span class="compat-sub"><span class="compat-behavior"><code>FullMetadata.md5Hash</code> populated on uploads</span></span></span></summary>
528
- <div class="compat-evidence"><div class="compat-note">divergence: sandbox does NOT compute <code>md5Hash</code>. Oracle-locked: <code>packages/conformance/observations/storage/storage-upload-then-getmetadata.json</code> confirms prod sets <code>md5Hash</code> (<code>hasMd5Hash: true</code> after a vanilla <code>uploadBytes</code>). Both sides pinned in <code>oracle-conformance.test.ts</code>. Aligning the sandbox is a one-spot fix in <code>upload.ts</code>'s <code>buildStoredMetadata</code>.</div></div></details>
535
+ <div class="compat-evidence"><div class="compat-note">divergence: sandbox does NOT compute <code>md5Hash</code>. Oracle-locked: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/storage/storage-upload-then-getmetadata.json" target="_blank" rel="noopener noreferrer"><code>packages/conformance/observations/storage/storage-upload-then-getmetadata.json</code></a> confirms prod sets <code>md5Hash</code> (<code>hasMd5Hash: true</code> after a vanilla <code>uploadBytes</code>). Both sides pinned in <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/storage/oracle-conformance.test.ts#L207" target="_blank" rel="noopener noreferrer"><code>oracle-conformance.test.ts</code></a>. Aligning the sandbox is a one-spot fix in <code>upload.ts</code>'s <code>buildStoredMetadata</code>.</div></div></details>
529
536
  </div>
530
537
 
531
538
  ### Not implemented yet
@@ -533,14 +540,6 @@ Known differences between Pyric and production Firebase. Each remains tracked as
533
540
  Tracked behavior that is not implemented in the current contract.
534
541
 
535
542
  <div class="compat-list">
536
- <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">uploadBytesResumable(ref, data, metadata?)</code><span class="compat-sub"><span class="compat-behavior">Exported by <code>firebase/storage</code>; returns an <code>UploadTask</code> with <code>pause()</code> / <code>resume()</code> / <code>cancel()</code></span></span></span></summary>
537
- <div class="compat-evidence"><div class="compat-note">No <code>pyric/storage</code> implementation or behavioral probe exists; availability is classified by the owning surface contract.</div></div></details>
538
- <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">uploadBytesResumable(ref, data, metadata?)</code><span class="compat-sub"><span class="compat-behavior"><code>task.on('state_changed', next, error, complete)</code> fires <code>next</code> with <code>{bytesTransferred, totalBytes, state}</code> snapshots</span></span></span></summary>
539
- <div class="compat-evidence"><div class="compat-note">not implemented</div></div></details>
540
- <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">uploadBytesResumable(ref, data, metadata?)</code><span class="compat-sub"><span class="compat-behavior"><code>task.pause()</code> flips <code>state</code> to <code>'paused'</code>; <code>task.resume()</code> continues</span></span></span></summary>
541
- <div class="compat-evidence"><div class="compat-note">not implemented</div></div></details>
542
- <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">uploadBytesResumable(ref, data, metadata?)</code><span class="compat-sub"><span class="compat-behavior"><code>task.cancel()</code> rejects the upload with <code>storage/canceled</code></span></span></span></summary>
543
- <div class="compat-evidence"><div class="compat-note">not implemented</div></div></details>
544
543
  <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">getStream(ref, maxDownloadSize?)</code><span class="compat-sub"><span class="compat-behavior">Exported by <code>firebase/storage</code> (Node entry only); returns a Node <code>Readable</code></span></span></span></summary>
545
544
  <div class="compat-evidence"><div class="compat-note">No <code>pyric/storage</code> implementation or behavioral probe exists; availability is classified by the owning surface contract.</div></div></details>
546
545
  <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">list(ref, options?)</code><span class="compat-sub"><span class="compat-behavior">Exported by <code>firebase/storage</code>; accepts <code>{ maxResults, pageToken }</code>, returns a <code>ListResult</code> with <code>nextPageToken</code> set when more pages remain</span></span></span></summary>
@@ -569,9 +568,4 @@ Tracked behavior that is not implemented in the current contract.
569
568
  <div class="compat-evidence"><div class="compat-note"><code>list</code></div>
570
569
  <div class="compat-note">registry:storage#78</div></div>
571
570
  </details>
572
- <details class="compat-row" data-status="unsupported">
573
- <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">storage.resumable-upload</code><span class="compat-sub"><span class="compat-behavior">The UploadTask and observer surface is deferred — v1 shipped the one-shot uploadBytes driver first; the resumable/observable variant is unbuilt, not unbuildable.</span></span></span></summary>
574
- <div class="compat-evidence"><div class="compat-note"><code>uploadBytesResumable</code></div>
575
- <div class="compat-note">registry:storage#47</div></div>
576
- </details>
577
571
  </div>