@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
@@ -129,6 +129,11 @@ Link-compatible mirror of `firebase-admin/app`'s `cert(serviceAccountPathOrObjec
129
129
  Returns an inert sandbox credential token so unchanged application setup code
130
130
  can assign `appOptions.credential = cert(...)` under `@pyric/cli/register`.
131
131
 
132
+ It keeps one field, the service account's project id, because firebase-admin
133
+ finds an app's project there when the options name none. A path is read as a
134
+ JSON file; a file that is missing, unreadable, or has no project id gives a
135
+ credential without one, and never throws, so apps run without a key on disk.
136
+
132
137
  #### Parameters
133
138
 
134
139
  | Parameter | Type |
@@ -39,6 +39,8 @@ Generated from the TypeScript declarations shipped at this import path.
39
39
  <pre class="astro-code one-dark-pro" style="background-color:#282c34;color:#abb2bf; overflow-x: auto;" tabindex="0" data-language="ts"><code><span class="line"><span style="color:#C678DD">function</span><span style="color:#61AFEF"> initializeAppCheck</span><span style="color:#ABB2BF">(</span><span style="color:#E06C75;font-style:italic">app</span><span style="color:#ABB2BF">: {</span></span>
40
40
  <span class="line"><span style="color:#E06C75"> name</span><span style="color:#ABB2BF">: </span><span style="color:#E5C07B">string</span><span style="color:#ABB2BF">;</span></span>
41
41
  <span class="line"><span style="color:#ABB2BF">}, </span><span style="color:#E06C75;font-style:italic">options</span><span style="color:#ABB2BF">: </span><span style="color:#E5C07B">unknown</span><span style="color:#ABB2BF">): </span><span style="color:#E5C07B">unknown</span><span style="color:#ABB2BF">;</span></span></code></pre>
42
+ <p><code>shadowAppOf</code> throws when the shadow app could not be created, so that
43
+ cause surfaces here before the SDK would fail on a missing container.</p>
42
44
  <h4 id="parameters">Parameters</h4>
43
45
  <div class="table-scroll">
44
46
 
@@ -82,6 +82,9 @@ function initializeAppCheck(app: {
82
82
  }, options: unknown): unknown;
83
83
  ```
84
84
 
85
+ `shadowAppOf` throws when the shadow app could not be created, so that
86
+ cause surfaces here before the SDK would fail on a missing container.
87
+
85
88
  #### Parameters
86
89
 
87
90
  | Parameter | Type |
@@ -77,7 +77,7 @@ html{scrollbar-gutter:stable}
77
77
  </details>
78
78
  <details class="compat-row" data-status="ok">
79
79
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">getStorage(app, bucketUrl?)<code> / </code>getStorageSandbox(target, options?)</code><span class="compat-sub"><span class="compat-behavior">The served <code>firebase/storage</code> entry accepts bare <code>getStorage()</code> and returns the page's shared sandbox handle</span></span></span></summary>
80
- <div class="compat-evidence"><div class="compat-probe">The canonical served entry supplies the page sandbox when the app argument is omitted; the entry-path and bundler suites execute the public package shape. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/entry-path/storage.ts#L16" target="_blank" rel="noopener noreferrer"><code>storage.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/bundler.test.ts#L459" target="_blank" rel="noopener noreferrer"><code>bundler.test.ts</code></a>)</div></div>
80
+ <div class="compat-evidence"><div class="compat-probe">The canonical served entry supplies the page sandbox when the app argument is omitted; the entry-path and bundler suites execute the public package shape. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/entry-path/storage.ts#L16" target="_blank" rel="noopener noreferrer"><code>storage.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/bundler.test.ts#L546" target="_blank" rel="noopener noreferrer"><code>bundler.test.ts</code></a>)</div></div>
81
81
  </details>
82
82
  </div>
83
83
  <h2 id="refstorage-path--refparent-path--reference-constructor"><code>ref(storage[, path])</code> / <code>ref(parent, path)</code> — reference constructor</h2>
@@ -564,7 +564,7 @@ API) moved to the native <code>storage-rules</code> surface (<code>docs/rules/CO
564
564
  <div class="compat-list">
565
565
  <details class="compat-row" data-status="diverged">
566
566
  <summary class="compat-line"><span class="compat-dot" data-status="diverged" role="img" aria-label="Diverged (documented)" title="Diverged (documented)"></span><span class="compat-main"><code class="compat-api">getStorage(app) 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/storage</code> in a server process uploads and reads the host's objects under its rules, over the host's byte route. An upload issued before the process's connection has attached waits for the attachment, keeps its place in the call order, and never sends its bytes as frames to a host that takes them only over the route; it fails as a held call does if the connection is not restored within 10 seconds. A finished upload does not keep the 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>
567
- <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 an upload issued as the process's first call that firebase-admin downloads, its HTTP download URL, and that a finished upload does not hold the process. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/worker/storage-byte-route-settled.test.ts#L11" target="_blank" rel="noopener noreferrer"><code>storage-byte-route-settled.test.ts</code></a> pins the wait, the call order and the deadline. 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#L593" target="_blank" rel="noopener noreferrer"><code>register-child.test.ts</code></a>)</div>
567
+ <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 an upload issued as the process's first call that firebase-admin downloads, its HTTP download URL, and that a finished upload does not hold the process. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/worker/storage-byte-route-settled.test.ts#L11" target="_blank" rel="noopener noreferrer"><code>storage-byte-route-settled.test.ts</code></a> pins the wait, the call order and the deadline. 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#L593" target="_blank" rel="noopener noreferrer"><code>register-child.test.ts</code></a>)</div>
568
568
  <div class="compat-note">a resumable upload issued before the connection attaches reports its progress in one step on a byte-route host</div></div>
569
569
  </details>
570
570
  </div>
@@ -573,7 +573,7 @@ API) moved to the native <code>storage-rules</code> surface (<code>docs/rules/CO
573
573
  <div class="compat-list">
574
574
  <details class="compat-row" data-status="diverged">
575
575
  <summary class="compat-line"><span class="compat-dot" data-status="diverged" role="img" aria-label="Diverged (documented)" title="Diverged (documented)"></span><span class="compat-main"><code class="compat-api">getStorage(app) in a server-rendered module under vite dev</code><span class="compat-sub"><span class="compat-behavior">On a hosted dev server, <code>firebase/storage</code> in a server-rendered module uploads and reads the host's objects under its rules as the SSR session's user, over the host's byte route: an upload to another user's path fails with <code>storage/unauthorized</code>, and a download URL names the host's byte route. 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>
576
- <div class="compat-evidence"><div class="compat-probe">App scenario <code>test/apps/vite-ssr-client-sdk</code> uploads to the SSR user's own path, reads its download URL, is denied another user's path, and downloads the object with <code>firebase-admin</code> in a process on the host. 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. (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>
576
+ <div class="compat-evidence"><div class="compat-probe">App scenario <code>test/apps/vite-ssr-client-sdk</code> uploads to the SSR user's own path, reads its download URL, is denied another user's path, and downloads the object with <code>firebase-admin</code> in a process on the host. 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. (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>
577
577
  <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>
578
578
  </details>
579
579
  </div>
@@ -616,9 +616,9 @@ API) moved to the native <code>storage-rules</code> surface (<code>docs/rules/CO
616
616
  <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">Operations in flight at a hosted connection drop (web hosted client)</code><span class="compat-sub"><span class="compat-behavior">A Storage operation (<code>getMetadata</code>, <code>deleteObject</code>, <code>list</code>, or an upload step) 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 Storage Web SDK retries a request that fails on the network with exponential backoff, for up to 2 minutes for an operation and 10 minutes for an upload, and then fails it with <code>storage/retry-limit-exceeded</code>.</span></span></span></summary>
617
617
  <div class="compat-evidence"><div class="compat-note">Production behavior read from <code>@firebase/storage</code> 0.14.3 <code>dist/index.esm.js</code>: <code>DEFAULT_MAX_OPERATION_RETRY_TIME</code> (2 minutes), <code>DEFAULT_MAX_UPLOAD_RETRY_TIME</code> (10 minutes), and <code>retryLimitExceeded()</code> in the request retry loop. <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>storage.getMetadata</code> and <code>storage.deleteObject</code> in flight and asserts each fails once with that message and reaches the host once.</div></div></details>
618
618
  <details class="compat-row" data-status="diverged"><summary class="compat-line"><span class="compat-dot" data-status="diverged" role="img" aria-label="Diverged (documented)" title="Diverged (documented)"></span><span class="compat-main"><code class="compat-api">getStorage(app) 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/storage</code> in a server process uploads and reads the host's objects under its rules, over the host's byte route. An upload issued before the process's connection has attached waits for the attachment, keeps its place in the call order, and never sends its bytes as frames to a host that takes them only over the route; it fails as a held call does if the connection is not restored within 10 seconds. A finished upload does not keep the 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>
619
- <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 an upload issued as the process's first call that firebase-admin downloads, its HTTP download URL, and that a finished upload does not hold the process. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/worker/storage-byte-route-settled.test.ts#L11" target="_blank" rel="noopener noreferrer"><code>storage-byte-route-settled.test.ts</code></a> pins the wait, the call order and the deadline. 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#L593" target="_blank" rel="noopener noreferrer"><code>register-child.test.ts</code></a>)</div></div></details>
619
+ <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 an upload issued as the process's first call that firebase-admin downloads, its HTTP download URL, and that a finished upload does not hold the process. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/worker/storage-byte-route-settled.test.ts#L11" target="_blank" rel="noopener noreferrer"><code>storage-byte-route-settled.test.ts</code></a> pins the wait, the call order and the deadline. 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#L593" target="_blank" rel="noopener noreferrer"><code>register-child.test.ts</code></a>)</div></div></details>
620
620
  <details class="compat-row" data-status="diverged"><summary class="compat-line"><span class="compat-dot" data-status="diverged" role="img" aria-label="Diverged (documented)" title="Diverged (documented)"></span><span class="compat-main"><code class="compat-api">getStorage(app) in a server-rendered module under vite dev</code><span class="compat-sub"><span class="compat-behavior">On a hosted dev server, <code>firebase/storage</code> in a server-rendered module uploads and reads the host's objects under its rules as the SSR session's user, over the host's byte route: an upload to another user's path fails with <code>storage/unauthorized</code>, and a download URL names the host's byte route. 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>
621
- <div class="compat-evidence"><div class="compat-note">App scenario <code>test/apps/vite-ssr-client-sdk</code> uploads to the SSR user's own path, reads its download URL, is denied another user's path, and downloads the object with <code>firebase-admin</code> in a process on the host. 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. (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>
621
+ <div class="compat-evidence"><div class="compat-note">App scenario <code>test/apps/vite-ssr-client-sdk</code> uploads to the SSR user's own path, reads its download URL, is denied another user's path, and downloads the object with <code>firebase-admin</code> in a process on the host. 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. (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>
622
622
  </div>
623
623
  <h3 id="not-implemented-yet">Not implemented yet</h3>
624
624
  <p>Tracked behavior that is not implemented in the current contract.</p>
@@ -80,7 +80,7 @@
80
80
  </details>
81
81
  <details class="compat-row" data-status="ok">
82
82
  <summary class="compat-line"><span class="compat-dot" data-status="ok" role="img" aria-label="Conforming" title="Conforming"></span><span class="compat-main"><code class="compat-api">getStorage(app, bucketUrl?)<code> / </code>getStorageSandbox(target, options?)</code><span class="compat-sub"><span class="compat-behavior">The served <code>firebase/storage</code> entry accepts bare <code>getStorage()</code> and returns the page's shared sandbox handle</span></span></span></summary>
83
- <div class="compat-evidence"><div class="compat-probe">The canonical served entry supplies the page sandbox when the app argument is omitted; the entry-path and bundler suites execute the public package shape. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/entry-path/storage.ts#L16" target="_blank" rel="noopener noreferrer"><code>storage.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/bundler.test.ts#L459" target="_blank" rel="noopener noreferrer"><code>bundler.test.ts</code></a>)</div></div>
83
+ <div class="compat-evidence"><div class="compat-probe">The canonical served entry supplies the page sandbox when the app argument is omitted; the entry-path and bundler suites execute the public package shape. (Structured evidence: <a href="https://github.com/davideast/pyric/blob/main/packages/conformance/entry-path/storage.ts#L16" target="_blank" rel="noopener noreferrer"><code>storage.ts</code></a>, <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/bundler.test.ts#L546" target="_blank" rel="noopener noreferrer"><code>bundler.test.ts</code></a>)</div></div>
84
84
  </details>
85
85
  </div>
86
86
 
@@ -608,7 +608,7 @@ A server process started with `node --import @pyric/cli/register` and `PYRIC_SAN
608
608
  <div class="compat-list">
609
609
  <details class="compat-row" data-status="diverged">
610
610
  <summary class="compat-line"><span class="compat-dot" data-status="diverged" role="img" aria-label="Diverged (documented)" title="Diverged (documented)"></span><span class="compat-main"><code class="compat-api">getStorage(app) 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/storage</code> in a server process uploads and reads the host's objects under its rules, over the host's byte route. An upload issued before the process's connection has attached waits for the attachment, keeps its place in the call order, and never sends its bytes as frames to a host that takes them only over the route; it fails as a held call does if the connection is not restored within 10 seconds. A finished upload does not keep the 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>
611
- <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 an upload issued as the process's first call that firebase-admin downloads, its HTTP download URL, and that a finished upload does not hold the process. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/worker/storage-byte-route-settled.test.ts#L11" target="_blank" rel="noopener noreferrer"><code>storage-byte-route-settled.test.ts</code></a> pins the wait, the call order and the deadline. 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#L593" target="_blank" rel="noopener noreferrer"><code>register-child.test.ts</code></a>)</div>
611
+ <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 an upload issued as the process's first call that firebase-admin downloads, its HTTP download URL, and that a finished upload does not hold the process. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/worker/storage-byte-route-settled.test.ts#L11" target="_blank" rel="noopener noreferrer"><code>storage-byte-route-settled.test.ts</code></a> pins the wait, the call order and the deadline. 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#L593" target="_blank" rel="noopener noreferrer"><code>register-child.test.ts</code></a>)</div>
612
612
  <div class="compat-note">a resumable upload issued before the connection attaches reports its progress in one step on a byte-route host</div></div>
613
613
  </details>
614
614
  </div>
@@ -620,7 +620,7 @@ Modules Vite loads for server rendering under `vite dev` with `pyric()` (`server
620
620
  <div class="compat-list">
621
621
  <details class="compat-row" data-status="diverged">
622
622
  <summary class="compat-line"><span class="compat-dot" data-status="diverged" role="img" aria-label="Diverged (documented)" title="Diverged (documented)"></span><span class="compat-main"><code class="compat-api">getStorage(app) in a server-rendered module under vite dev</code><span class="compat-sub"><span class="compat-behavior">On a hosted dev server, <code>firebase/storage</code> in a server-rendered module uploads and reads the host's objects under its rules as the SSR session's user, over the host's byte route: an upload to another user's path fails with <code>storage/unauthorized</code>, and a download URL names the host's byte route. 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>
623
- <div class="compat-evidence"><div class="compat-probe">App scenario <code>test/apps/vite-ssr-client-sdk</code> uploads to the SSR user's own path, reads its download URL, is denied another user's path, and downloads the object with <code>firebase-admin</code> in a process on the host. 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. (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>
623
+ <div class="compat-evidence"><div class="compat-probe">App scenario <code>test/apps/vite-ssr-client-sdk</code> uploads to the SSR user's own path, reads its download URL, is denied another user's path, and downloads the object with <code>firebase-admin</code> in a process on the host. 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. (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>
624
624
  <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>
625
625
  </details>
626
626
  </div>
@@ -667,9 +667,9 @@ Known differences between Pyric and production Firebase. Each remains tracked as
667
667
  <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">Operations in flight at a hosted connection drop (web hosted client)</code><span class="compat-sub"><span class="compat-behavior">A Storage operation (<code>getMetadata</code>, <code>deleteObject</code>, <code>list</code>, or an upload step) 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 Storage Web SDK retries a request that fails on the network with exponential backoff, for up to 2 minutes for an operation and 10 minutes for an upload, and then fails it with <code>storage/retry-limit-exceeded</code>.</span></span></span></summary>
668
668
  <div class="compat-evidence"><div class="compat-note">Production behavior read from <code>@firebase/storage</code> 0.14.3 <code>dist/index.esm.js</code>: <code>DEFAULT_MAX_OPERATION_RETRY_TIME</code> (2 minutes), <code>DEFAULT_MAX_UPLOAD_RETRY_TIME</code> (10 minutes), and <code>retryLimitExceeded()</code> in the request retry loop. <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>storage.getMetadata</code> and <code>storage.deleteObject</code> in flight and asserts each fails once with that message and reaches the host once.</div></div></details>
669
669
  <details class="compat-row" data-status="diverged"><summary class="compat-line"><span class="compat-dot" data-status="diverged" role="img" aria-label="Diverged (documented)" title="Diverged (documented)"></span><span class="compat-main"><code class="compat-api">getStorage(app) 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/storage</code> in a server process uploads and reads the host's objects under its rules, over the host's byte route. An upload issued before the process's connection has attached waits for the attachment, keeps its place in the call order, and never sends its bytes as frames to a host that takes them only over the route; it fails as a held call does if the connection is not restored within 10 seconds. A finished upload does not keep the 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>
670
- <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 an upload issued as the process's first call that firebase-admin downloads, its HTTP download URL, and that a finished upload does not hold the process. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/worker/storage-byte-route-settled.test.ts#L11" target="_blank" rel="noopener noreferrer"><code>storage-byte-route-settled.test.ts</code></a> pins the wait, the call order and the deadline. 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#L593" target="_blank" rel="noopener noreferrer"><code>register-child.test.ts</code></a>)</div></div></details>
670
+ <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 an upload issued as the process's first call that firebase-admin downloads, its HTTP download URL, and that a finished upload does not hold the process. <a href="https://github.com/davideast/pyric/blob/main/packages/cli/test/serve/worker/storage-byte-route-settled.test.ts#L11" target="_blank" rel="noopener noreferrer"><code>storage-byte-route-settled.test.ts</code></a> pins the wait, the call order and the deadline. 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#L593" target="_blank" rel="noopener noreferrer"><code>register-child.test.ts</code></a>)</div></div></details>
671
671
  <details class="compat-row" data-status="diverged"><summary class="compat-line"><span class="compat-dot" data-status="diverged" role="img" aria-label="Diverged (documented)" title="Diverged (documented)"></span><span class="compat-main"><code class="compat-api">getStorage(app) in a server-rendered module under vite dev</code><span class="compat-sub"><span class="compat-behavior">On a hosted dev server, <code>firebase/storage</code> in a server-rendered module uploads and reads the host's objects under its rules as the SSR session's user, over the host's byte route: an upload to another user's path fails with <code>storage/unauthorized</code>, and a download URL names the host's byte route. 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>
672
- <div class="compat-evidence"><div class="compat-note">App scenario <code>test/apps/vite-ssr-client-sdk</code> uploads to the SSR user's own path, reads its download URL, is denied another user's path, and downloads the object with <code>firebase-admin</code> in a process on the host. 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. (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>
672
+ <div class="compat-evidence"><div class="compat-note">App scenario <code>test/apps/vite-ssr-client-sdk</code> uploads to the SSR user's own path, reads its download URL, is denied another user's path, and downloads the object with <code>firebase-admin</code> in a process on the host. 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. (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>
673
673
  </div>
674
674
 
675
675
  ### Not implemented yet
@@ -1,4 +1,4 @@
1
1
  <!DOCTYPE html><html lang="en"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover"><title>Play chess against Security Rules</title><link rel="icon" type="image/svg+xml" href="/__pyric/ui/pyric-logo.svg"><link rel="preconnect" href="https://use.typekit.net" crossorigin><link rel="stylesheet" href="https://use.typekit.net/jwb3ycu.css"><link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet"><style>html{scrollbar-gutter:stable}
2
2
  </style>
3
3
  <link rel="stylesheet" href="/__pyric/ui/_astro/_example_.B9DG9HLJ.css">
4
- <link rel="stylesheet" href="/__pyric/ui/_astro/app.8TtSeQQX.css"></head> <body> <style>astro-island,astro-slot,astro-static-slot{display:contents}</style><script>(()=>{var e=async t=>{await(await t())()};(self.Astro||(self.Astro={})).only=e;window.dispatchEvent(new Event("astro:only"));})();</script><script>(()=>{var A=Object.defineProperty;var g=(i,o,a)=>o in i?A(i,o,{enumerable:!0,configurable:!0,writable:!0,value:a}):i[o]=a;var d=(i,o,a)=>g(i,typeof o!="symbol"?o+"":o,a);{let i={0:t=>m(t),1:t=>a(t),2:t=>new RegExp(t),3:t=>new Date(t),4:t=>new Map(a(t)),5:t=>new Set(a(t)),6:t=>BigInt(t),7:t=>new URL(t),8:t=>new Uint8Array(t),9:t=>new Uint16Array(t),10:t=>new Uint32Array(t),11:t=>1/0*t},o=t=>{let[l,e]=t;return l in i?i[l](e):void 0},a=t=>t.map(o),m=t=>typeof t!="object"||t===null?t:Object.fromEntries(Object.entries(t).map(([l,e])=>[l,o(e)]));class y extends HTMLElement{constructor(){super(...arguments);d(this,"Component");d(this,"hydrator");d(this,"hydrate",async()=>{var b;if(!this.hydrator||!this.isConnected)return;let e=(b=this.parentElement)==null?void 0:b.closest("astro-island[ssr]");if(e){e.addEventListener("astro:hydrate",this.hydrate,{once:!0});return}let c=this.querySelectorAll("astro-slot"),n={},h=this.querySelectorAll("template[data-astro-template]");for(let r of h){let s=r.closest(this.tagName);s!=null&&s.isSameNode(this)&&(n[r.getAttribute("data-astro-template")||"default"]=r.innerHTML,r.remove())}for(let r of c){let s=r.closest(this.tagName);s!=null&&s.isSameNode(this)&&(n[r.getAttribute("name")||"default"]=r.innerHTML)}let p;try{p=this.hasAttribute("props")?m(JSON.parse(this.getAttribute("props"))):{}}catch(r){let s=this.getAttribute("component-url")||"<unknown>",v=this.getAttribute("component-export");throw v&&(s+=` (export ${v})`),console.error(`[hydrate] Error parsing props for component ${s}`,this.getAttribute("props"),r),r}let u;await this.hydrator(this)(this.Component,p,n,{client:this.getAttribute("client")}),this.removeAttribute("ssr"),this.dispatchEvent(new CustomEvent("astro:hydrate"))});d(this,"unmount",()=>{this.isConnected||this.dispatchEvent(new CustomEvent("astro:unmount"))})}disconnectedCallback(){document.removeEventListener("astro:after-swap",this.unmount),document.addEventListener("astro:after-swap",this.unmount,{once:!0})}connectedCallback(){if(!this.hasAttribute("await-children")||document.readyState==="interactive"||document.readyState==="complete")this.childrenConnectedCallback();else{let e=()=>{document.removeEventListener("DOMContentLoaded",e),c.disconnect(),this.childrenConnectedCallback()},c=new MutationObserver(()=>{var n;((n=this.lastChild)==null?void 0:n.nodeType)===Node.COMMENT_NODE&&this.lastChild.nodeValue==="astro:end"&&(this.lastChild.remove(),e())});c.observe(this,{childList:!0}),document.addEventListener("DOMContentLoaded",e)}}async childrenConnectedCallback(){let e=this.getAttribute("before-hydration-url");e&&await import(e),this.start()}async start(){let e=JSON.parse(this.getAttribute("opts")),c=this.getAttribute("client");if(Astro[c]===void 0){window.addEventListener(`astro:${c}`,()=>this.start(),{once:!0});return}try{await Astro[c](async()=>{let n=this.getAttribute("renderer-url"),[h,{default:p}]=await Promise.all([import(this.getAttribute("component-url")),n?import(n):()=>()=>{}]),u=this.getAttribute("component-export")||"default";if(!u.includes("."))this.Component=h[u];else{this.Component=h;for(let f of u.split("."))this.Component=this.Component[f]}return this.hydrator=p,this.hydrate},e,this)}catch(n){console.error(`[astro-island] Error hydrating ${this.getAttribute("component-url")}`,n)}}attributeChangedCallback(){this.hydrate()}}d(y,"observedAttributes",["props"]),customElements.get("astro-island")||customElements.define("astro-island",y)}})();</script><astro-island uid="1vNdRR" component-url="/__pyric/ui/_astro/chess-showcase.CVhCNUi_.js" component-export="ChessShowcase" renderer-url="/__pyric/ui/_astro/client.D2ORs4B-.js" props="{}" ssr client="only" opts="{&quot;name&quot;:&quot;ChessShowcase&quot;,&quot;value&quot;:&quot;react&quot;}"></astro-island> </body></html>
4
+ <link rel="stylesheet" href="/__pyric/ui/_astro/app.8TtSeQQX.css"></head> <body> <style>astro-island,astro-slot,astro-static-slot{display:contents}</style><script>(()=>{var e=async t=>{await(await t())()};(self.Astro||(self.Astro={})).only=e;window.dispatchEvent(new Event("astro:only"));})();</script><script>(()=>{var A=Object.defineProperty;var g=(i,o,a)=>o in i?A(i,o,{enumerable:!0,configurable:!0,writable:!0,value:a}):i[o]=a;var d=(i,o,a)=>g(i,typeof o!="symbol"?o+"":o,a);{let i={0:t=>m(t),1:t=>a(t),2:t=>new RegExp(t),3:t=>new Date(t),4:t=>new Map(a(t)),5:t=>new Set(a(t)),6:t=>BigInt(t),7:t=>new URL(t),8:t=>new Uint8Array(t),9:t=>new Uint16Array(t),10:t=>new Uint32Array(t),11:t=>1/0*t},o=t=>{let[l,e]=t;return l in i?i[l](e):void 0},a=t=>t.map(o),m=t=>typeof t!="object"||t===null?t:Object.fromEntries(Object.entries(t).map(([l,e])=>[l,o(e)]));class y extends HTMLElement{constructor(){super(...arguments);d(this,"Component");d(this,"hydrator");d(this,"hydrate",async()=>{var b;if(!this.hydrator||!this.isConnected)return;let e=(b=this.parentElement)==null?void 0:b.closest("astro-island[ssr]");if(e){e.addEventListener("astro:hydrate",this.hydrate,{once:!0});return}let c=this.querySelectorAll("astro-slot"),n={},h=this.querySelectorAll("template[data-astro-template]");for(let r of h){let s=r.closest(this.tagName);s!=null&&s.isSameNode(this)&&(n[r.getAttribute("data-astro-template")||"default"]=r.innerHTML,r.remove())}for(let r of c){let s=r.closest(this.tagName);s!=null&&s.isSameNode(this)&&(n[r.getAttribute("name")||"default"]=r.innerHTML)}let p;try{p=this.hasAttribute("props")?m(JSON.parse(this.getAttribute("props"))):{}}catch(r){let s=this.getAttribute("component-url")||"<unknown>",v=this.getAttribute("component-export");throw v&&(s+=` (export ${v})`),console.error(`[hydrate] Error parsing props for component ${s}`,this.getAttribute("props"),r),r}let u;await this.hydrator(this)(this.Component,p,n,{client:this.getAttribute("client")}),this.removeAttribute("ssr"),this.dispatchEvent(new CustomEvent("astro:hydrate"))});d(this,"unmount",()=>{this.isConnected||this.dispatchEvent(new CustomEvent("astro:unmount"))})}disconnectedCallback(){document.removeEventListener("astro:after-swap",this.unmount),document.addEventListener("astro:after-swap",this.unmount,{once:!0})}connectedCallback(){if(!this.hasAttribute("await-children")||document.readyState==="interactive"||document.readyState==="complete")this.childrenConnectedCallback();else{let e=()=>{document.removeEventListener("DOMContentLoaded",e),c.disconnect(),this.childrenConnectedCallback()},c=new MutationObserver(()=>{var n;((n=this.lastChild)==null?void 0:n.nodeType)===Node.COMMENT_NODE&&this.lastChild.nodeValue==="astro:end"&&(this.lastChild.remove(),e())});c.observe(this,{childList:!0}),document.addEventListener("DOMContentLoaded",e)}}async childrenConnectedCallback(){let e=this.getAttribute("before-hydration-url");e&&await import(e),this.start()}async start(){let e=JSON.parse(this.getAttribute("opts")),c=this.getAttribute("client");if(Astro[c]===void 0){window.addEventListener(`astro:${c}`,()=>this.start(),{once:!0});return}try{await Astro[c](async()=>{let n=this.getAttribute("renderer-url"),[h,{default:p}]=await Promise.all([import(this.getAttribute("component-url")),n?import(n):()=>()=>{}]),u=this.getAttribute("component-export")||"default";if(!u.includes("."))this.Component=h[u];else{this.Component=h;for(let f of u.split("."))this.Component=this.Component[f]}return this.hydrator=p,this.hydrate},e,this)}catch(n){console.error(`[astro-island] Error hydrating ${this.getAttribute("component-url")}`,n)}}attributeChangedCallback(){this.hydrate()}}d(y,"observedAttributes",["props"]),customElements.get("astro-island")||customElements.define("astro-island",y)}})();</script><astro-island uid="9K7Ge" component-url="/__pyric/ui/_astro/chess-showcase.CVhCNUi_.js" component-export="ChessShowcase" renderer-url="/__pyric/ui/_astro/client.D2ORs4B-.js" props="{}" ssr client="only" opts="{&quot;name&quot;:&quot;ChessShowcase&quot;,&quot;value&quot;:&quot;react&quot;}"></astro-island> </body></html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pyric/cli",
3
- "version": "0.1.0-alpha.30",
3
+ "version": "0.1.0-alpha.30.1",
4
4
  "license": "Apache-2.0",
5
5
  "homepage": "https://pyric.dev",
6
6
  "repository": {
@@ -256,12 +256,12 @@
256
256
  },
257
257
  "dependencies": {
258
258
  "@modelcontextprotocol/sdk": "^1.0.0",
259
- "create-pyric": "^0.1.0-alpha.30",
259
+ "create-pyric": "^0.1.0-alpha.30.1",
260
260
  "esbuild": "^0.28.0",
261
261
  "fake-indexeddb": "^6.0.0",
262
262
  "jsonc-parser": "^3.3.1",
263
- "pyric": "^0.1.0-alpha.30",
264
- "pyric-admin": "^0.1.0-alpha.30",
263
+ "pyric": "^0.1.0-alpha.30.1",
264
+ "pyric-admin": "^0.1.0-alpha.30.1",
265
265
  "ws": "^8.18.0",
266
266
  "zod": "^3.23.0"
267
267
  },
@@ -133,9 +133,13 @@ export const CONFORMANCE_VERDICTS = {
133
133
  "app-check-admin#create-token-shape": "supported",
134
134
  "app-check-admin#create-token-ttl": "supported",
135
135
  "app-check-admin#create-token-unknown-app": "qualified",
136
+ "app-check-admin#project-from-cert": "qualified",
136
137
  "app-check-admin#verify-token": "supported",
137
138
  "app-check-admin#verify-token-consume": "supported",
138
139
  "app-check-admin#verify-token-content-checks": "qualified",
140
+ "app-check-admin#verify-token-firebase-token": "qualified",
141
+ "app-check-admin#verify-token-firebase-token-consume": "qualified",
142
+ "app-check-admin#verify-token-firebase-token-production-ai-mode": "qualified",
139
143
  "app-check-admin#verify-token-rejections": "supported",
140
144
  "auth#1": "supported",
141
145
  "auth#10": "supported",
package/src/cli/serve.ts CHANGED
@@ -16,7 +16,7 @@ import { existsSync } from 'node:fs';
16
16
  import type { ParsedArgs } from './parse-args.js';
17
17
  import { readFirebaseJson, readFirebaseRc, type FirebaseJson } from './firebase-json.js';
18
18
  import { resolveFirebaseProject, type FirebaseProjectResolution } from './firebase-project.js';
19
- import { bundleSdk, bundleWorker, defaultSdkEntries, resolveSiteUiDir } from '../serve/bundler.js';
19
+ import { bundleSdk, bundleWorker, defaultSdkEntries, planProjectFirebase, resolveSiteUiDir } from '../serve/bundler.js';
20
20
  import {
21
21
  isStandalone,
22
22
  materializeServeAssets,
@@ -213,6 +213,8 @@ async function startServeRuntime(opts: {
213
213
  /** Explicit opt-in to permissive mode (default-allow RTDB rules when unconfigured). */
214
214
  permissive?: boolean;
215
215
  logger?: Parameters<typeof startStaticServer>[0]['logger'];
216
+ /** A child command runs beside the server and resolves `firebase` itself. */
217
+ hasChildCommand?: boolean;
216
218
  }, releaseOwnership?: () => void): Promise<ServeRuntime> {
217
219
  const logger = opts.logger ?? consoleServeLogger();
218
220
 
@@ -334,6 +336,15 @@ async function startServeRuntime(opts: {
334
336
  // stale, not auto-replaced; the user closes all tabs to load the new worker.
335
337
  let workerVersion: string;
336
338
  const usesEmbeddedAssets = isStandalone();
339
+ const aiMode = resolveAiMode(undefined, process.env);
340
+ // Production AI mode serves the project's own Firebase SDK for firebase/ai,
341
+ // firebase/app and firebase/app-check (#1290).
342
+ let projectFirebase: { projectDir: string } | undefined;
343
+ if (aiMode === 'production') {
344
+ const firebasePlan = planProjectFirebase(opts.cwd, Boolean(opts.hasChildCommand));
345
+ projectFirebase = firebasePlan.projectFirebase;
346
+ if (firebasePlan.warning) logger.note(firebasePlan.warning);
347
+ }
337
348
  if (usesEmbeddedAssets) {
338
349
  bundle = await materializeServeAssets();
339
350
  workerVersion = embeddedWorkerVersion();
@@ -342,6 +353,7 @@ async function startServeRuntime(opts: {
342
353
  entries: defaultSdkEntries(),
343
354
  noCache: opts.noCache,
344
355
  cacheRoot: opts.cacheRoot,
356
+ projectFirebase,
345
357
  });
346
358
  // The SharedWorker host (Phase 3c): bundled into the SAME sdk dir so the
347
359
  // existing /__pyric/sdk/worker.js route serves it. The page's entry
@@ -400,7 +412,6 @@ async function startServeRuntime(opts: {
400
412
  const usesPersistence = Boolean(opts.persist || opts.hosted);
401
413
  const persistenceOptions = usesPersistence ? { fresh: opts.fresh } : undefined;
402
414
  const studioOptions: Parameters<typeof createSandboxSession>[0]['studio'] = mountsStudio ? { siteUiDir } : false;
403
- const aiMode = resolveAiMode(undefined, process.env);
404
415
  const usesHostedSandbox = opts.hosted === true;
405
416
  let session: SandboxSession;
406
417
  try {
@@ -989,6 +1000,7 @@ export async function runServe(parsed: ParsedArgs): Promise<number> {
989
1000
  allowedHosts,
990
1001
  project: firebaseProject.projectId,
991
1002
  firebaseProject,
1003
+ hasChildCommand: plan !== null,
992
1004
  });
993
1005
  } catch (e) {
994
1006
  const message = serveErrorMessage(e);