Skip to content

WIP: feat(api,host): platform CDN provider — deploy.cdn "platform" (plan 029) - #133

Draft
elliotBraem wants to merge 6 commits into
mainfrom
feat/platform-cdn
Draft

elliotBraem wants to merge 6 commits into
mainfrom
feat/platform-cdn

Conversation

@elliotBraem

@elliotBraem elliotBraem commented Sep 19, 2026

Copy link
Copy Markdown

Implements plan 029: deploy.cdn: "platform"bos publish --deploy uploads MF bundles to the platform storage via the CLI session (same trust model as the gasless relay); the host serves them publicly from /bundles/* with immutable cache headers. No Zephyr/Cloudflare account needed.

WIP draft — remaining item is the operator-run live E2E below. Two-axis code review already ran; all findings fixed or documented.

What's here

  • api: bundle_objects table + migration; BundleStorage Effect service (api/src/services/storage.ts — R2 drop-in seam); oRPC contract routes:
    • POST /storage/bundles — authed (session/API-key, relay trust family), path allowlist + traversal guard, 64 MB total ceiling (BOS_MAX_BUNDLE_UPLOAD_BYTES), SRI computed server-side over stored bytes
    • GET /bundles/{account}/{gateway}/{workspace}/{+path} — public, File output, immutable cache headers
  • host: route-scoped bodyLimit for uploads, second prefix-symmetric OpenAPIHandler mount at /bundles/* (+ proxy mode), generic ResponseHeadersHandlerPlugin
  • cli: deploy.cdn config (default zephyr), --cdn flag, platform uploader (dist/ artifacts → platform storage), bos login refusal without a session, account-match check
  • docs: ADR 0007 (file transport as oRPC contract concern + handler convention + seam deviations), plan 033 follow-up (derived OpenAPI mounts), AGENTS.md (deploy.cdn + enforced .effect()/yield* Tag handler convention), changeset

Review status (Standards / Spec) — all findings closed

  • SRI over stored bytes, traversal guard, ceilings, key layout bundles/<account>/<gateway>/<workspace>/<path> — spec-verified
  • Storage handlers converted to Effect-native .effect() + yield* StorageTag (template convention); inline auth via Effect.fail(errors.UNAUTHORIZED/FORBIDDEN) with the shared error data shapes
  • Handler convention enforced: AGENTS.md "Adding API Endpoints" (.effect + yield* Tag required for new routes; Context.get reserved for streaming; requireAuthOrApiKey .use() typing gap documented) + ADR 0007
  • withPluginDeploy seam deviation recorded in ADR 0007 (platform skips scripts.deploy; parent uploads dist via applyDeployResults)
  • Dead cdnUploadHandled param removed (review finding)
  • Provider matrix + uploader + refusal tests; typecheck 8/8, lint clean, full suite green (api 118, everything-dev 325, host 175, e2e 4)
  • Live dev-stack E2E (operator): bos publish --deploy --cdn platform → verify a full page render + plugin RPC through the served artifacts → bos mf check green

Known limitations (documented, deliberate)

  • API-key-only uploads are not account-pinned (no NEAR principal in apiKey context) — bounded by platform ceilings; SIWN sessions are pinned server-side (ADR 0007)
  • Bundle GET routes appear in OpenAPI/Scalar/MCP surfaces (read-only, tagged Storage)

🚢 Do not merge until the operator E2E is verified.

Plan 023. The graft protocol's mount vocabulary becomes a compile-time
contract instead of a stringly convention:

- mount-registry.ts: MOUNT_REGISTRY now satisfies Record<string, MountEntry>
  so MountId derives from its canonical keys; MOUNTS exports the union
  value; MOUNT_ALIASES is typed Record<string, MountId> (moved here from
  types.ts, which re-exports both for surface stability)
- define.ts: defineUiPlugin({ name, mounts, tree }) validates the tree at
  construction — every root child declaring a _mount must resolve to a
  declared canonical mount, else it throws with the child id and a
  closest-mount hint; associations ride a module-scoped WeakMap
- compose.ts: defined modules graft via declared mounts (no
  re-derivation); raw trees keep the deriveMountId fallback, now the
  declaredSegment derivation with a MountId return type
- digest-version.ts: MOUNT_REGISTRY_VERSION -> 2026-09-19.1 (graft input
  semantics changed; all compose digests invalidate)
- README row records the agreed deviation (validation covers _-roots
  only) and the plan-024 note (_template has no ui tree yet)

Verification: everything-dev 489 tests green (compose suite 20 incl. 9
new), host 175 green; no new typecheck or lint findings vs baseline.
…n 029)

Platform CDN provider so bos publish --deploy needs no Zephyr/Cloudflare
account: bundles upload to the platform storage via the CLI session and
are served publicly from /bundles/* with immutable cache headers.

- api: bundle_objects table + BundleStorage Effect service (R2 drop-in
  seam), oRPC contract routes POST /storage/bundles (auth'd, ceilings,
  path allowlist, traversal guard, server-side SRI) and
  GET /bundles/{account}/{gateway}/{workspace}/{+path} (public, File
  output)
- host: route-scoped bodyLimit for uploads, second prefix-symmetric
  OpenAPIHandler mount at /bundles/* (+ proxy mode), generic
  ResponseHeadersHandlerPlugin
- cli: deploy.cdn config (default zephyr), --cdn flag, platform
  uploader (dist/ artifacts -> platform storage), session requirement,
  bos login refusal without one
- docs: ADR 0007 (file transport as oRPC contract concern), plan 033
  (derived OpenAPI mounts follow-up), AGENTS.md deploy.cdn, changeset

Live dev-stack E2E (bos publish --deploy --cdn platform + bos mf check)
left for the operator.
…orageTag (code-review follow-ups)

- storage routes follow the template convention: .effect() generators with
  yield* StorageTag (StorageTag now exposed from initialize's returned
  layer); inline auth via Effect.fail(errors.UNAUTHORIZED/FORBIDDEN) with
  the every-plugin/errors data shapes
- AGENTS.md 'Adding API Endpoints': new-route handler convention enforced
  (.effect + yield* Tag; Context.get reserved for streaming; shared
  auth middlewares' .use() typing gap documented)
- ADR 0007: handler-convention bullet + recorded seam deviation
  (platform provider skips workspace scripts.deploy; parent uploads
  dist/ via applyDeployResults)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant