Context
PR #109 reports a real total-availability failure after app modules moved to the parent-brokered blob loader:
- the live platform code imports the transferred module through
import(blobUrl);
- an older or separately managed edge CSP still serves
script-src without blob:;
- every mini-app then fails with
Failed to fetch dynamically imported module: blob:null/....
The current bundled Caddyfile has the required route-specific blob: policy, but that does not by itself make the migration safe for existing self-hosted installations.
Why this can affect the standard self-hosted update path
In docker-compose.yml, Caddy reads ./Caddyfile from the host checkout. The in-product platform updater advances /data/platform inside the app's persistent volume and rebuilds/restarts the served platform, but it cannot update or reload the host-mounted Caddy configuration.
That means an ordinary self-hosted instance can legitimately reach this skew:
- host Caddyfile remains from before the app-frame blob policy;
- Settings applies a newer live platform containing the blob-only loader;
- the old edge policy overrides the frame response CSP and blocks every app.
This is therefore an update/deployment compatibility gap, not only a custom-proxy misconfiguration.
Assessment of PR #109
The availability finding is useful, but the proposed direct-import fallback should not merge as written.
Valid concerns from the review:
- a genuine top-level module evaluation failure can run twice: once from the blob, then again from the direct URL;
- the second import bypasses the exact-parent broker's size, source/version and controlled offline path;
- it omits the module
v= key;
- the app bearer appears in the imported URL, where browser-generated errors or console output may retain it;
- the current static test forbids only the old
import(moduleUrl(...)) spelling and does not exercise this renamed fallback;
- current
main now has isBlobModuleLoadFailure(), so any compatibility behavior must be placed after precise classification and a bounded retry rather than catching arbitrary evaluation failures inside importBrokeredModule().
One correction to the review: the patch's inner catch does not catch failures from requestModuleBytes(). That await occurs before its try, so broker network/HTTP/token-expiry failures do not enter the direct fallback. The double-evaluation and second-request contract problems still stand.
Architectural constraint
This should not be solved by restoring allow-same-origin on a shell-origin scripted frame. The opaque origin intentionally removes ambient owner authority: shell DOM access, shell storage and the owner JWT.
It also should not imply that every possible application must be recreated as a bespoke compatibility API. The current capability architecture already has the right split:
- ordinary mini-apps use an opaque frame, an app-scoped principal and general, reviewable host capabilities;
- reusable browser/device primitives belong in the capability broker as real apps require them;
- full applications needing cookies, origin storage, their own service worker or broad web-platform behavior use a separate service origin/gateway;
- privileged reusable integrations can become platform capability providers;
- fully trusted behavior can become explicit platform code.
The goal is high capability without ambient shell authority, not a fake web platform inside the iframe.
A future generic host-mediated request primitive could also keep the app-scoped bearer in the host while preserving server-side app permissions, avoiding a bespoke wrapper for every Möbius API route. That is broader than this loader migration and does not replace lifecycle-aware browser capabilities or distinct origins for full services.
Desired outcome
Define and test an update contract in which a platform update cannot turn a valid existing self-hosted deployment into an all-app outage when the edge policy is older than the app-frame loader.
A solution should satisfy all of these:
- No total outage across the supported update path. Either verify the actual public frame CSP before activating a blob-only loader, or retain a safe compatibility path until the edge is updated.
- Clear operator recovery. Detect the missing
blob: policy and explain how to update and reload the host Caddy/proxy configuration.
- No broad failure catch. Network, HTTP, token-expiry, syntax and app-evaluation failures must keep their existing deterministic paths; app code must never execute twice.
- Preserve the broker invariants. Exact frame/app attribution, installation identity, version key, size bound, token refresh and service-worker/offline behavior must remain intact.
- No bearer leakage through module URLs or telemetry. Prefer keeping credentials in the host-mediated path.
- Exercise the real migration. Add a test for an old bundled Caddy policy combined with the new live platform, rather than only asserting that the current Caddyfile contains
blob:.
Possible directions
- Add an update preflight/health check against the actual public
/api/apps/{id}/frame response and stop with actionable guidance when its effective script-src cannot run the incoming loader.
- Provide an explicit, deterministic host-side Caddy update/reload step as part of the self-hosted upgrade documentation or tooling.
- If a code compatibility bridge is still needed, place it only after the exact blob-load classifier and bounded retry, and design it so it does not re-fetch/evaluate an unverified current module or expose the app token in an import URL.
PR #109 can be closed or substantially rewritten after this migration contract is chosen; its reproduced failure remains the evidence for this issue.
Context
PR #109 reports a real total-availability failure after app modules moved to the parent-brokered blob loader:
import(blobUrl);script-srcwithoutblob:;Failed to fetch dynamically imported module: blob:null/....The current bundled Caddyfile has the required route-specific
blob:policy, but that does not by itself make the migration safe for existing self-hosted installations.Why this can affect the standard self-hosted update path
In
docker-compose.yml, Caddy reads./Caddyfilefrom the host checkout. The in-product platform updater advances/data/platforminside the app's persistent volume and rebuilds/restarts the served platform, but it cannot update or reload the host-mounted Caddy configuration.That means an ordinary self-hosted instance can legitimately reach this skew:
This is therefore an update/deployment compatibility gap, not only a custom-proxy misconfiguration.
Assessment of PR #109
The availability finding is useful, but the proposed direct-import fallback should not merge as written.
Valid concerns from the review:
v=key;import(moduleUrl(...))spelling and does not exercise this renamed fallback;mainnow hasisBlobModuleLoadFailure(), so any compatibility behavior must be placed after precise classification and a bounded retry rather than catching arbitrary evaluation failures insideimportBrokeredModule().One correction to the review: the patch's inner
catchdoes not catch failures fromrequestModuleBytes(). That await occurs before itstry, so broker network/HTTP/token-expiry failures do not enter the direct fallback. The double-evaluation and second-request contract problems still stand.Architectural constraint
This should not be solved by restoring
allow-same-originon a shell-origin scripted frame. The opaque origin intentionally removes ambient owner authority: shell DOM access, shell storage and the owner JWT.It also should not imply that every possible application must be recreated as a bespoke compatibility API. The current capability architecture already has the right split:
The goal is high capability without ambient shell authority, not a fake web platform inside the iframe.
A future generic host-mediated request primitive could also keep the app-scoped bearer in the host while preserving server-side app permissions, avoiding a bespoke wrapper for every Möbius API route. That is broader than this loader migration and does not replace lifecycle-aware browser capabilities or distinct origins for full services.
Desired outcome
Define and test an update contract in which a platform update cannot turn a valid existing self-hosted deployment into an all-app outage when the edge policy is older than the app-frame loader.
A solution should satisfy all of these:
blob:policy and explain how to update and reload the host Caddy/proxy configuration.blob:.Possible directions
/api/apps/{id}/frameresponse and stop with actionable guidance when its effectivescript-srccannot run the incoming loader.PR #109 can be closed or substantially rewritten after this migration contract is chosen; its reproduced failure remains the evidence for this issue.