@pyric/cli 0.1.0-alpha.30 → 0.1.0-alpha.30.1

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 (87) hide show
  1. package/dist/assurance/.generated/conformance-verdicts.d.ts +4 -0
  2. package/dist/assurance/.generated/conformance-verdicts.d.ts.map +1 -1
  3. package/dist/assurance/.generated/conformance-verdicts.js +4 -0
  4. package/dist/assurance/.generated/conformance-verdicts.js.map +1 -1
  5. package/dist/cli/serve.d.ts +2 -0
  6. package/dist/cli/serve.d.ts.map +1 -1
  7. package/dist/cli/serve.js +13 -2
  8. package/dist/cli/serve.js.map +1 -1
  9. package/dist/conformance/.generated/can-i-use-browser.js +2 -2
  10. package/dist/conformance/.generated/can-i-use-browser.js.map +1 -1
  11. package/dist/conformance/.generated/can-i-use.d.ts.map +1 -1
  12. package/dist/conformance/.generated/can-i-use.js +1 -1
  13. package/dist/conformance/.generated/can-i-use.js.map +1 -1
  14. package/dist/conformance/.generated/conformance-docs.d.ts.map +1 -1
  15. package/dist/conformance/.generated/conformance-docs.js +1 -1
  16. package/dist/conformance/.generated/conformance-docs.js.map +1 -1
  17. package/dist/register/app-bridge-admin-app-check.d.ts +3 -0
  18. package/dist/register/app-bridge-admin-app-check.d.ts.map +1 -0
  19. package/dist/register/app-bridge-admin-app-check.js +43 -0
  20. package/dist/register/app-bridge-admin-app-check.js.map +1 -0
  21. package/dist/register/app-bridge-app-check.d.ts +4 -0
  22. package/dist/register/app-bridge-app-check.d.ts.map +1 -1
  23. package/dist/register/app-bridge-app-check.js +4 -0
  24. package/dist/register/app-bridge-app-check.js.map +1 -1
  25. package/dist/register/app-bridge-shadow.d.ts +4 -2
  26. package/dist/register/app-bridge-shadow.d.ts.map +1 -1
  27. package/dist/register/app-bridge-shadow.js +29 -5
  28. package/dist/register/app-bridge-shadow.js.map +1 -1
  29. package/dist/register/index.d.ts.map +1 -1
  30. package/dist/register/index.js +21 -0
  31. package/dist/register/index.js.map +1 -1
  32. package/dist/serve/bundler.d.ts +40 -0
  33. package/dist/serve/bundler.d.ts.map +1 -1
  34. package/dist/serve/bundler.js +133 -2
  35. package/dist/serve/bundler.js.map +1 -1
  36. package/dist/serve/entries/app-ai-passthrough.d.ts +4 -4
  37. package/dist/serve/entries/app-ai-passthrough.d.ts.map +1 -1
  38. package/dist/serve/entries/app-ai-passthrough.js +8 -52
  39. package/dist/serve/entries/app-ai-passthrough.js.map +1 -1
  40. package/dist/serve/firebase-module-swap.d.ts +22 -0
  41. package/dist/serve/firebase-module-swap.d.ts.map +1 -1
  42. package/dist/serve/firebase-module-swap.js +51 -3
  43. package/dist/serve/firebase-module-swap.js.map +1 -1
  44. package/dist/serve/hosted/method-protocol.d.ts +4 -4
  45. package/dist/serve/html-injection.d.ts +2 -0
  46. package/dist/serve/html-injection.d.ts.map +1 -1
  47. package/dist/serve/html-injection.js +4 -2
  48. package/dist/serve/html-injection.js.map +1 -1
  49. package/dist/serve/namespace.d.ts.map +1 -1
  50. package/dist/serve/namespace.js +5 -1
  51. package/dist/serve/namespace.js.map +1 -1
  52. package/dist/serve/site-ui/docs/ai-compat/index.html +9 -9
  53. package/dist/serve/site-ui/docs/ai-compat.md +9 -9
  54. package/dist/serve/site-ui/docs/app-check-compat/index.html +42 -18
  55. package/dist/serve/site-ui/docs/app-check-compat.md +42 -18
  56. package/dist/serve/site-ui/docs/auth-compat/index.html +4 -4
  57. package/dist/serve/site-ui/docs/auth-compat.md +4 -4
  58. package/dist/serve/site-ui/docs/build/ai-logic/index.html +2 -0
  59. package/dist/serve/site-ui/docs/build/ai-logic.md +4 -0
  60. package/dist/serve/site-ui/docs/database-compat/index.html +4 -4
  61. package/dist/serve/site-ui/docs/database-compat.md +4 -4
  62. package/dist/serve/site-ui/docs/firestore-compat/index.html +4 -4
  63. package/dist/serve/site-ui/docs/firestore-compat.md +4 -4
  64. package/dist/serve/site-ui/docs/messaging-compat/index.html +4 -4
  65. package/dist/serve/site-ui/docs/messaging-compat.md +4 -4
  66. package/dist/serve/site-ui/docs/pyric-admin-app-reference-api/index.html +4 -0
  67. package/dist/serve/site-ui/docs/pyric-admin-app-reference-api.md +5 -0
  68. package/dist/serve/site-ui/docs/pyric-cli-register-app-bridge-app-check-reference-api/index.html +2 -0
  69. package/dist/serve/site-ui/docs/pyric-cli-register-app-bridge-app-check-reference-api.md +3 -0
  70. package/dist/serve/site-ui/docs/storage-compat/index.html +5 -5
  71. package/dist/serve/site-ui/docs/storage-compat.md +5 -5
  72. package/dist/serve/site-ui/examples/chess/index.html +1 -1
  73. package/package.json +4 -4
  74. package/src/assurance/.generated/conformance-verdicts.ts +4 -0
  75. package/src/cli/serve.ts +14 -2
  76. package/src/conformance/.generated/can-i-use-browser.ts +2 -2
  77. package/src/conformance/.generated/can-i-use.ts +1 -1
  78. package/src/conformance/.generated/conformance-docs.ts +1 -1
  79. package/src/register/app-bridge-admin-app-check.ts +46 -0
  80. package/src/register/app-bridge-app-check.ts +4 -0
  81. package/src/register/app-bridge-shadow.ts +35 -5
  82. package/src/register/index.ts +23 -0
  83. package/src/serve/bundler.ts +163 -2
  84. package/src/serve/entries/app-ai-passthrough.ts +8 -58
  85. package/src/serve/firebase-module-swap.ts +52 -3
  86. package/src/serve/html-injection.ts +4 -2
  87. package/src/serve/namespace.ts +5 -1
@@ -858,7 +858,7 @@ html{scrollbar-gutter:stable}
858
858
  <div class="compat-list">
859
859
  <details class="compat-row" data-status="diverged">
860
860
  <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">getDatabase(app) in a registered server process</code><span class="compat-sub"><span class="compat-behavior">Under a remote register activation on the Node host, <code>firebase/database</code> in a server process reads and writes the host's data under its rules as the process's signed-in user: a write the rules deny for that user fails with <code>PERMISSION_DENIED</code>, <code>.info/connected</code> follows the process's connection, and an <code>onDisconnect</code> write runs when that connection ends. From its first RTDB call on an instance the process stays up until <code>goOffline()</code> on that instance or <code>deleteApp()</code>, as <code>@firebase/database</code> keeps a Node process up. When the host is not the Node host, every call fails with <code>failed-precondition</code> and a message naming the Node host (<code>pyric sandbox --hosted</code> or <code>pyric({ hosted: true })</code>); when no host is found, with the discovery error <code>pyric-admin</code> gets. No call falls back to an in-process sandbox.</span></span></span></summary>
861
- <div class="compat-evidence"><div class="compat-probe">App scenario <code>test/apps/client-sdk-under-register</code> runs the client SDKs under <code>node --import @pyric/cli/register</code> against a hosted Vite dev server, reads back with <code>firebase-admin</code> and a page, and asserts a write and a read, a denied write to another user's path, <code>.info/connected</code>, an <code>onDisconnect</code> that ran after the process exited, and that a process stays up after a write until <code>goOffline()</code>. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-realm.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>node-realm.test.ts</code></a> pins when the process is held. CDD assertion <code>rtdb-modular#RS1</code> pins the sandbox side: rules evaluate per signed-in session, and a registry bound to the refusal opens no database handle. On main (942cb175) it failed: the calls ran against a private in-process sandbox, so an upload returned a <a href="https://github.com/davideast/pyric/blob/main/test/apps/client-sdk-under-register/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>data:</code></a> URL and firebase-admin read nothing they wrote. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-origin.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>packages/cli/test/register/node-origin.test.ts</code></a> runs the served surfaces under a Node origin whose host is not the Node host, or cannot be found, and asserts every call fails, <code>useWorker</code> stays true, the served app entry's placeholder sandbox stays empty, and a <code>pyric/*</code> mirror resolved from the app refuses. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/mapping.test.ts#L141" target="_blank" rel="noopener noreferrer"><code>mapping.test.ts</code></a> pins the mapping per activation value.. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/register-child.test.ts#L138" target="_blank" rel="noopener noreferrer"><code>register-child.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/database/cdd/lifecycle-and-identity.test.ts#L28" target="_blank" rel="noopener noreferrer"><code>lifecycle-and-identity.test.ts</code></a>)</div>
861
+ <div class="compat-evidence"><div class="compat-probe">App scenario <code>test/apps/client-sdk-under-register</code> runs the client SDKs under <code>node --import @pyric/cli/register</code> against a hosted Vite dev server, reads back with <code>firebase-admin</code> and a page, and asserts a write and a read, a denied write to another user's path, <code>.info/connected</code>, an <code>onDisconnect</code> that ran after the process exited, and that a process stays up after a write until <code>goOffline()</code>. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-realm.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>node-realm.test.ts</code></a> pins when the process is held. CDD assertion <code>rtdb-modular#RS1</code> pins the sandbox side: rules evaluate per signed-in session, and a registry bound to the refusal opens no database handle. On main (942cb175) it failed: the calls ran against a private in-process sandbox, so an upload returned a <a href="https://github.com/davideast/pyric/blob/main/test/apps/client-sdk-under-register/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>data:</code></a> URL and firebase-admin read nothing they wrote. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-origin.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>packages/cli/test/register/node-origin.test.ts</code></a> runs the served surfaces under a Node origin whose host is not the Node host, or cannot be found, and asserts every call fails, <code>useWorker</code> stays true, the served app entry's placeholder sandbox stays empty, and a <code>pyric/*</code> mirror resolved from the app refuses. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/mapping.test.ts#L80" target="_blank" rel="noopener noreferrer"><code>mapping.test.ts</code></a> pins the mapping per activation value.. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/register-child.test.ts#L138" target="_blank" rel="noopener noreferrer"><code>register-child.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/database/cdd/lifecycle-and-identity.test.ts#L28" target="_blank" rel="noopener noreferrer"><code>lifecycle-and-identity.test.ts</code></a>)</div>
862
862
  <div class="compat-note">the app must use the same Firebase options as the host's other clients (one configuration per runtime)</div></div>
863
863
  </details>
864
864
  </div>
@@ -867,7 +867,7 @@ html{scrollbar-gutter:stable}
867
867
  <div class="compat-list">
868
868
  <details class="compat-row" data-status="diverged">
869
869
  <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">getDatabase(app) in a server-rendered module under vite dev</code><span class="compat-sub"><span class="compat-behavior">On a hosted dev server, <code>firebase/database</code> in a server-rendered module reads and writes the host's data under its rules as the SSR session's user: a write the rules deny for that user fails with <code>PERMISSION_DENIED</code>. <code>ssrLoadModule</code>, the environment runner and every <code>ssr.noExternal</code> setting share one app and one session. Closing the dev server lets the process go, so a process that created the server through Vite's API ends on its own, though RTDB would otherwise hold it until <code>goOffline()</code>. On a SharedWorker dev server every call fails with <code>failed-precondition</code> and a message naming <code>pyric({ hosted: true })</code>. No call reaches production or an in-process sandbox.</span></span></span></summary>
870
- <div class="compat-evidence"><div class="compat-probe">App scenario <code>test/apps/vite-ssr-client-sdk</code> writes and reads the SSR user's own path, is denied another user's, reads the writes back with <code>firebase-admin</code> in SSR and in a process on the host, repeats it with <code>ssr.noExternal</code> <code>['firebase']</code> and <code>true</code>, refuses on a SharedWorker dev server, and creates a dev server through Vite's API that ends on its own after SSR used RTDB. On main (ba2c0bf0a) the scenario failed: SSR sign-in rejected with <code>auth/network-request-failed</code> from the network guard, and the RTDB, Firestore and Storage writes did not settle, because the imports loaded the real SDK. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-module-swap.test.ts#L9" target="_blank" rel="noopener noreferrer"><code>vite-module-swap.test.ts</code></a> pins the routing per specifier, AI mode and environment (Vite 5's <code>ssr</code> option, a runnable environment, a non-runnable one), <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-ssr-activation.test.ts#L1" target="_blank" rel="noopener noreferrer"><code>vite-ssr-activation.test.ts</code></a> the host, the restart hand-over and the SharedWorker refusal, and <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-plugin-firestore.test.ts#L14" target="_blank" rel="noopener noreferrer"><code>vite-plugin-firestore.test.ts</code></a> runs Vite with <code>firebase</code> installed, so it takes the externalized path. CDD assertion <code>rtdb-modular#RS2</code> pins the sandbox side. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-realm.test.ts#L1" target="_blank" rel="noopener noreferrer"><code>node-realm.test.ts</code></a> pins the release when the in-process host ends. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/test/apps/vite-ssr-client-sdk/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/database/cdd/lifecycle-and-identity.test.ts#L28" target="_blank" rel="noopener noreferrer"><code>lifecycle-and-identity.test.ts</code></a>)</div>
870
+ <div class="compat-evidence"><div class="compat-probe">App scenario <code>test/apps/vite-ssr-client-sdk</code> writes and reads the SSR user's own path, is denied another user's, reads the writes back with <code>firebase-admin</code> in SSR and in a process on the host, repeats it with <code>ssr.noExternal</code> <code>['firebase']</code> and <code>true</code>, refuses on a SharedWorker dev server, and creates a dev server through Vite's API that ends on its own after SSR used RTDB. On main (ba2c0bf0a) the scenario failed: SSR sign-in rejected with <code>auth/network-request-failed</code> from the network guard, and the RTDB, Firestore and Storage writes did not settle, because the imports loaded the real SDK. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-module-swap.test.ts#L4" target="_blank" rel="noopener noreferrer"><code>vite-module-swap.test.ts</code></a> pins the routing per specifier, AI mode and environment (Vite 5's <code>ssr</code> option, a runnable environment, a non-runnable one), <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-ssr-activation.test.ts#L1" target="_blank" rel="noopener noreferrer"><code>vite-ssr-activation.test.ts</code></a> the host, the restart hand-over and the SharedWorker refusal, and <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-plugin-firestore.test.ts#L14" target="_blank" rel="noopener noreferrer"><code>vite-plugin-firestore.test.ts</code></a> runs Vite with <code>firebase</code> installed, so it takes the externalized path. CDD assertion <code>rtdb-modular#RS2</code> pins the sandbox side. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-realm.test.ts#L1" target="_blank" rel="noopener noreferrer"><code>node-realm.test.ts</code></a> pins the release when the in-process host ends. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/test/apps/vite-ssr-client-sdk/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/database/cdd/lifecycle-and-identity.test.ts#L28" target="_blank" rel="noopener noreferrer"><code>lifecycle-and-identity.test.ts</code></a>)</div>
871
871
  <div class="compat-note">one SSR session per dev server process, as one Node server process has one auth state; per-request identity (<code>initializeServerApp</code>) is unsupported</div></div>
872
872
  </details>
873
873
  <details class="compat-row" data-status="diverged">
@@ -907,9 +907,9 @@ html{scrollbar-gutter:stable}
907
907
  <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"><span class="compat-sub"><span class="compat-behavior">**Divergence:** <code>refFromURL(db, url)</code> matches Firebase's path parsing, query ignoring, FTP-to-database normalization, and fragment rejection, but the single-database sandbox does not validate the URL host/namespace against the handle; Firebase rejects a mismatched host while the sandbox uses its path.</span></span></span></summary>
908
908
  <div class="compat-evidence"><div class="compat-note">Oracle <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/rtdb-modular/rtdb-modular-reference-shape-url.json" target="_blank" rel="noopener noreferrer"><code>rtdb-modular-reference-shape-url</code></a> captures accepted FTP/query URLs and rejected fragments, malformed URLs, and host mismatches; <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/database/modular/oracle-conformance-reference-writes.test.ts#L33" target="_blank" rel="noopener noreferrer"><code>unit:modular/oracle-conformance-reference-writes.test.ts</code></a> pins the conforming validations plus both sides of the host divergence.</div></div></details>
909
909
  <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">getDatabase(app) in a registered server process</code><span class="compat-sub"><span class="compat-behavior">Under a remote register activation on the Node host, <code>firebase/database</code> in a server process reads and writes the host's data under its rules as the process's signed-in user: a write the rules deny for that user fails with <code>PERMISSION_DENIED</code>, <code>.info/connected</code> follows the process's connection, and an <code>onDisconnect</code> write runs when that connection ends. From its first RTDB call on an instance the process stays up until <code>goOffline()</code> on that instance or <code>deleteApp()</code>, as <code>@firebase/database</code> keeps a Node process up. When the host is not the Node host, every call fails with <code>failed-precondition</code> and a message naming the Node host (<code>pyric sandbox --hosted</code> or <code>pyric({ hosted: true })</code>); when no host is found, with the discovery error <code>pyric-admin</code> gets. No call falls back to an in-process sandbox.</span></span></span></summary>
910
- <div class="compat-evidence"><div class="compat-note">App scenario <code>test/apps/client-sdk-under-register</code> runs the client SDKs under <code>node --import @pyric/cli/register</code> against a hosted Vite dev server, reads back with <code>firebase-admin</code> and a page, and asserts a write and a read, a denied write to another user's path, <code>.info/connected</code>, an <code>onDisconnect</code> that ran after the process exited, and that a process stays up after a write until <code>goOffline()</code>. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-realm.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>node-realm.test.ts</code></a> pins when the process is held. CDD assertion <code>rtdb-modular#RS1</code> pins the sandbox side: rules evaluate per signed-in session, and a registry bound to the refusal opens no database handle. On main (942cb175) it failed: the calls ran against a private in-process sandbox, so an upload returned a <a href="https://github.com/davideast/pyric/blob/main/test/apps/client-sdk-under-register/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>data:</code></a> URL and firebase-admin read nothing they wrote. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-origin.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>packages/cli/test/register/node-origin.test.ts</code></a> runs the served surfaces under a Node origin whose host is not the Node host, or cannot be found, and asserts every call fails, <code>useWorker</code> stays true, the served app entry's placeholder sandbox stays empty, and a <code>pyric/*</code> mirror resolved from the app refuses. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/mapping.test.ts#L141" target="_blank" rel="noopener noreferrer"><code>mapping.test.ts</code></a> pins the mapping per activation value.. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/register-child.test.ts#L138" target="_blank" rel="noopener noreferrer"><code>register-child.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/database/cdd/lifecycle-and-identity.test.ts#L28" target="_blank" rel="noopener noreferrer"><code>lifecycle-and-identity.test.ts</code></a>)</div></div></details>
910
+ <div class="compat-evidence"><div class="compat-note">App scenario <code>test/apps/client-sdk-under-register</code> runs the client SDKs under <code>node --import @pyric/cli/register</code> against a hosted Vite dev server, reads back with <code>firebase-admin</code> and a page, and asserts a write and a read, a denied write to another user's path, <code>.info/connected</code>, an <code>onDisconnect</code> that ran after the process exited, and that a process stays up after a write until <code>goOffline()</code>. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-realm.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>node-realm.test.ts</code></a> pins when the process is held. CDD assertion <code>rtdb-modular#RS1</code> pins the sandbox side: rules evaluate per signed-in session, and a registry bound to the refusal opens no database handle. On main (942cb175) it failed: the calls ran against a private in-process sandbox, so an upload returned a <a href="https://github.com/davideast/pyric/blob/main/test/apps/client-sdk-under-register/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>data:</code></a> URL and firebase-admin read nothing they wrote. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-origin.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>packages/cli/test/register/node-origin.test.ts</code></a> runs the served surfaces under a Node origin whose host is not the Node host, or cannot be found, and asserts every call fails, <code>useWorker</code> stays true, the served app entry's placeholder sandbox stays empty, and a <code>pyric/*</code> mirror resolved from the app refuses. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/mapping.test.ts#L80" target="_blank" rel="noopener noreferrer"><code>mapping.test.ts</code></a> pins the mapping per activation value.. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/register-child.test.ts#L138" target="_blank" rel="noopener noreferrer"><code>register-child.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/database/cdd/lifecycle-and-identity.test.ts#L28" target="_blank" rel="noopener noreferrer"><code>lifecycle-and-identity.test.ts</code></a>)</div></div></details>
911
911
  <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">getDatabase(app) in a server-rendered module under vite dev</code><span class="compat-sub"><span class="compat-behavior">On a hosted dev server, <code>firebase/database</code> in a server-rendered module reads and writes the host's data under its rules as the SSR session's user: a write the rules deny for that user fails with <code>PERMISSION_DENIED</code>. <code>ssrLoadModule</code>, the environment runner and every <code>ssr.noExternal</code> setting share one app and one session. Closing the dev server lets the process go, so a process that created the server through Vite's API ends on its own, though RTDB would otherwise hold it until <code>goOffline()</code>. On a SharedWorker dev server every call fails with <code>failed-precondition</code> and a message naming <code>pyric({ hosted: true })</code>. No call reaches production or an in-process sandbox.</span></span></span></summary>
912
- <div class="compat-evidence"><div class="compat-note">App scenario <code>test/apps/vite-ssr-client-sdk</code> writes and reads the SSR user's own path, is denied another user's, reads the writes back with <code>firebase-admin</code> in SSR and in a process on the host, repeats it with <code>ssr.noExternal</code> <code>['firebase']</code> and <code>true</code>, refuses on a SharedWorker dev server, and creates a dev server through Vite's API that ends on its own after SSR used RTDB. On main (ba2c0bf0a) the scenario failed: SSR sign-in rejected with <code>auth/network-request-failed</code> from the network guard, and the RTDB, Firestore and Storage writes did not settle, because the imports loaded the real SDK. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-module-swap.test.ts#L9" target="_blank" rel="noopener noreferrer"><code>vite-module-swap.test.ts</code></a> pins the routing per specifier, AI mode and environment (Vite 5's <code>ssr</code> option, a runnable environment, a non-runnable one), <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-ssr-activation.test.ts#L1" target="_blank" rel="noopener noreferrer"><code>vite-ssr-activation.test.ts</code></a> the host, the restart hand-over and the SharedWorker refusal, and <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-plugin-firestore.test.ts#L14" target="_blank" rel="noopener noreferrer"><code>vite-plugin-firestore.test.ts</code></a> runs Vite with <code>firebase</code> installed, so it takes the externalized path. CDD assertion <code>rtdb-modular#RS2</code> pins the sandbox side. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-realm.test.ts#L1" target="_blank" rel="noopener noreferrer"><code>node-realm.test.ts</code></a> pins the release when the in-process host ends. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/test/apps/vite-ssr-client-sdk/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/database/cdd/lifecycle-and-identity.test.ts#L28" target="_blank" rel="noopener noreferrer"><code>lifecycle-and-identity.test.ts</code></a>)</div></div></details>
912
+ <div class="compat-evidence"><div class="compat-note">App scenario <code>test/apps/vite-ssr-client-sdk</code> writes and reads the SSR user's own path, is denied another user's, reads the writes back with <code>firebase-admin</code> in SSR and in a process on the host, repeats it with <code>ssr.noExternal</code> <code>['firebase']</code> and <code>true</code>, refuses on a SharedWorker dev server, and creates a dev server through Vite's API that ends on its own after SSR used RTDB. On main (ba2c0bf0a) the scenario failed: SSR sign-in rejected with <code>auth/network-request-failed</code> from the network guard, and the RTDB, Firestore and Storage writes did not settle, because the imports loaded the real SDK. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-module-swap.test.ts#L4" target="_blank" rel="noopener noreferrer"><code>vite-module-swap.test.ts</code></a> pins the routing per specifier, AI mode and environment (Vite 5's <code>ssr</code> option, a runnable environment, a non-runnable one), <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-ssr-activation.test.ts#L1" target="_blank" rel="noopener noreferrer"><code>vite-ssr-activation.test.ts</code></a> the host, the restart hand-over and the SharedWorker refusal, and <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-plugin-firestore.test.ts#L14" target="_blank" rel="noopener noreferrer"><code>vite-plugin-firestore.test.ts</code></a> runs Vite with <code>firebase</code> installed, so it takes the externalized path. CDD assertion <code>rtdb-modular#RS2</code> pins the sandbox side. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-realm.test.ts#L1" target="_blank" rel="noopener noreferrer"><code>node-realm.test.ts</code></a> pins the release when the in-process host ends. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/test/apps/vite-ssr-client-sdk/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/database/cdd/lifecycle-and-identity.test.ts#L28" target="_blank" rel="noopener noreferrer"><code>lifecycle-and-identity.test.ts</code></a>)</div></div></details>
913
913
  <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">firebase-admin/database in a server-rendered module under vite dev</code><span class="compat-sub"><span class="compat-behavior">On a hosted dev server, <code>firebase-admin</code> in a server-rendered module resolves to <code>pyric-admin</code> bound to the dev server's host, as a <code>firebase-admin</code> process on the host is: a bare <code>initializeApp()</code> connects to it, reads bypass the rules, and the writes of browsers and SSR sessions are visible. On a SharedWorker dev server its first call fails with <code>failed-precondition</code> and a message naming <code>pyric({ hosted: true })</code>, without dialing.</span></span></span></summary>
914
914
  <div class="compat-evidence"><div class="compat-note">App scenario <code>test/apps/vite-ssr-client-sdk</code> reads a path the rules let only the SSR user read, and a browser's write, with <code>firebase-admin</code> in SSR through both loaders and with each externalization setting, and gets the refusal on a SharedWorker dev server. On main (ba2c0bf0a) <code>firebase-admin</code> in SSR was the real SDK. <a href="https://github.com/davideast/pyric/blob/main/packages/pyric-admin/test/app/ambient-init.test.ts#L128" target="_blank" rel="noopener noreferrer"><code>ambient-init.test.ts</code></a> pins that a bare <code>initializeApp()</code> binds the in-process activation's sandbox when PYRIC_SANDBOX is unset, and that the variable wins when set. CDD assertion <code>rtdb-modular#RS3</code> pins the sandbox side. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/test/apps/vite-ssr-client-sdk/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/database/cdd/lifecycle-and-identity.test.ts#L28" target="_blank" rel="noopener noreferrer"><code>lifecycle-and-identity.test.ts</code></a>)</div></div></details>
915
915
  </div> </article> </main> <aside class="doc-toc" aria-label="On this page" data-astro-cid-mw7aashj> <h2 class="doc-toc-heading" data-astro-cid-mw7aashj>On this page</h2> <ul data-astro-cid-mw7aashj> <li data-depth="2" data-astro-cid-mw7aashj> <a href="#status-legend" data-astro-cid-mw7aashj>Status legend</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#public-api" data-astro-cid-mw7aashj>Public API</a> </li><li data-depth="3" data-astro-cid-mw7aashj> <a href="#getdatabasetarget--initializer" data-astro-cid-mw7aashj>getDatabase(target) — initializer</a> </li><li data-depth="3" data-astro-cid-mw7aashj> <a href="#multiple-database-instances" data-astro-cid-mw7aashj>Multiple database instances</a> </li><li data-depth="3" data-astro-cid-mw7aashj> <a href="#refdb-path--child--parent--root" data-astro-cid-mw7aashj>ref(db, path) / child / parent / root</a> </li><li data-depth="3" data-astro-cid-mw7aashj> <a href="#getref--single-read" data-astro-cid-mw7aashj>get(ref) — single read</a> </li><li data-depth="3" data-astro-cid-mw7aashj> <a href="#setref-value--full-write" data-astro-cid-mw7aashj>set(ref, value) — full write</a> </li><li data-depth="3" data-astro-cid-mw7aashj> <a href="#updateref-values--partial--multi-path-update" data-astro-cid-mw7aashj>update(ref, values) — partial / multi-path update</a> </li><li data-depth="3" data-astro-cid-mw7aashj> <a href="#removeref--delete" data-astro-cid-mw7aashj>remove(ref) — delete</a> </li><li data-depth="3" data-astro-cid-mw7aashj> <a href="#pushref-value--auto-id-append" data-astro-cid-mw7aashj>push(ref, value?) — auto-id append</a> </li><li data-depth="3" data-astro-cid-mw7aashj> <a href="#onvalueref-cb--value-level-listener" data-astro-cid-mw7aashj>onValue(ref, cb) — value-level listener</a> </li><li data-depth="3" data-astro-cid-mw7aashj> <a href="#onchildadded--onchildchanged--onchildremoved--onchildmoved" data-astro-cid-mw7aashj>onChildAdded / onChildChanged / onChildRemoved / onChildMoved</a> </li><li data-depth="3" data-astro-cid-mw7aashj> <a href="#offref-eventtype-callback--unsubscribe-variants" data-astro-cid-mw7aashj>off(ref, eventType?, callback?) — unsubscribe variants</a> </li><li data-depth="3" data-astro-cid-mw7aashj> <a href="#queryref-constraints--ordering--bounds--limits" data-astro-cid-mw7aashj>query(ref, ...constraints) + ordering / bounds / limits</a> </li><li data-depth="3" data-astro-cid-mw7aashj> <a href="#sentinels--servertimestamp--incrementn" data-astro-cid-mw7aashj>Sentinels — serverTimestamp() / increment(n)</a> </li><li data-depth="3" data-astro-cid-mw7aashj> <a href="#runtransactionref-transactionupdate-options--optimistic-concurrency" data-astro-cid-mw7aashj>runTransaction(ref, transactionUpdate, options?) — optimistic concurrency</a> </li><li data-depth="3" data-astro-cid-mw7aashj> <a href="#goonline--gooffline--connection-control" data-astro-cid-mw7aashj>goOnline / goOffline — connection control</a> </li><li data-depth="3" data-astro-cid-mw7aashj> <a href="#transport-logging-and-url-reference-exports" data-astro-cid-mw7aashj>Transport, logging, and URL-reference exports</a> </li><li data-depth="3" data-astro-cid-mw7aashj> <a href="#calls-in-flight-when-the-hosted-connection-drops" data-astro-cid-mw7aashj>Calls in flight when the hosted connection drops</a> </li><li data-depth="3" data-astro-cid-mw7aashj> <a href="#client-sdks-in-a-registered-server-process" data-astro-cid-mw7aashj>Client SDKs in a registered server process</a> </li><li data-depth="3" data-astro-cid-mw7aashj> <a href="#server-rendered-modules-under-vite-dev" data-astro-cid-mw7aashj>Server-rendered modules under vite dev</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#current-gaps" data-astro-cid-mw7aashj>Current gaps</a> </li><li data-depth="3" data-astro-cid-mw7aashj> <a href="#documented-divergences" data-astro-cid-mw7aashj>Documented divergences</a> </li> </ul> </aside> </div> <script type="module">const a=document.querySelector("[data-api-symbol-filter]"),d=[...document.querySelectorAll("[data-api-symbol-group]")],n=document.querySelector("[data-api-symbol-empty]");a?.addEventListener("input",()=>{const t=a.value.trim().toLowerCase();let o=0;for(const i of d){let e=0;i.querySelectorAll("[data-api-symbol]").forEach(l=>{const r=!t||(l.dataset.apiSymbol??"").includes(t);l.hidden=!r,r&&e++}),i.hidden=e===0,o+=e}n&&(n.hidden=o>0)});</script> <script type="module">const s=new Map;document.querySelectorAll('.doc-toc a[href^="#"]').forEach(t=>s.set(decodeURIComponent(t.hash.slice(1)),t));if(s.size>0){const t=new Set,n=()=>{let o=null;for(const e of s.keys())if(t.has(e)){o=e;break}s.forEach((e,c)=>e.classList.toggle("active",c===o))},r=new IntersectionObserver(o=>{for(const e of o)e.isIntersecting?t.add(e.target.id):t.delete(e.target.id);n()},{rootMargin:"-72px 0px -60% 0px"});s.forEach((o,e)=>{const c=document.getElementById(e);c&&r.observe(c)})}</script> <script type="module">document.querySelector('.studio__nav [aria-current="page"]')?.scrollIntoView({block:"nearest",inline:"center"});</script> </body></html>
@@ -901,7 +901,7 @@ A server process started with `node --import @pyric/cli/register` and `PYRIC_SAN
901
901
  <div class="compat-list">
902
902
  <details class="compat-row" data-status="diverged">
903
903
  <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">getDatabase(app) in a registered server process</code><span class="compat-sub"><span class="compat-behavior">Under a remote register activation on the Node host, <code>firebase/database</code> in a server process reads and writes the host's data under its rules as the process's signed-in user: a write the rules deny for that user fails with <code>PERMISSION_DENIED</code>, <code>.info/connected</code> follows the process's connection, and an <code>onDisconnect</code> write runs when that connection ends. From its first RTDB call on an instance the process stays up until <code>goOffline()</code> on that instance or <code>deleteApp()</code>, as <code>@firebase/database</code> keeps a Node process up. When the host is not the Node host, every call fails with <code>failed-precondition</code> and a message naming the Node host (<code>pyric sandbox --hosted</code> or <code>pyric({ hosted: true })</code>); when no host is found, with the discovery error <code>pyric-admin</code> gets. No call falls back to an in-process sandbox.</span></span></span></summary>
904
- <div class="compat-evidence"><div class="compat-probe">App scenario <code>test/apps/client-sdk-under-register</code> runs the client SDKs under <code>node --import @pyric/cli/register</code> against a hosted Vite dev server, reads back with <code>firebase-admin</code> and a page, and asserts a write and a read, a denied write to another user's path, <code>.info/connected</code>, an <code>onDisconnect</code> that ran after the process exited, and that a process stays up after a write until <code>goOffline()</code>. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-realm.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>node-realm.test.ts</code></a> pins when the process is held. CDD assertion <code>rtdb-modular#RS1</code> pins the sandbox side: rules evaluate per signed-in session, and a registry bound to the refusal opens no database handle. On main (942cb175) it failed: the calls ran against a private in-process sandbox, so an upload returned a <a href="https://github.com/davideast/pyric/blob/main/test/apps/client-sdk-under-register/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>data:</code></a> URL and firebase-admin read nothing they wrote. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-origin.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>packages/cli/test/register/node-origin.test.ts</code></a> runs the served surfaces under a Node origin whose host is not the Node host, or cannot be found, and asserts every call fails, <code>useWorker</code> stays true, the served app entry's placeholder sandbox stays empty, and a <code>pyric/*</code> mirror resolved from the app refuses. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/mapping.test.ts#L141" target="_blank" rel="noopener noreferrer"><code>mapping.test.ts</code></a> pins the mapping per activation value.. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/register-child.test.ts#L138" target="_blank" rel="noopener noreferrer"><code>register-child.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/database/cdd/lifecycle-and-identity.test.ts#L28" target="_blank" rel="noopener noreferrer"><code>lifecycle-and-identity.test.ts</code></a>)</div>
904
+ <div class="compat-evidence"><div class="compat-probe">App scenario <code>test/apps/client-sdk-under-register</code> runs the client SDKs under <code>node --import @pyric/cli/register</code> against a hosted Vite dev server, reads back with <code>firebase-admin</code> and a page, and asserts a write and a read, a denied write to another user's path, <code>.info/connected</code>, an <code>onDisconnect</code> that ran after the process exited, and that a process stays up after a write until <code>goOffline()</code>. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-realm.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>node-realm.test.ts</code></a> pins when the process is held. CDD assertion <code>rtdb-modular#RS1</code> pins the sandbox side: rules evaluate per signed-in session, and a registry bound to the refusal opens no database handle. On main (942cb175) it failed: the calls ran against a private in-process sandbox, so an upload returned a <a href="https://github.com/davideast/pyric/blob/main/test/apps/client-sdk-under-register/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>data:</code></a> URL and firebase-admin read nothing they wrote. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-origin.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>packages/cli/test/register/node-origin.test.ts</code></a> runs the served surfaces under a Node origin whose host is not the Node host, or cannot be found, and asserts every call fails, <code>useWorker</code> stays true, the served app entry's placeholder sandbox stays empty, and a <code>pyric/*</code> mirror resolved from the app refuses. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/mapping.test.ts#L80" target="_blank" rel="noopener noreferrer"><code>mapping.test.ts</code></a> pins the mapping per activation value.. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/register-child.test.ts#L138" target="_blank" rel="noopener noreferrer"><code>register-child.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/database/cdd/lifecycle-and-identity.test.ts#L28" target="_blank" rel="noopener noreferrer"><code>lifecycle-and-identity.test.ts</code></a>)</div>
905
905
  <div class="compat-note">the app must use the same Firebase options as the host's other clients (one configuration per runtime)</div></div>
906
906
  </details>
907
907
  </div>
@@ -913,7 +913,7 @@ Modules Vite loads for server rendering under `vite dev` with `pyric()` (`server
913
913
  <div class="compat-list">
914
914
  <details class="compat-row" data-status="diverged">
915
915
  <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">getDatabase(app) in a server-rendered module under vite dev</code><span class="compat-sub"><span class="compat-behavior">On a hosted dev server, <code>firebase/database</code> in a server-rendered module reads and writes the host's data under its rules as the SSR session's user: a write the rules deny for that user fails with <code>PERMISSION_DENIED</code>. <code>ssrLoadModule</code>, the environment runner and every <code>ssr.noExternal</code> setting share one app and one session. Closing the dev server lets the process go, so a process that created the server through Vite's API ends on its own, though RTDB would otherwise hold it until <code>goOffline()</code>. On a SharedWorker dev server every call fails with <code>failed-precondition</code> and a message naming <code>pyric({ hosted: true })</code>. No call reaches production or an in-process sandbox.</span></span></span></summary>
916
- <div class="compat-evidence"><div class="compat-probe">App scenario <code>test/apps/vite-ssr-client-sdk</code> writes and reads the SSR user's own path, is denied another user's, reads the writes back with <code>firebase-admin</code> in SSR and in a process on the host, repeats it with <code>ssr.noExternal</code> <code>['firebase']</code> and <code>true</code>, refuses on a SharedWorker dev server, and creates a dev server through Vite's API that ends on its own after SSR used RTDB. On main (ba2c0bf0a) the scenario failed: SSR sign-in rejected with <code>auth/network-request-failed</code> from the network guard, and the RTDB, Firestore and Storage writes did not settle, because the imports loaded the real SDK. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-module-swap.test.ts#L9" target="_blank" rel="noopener noreferrer"><code>vite-module-swap.test.ts</code></a> pins the routing per specifier, AI mode and environment (Vite 5's <code>ssr</code> option, a runnable environment, a non-runnable one), <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-ssr-activation.test.ts#L1" target="_blank" rel="noopener noreferrer"><code>vite-ssr-activation.test.ts</code></a> the host, the restart hand-over and the SharedWorker refusal, and <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-plugin-firestore.test.ts#L14" target="_blank" rel="noopener noreferrer"><code>vite-plugin-firestore.test.ts</code></a> runs Vite with <code>firebase</code> installed, so it takes the externalized path. CDD assertion <code>rtdb-modular#RS2</code> pins the sandbox side. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-realm.test.ts#L1" target="_blank" rel="noopener noreferrer"><code>node-realm.test.ts</code></a> pins the release when the in-process host ends. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/test/apps/vite-ssr-client-sdk/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/database/cdd/lifecycle-and-identity.test.ts#L28" target="_blank" rel="noopener noreferrer"><code>lifecycle-and-identity.test.ts</code></a>)</div>
916
+ <div class="compat-evidence"><div class="compat-probe">App scenario <code>test/apps/vite-ssr-client-sdk</code> writes and reads the SSR user's own path, is denied another user's, reads the writes back with <code>firebase-admin</code> in SSR and in a process on the host, repeats it with <code>ssr.noExternal</code> <code>['firebase']</code> and <code>true</code>, refuses on a SharedWorker dev server, and creates a dev server through Vite's API that ends on its own after SSR used RTDB. On main (ba2c0bf0a) the scenario failed: SSR sign-in rejected with <code>auth/network-request-failed</code> from the network guard, and the RTDB, Firestore and Storage writes did not settle, because the imports loaded the real SDK. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-module-swap.test.ts#L4" target="_blank" rel="noopener noreferrer"><code>vite-module-swap.test.ts</code></a> pins the routing per specifier, AI mode and environment (Vite 5's <code>ssr</code> option, a runnable environment, a non-runnable one), <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-ssr-activation.test.ts#L1" target="_blank" rel="noopener noreferrer"><code>vite-ssr-activation.test.ts</code></a> the host, the restart hand-over and the SharedWorker refusal, and <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-plugin-firestore.test.ts#L14" target="_blank" rel="noopener noreferrer"><code>vite-plugin-firestore.test.ts</code></a> runs Vite with <code>firebase</code> installed, so it takes the externalized path. CDD assertion <code>rtdb-modular#RS2</code> pins the sandbox side. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-realm.test.ts#L1" target="_blank" rel="noopener noreferrer"><code>node-realm.test.ts</code></a> pins the release when the in-process host ends. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/test/apps/vite-ssr-client-sdk/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/database/cdd/lifecycle-and-identity.test.ts#L28" target="_blank" rel="noopener noreferrer"><code>lifecycle-and-identity.test.ts</code></a>)</div>
917
917
  <div class="compat-note">one SSR session per dev server process, as one Node server process has one auth state; per-request identity (<code>initializeServerApp</code>) is unsupported</div></div>
918
918
  </details>
919
919
  <details class="compat-row" data-status="diverged">
@@ -957,9 +957,9 @@ Known differences between Pyric and production Firebase. Each remains tracked as
957
957
  <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"><span class="compat-sub"><span class="compat-behavior">**Divergence:** <code>refFromURL(db, url)</code> matches Firebase's path parsing, query ignoring, FTP-to-database normalization, and fragment rejection, but the single-database sandbox does not validate the URL host/namespace against the handle; Firebase rejects a mismatched host while the sandbox uses its path.</span></span></span></summary>
958
958
  <div class="compat-evidence"><div class="compat-note">Oracle <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/observations/rtdb-modular/rtdb-modular-reference-shape-url.json" target="_blank" rel="noopener noreferrer"><code>rtdb-modular-reference-shape-url</code></a> captures accepted FTP/query URLs and rejected fragments, malformed URLs, and host mismatches; <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/database/modular/oracle-conformance-reference-writes.test.ts#L33" target="_blank" rel="noopener noreferrer"><code>unit:modular/oracle-conformance-reference-writes.test.ts</code></a> pins the conforming validations plus both sides of the host divergence.</div></div></details>
959
959
  <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">getDatabase(app) in a registered server process</code><span class="compat-sub"><span class="compat-behavior">Under a remote register activation on the Node host, <code>firebase/database</code> in a server process reads and writes the host's data under its rules as the process's signed-in user: a write the rules deny for that user fails with <code>PERMISSION_DENIED</code>, <code>.info/connected</code> follows the process's connection, and an <code>onDisconnect</code> write runs when that connection ends. From its first RTDB call on an instance the process stays up until <code>goOffline()</code> on that instance or <code>deleteApp()</code>, as <code>@firebase/database</code> keeps a Node process up. When the host is not the Node host, every call fails with <code>failed-precondition</code> and a message naming the Node host (<code>pyric sandbox --hosted</code> or <code>pyric({ hosted: true })</code>); when no host is found, with the discovery error <code>pyric-admin</code> gets. No call falls back to an in-process sandbox.</span></span></span></summary>
960
- <div class="compat-evidence"><div class="compat-note">App scenario <code>test/apps/client-sdk-under-register</code> runs the client SDKs under <code>node --import @pyric/cli/register</code> against a hosted Vite dev server, reads back with <code>firebase-admin</code> and a page, and asserts a write and a read, a denied write to another user's path, <code>.info/connected</code>, an <code>onDisconnect</code> that ran after the process exited, and that a process stays up after a write until <code>goOffline()</code>. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-realm.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>node-realm.test.ts</code></a> pins when the process is held. CDD assertion <code>rtdb-modular#RS1</code> pins the sandbox side: rules evaluate per signed-in session, and a registry bound to the refusal opens no database handle. On main (942cb175) it failed: the calls ran against a private in-process sandbox, so an upload returned a <a href="https://github.com/davideast/pyric/blob/main/test/apps/client-sdk-under-register/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>data:</code></a> URL and firebase-admin read nothing they wrote. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-origin.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>packages/cli/test/register/node-origin.test.ts</code></a> runs the served surfaces under a Node origin whose host is not the Node host, or cannot be found, and asserts every call fails, <code>useWorker</code> stays true, the served app entry's placeholder sandbox stays empty, and a <code>pyric/*</code> mirror resolved from the app refuses. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/mapping.test.ts#L141" target="_blank" rel="noopener noreferrer"><code>mapping.test.ts</code></a> pins the mapping per activation value.. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/register-child.test.ts#L138" target="_blank" rel="noopener noreferrer"><code>register-child.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/database/cdd/lifecycle-and-identity.test.ts#L28" target="_blank" rel="noopener noreferrer"><code>lifecycle-and-identity.test.ts</code></a>)</div></div></details>
960
+ <div class="compat-evidence"><div class="compat-note">App scenario <code>test/apps/client-sdk-under-register</code> runs the client SDKs under <code>node --import @pyric/cli/register</code> against a hosted Vite dev server, reads back with <code>firebase-admin</code> and a page, and asserts a write and a read, a denied write to another user's path, <code>.info/connected</code>, an <code>onDisconnect</code> that ran after the process exited, and that a process stays up after a write until <code>goOffline()</code>. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-realm.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>node-realm.test.ts</code></a> pins when the process is held. CDD assertion <code>rtdb-modular#RS1</code> pins the sandbox side: rules evaluate per signed-in session, and a registry bound to the refusal opens no database handle. On main (942cb175) it failed: the calls ran against a private in-process sandbox, so an upload returned a <a href="https://github.com/davideast/pyric/blob/main/test/apps/client-sdk-under-register/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>data:</code></a> URL and firebase-admin read nothing they wrote. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-origin.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>packages/cli/test/register/node-origin.test.ts</code></a> runs the served surfaces under a Node origin whose host is not the Node host, or cannot be found, and asserts every call fails, <code>useWorker</code> stays true, the served app entry's placeholder sandbox stays empty, and a <code>pyric/*</code> mirror resolved from the app refuses. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/mapping.test.ts#L80" target="_blank" rel="noopener noreferrer"><code>mapping.test.ts</code></a> pins the mapping per activation value.. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/register-child.test.ts#L138" target="_blank" rel="noopener noreferrer"><code>register-child.test.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/database/cdd/lifecycle-and-identity.test.ts#L28" target="_blank" rel="noopener noreferrer"><code>lifecycle-and-identity.test.ts</code></a>)</div></div></details>
961
961
  <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">getDatabase(app) in a server-rendered module under vite dev</code><span class="compat-sub"><span class="compat-behavior">On a hosted dev server, <code>firebase/database</code> in a server-rendered module reads and writes the host's data under its rules as the SSR session's user: a write the rules deny for that user fails with <code>PERMISSION_DENIED</code>. <code>ssrLoadModule</code>, the environment runner and every <code>ssr.noExternal</code> setting share one app and one session. Closing the dev server lets the process go, so a process that created the server through Vite's API ends on its own, though RTDB would otherwise hold it until <code>goOffline()</code>. On a SharedWorker dev server every call fails with <code>failed-precondition</code> and a message naming <code>pyric({ hosted: true })</code>. No call reaches production or an in-process sandbox.</span></span></span></summary>
962
- <div class="compat-evidence"><div class="compat-note">App scenario <code>test/apps/vite-ssr-client-sdk</code> writes and reads the SSR user's own path, is denied another user's, reads the writes back with <code>firebase-admin</code> in SSR and in a process on the host, repeats it with <code>ssr.noExternal</code> <code>['firebase']</code> and <code>true</code>, refuses on a SharedWorker dev server, and creates a dev server through Vite's API that ends on its own after SSR used RTDB. On main (ba2c0bf0a) the scenario failed: SSR sign-in rejected with <code>auth/network-request-failed</code> from the network guard, and the RTDB, Firestore and Storage writes did not settle, because the imports loaded the real SDK. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-module-swap.test.ts#L9" target="_blank" rel="noopener noreferrer"><code>vite-module-swap.test.ts</code></a> pins the routing per specifier, AI mode and environment (Vite 5's <code>ssr</code> option, a runnable environment, a non-runnable one), <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-ssr-activation.test.ts#L1" target="_blank" rel="noopener noreferrer"><code>vite-ssr-activation.test.ts</code></a> the host, the restart hand-over and the SharedWorker refusal, and <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-plugin-firestore.test.ts#L14" target="_blank" rel="noopener noreferrer"><code>vite-plugin-firestore.test.ts</code></a> runs Vite with <code>firebase</code> installed, so it takes the externalized path. CDD assertion <code>rtdb-modular#RS2</code> pins the sandbox side. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-realm.test.ts#L1" target="_blank" rel="noopener noreferrer"><code>node-realm.test.ts</code></a> pins the release when the in-process host ends. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/test/apps/vite-ssr-client-sdk/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/database/cdd/lifecycle-and-identity.test.ts#L28" target="_blank" rel="noopener noreferrer"><code>lifecycle-and-identity.test.ts</code></a>)</div></div></details>
962
+ <div class="compat-evidence"><div class="compat-note">App scenario <code>test/apps/vite-ssr-client-sdk</code> writes and reads the SSR user's own path, is denied another user's, reads the writes back with <code>firebase-admin</code> in SSR and in a process on the host, repeats it with <code>ssr.noExternal</code> <code>['firebase']</code> and <code>true</code>, refuses on a SharedWorker dev server, and creates a dev server through Vite's API that ends on its own after SSR used RTDB. On main (ba2c0bf0a) the scenario failed: SSR sign-in rejected with <code>auth/network-request-failed</code> from the network guard, and the RTDB, Firestore and Storage writes did not settle, because the imports loaded the real SDK. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-module-swap.test.ts#L4" target="_blank" rel="noopener noreferrer"><code>vite-module-swap.test.ts</code></a> pins the routing per specifier, AI mode and environment (Vite 5's <code>ssr</code> option, a runnable environment, a non-runnable one), <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-ssr-activation.test.ts#L1" target="_blank" rel="noopener noreferrer"><code>vite-ssr-activation.test.ts</code></a> the host, the restart hand-over and the SharedWorker refusal, and <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-plugin-firestore.test.ts#L14" target="_blank" rel="noopener noreferrer"><code>vite-plugin-firestore.test.ts</code></a> runs Vite with <code>firebase</code> installed, so it takes the externalized path. CDD assertion <code>rtdb-modular#RS2</code> pins the sandbox side. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-realm.test.ts#L1" target="_blank" rel="noopener noreferrer"><code>node-realm.test.ts</code></a> pins the release when the in-process host ends. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/test/apps/vite-ssr-client-sdk/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/database/cdd/lifecycle-and-identity.test.ts#L28" target="_blank" rel="noopener noreferrer"><code>lifecycle-and-identity.test.ts</code></a>)</div></div></details>
963
963
  <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">firebase-admin/database in a server-rendered module under vite dev</code><span class="compat-sub"><span class="compat-behavior">On a hosted dev server, <code>firebase-admin</code> in a server-rendered module resolves to <code>pyric-admin</code> bound to the dev server's host, as a <code>firebase-admin</code> process on the host is: a bare <code>initializeApp()</code> connects to it, reads bypass the rules, and the writes of browsers and SSR sessions are visible. On a SharedWorker dev server its first call fails with <code>failed-precondition</code> and a message naming <code>pyric({ hosted: true })</code>, without dialing.</span></span></span></summary>
964
964
  <div class="compat-evidence"><div class="compat-note">App scenario <code>test/apps/vite-ssr-client-sdk</code> reads a path the rules let only the SSR user read, and a browser's write, with <code>firebase-admin</code> in SSR through both loaders and with each externalization setting, and gets the refusal on a SharedWorker dev server. On main (ba2c0bf0a) <code>firebase-admin</code> in SSR was the real SDK. <a href="https://github.com/davideast/pyric/blob/main/packages/pyric-admin/test/app/ambient-init.test.ts#L128" target="_blank" rel="noopener noreferrer"><code>ambient-init.test.ts</code></a> pins that a bare <code>initializeApp()</code> binds the in-process activation's sandbox when PYRIC_SANDBOX is unset, and that the variable wins when set. CDD assertion <code>rtdb-modular#RS3</code> pins the sandbox side. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/test/apps/vite-ssr-client-sdk/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/database/cdd/lifecycle-and-identity.test.ts#L28" target="_blank" rel="noopener noreferrer"><code>lifecycle-and-identity.test.ts</code></a>)</div></div></details>
965
965
  </div>
@@ -823,7 +823,7 @@ on a compiled ruleset share one parse per ruleset source, and the next
823
823
  <div class="compat-list">
824
824
  <details class="compat-row" data-status="diverged">
825
825
  <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">getFirestore(app) in a registered server process</code><span class="compat-sub"><span class="compat-behavior">Under a remote register activation on the Node host, <code>firebase/firestore</code> in a server process reads and writes the host's documents under its rules as the process's signed-in user. An open listener keeps the process up; a finished write releases it at once. A CommonJS <code>require('firebase/firestore')</code> resolves the same client. An app whose options differ from the host's gets each call rejected with <code>app/multiple-configs-not-supported</code>, and nothing throws past the call. When the host is not the Node host, every call fails with <code>failed-precondition</code> and a message naming the Node host (<code>pyric sandbox --hosted</code> or <code>pyric({ hosted: true })</code>); when no host is found, with the discovery error <code>pyric-admin</code> gets. No call falls back to an in-process sandbox.</span></span></span></summary>
826
- <div class="compat-evidence"><div class="compat-probe">App scenario <code>test/apps/client-sdk-under-register</code> runs the client SDKs under <code>node --import @pyric/cli/register</code> against a hosted Vite dev server, reads back with <code>firebase-admin</code> and a page, and asserts a write the page and firebase-admin read, a CommonJS require, an app with other options rejected per call, and that a listener holds the process while a finished write does not. On main (942cb175) it failed: the calls ran against a private in-process sandbox, so an upload returned a <a href="https://github.com/davideast/pyric/blob/main/test/apps/client-sdk-under-register/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>data:</code></a> URL and firebase-admin read nothing they wrote. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-origin.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>packages/cli/test/register/node-origin.test.ts</code></a> runs the served surfaces under a Node origin whose host is not the Node host, or cannot be found, and asserts every call fails, <code>useWorker</code> stays true, the served app entry's placeholder sandbox stays empty, and a <code>pyric/*</code> mirror resolved from the app refuses. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/mapping.test.ts#L141" target="_blank" rel="noopener noreferrer"><code>mapping.test.ts</code></a> pins the mapping per activation value.. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/register-child.test.ts#L201" target="_blank" rel="noopener noreferrer"><code>register-child.test.ts</code></a>)</div>
826
+ <div class="compat-evidence"><div class="compat-probe">App scenario <code>test/apps/client-sdk-under-register</code> runs the client SDKs under <code>node --import @pyric/cli/register</code> against a hosted Vite dev server, reads back with <code>firebase-admin</code> and a page, and asserts a write the page and firebase-admin read, a CommonJS require, an app with other options rejected per call, and that a listener holds the process while a finished write does not. On main (942cb175) it failed: the calls ran against a private in-process sandbox, so an upload returned a <a href="https://github.com/davideast/pyric/blob/main/test/apps/client-sdk-under-register/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>data:</code></a> URL and firebase-admin read nothing they wrote. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-origin.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>packages/cli/test/register/node-origin.test.ts</code></a> runs the served surfaces under a Node origin whose host is not the Node host, or cannot be found, and asserts every call fails, <code>useWorker</code> stays true, the served app entry's placeholder sandbox stays empty, and a <code>pyric/*</code> mirror resolved from the app refuses. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/mapping.test.ts#L80" target="_blank" rel="noopener noreferrer"><code>mapping.test.ts</code></a> pins the mapping per activation value.. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/register-child.test.ts#L201" target="_blank" rel="noopener noreferrer"><code>register-child.test.ts</code></a>)</div>
827
827
  <div class="compat-note">a finished write releases the process at once, where production holds it until the write stream's 60 s idle timeout; one configuration per runtime</div></div>
828
828
  </details>
829
829
  </div>
@@ -832,7 +832,7 @@ on a compiled ruleset share one parse per ruleset source, and the next
832
832
  <div class="compat-list">
833
833
  <details class="compat-row" data-status="diverged">
834
834
  <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">getFirestore(app) in a server-rendered module under vite dev</code><span class="compat-sub"><span class="compat-behavior">On a hosted dev server, <code>firebase/firestore</code> in a server-rendered module reads and writes the host's documents under its rules as the SSR session's user: a write the rules deny for that user fails with <code>permission-denied</code>. <code>ssrLoadModule</code>, the environment runner and every <code>ssr.noExternal</code> setting share one app and one session. On a SharedWorker dev server every call fails with <code>failed-precondition</code> and a message naming <code>pyric({ hosted: true })</code>. No call reaches production or an in-process sandbox.</span></span></span></summary>
835
- <div class="compat-evidence"><div class="compat-probe">App scenario <code>test/apps/vite-ssr-client-sdk</code> writes and reads the SSR user's own document, is denied another user's, reads the writes back with <code>firebase-admin</code> in SSR and in a process on the host, with each externalization setting, and refuses on a SharedWorker dev server. On main (ba2c0bf0a) the scenario failed: SSR sign-in rejected with <code>auth/network-request-failed</code> from the network guard, and the RTDB, Firestore and Storage writes did not settle, because the imports loaded the real SDK. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-module-swap.test.ts#L9" target="_blank" rel="noopener noreferrer"><code>vite-module-swap.test.ts</code></a> pins the routing per specifier, AI mode and environment (Vite 5's <code>ssr</code> option, a runnable environment, a non-runnable one), <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-ssr-activation.test.ts#L1" target="_blank" rel="noopener noreferrer"><code>vite-ssr-activation.test.ts</code></a> the host, the restart hand-over and the SharedWorker refusal, and <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-plugin-firestore.test.ts#L36" target="_blank" rel="noopener noreferrer"><code>vite-plugin-firestore.test.ts</code></a> runs Vite with <code>firebase</code> installed, so it takes the externalized path. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/test/apps/vite-ssr-client-sdk/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>)</div>
835
+ <div class="compat-evidence"><div class="compat-probe">App scenario <code>test/apps/vite-ssr-client-sdk</code> writes and reads the SSR user's own document, is denied another user's, reads the writes back with <code>firebase-admin</code> in SSR and in a process on the host, with each externalization setting, and refuses on a SharedWorker dev server. On main (ba2c0bf0a) the scenario failed: SSR sign-in rejected with <code>auth/network-request-failed</code> from the network guard, and the RTDB, Firestore and Storage writes did not settle, because the imports loaded the real SDK. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-module-swap.test.ts#L4" target="_blank" rel="noopener noreferrer"><code>vite-module-swap.test.ts</code></a> pins the routing per specifier, AI mode and environment (Vite 5's <code>ssr</code> option, a runnable environment, a non-runnable one), <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-ssr-activation.test.ts#L1" target="_blank" rel="noopener noreferrer"><code>vite-ssr-activation.test.ts</code></a> the host, the restart hand-over and the SharedWorker refusal, and <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-plugin-firestore.test.ts#L36" target="_blank" rel="noopener noreferrer"><code>vite-plugin-firestore.test.ts</code></a> runs Vite with <code>firebase</code> installed, so it takes the externalized path. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/test/apps/vite-ssr-client-sdk/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>)</div>
836
836
  <div class="compat-note">one SSR session per dev server process, as one Node server process has one auth state; per-request identity (<code>initializeServerApp</code>) is unsupported</div></div>
837
837
  </details>
838
838
  <details class="compat-row" data-status="diverged">
@@ -868,9 +868,9 @@ on a compiled ruleset share one parse per ruleset source, and the next
868
868
  <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">Writes and reads in flight at a hosted connection drop (web hosted client)</code><span class="compat-sub"><span class="compat-behavior">A Firestore write (<code>setDoc</code>, <code>updateDoc</code>, <code>deleteDoc</code>, a batch <code>commit</code>) or read (<code>getDoc</code>, <code>getDocs</code>) the web hosted client's socket had taken when it dropped fails once with <code>unavailable</code> and the message "The hosted sandbox connection was lost. Requests already sent may have completed; check state before retrying." It is not sent again. In production the Firestore Web SDK keeps a write in its mutation queue and sends it again on the next write stream until the backend acknowledges it, and answers a <code>getDoc</code> from its local cache, with <code>fromCache</code>, or fails it with <code>unavailable</code> when the document is not cached.</span></span></span></summary>
869
869
  <div class="compat-evidence"><div class="compat-note">Production behavior read from <code>@firebase/firestore</code> 4.14.1, as recorded in ADR-0019 ("Reads and writes are treated alike, unlike production"). <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/worker/hosted-reconnect-hold.test.ts#L412" target="_blank" rel="noopener noreferrer"><code>packages/cli/test/serve/worker/hosted-reconnect-hold.test.ts</code></a> drops a real loopback socket with <code>setDoc</code>, <code>getDoc</code> and <code>batchCommit</code> in flight and asserts each fails once with that message and reaches the host once. The Realtime Database follows its SDK here instead (row <code>rtdb-modular#HD1</code>, #1088).</div></div></details>
870
870
  <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">getFirestore(app) in a registered server process</code><span class="compat-sub"><span class="compat-behavior">Under a remote register activation on the Node host, <code>firebase/firestore</code> in a server process reads and writes the host's documents under its rules as the process's signed-in user. An open listener keeps the process up; a finished write releases it at once. A CommonJS <code>require('firebase/firestore')</code> resolves the same client. An app whose options differ from the host's gets each call rejected with <code>app/multiple-configs-not-supported</code>, and nothing throws past the call. When the host is not the Node host, every call fails with <code>failed-precondition</code> and a message naming the Node host (<code>pyric sandbox --hosted</code> or <code>pyric({ hosted: true })</code>); when no host is found, with the discovery error <code>pyric-admin</code> gets. No call falls back to an in-process sandbox.</span></span></span></summary>
871
- <div class="compat-evidence"><div class="compat-note">App scenario <code>test/apps/client-sdk-under-register</code> runs the client SDKs under <code>node --import @pyric/cli/register</code> against a hosted Vite dev server, reads back with <code>firebase-admin</code> and a page, and asserts a write the page and firebase-admin read, a CommonJS require, an app with other options rejected per call, and that a listener holds the process while a finished write does not. On main (942cb175) it failed: the calls ran against a private in-process sandbox, so an upload returned a <a href="https://github.com/davideast/pyric/blob/main/test/apps/client-sdk-under-register/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>data:</code></a> URL and firebase-admin read nothing they wrote. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-origin.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>packages/cli/test/register/node-origin.test.ts</code></a> runs the served surfaces under a Node origin whose host is not the Node host, or cannot be found, and asserts every call fails, <code>useWorker</code> stays true, the served app entry's placeholder sandbox stays empty, and a <code>pyric/*</code> mirror resolved from the app refuses. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/mapping.test.ts#L141" target="_blank" rel="noopener noreferrer"><code>mapping.test.ts</code></a> pins the mapping per activation value.. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/register-child.test.ts#L201" target="_blank" rel="noopener noreferrer"><code>register-child.test.ts</code></a>)</div></div></details>
871
+ <div class="compat-evidence"><div class="compat-note">App scenario <code>test/apps/client-sdk-under-register</code> runs the client SDKs under <code>node --import @pyric/cli/register</code> against a hosted Vite dev server, reads back with <code>firebase-admin</code> and a page, and asserts a write the page and firebase-admin read, a CommonJS require, an app with other options rejected per call, and that a listener holds the process while a finished write does not. On main (942cb175) it failed: the calls ran against a private in-process sandbox, so an upload returned a <a href="https://github.com/davideast/pyric/blob/main/test/apps/client-sdk-under-register/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>data:</code></a> URL and firebase-admin read nothing they wrote. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-origin.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>packages/cli/test/register/node-origin.test.ts</code></a> runs the served surfaces under a Node origin whose host is not the Node host, or cannot be found, and asserts every call fails, <code>useWorker</code> stays true, the served app entry's placeholder sandbox stays empty, and a <code>pyric/*</code> mirror resolved from the app refuses. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/mapping.test.ts#L80" target="_blank" rel="noopener noreferrer"><code>mapping.test.ts</code></a> pins the mapping per activation value.. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/register-child.test.ts#L201" target="_blank" rel="noopener noreferrer"><code>register-child.test.ts</code></a>)</div></div></details>
872
872
  <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">getFirestore(app) in a server-rendered module under vite dev</code><span class="compat-sub"><span class="compat-behavior">On a hosted dev server, <code>firebase/firestore</code> in a server-rendered module reads and writes the host's documents under its rules as the SSR session's user: a write the rules deny for that user fails with <code>permission-denied</code>. <code>ssrLoadModule</code>, the environment runner and every <code>ssr.noExternal</code> setting share one app and one session. On a SharedWorker dev server every call fails with <code>failed-precondition</code> and a message naming <code>pyric({ hosted: true })</code>. No call reaches production or an in-process sandbox.</span></span></span></summary>
873
- <div class="compat-evidence"><div class="compat-note">App scenario <code>test/apps/vite-ssr-client-sdk</code> writes and reads the SSR user's own document, is denied another user's, reads the writes back with <code>firebase-admin</code> in SSR and in a process on the host, with each externalization setting, and refuses on a SharedWorker dev server. On main (ba2c0bf0a) the scenario failed: SSR sign-in rejected with <code>auth/network-request-failed</code> from the network guard, and the RTDB, Firestore and Storage writes did not settle, because the imports loaded the real SDK. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-module-swap.test.ts#L9" target="_blank" rel="noopener noreferrer"><code>vite-module-swap.test.ts</code></a> pins the routing per specifier, AI mode and environment (Vite 5's <code>ssr</code> option, a runnable environment, a non-runnable one), <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-ssr-activation.test.ts#L1" target="_blank" rel="noopener noreferrer"><code>vite-ssr-activation.test.ts</code></a> the host, the restart hand-over and the SharedWorker refusal, and <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-plugin-firestore.test.ts#L36" target="_blank" rel="noopener noreferrer"><code>vite-plugin-firestore.test.ts</code></a> runs Vite with <code>firebase</code> installed, so it takes the externalized path. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/test/apps/vite-ssr-client-sdk/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>)</div></div></details>
873
+ <div class="compat-evidence"><div class="compat-note">App scenario <code>test/apps/vite-ssr-client-sdk</code> writes and reads the SSR user's own document, is denied another user's, reads the writes back with <code>firebase-admin</code> in SSR and in a process on the host, with each externalization setting, and refuses on a SharedWorker dev server. On main (ba2c0bf0a) the scenario failed: SSR sign-in rejected with <code>auth/network-request-failed</code> from the network guard, and the RTDB, Firestore and Storage writes did not settle, because the imports loaded the real SDK. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-module-swap.test.ts#L4" target="_blank" rel="noopener noreferrer"><code>vite-module-swap.test.ts</code></a> pins the routing per specifier, AI mode and environment (Vite 5's <code>ssr</code> option, a runnable environment, a non-runnable one), <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-ssr-activation.test.ts#L1" target="_blank" rel="noopener noreferrer"><code>vite-ssr-activation.test.ts</code></a> the host, the restart hand-over and the SharedWorker refusal, and <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-plugin-firestore.test.ts#L36" target="_blank" rel="noopener noreferrer"><code>vite-plugin-firestore.test.ts</code></a> runs Vite with <code>firebase</code> installed, so it takes the externalized path. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/test/apps/vite-ssr-client-sdk/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>)</div></div></details>
874
874
  <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">firebase-admin/firestore in a server-rendered module under vite dev</code><span class="compat-sub"><span class="compat-behavior">On a hosted dev server, <code>firebase-admin/firestore</code> in a server-rendered module reads and writes the dev server host's documents as admin, past the rules, as a <code>firebase-admin</code> process on the host does. On a SharedWorker dev server its first call fails with <code>failed-precondition</code> and a message naming <code>pyric({ hosted: true })</code>.</span></span></span></summary>
875
875
  <div class="compat-evidence"><div class="compat-note">App scenario <code>test/apps/vite-ssr-client-sdk</code> reads a document the rules let only the SSR user read with <code>firebase-admin</code> in SSR through both loaders and with each externalization setting. On main (ba2c0bf0a) <code>firebase-admin</code> in SSR was the real SDK. <a href="https://github.com/davideast/pyric/blob/main/packages/pyric-admin/test/app/ambient-init.test.ts#L128" target="_blank" rel="noopener noreferrer"><code>ambient-init.test.ts</code></a> pins the ambient binding. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/test/apps/vite-ssr-client-sdk/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>)</div></div></details>
876
876
  </div> </article> </main> <aside class="doc-toc" aria-label="On this page" data-astro-cid-mw7aashj> <h2 class="doc-toc-heading" data-astro-cid-mw7aashj>On this page</h2> <ul data-astro-cid-mw7aashj> <li data-depth="2" data-astro-cid-mw7aashj> <a href="#status-legend" data-astro-cid-mw7aashj>Status legend</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#getfirestoretarget--initializer" data-astro-cid-mw7aashj>getFirestore(target) — initializer</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#named-databases" data-astro-cid-mw7aashj>Named databases</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#path-constructors--doc--collection--collectiongroup" data-astro-cid-mw7aashj>Path constructors — doc / collection / collectionGroup</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#getdocref--single-doc-read" data-astro-cid-mw7aashj>getDoc(ref) — single-doc read</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#getdocsquery--bulk-read" data-astro-cid-mw7aashj>getDocs(query) — bulk read</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#setdocref-data-options--full-write" data-astro-cid-mw7aashj>setDoc(ref, data[, options]) — full write</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#updatedocref-data--partial-write" data-astro-cid-mw7aashj>updateDoc(ref, data) — partial write</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#deletedocref" data-astro-cid-mw7aashj>deleteDoc(ref)</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#adddoccoll-data--auto-id-write" data-astro-cid-mw7aashj>addDoc(coll, data) — auto-id write</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#withconverter--typed-refs" data-astro-cid-mw7aashj>withConverter — typed refs</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#query-construction--query--where--or--and--orderby--limit" data-astro-cid-mw7aashj>Query construction — query / where / or / and / orderBy / limit</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#cursor-pagination--startat--startafter--endat--endbefore" data-astro-cid-mw7aashj>Cursor pagination — startAt / startAfter / endAt / endBefore</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#aggregates--getcountfromserver--getaggregatefromserver--count--sum--average" data-astro-cid-mw7aashj>Aggregates — getCountFromServer / getAggregateFromServer / count / sum / average</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#onsnapshotreforquery---listeners" data-astro-cid-mw7aashj>onSnapshot(refOrQuery, …) — listeners</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#runtransactiondb-fn" data-astro-cid-mw7aashj>runTransaction(db, fn)</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#writebatchdb" data-astro-cid-mw7aashj>writeBatch(db)</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#sentinels--servertimestamp--increment--arrayunion--arrayremove--deletefield--fieldvalue--timestamp" data-astro-cid-mw7aashj>Sentinels — serverTimestamp / increment / arrayUnion / arrayRemove / deleteField / FieldValue / Timestamp</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#scalar-types--bytes--geopoint--fieldpath--documentid" data-astro-cid-mw7aashj>Scalar types — Bytes / GeoPoint / FieldPath / documentId</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#equality-helpers--refequal--queryequal--snapshotequal" data-astro-cid-mw7aashj>Equality helpers — refEqual / queryEqual / snapshotEqual</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#connectfirestoreemulator" data-astro-cid-mw7aashj>connectFirestoreEmulator</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#offline--persistence--network-family" data-astro-cid-mw7aashj>Offline / persistence / network family</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#offline--persistence--network-family-continued" data-astro-cid-mw7aashj>Offline / persistence / network family (continued)</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#tier-1-cache-init--get-from--family" data-astro-cid-mw7aashj>Tier-1 cache-init + get-from-* family</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#tier-1-cache-init--get-from--family-continued" data-astro-cid-mw7aashj>Tier-1 cache-init + get-from-* family (continued)</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#rules-engine-via-setrules-from-pyricsandboxfirestore" data-astro-cid-mw7aashj>Rules engine (via setRules from pyric/sandbox/firestore)</a> </li><li data-depth="3" data-astro-cid-mw7aashj> <a href="#calls-in-flight-when-the-hosted-connection-drops" data-astro-cid-mw7aashj>Calls in flight when the hosted connection drops</a> </li><li data-depth="3" data-astro-cid-mw7aashj> <a href="#client-sdks-in-a-registered-server-process" data-astro-cid-mw7aashj>Client SDKs in a registered server process</a> </li><li data-depth="3" data-astro-cid-mw7aashj> <a href="#server-rendered-modules-under-vite-dev" data-astro-cid-mw7aashj>Server-rendered modules under vite dev</a> </li><li data-depth="2" data-astro-cid-mw7aashj> <a href="#current-gaps" data-astro-cid-mw7aashj>Current gaps</a> </li><li data-depth="3" data-astro-cid-mw7aashj> <a href="#documented-divergences" data-astro-cid-mw7aashj>Documented divergences</a> </li> </ul> </aside> </div> <script type="module">const a=document.querySelector("[data-api-symbol-filter]"),d=[...document.querySelectorAll("[data-api-symbol-group]")],n=document.querySelector("[data-api-symbol-empty]");a?.addEventListener("input",()=>{const t=a.value.trim().toLowerCase();let o=0;for(const i of d){let e=0;i.querySelectorAll("[data-api-symbol]").forEach(l=>{const r=!t||(l.dataset.apiSymbol??"").includes(t);l.hidden=!r,r&&e++}),i.hidden=e===0,o+=e}n&&(n.hidden=o>0)});</script> <script type="module">const s=new Map;document.querySelectorAll('.doc-toc a[href^="#"]').forEach(t=>s.set(decodeURIComponent(t.hash.slice(1)),t));if(s.size>0){const t=new Set,n=()=>{let o=null;for(const e of s.keys())if(t.has(e)){o=e;break}s.forEach((e,c)=>e.classList.toggle("active",c===o))},r=new IntersectionObserver(o=>{for(const e of o)e.isIntersecting?t.add(e.target.id):t.delete(e.target.id);n()},{rootMargin:"-72px 0px -60% 0px"});s.forEach((o,e)=>{const c=document.getElementById(e);c&&r.observe(c)})}</script> <script type="module">document.querySelector('.studio__nav [aria-current="page"]')?.scrollIntoView({block:"nearest",inline:"center"});</script> </body></html>
@@ -881,7 +881,7 @@ A server process started with `node --import @pyric/cli/register` and `PYRIC_SAN
881
881
  <div class="compat-list">
882
882
  <details class="compat-row" data-status="diverged">
883
883
  <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">getFirestore(app) in a registered server process</code><span class="compat-sub"><span class="compat-behavior">Under a remote register activation on the Node host, <code>firebase/firestore</code> in a server process reads and writes the host's documents under its rules as the process's signed-in user. An open listener keeps the process up; a finished write releases it at once. A CommonJS <code>require('firebase/firestore')</code> resolves the same client. An app whose options differ from the host's gets each call rejected with <code>app/multiple-configs-not-supported</code>, and nothing throws past the call. When the host is not the Node host, every call fails with <code>failed-precondition</code> and a message naming the Node host (<code>pyric sandbox --hosted</code> or <code>pyric({ hosted: true })</code>); when no host is found, with the discovery error <code>pyric-admin</code> gets. No call falls back to an in-process sandbox.</span></span></span></summary>
884
- <div class="compat-evidence"><div class="compat-probe">App scenario <code>test/apps/client-sdk-under-register</code> runs the client SDKs under <code>node --import @pyric/cli/register</code> against a hosted Vite dev server, reads back with <code>firebase-admin</code> and a page, and asserts a write the page and firebase-admin read, a CommonJS require, an app with other options rejected per call, and that a listener holds the process while a finished write does not. On main (942cb175) it failed: the calls ran against a private in-process sandbox, so an upload returned a <a href="https://github.com/davideast/pyric/blob/main/test/apps/client-sdk-under-register/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>data:</code></a> URL and firebase-admin read nothing they wrote. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-origin.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>packages/cli/test/register/node-origin.test.ts</code></a> runs the served surfaces under a Node origin whose host is not the Node host, or cannot be found, and asserts every call fails, <code>useWorker</code> stays true, the served app entry's placeholder sandbox stays empty, and a <code>pyric/*</code> mirror resolved from the app refuses. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/mapping.test.ts#L141" target="_blank" rel="noopener noreferrer"><code>mapping.test.ts</code></a> pins the mapping per activation value.. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/register-child.test.ts#L201" target="_blank" rel="noopener noreferrer"><code>register-child.test.ts</code></a>)</div>
884
+ <div class="compat-evidence"><div class="compat-probe">App scenario <code>test/apps/client-sdk-under-register</code> runs the client SDKs under <code>node --import @pyric/cli/register</code> against a hosted Vite dev server, reads back with <code>firebase-admin</code> and a page, and asserts a write the page and firebase-admin read, a CommonJS require, an app with other options rejected per call, and that a listener holds the process while a finished write does not. On main (942cb175) it failed: the calls ran against a private in-process sandbox, so an upload returned a <a href="https://github.com/davideast/pyric/blob/main/test/apps/client-sdk-under-register/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>data:</code></a> URL and firebase-admin read nothing they wrote. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-origin.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>packages/cli/test/register/node-origin.test.ts</code></a> runs the served surfaces under a Node origin whose host is not the Node host, or cannot be found, and asserts every call fails, <code>useWorker</code> stays true, the served app entry's placeholder sandbox stays empty, and a <code>pyric/*</code> mirror resolved from the app refuses. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/mapping.test.ts#L80" target="_blank" rel="noopener noreferrer"><code>mapping.test.ts</code></a> pins the mapping per activation value.. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/register-child.test.ts#L201" target="_blank" rel="noopener noreferrer"><code>register-child.test.ts</code></a>)</div>
885
885
  <div class="compat-note">a finished write releases the process at once, where production holds it until the write stream's 60 s idle timeout; one configuration per runtime</div></div>
886
886
  </details>
887
887
  </div>
@@ -893,7 +893,7 @@ Modules Vite loads for server rendering under `vite dev` with `pyric()` (`server
893
893
  <div class="compat-list">
894
894
  <details class="compat-row" data-status="diverged">
895
895
  <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">getFirestore(app) in a server-rendered module under vite dev</code><span class="compat-sub"><span class="compat-behavior">On a hosted dev server, <code>firebase/firestore</code> in a server-rendered module reads and writes the host's documents under its rules as the SSR session's user: a write the rules deny for that user fails with <code>permission-denied</code>. <code>ssrLoadModule</code>, the environment runner and every <code>ssr.noExternal</code> setting share one app and one session. On a SharedWorker dev server every call fails with <code>failed-precondition</code> and a message naming <code>pyric({ hosted: true })</code>. No call reaches production or an in-process sandbox.</span></span></span></summary>
896
- <div class="compat-evidence"><div class="compat-probe">App scenario <code>test/apps/vite-ssr-client-sdk</code> writes and reads the SSR user's own document, is denied another user's, reads the writes back with <code>firebase-admin</code> in SSR and in a process on the host, with each externalization setting, and refuses on a SharedWorker dev server. On main (ba2c0bf0a) the scenario failed: SSR sign-in rejected with <code>auth/network-request-failed</code> from the network guard, and the RTDB, Firestore and Storage writes did not settle, because the imports loaded the real SDK. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-module-swap.test.ts#L9" target="_blank" rel="noopener noreferrer"><code>vite-module-swap.test.ts</code></a> pins the routing per specifier, AI mode and environment (Vite 5's <code>ssr</code> option, a runnable environment, a non-runnable one), <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-ssr-activation.test.ts#L1" target="_blank" rel="noopener noreferrer"><code>vite-ssr-activation.test.ts</code></a> the host, the restart hand-over and the SharedWorker refusal, and <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-plugin-firestore.test.ts#L36" target="_blank" rel="noopener noreferrer"><code>vite-plugin-firestore.test.ts</code></a> runs Vite with <code>firebase</code> installed, so it takes the externalized path. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/test/apps/vite-ssr-client-sdk/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>)</div>
896
+ <div class="compat-evidence"><div class="compat-probe">App scenario <code>test/apps/vite-ssr-client-sdk</code> writes and reads the SSR user's own document, is denied another user's, reads the writes back with <code>firebase-admin</code> in SSR and in a process on the host, with each externalization setting, and refuses on a SharedWorker dev server. On main (ba2c0bf0a) the scenario failed: SSR sign-in rejected with <code>auth/network-request-failed</code> from the network guard, and the RTDB, Firestore and Storage writes did not settle, because the imports loaded the real SDK. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-module-swap.test.ts#L4" target="_blank" rel="noopener noreferrer"><code>vite-module-swap.test.ts</code></a> pins the routing per specifier, AI mode and environment (Vite 5's <code>ssr</code> option, a runnable environment, a non-runnable one), <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-ssr-activation.test.ts#L1" target="_blank" rel="noopener noreferrer"><code>vite-ssr-activation.test.ts</code></a> the host, the restart hand-over and the SharedWorker refusal, and <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-plugin-firestore.test.ts#L36" target="_blank" rel="noopener noreferrer"><code>vite-plugin-firestore.test.ts</code></a> runs Vite with <code>firebase</code> installed, so it takes the externalized path. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/test/apps/vite-ssr-client-sdk/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>)</div>
897
897
  <div class="compat-note">one SSR session per dev server process, as one Node server process has one auth state; per-request identity (<code>initializeServerApp</code>) is unsupported</div></div>
898
898
  </details>
899
899
  <details class="compat-row" data-status="diverged">
@@ -933,9 +933,9 @@ Known differences between Pyric and production Firebase. Each remains tracked as
933
933
  <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">Writes and reads in flight at a hosted connection drop (web hosted client)</code><span class="compat-sub"><span class="compat-behavior">A Firestore write (<code>setDoc</code>, <code>updateDoc</code>, <code>deleteDoc</code>, a batch <code>commit</code>) or read (<code>getDoc</code>, <code>getDocs</code>) the web hosted client's socket had taken when it dropped fails once with <code>unavailable</code> and the message "The hosted sandbox connection was lost. Requests already sent may have completed; check state before retrying." It is not sent again. In production the Firestore Web SDK keeps a write in its mutation queue and sends it again on the next write stream until the backend acknowledges it, and answers a <code>getDoc</code> from its local cache, with <code>fromCache</code>, or fails it with <code>unavailable</code> when the document is not cached.</span></span></span></summary>
934
934
  <div class="compat-evidence"><div class="compat-note">Production behavior read from <code>@firebase/firestore</code> 4.14.1, as recorded in ADR-0019 ("Reads and writes are treated alike, unlike production"). <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/worker/hosted-reconnect-hold.test.ts#L412" target="_blank" rel="noopener noreferrer"><code>packages/cli/test/serve/worker/hosted-reconnect-hold.test.ts</code></a> drops a real loopback socket with <code>setDoc</code>, <code>getDoc</code> and <code>batchCommit</code> in flight and asserts each fails once with that message and reaches the host once. The Realtime Database follows its SDK here instead (row <code>rtdb-modular#HD1</code>, #1088).</div></div></details>
935
935
  <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">getFirestore(app) in a registered server process</code><span class="compat-sub"><span class="compat-behavior">Under a remote register activation on the Node host, <code>firebase/firestore</code> in a server process reads and writes the host's documents under its rules as the process's signed-in user. An open listener keeps the process up; a finished write releases it at once. A CommonJS <code>require('firebase/firestore')</code> resolves the same client. An app whose options differ from the host's gets each call rejected with <code>app/multiple-configs-not-supported</code>, and nothing throws past the call. When the host is not the Node host, every call fails with <code>failed-precondition</code> and a message naming the Node host (<code>pyric sandbox --hosted</code> or <code>pyric({ hosted: true })</code>); when no host is found, with the discovery error <code>pyric-admin</code> gets. No call falls back to an in-process sandbox.</span></span></span></summary>
936
- <div class="compat-evidence"><div class="compat-note">App scenario <code>test/apps/client-sdk-under-register</code> runs the client SDKs under <code>node --import @pyric/cli/register</code> against a hosted Vite dev server, reads back with <code>firebase-admin</code> and a page, and asserts a write the page and firebase-admin read, a CommonJS require, an app with other options rejected per call, and that a listener holds the process while a finished write does not. On main (942cb175) it failed: the calls ran against a private in-process sandbox, so an upload returned a <a href="https://github.com/davideast/pyric/blob/main/test/apps/client-sdk-under-register/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>data:</code></a> URL and firebase-admin read nothing they wrote. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-origin.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>packages/cli/test/register/node-origin.test.ts</code></a> runs the served surfaces under a Node origin whose host is not the Node host, or cannot be found, and asserts every call fails, <code>useWorker</code> stays true, the served app entry's placeholder sandbox stays empty, and a <code>pyric/*</code> mirror resolved from the app refuses. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/mapping.test.ts#L141" target="_blank" rel="noopener noreferrer"><code>mapping.test.ts</code></a> pins the mapping per activation value.. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/register-child.test.ts#L201" target="_blank" rel="noopener noreferrer"><code>register-child.test.ts</code></a>)</div></div></details>
936
+ <div class="compat-evidence"><div class="compat-note">App scenario <code>test/apps/client-sdk-under-register</code> runs the client SDKs under <code>node --import @pyric/cli/register</code> against a hosted Vite dev server, reads back with <code>firebase-admin</code> and a page, and asserts a write the page and firebase-admin read, a CommonJS require, an app with other options rejected per call, and that a listener holds the process while a finished write does not. On main (942cb175) it failed: the calls ran against a private in-process sandbox, so an upload returned a <a href="https://github.com/davideast/pyric/blob/main/test/apps/client-sdk-under-register/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>data:</code></a> URL and firebase-admin read nothing they wrote. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-origin.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>packages/cli/test/register/node-origin.test.ts</code></a> runs the served surfaces under a Node origin whose host is not the Node host, or cannot be found, and asserts every call fails, <code>useWorker</code> stays true, the served app entry's placeholder sandbox stays empty, and a <code>pyric/*</code> mirror resolved from the app refuses. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/mapping.test.ts#L80" target="_blank" rel="noopener noreferrer"><code>mapping.test.ts</code></a> pins the mapping per activation value.. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/register-child.test.ts#L201" target="_blank" rel="noopener noreferrer"><code>register-child.test.ts</code></a>)</div></div></details>
937
937
  <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">getFirestore(app) in a server-rendered module under vite dev</code><span class="compat-sub"><span class="compat-behavior">On a hosted dev server, <code>firebase/firestore</code> in a server-rendered module reads and writes the host's documents under its rules as the SSR session's user: a write the rules deny for that user fails with <code>permission-denied</code>. <code>ssrLoadModule</code>, the environment runner and every <code>ssr.noExternal</code> setting share one app and one session. On a SharedWorker dev server every call fails with <code>failed-precondition</code> and a message naming <code>pyric({ hosted: true })</code>. No call reaches production or an in-process sandbox.</span></span></span></summary>
938
- <div class="compat-evidence"><div class="compat-note">App scenario <code>test/apps/vite-ssr-client-sdk</code> writes and reads the SSR user's own document, is denied another user's, reads the writes back with <code>firebase-admin</code> in SSR and in a process on the host, with each externalization setting, and refuses on a SharedWorker dev server. On main (ba2c0bf0a) the scenario failed: SSR sign-in rejected with <code>auth/network-request-failed</code> from the network guard, and the RTDB, Firestore and Storage writes did not settle, because the imports loaded the real SDK. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-module-swap.test.ts#L9" target="_blank" rel="noopener noreferrer"><code>vite-module-swap.test.ts</code></a> pins the routing per specifier, AI mode and environment (Vite 5's <code>ssr</code> option, a runnable environment, a non-runnable one), <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-ssr-activation.test.ts#L1" target="_blank" rel="noopener noreferrer"><code>vite-ssr-activation.test.ts</code></a> the host, the restart hand-over and the SharedWorker refusal, and <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-plugin-firestore.test.ts#L36" target="_blank" rel="noopener noreferrer"><code>vite-plugin-firestore.test.ts</code></a> runs Vite with <code>firebase</code> installed, so it takes the externalized path. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/test/apps/vite-ssr-client-sdk/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>)</div></div></details>
938
+ <div class="compat-evidence"><div class="compat-note">App scenario <code>test/apps/vite-ssr-client-sdk</code> writes and reads the SSR user's own document, is denied another user's, reads the writes back with <code>firebase-admin</code> in SSR and in a process on the host, with each externalization setting, and refuses on a SharedWorker dev server. On main (ba2c0bf0a) the scenario failed: SSR sign-in rejected with <code>auth/network-request-failed</code> from the network guard, and the RTDB, Firestore and Storage writes did not settle, because the imports loaded the real SDK. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-module-swap.test.ts#L4" target="_blank" rel="noopener noreferrer"><code>vite-module-swap.test.ts</code></a> pins the routing per specifier, AI mode and environment (Vite 5's <code>ssr</code> option, a runnable environment, a non-runnable one), <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-ssr-activation.test.ts#L1" target="_blank" rel="noopener noreferrer"><code>vite-ssr-activation.test.ts</code></a> the host, the restart hand-over and the SharedWorker refusal, and <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-plugin-firestore.test.ts#L36" target="_blank" rel="noopener noreferrer"><code>vite-plugin-firestore.test.ts</code></a> runs Vite with <code>firebase</code> installed, so it takes the externalized path. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/test/apps/vite-ssr-client-sdk/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>)</div></div></details>
939
939
  <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">firebase-admin/firestore in a server-rendered module under vite dev</code><span class="compat-sub"><span class="compat-behavior">On a hosted dev server, <code>firebase-admin/firestore</code> in a server-rendered module reads and writes the dev server host's documents as admin, past the rules, as a <code>firebase-admin</code> process on the host does. On a SharedWorker dev server its first call fails with <code>failed-precondition</code> and a message naming <code>pyric({ hosted: true })</code>.</span></span></span></summary>
940
940
  <div class="compat-evidence"><div class="compat-note">App scenario <code>test/apps/vite-ssr-client-sdk</code> reads a document the rules let only the SSR user read with <code>firebase-admin</code> in SSR through both loaders and with each externalization setting. On main (ba2c0bf0a) <code>firebase-admin</code> in SSR was the real SDK. <a href="https://github.com/davideast/pyric/blob/main/packages/pyric-admin/test/app/ambient-init.test.ts#L128" target="_blank" rel="noopener noreferrer"><code>ambient-init.test.ts</code></a> pins the ambient binding. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/test/apps/vite-ssr-client-sdk/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>)</div></div></details>
941
941
  </div>
@@ -307,12 +307,12 @@ html{scrollbar-gutter:stable}
307
307
  <div class="compat-list">
308
308
  <details class="compat-row" data-status="diverged">
309
309
  <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">getMessaging(app) in a registered server process</code><span class="compat-sub"><span class="compat-behavior">Under a remote register activation, <code>getMessaging(app)</code> in a server process fails with <code>failed-precondition</code> and a message that names the client services that do reach the Node host: <code>firebase/messaging</code> has no path to the host, and no in-process sandbox stands in for it.</span></span></span></summary>
310
- <div class="compat-evidence"><div class="compat-probe">App scenario <code>test/apps/client-sdk-under-register</code> calls it under <code>node --import @pyric/cli/register</code> against a hosted Vite dev server and asserts the refusal; on main (942cb175) it returned a handle on a private in-process sandbox. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/mapping.test.ts#L141" target="_blank" rel="noopener noreferrer"><code>mapping.test.ts</code></a> pins that the subpath keeps its mirror under a remote activation, and <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-origin.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>node-origin.test.ts</code></a> that a mirror resolved from such an app refuses. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/test/apps/client-sdk-under-register/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/messaging/oracle-conformance.test.ts#L479" target="_blank" rel="noopener noreferrer"><code>oracle-conformance.test.ts</code></a>)</div>
310
+ <div class="compat-evidence"><div class="compat-probe">App scenario <code>test/apps/client-sdk-under-register</code> calls it under <code>node --import @pyric/cli/register</code> against a hosted Vite dev server and asserts the refusal; on main (942cb175) it returned a handle on a private in-process sandbox. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/mapping.test.ts#L80" target="_blank" rel="noopener noreferrer"><code>mapping.test.ts</code></a> pins that the subpath keeps its mirror under a remote activation, and <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-origin.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>node-origin.test.ts</code></a> that a mirror resolved from such an app refuses. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/test/apps/client-sdk-under-register/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/messaging/oracle-conformance.test.ts#L479" target="_blank" rel="noopener noreferrer"><code>oracle-conformance.test.ts</code></a>)</div>
311
311
  <div class="compat-note">refused in a server process; production runs it in Node</div></div>
312
312
  </details>
313
313
  <details class="compat-row" data-status="diverged">
314
314
  <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">getMessaging(app) in a server-rendered module under vite dev</code><span class="compat-sub"><span class="compat-behavior">Under <code>vite dev</code>, <code>getMessaging(app)</code> in a server-rendered module fails with <code>failed-precondition</code>, naming the client services that reach the host: <code>firebase/messaging</code> has no path to the dev server's host from server code, and the real SDK does not load.</span></span></span></summary>
315
- <div class="compat-evidence"><div class="compat-probe">App scenario <code>test/apps/vite-ssr-client-sdk</code> calls it in SSR on a hosted dev server and asserts the refusal. On main (ba2c0bf0a) it was the real SDK. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-module-swap.test.ts#L9" target="_blank" rel="noopener noreferrer"><code>vite-module-swap.test.ts</code></a> pins the routing. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/test/apps/vite-ssr-client-sdk/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/messaging/oracle-conformance.test.ts#L493" target="_blank" rel="noopener noreferrer"><code>oracle-conformance.test.ts</code></a>)</div>
315
+ <div class="compat-evidence"><div class="compat-probe">App scenario <code>test/apps/vite-ssr-client-sdk</code> calls it in SSR on a hosted dev server and asserts the refusal. On main (ba2c0bf0a) it was the real SDK. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-module-swap.test.ts#L4" target="_blank" rel="noopener noreferrer"><code>vite-module-swap.test.ts</code></a> pins the routing. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/test/apps/vite-ssr-client-sdk/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/messaging/oracle-conformance.test.ts#L493" target="_blank" rel="noopener noreferrer"><code>oracle-conformance.test.ts</code></a>)</div>
316
316
  <div class="compat-note">refused in server-rendered modules under vite dev; production runs it in the server's runtime</div></div>
317
317
  </details>
318
318
  </div>
@@ -321,9 +321,9 @@ html{scrollbar-gutter:stable}
321
321
  <p>Known differences between Pyric and production Firebase. Each remains tracked as a non-conforming row.</p>
322
322
  <div class="compat-list">
323
323
  <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">getMessaging(app) in a registered server process</code><span class="compat-sub"><span class="compat-behavior">Under a remote register activation, <code>getMessaging(app)</code> in a server process fails with <code>failed-precondition</code> and a message that names the client services that do reach the Node host: <code>firebase/messaging</code> has no path to the host, and no in-process sandbox stands in for it.</span></span></span></summary>
324
- <div class="compat-evidence"><div class="compat-note">App scenario <code>test/apps/client-sdk-under-register</code> calls it under <code>node --import @pyric/cli/register</code> against a hosted Vite dev server and asserts the refusal; on main (942cb175) it returned a handle on a private in-process sandbox. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/mapping.test.ts#L141" target="_blank" rel="noopener noreferrer"><code>mapping.test.ts</code></a> pins that the subpath keeps its mirror under a remote activation, and <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-origin.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>node-origin.test.ts</code></a> that a mirror resolved from such an app refuses. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/test/apps/client-sdk-under-register/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/messaging/oracle-conformance.test.ts#L479" target="_blank" rel="noopener noreferrer"><code>oracle-conformance.test.ts</code></a>)</div></div></details>
324
+ <div class="compat-evidence"><div class="compat-note">App scenario <code>test/apps/client-sdk-under-register</code> calls it under <code>node --import @pyric/cli/register</code> against a hosted Vite dev server and asserts the refusal; on main (942cb175) it returned a handle on a private in-process sandbox. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/mapping.test.ts#L80" target="_blank" rel="noopener noreferrer"><code>mapping.test.ts</code></a> pins that the subpath keeps its mirror under a remote activation, and <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-origin.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>node-origin.test.ts</code></a> that a mirror resolved from such an app refuses. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/test/apps/client-sdk-under-register/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/messaging/oracle-conformance.test.ts#L479" target="_blank" rel="noopener noreferrer"><code>oracle-conformance.test.ts</code></a>)</div></div></details>
325
325
  <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">getMessaging(app) in a server-rendered module under vite dev</code><span class="compat-sub"><span class="compat-behavior">Under <code>vite dev</code>, <code>getMessaging(app)</code> in a server-rendered module fails with <code>failed-precondition</code>, naming the client services that reach the host: <code>firebase/messaging</code> has no path to the dev server's host from server code, and the real SDK does not load.</span></span></span></summary>
326
- <div class="compat-evidence"><div class="compat-note">App scenario <code>test/apps/vite-ssr-client-sdk</code> calls it in SSR on a hosted dev server and asserts the refusal. On main (ba2c0bf0a) it was the real SDK. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-module-swap.test.ts#L9" target="_blank" rel="noopener noreferrer"><code>vite-module-swap.test.ts</code></a> pins the routing. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/test/apps/vite-ssr-client-sdk/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/messaging/oracle-conformance.test.ts#L493" target="_blank" rel="noopener noreferrer"><code>oracle-conformance.test.ts</code></a>)</div></div></details>
326
+ <div class="compat-evidence"><div class="compat-note">App scenario <code>test/apps/vite-ssr-client-sdk</code> calls it in SSR on a hosted dev server and asserts the refusal. On main (ba2c0bf0a) it was the real SDK. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-module-swap.test.ts#L4" target="_blank" rel="noopener noreferrer"><code>vite-module-swap.test.ts</code></a> pins the routing. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/test/apps/vite-ssr-client-sdk/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/messaging/oracle-conformance.test.ts#L493" target="_blank" rel="noopener noreferrer"><code>oracle-conformance.test.ts</code></a>)</div></div></details>
327
327
  </div>
328
328
  <h3 id="not-implemented-yet">Not implemented yet</h3>
329
329
  <p>Tracked behavior that is not implemented in the current contract.</p>
@@ -330,12 +330,12 @@
330
330
  <div class="compat-list">
331
331
  <details class="compat-row" data-status="diverged">
332
332
  <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">getMessaging(app) in a registered server process</code><span class="compat-sub"><span class="compat-behavior">Under a remote register activation, <code>getMessaging(app)</code> in a server process fails with <code>failed-precondition</code> and a message that names the client services that do reach the Node host: <code>firebase/messaging</code> has no path to the host, and no in-process sandbox stands in for it.</span></span></span></summary>
333
- <div class="compat-evidence"><div class="compat-probe">App scenario <code>test/apps/client-sdk-under-register</code> calls it under <code>node --import @pyric/cli/register</code> against a hosted Vite dev server and asserts the refusal; on main (942cb175) it returned a handle on a private in-process sandbox. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/mapping.test.ts#L141" target="_blank" rel="noopener noreferrer"><code>mapping.test.ts</code></a> pins that the subpath keeps its mirror under a remote activation, and <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-origin.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>node-origin.test.ts</code></a> that a mirror resolved from such an app refuses. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/test/apps/client-sdk-under-register/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/messaging/oracle-conformance.test.ts#L479" target="_blank" rel="noopener noreferrer"><code>oracle-conformance.test.ts</code></a>)</div>
333
+ <div class="compat-evidence"><div class="compat-probe">App scenario <code>test/apps/client-sdk-under-register</code> calls it under <code>node --import @pyric/cli/register</code> against a hosted Vite dev server and asserts the refusal; on main (942cb175) it returned a handle on a private in-process sandbox. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/mapping.test.ts#L80" target="_blank" rel="noopener noreferrer"><code>mapping.test.ts</code></a> pins that the subpath keeps its mirror under a remote activation, and <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-origin.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>node-origin.test.ts</code></a> that a mirror resolved from such an app refuses. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/test/apps/client-sdk-under-register/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/messaging/oracle-conformance.test.ts#L479" target="_blank" rel="noopener noreferrer"><code>oracle-conformance.test.ts</code></a>)</div>
334
334
  <div class="compat-note">refused in a server process; production runs it in Node</div></div>
335
335
  </details>
336
336
  <details class="compat-row" data-status="diverged">
337
337
  <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">getMessaging(app) in a server-rendered module under vite dev</code><span class="compat-sub"><span class="compat-behavior">Under <code>vite dev</code>, <code>getMessaging(app)</code> in a server-rendered module fails with <code>failed-precondition</code>, naming the client services that reach the host: <code>firebase/messaging</code> has no path to the dev server's host from server code, and the real SDK does not load.</span></span></span></summary>
338
- <div class="compat-evidence"><div class="compat-probe">App scenario <code>test/apps/vite-ssr-client-sdk</code> calls it in SSR on a hosted dev server and asserts the refusal. On main (ba2c0bf0a) it was the real SDK. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-module-swap.test.ts#L9" target="_blank" rel="noopener noreferrer"><code>vite-module-swap.test.ts</code></a> pins the routing. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/test/apps/vite-ssr-client-sdk/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/messaging/oracle-conformance.test.ts#L493" target="_blank" rel="noopener noreferrer"><code>oracle-conformance.test.ts</code></a>)</div>
338
+ <div class="compat-evidence"><div class="compat-probe">App scenario <code>test/apps/vite-ssr-client-sdk</code> calls it in SSR on a hosted dev server and asserts the refusal. On main (ba2c0bf0a) it was the real SDK. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-module-swap.test.ts#L4" target="_blank" rel="noopener noreferrer"><code>vite-module-swap.test.ts</code></a> pins the routing. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/test/apps/vite-ssr-client-sdk/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/messaging/oracle-conformance.test.ts#L493" target="_blank" rel="noopener noreferrer"><code>oracle-conformance.test.ts</code></a>)</div>
339
339
  <div class="compat-note">refused in server-rendered modules under vite dev; production runs it in the server's runtime</div></div>
340
340
  </details>
341
341
  </div>
@@ -348,9 +348,9 @@ Known differences between Pyric and production Firebase. Each remains tracked as
348
348
 
349
349
  <div class="compat-list">
350
350
  <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">getMessaging(app) in a registered server process</code><span class="compat-sub"><span class="compat-behavior">Under a remote register activation, <code>getMessaging(app)</code> in a server process fails with <code>failed-precondition</code> and a message that names the client services that do reach the Node host: <code>firebase/messaging</code> has no path to the host, and no in-process sandbox stands in for it.</span></span></span></summary>
351
- <div class="compat-evidence"><div class="compat-note">App scenario <code>test/apps/client-sdk-under-register</code> calls it under <code>node --import @pyric/cli/register</code> against a hosted Vite dev server and asserts the refusal; on main (942cb175) it returned a handle on a private in-process sandbox. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/mapping.test.ts#L141" target="_blank" rel="noopener noreferrer"><code>mapping.test.ts</code></a> pins that the subpath keeps its mirror under a remote activation, and <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-origin.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>node-origin.test.ts</code></a> that a mirror resolved from such an app refuses. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/test/apps/client-sdk-under-register/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/messaging/oracle-conformance.test.ts#L479" target="_blank" rel="noopener noreferrer"><code>oracle-conformance.test.ts</code></a>)</div></div></details>
351
+ <div class="compat-evidence"><div class="compat-note">App scenario <code>test/apps/client-sdk-under-register</code> calls it under <code>node --import @pyric/cli/register</code> against a hosted Vite dev server and asserts the refusal; on main (942cb175) it returned a handle on a private in-process sandbox. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/mapping.test.ts#L80" target="_blank" rel="noopener noreferrer"><code>mapping.test.ts</code></a> pins that the subpath keeps its mirror under a remote activation, and <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/register/node-origin.test.ts#L2" target="_blank" rel="noopener noreferrer"><code>node-origin.test.ts</code></a> that a mirror resolved from such an app refuses. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/test/apps/client-sdk-under-register/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/messaging/oracle-conformance.test.ts#L479" target="_blank" rel="noopener noreferrer"><code>oracle-conformance.test.ts</code></a>)</div></div></details>
352
352
  <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">getMessaging(app) in a server-rendered module under vite dev</code><span class="compat-sub"><span class="compat-behavior">Under <code>vite dev</code>, <code>getMessaging(app)</code> in a server-rendered module fails with <code>failed-precondition</code>, naming the client services that reach the host: <code>firebase/messaging</code> has no path to the dev server's host from server code, and the real SDK does not load.</span></span></span></summary>
353
- <div class="compat-evidence"><div class="compat-note">App scenario <code>test/apps/vite-ssr-client-sdk</code> calls it in SSR on a hosted dev server and asserts the refusal. On main (ba2c0bf0a) it was the real SDK. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-module-swap.test.ts#L9" target="_blank" rel="noopener noreferrer"><code>vite-module-swap.test.ts</code></a> pins the routing. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/test/apps/vite-ssr-client-sdk/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/messaging/oracle-conformance.test.ts#L493" target="_blank" rel="noopener noreferrer"><code>oracle-conformance.test.ts</code></a>)</div></div></details>
353
+ <div class="compat-evidence"><div class="compat-note">App scenario <code>test/apps/vite-ssr-client-sdk</code> calls it in SSR on a hosted dev server and asserts the refusal. On main (ba2c0bf0a) it was the real SDK. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/vite-module-swap.test.ts#L4" target="_blank" rel="noopener noreferrer"><code>vite-module-swap.test.ts</code></a> pins the routing. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/test/apps/vite-ssr-client-sdk/scenario.ts#L1" target="_blank" rel="noopener noreferrer"><code>scenario.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/pyric/test/messaging/oracle-conformance.test.ts#L493" target="_blank" rel="noopener noreferrer"><code>oracle-conformance.test.ts</code></a>)</div></div></details>
354
354
  </div>
355
355
 
356
356
  ### Not implemented yet
@@ -121,6 +121,10 @@ application code asks the development sandbox for production credentials.</p>
121
121
  <p>Link-compatible mirror of <code>firebase-admin/app</code>’s <code>cert(serviceAccountPathOrObject)</code>.
122
122
  Returns an inert sandbox credential token so unchanged application setup code
123
123
  can assign <code>appOptions.credential = cert(...)</code> under <code>@pyric/cli/register</code>.</p>
124
+ <p>It keeps one field, the service account’s project id, because firebase-admin
125
+ finds an app’s project there when the options name none. A path is read as a
126
+ JSON file; a file that is missing, unreadable, or has no project id gives a
127
+ credential without one, and never throws, so apps run without a key on disk.</p>
124
128
  <h4 id="parameters">Parameters</h4>
125
129
  <div class="table-scroll">
126
130