# Extension Runtime Metadata & Secrets Delivery Plan (Componentize-JS First) ## Overview - Deliver tenant/install-scoped configuration and secrets (e.g., Alga API tokens) to Wasm extensions while keeping values encrypted at rest and never logging plaintext. - Standardize extension builds on the Wasmtime component model and [`componentize-js`](https://github.com/bytecodealliance/componentize-js) so developers ship typed components instead of raw Wasm modules. - Reuse the existing composite secret infrastructure (`shared/core/secretProvider`, Vault, Docker secrets per `docs/secrets_management.md`) for both control plane storage and Runner decryption. - Treat host functionality (secrets, outbound HTTP, UI proxy calls) as declarative “capability providers” exposed via WIT, inspired by wasmCloud’s provider model. - Provide an opinionated SDK pipeline in `./sdk` that wraps generated bindings so TypeScript developers can consume secrets/config without writing Rust or manual ABI glue, mirroring wasmCloud’s `wash` developer experience. ## Goals - [ ] Define WIT worlds for Runner ↔ guest interactions (context, secrets, HTTP, storage, logging, UI proxy) and instantiate components via Wasmtime’s component APIs. - [ ] Shape host functionality as modular capability providers with explicit manifests so extensions declare the providers they need at publish time. - [ ] Integrate `componentize-js` (currently v0.19.3, released 2025-10-27 per `CHANGELOG.md`) into the build pipeline so JS/TS projects emit `.wasm` components with stable metadata. - [ ] Publish SDK packages (initially JS/TS) generated from WIT so developers retrieve secrets via `await ctx.secrets.get(...)` with minimal boilerplate. - [ ] Keep rotation fast: cache decrypted material briefly, respect version stamps, and rely on the composite secret provider as the system of record. ## Non-Goals - Replacing the secret provider stack—Vault/Docker secrets remain authoritative; the Runner consumes its outputs. - Shipping a JS VM in the Runner; extensions still compile to Wasm components using `componentize-js`. - Solving deep observability/runbook requirements in this pass (tracked separately once the foundation ships). - Providing backward compatibility adapters for legacy module-based extensions; the focus is the new component-based pipeline. ## Current State (2025-10-29) - Runner request model (`ee/runner/src/models.rs`) lacks config/secret fields; host context in `ee/runner/src/engine/loader.rs` tracks IDs only. - Host imports in `ee/runner/src/engine/host_api.rs` are hand-wired functions (logging, http, storage) built for module-style Wasmtime; no `alga.secrets` surface exists. - Gateway proxy (`ee/server/src/app/api/ext/[extensionId]/[...path]/route.ts`) forwards to `/v1/execute` without fetching install metadata or secrets. - Control-plane services already depend on `shared/core/secretProvider.ts` to source secrets from env/filesystem/Vault (documented in `docs/secrets_management.md`); Runner has no integration. - `./sdk` contains CLI tooling and iframe helpers but no generated runtime bindings or component build pipeline. Status update (2025-11-21): - Gateway now forwards `config`, `providers`, and `secretEnvelope` in execute payloads (`server/src/app/api/ext/[extensionId]/[[...path]]/route.ts`). - Runner host implements secrets and storage capability providers and calls `POST /api/internal/ext-storage/install/{installId}` with `RUNNER_STORAGE_API_TOKEN` (`ee/runner/src/engine/host_api.rs`). - Runtime uses Wasmtime Component Model; `wasm-js@1` is the enforced runtime and manifests are validated via `manifest-v2.schema.ts`. - Componentized SDK template exists (`sdk/alga-client-sdk/templates/component-basic`) and buildExtUiSrc/iframe bootstrap are stable; still need generated bindings packaging and schema/docs alignment (JSON vs zod). ## Tooling Status Snapshot (Oct 2025) - `componentize-js` latest tag `v0.19.3` (2025-10-27) fixes duplicate export naming, updates dependencies (`orca_wasm` → `wirm`), and keeps StarlingMonkey aligned—ensuring the `jco` toolchain is active and maintained. - Recent releases (`v0.19.2`, `v0.19.1`) focus on Windows CI stability and StarlingMonkey updates, confirming cross-platform support and ongoing maintenance cadence. - The toolchain now emits full component metadata compatible with Wasmtime 19.x+; we should plan to pin to `componentize-js` ≥ `0.19.3` and track upstream changelog for breaking changes. ## Requirements & Constraints - **Component Model First**: All new host APIs are defined in WIT and surfaced to guests through Wasmtime component instantiation. No new raw `func_wrap` imports. - **Secret Provenance**: The control plane packages secrets using `secretProvider` as Vault transit ciphertext. Runner decrypts using Vault tokens mounted as Docker secrets. - **Capability Enforcement**: Access to secrets/config is gated by manifest-declared scopes (`secrets.get`, `config.read`) and enforced in the host implementation. - **Componentize-JS Pipeline**: JS/TS extensions must build via `componentize-js` (`jco`) with a standardized project template in `sdk/`. The pipeline outputs `.wasm` + metadata for publishing. - **Rotation & Caching**: Envelopes include `version`/`expires_at`; Runner caches decrypted material in an LRU keyed by `(tenant, install, version)` with short TTL. - **DX**: Generated bindings (JS/TS) expose ergonomic helpers (`ctx.secrets.get`, `ctx.http.fetch`) and hide low-level ABI concerns. - **Security**: Plaintext secrets never hit logs, idempotency caches, or panic traces. Structures holding secrets must not implement `Debug` output. Browser/iframe surfaces never receive secrets; UI flows rely on host proxy APIs instead. ## Proposed Architecture ### 1. WIT Worlds & Capability Provider Surface - Author `wit/extension-runner.wit` describing: - `interface alga.context` (IDs, config map, request metadata). - `interface alga.secrets` (`get`, optional `list`, structured errors). - `interface alga.http`, `alga.storage`, `alga.log`, and `alga.ui_proxy` (for host-mediated UI actions). - Encode capability requirements using custom metadata (e.g., `@requires("cap:secrets.get")`) so registry validation, host enforcement, and generated bindings stay in sync. - Version the WIT world and publish alongside the SDK to support additive evolution. ### 2. Capability Provider Catalog - Treat each host feature as a provider, similar to wasmCloud: - Core providers: secrets, outbound HTTP, storage, logging. - UI proxy provider: exposes predefined host endpoints extensions can call from the browser via gateway. - Future providers: messaging, scheduler hooks, custom domain integrations. - Maintain provider definitions (WIT imports + manifest identifiers) so the registry knows which providers an extension needs and operators can enable/disable them per tenant. - Document provider lifecycle (enable/disable, version bump) and align on naming (`cap.alga.secrets`, `cap.alga.ui_proxy`, etc.). ### 3. Control Plane, Gateway & Declarative Metadata - Extend control-plane schema (`tenant_extension_install_config`, `tenant_extension_install_secrets`) storing Vault-transit ciphertext via `secretProvider`. - Capture provider requirements in install metadata: manifest declares required providers; registry enforces availability before activation. - Create service endpoint (`POST /internal/runner/install-config`) returning `{config, secret_envelope, provider_flags, version}`. - Update Gateway to: - Fetch install config/secrets before invoking Runner. - Attach provider flags + version headers (`x-ext-config-version`, `x-ext-secrets-version`). - Expose host proxy routes (UI → gateway → runner handler) mapped to the `alga.ui_proxy` provider so browser clients never see secrets. ### 4. Runner Component Host Runtime - Upgrade Wasmtime to a component-ready release and instantiate components via `componentize-js` metadata. - Extend host context to store config maps, provider flags, and secret envelopes. - Implement provider dispatch: the runner mounts each capability provider implementation (e.g., secrets provider decrypts Vault ciphertext mounted at `/run/secrets/...`). - Secret redemption: decrypt Vault transit ciphertext in-process using mounted tokens/keys, populate short-lived cache keyed by `(tenant, install, version)`. - Ensure provider calls zeroize buffers, apply capability checks, and emit structured errors. ### 5. Developer Workflow & CLI (Componentize-JS Pipeline) - Ship `sdk/packages/component-runtime-template` mirroring wasmCloud’s `wash` flow: 1. `alga-cli new component` scaffolds TS project with tests and provider manifests. 2. `alga-cli dev` builds via `componentize-js`, spins up a local runner shim, and exercises provider routes (including UI proxy). 3. `alga-cli publish` bundles `.wasm`, `.wit`, provider manifest, and metadata.json for registry upload. - Validate toolchain versions (`componentize-js` ≥ 0.19.3, Wasmtime version) and fail fast if mismatched. - Expose extension install automation via `POST /api/v1/extensions/install` so CLI workflows can bypass the admin UI once API keys are provisioned. - Provide smoke tests that run components against mocked capability providers to catch ABI drift before upload. ### 7. UI & Configuration Experience - Publish `@alga-psa/extension-runtime` (JS/TS) wrapping generated bindings with helpers (`createHandler`, `ctx.secrets.get`, `ctx.uiProxy.call`). - Ship UI-side helpers (`@alga/extension-ui`) that call gateway proxy endpoints with tenant/install context. - Document workflows in `sdk/docs`: local dev loop, invoking provider APIs, using UI proxy without handling secrets. - Provide runnable samples mirroring wasmCloud's language examples (TypeScript initially, add Rust/TinyGo later via `wit-bindgen`). ### 8. Extension Settings UI - **Extension Settings Page**: A dedicated configuration UI at `/msp/settings/extensions/[id]/settings` reachable from the extension management list. - **Dynamic Form Generation**: Render configuration inputs (text, number, boolean, select) based on the extension's manifest `settings` schema. - **Secret Management**: - Distinct UI section for encrypted values (secrets) separate from plain configuration. - Write-only inputs for secrets (never echo back values). - Version tracking (`secretsVersion`) to indicate if a secret is set and when it was last updated. - **Actions**: - "Save Changes": Persist both config (to `tenant_extension_install_config`) and secrets (to Vault via `tenant_extension_install_secrets`). - "Reset to Defaults": Revert configuration to manifest defaults and clear secrets. - **RBAC**: Ensure only admins with appropriate permissions can view/edit these settings. - **Entry Point**: Connect the "Settings" button in the Extension Management table (`SettingsPage.tsx` -> `Extensions.tsx`) to this new page. - Publish `@alga-psa/extension-runtime` (JS/TS) wrapping generated bindings with helpers (`createHandler`, `ctx.secrets.get`, `ctx.uiProxy.call`). - Ship UI-side helpers (`@alga/extension-ui`) that call gateway proxy endpoints with tenant/install context. - Document workflows in `sdk/docs`: local dev loop, invoking provider APIs, using UI proxy without handling secrets. - Provide runnable samples mirroring wasmCloud's language examples (TypeScript initially, add Rust/TinyGo later via `wit-bindgen`). ## Implementation Phases ### Phase 0 — Design & Toolchain Alignment - [x] Finalize WIT interface set and capability annotations. - [x] Pin toolchain versions (`componentize-js` ≥ 0.19.3, Wasmtime ≥ component-ready release) and capture upgrade strategy. - [x] Define secret envelope format (Vault transit ciphertext) and required Vault roles/tokens to mount into Runner containers. *(Implemented base64 fallback plus Vault transit support with token file + mount configuration.)* - [x] Design the capability provider catalog (IDs, manifests, lifecycle) and document how providers map to WIT imports. *(Initial catalog lives in `ee/runner/src/providers` with normalization + validation helpers.)* ### Phase 1 — Control Plane & Gateway - [x] Implement schema migrations and persistence for config + secrets (ciphertext via `secretProvider`). - Added `tenant_extension_install_config` and `tenant_extension_install_secrets` via `20251031130000_create_install_config_tables.cjs`, including UUID PKs, tenant/ install indexes, and Vault-friendly metadata (`algorithm`, `transit_key`, `version`, `expires_at`). - Introduced writer APIs in `ee/server/src/lib/extensions/installConfig.ts` (`upsertInstallConfigRecord`, `upsertInstallSecretsRecord`, `deleteInstallSecretsRecord`) that normalize capability sets, emit deterministic versions, and attempt Vault transit encryption with inline/base64 fallback. - Install flows (`ExtensionRegistryServiceV2.install`, `installExtensionForCurrentTenantV2`) now upsert config/secrets metadata and persist granted provider scopes alongside install records. - [x] Build the install-config service endpoint returning config + envelopes + version metadata. - `/api/internal/ext-runner/install-config` returns merged config/providers plus Vault envelope data, secured by `x-runner-auth`; re-used by runner and internal tooling. - Service hydrates bundle hash, manifest capabilities, install overrides, and exposes `configVersion` / `secretsVersion` with ISO timestamps for cache auditing. - [x] Update Gateway to call the endpoint, enrich execute payloads, and emit version headers. - Gateway routes obtain install state through `loadInstallConfigCached` (new helper under `ee/server/src/lib/extensions/lib/install-config-cache.ts`) before dispatching to `/v1/execute`. - Execute payload now carries `{ context.config, providers[], secret_envelope }` plus headers `x-ext-config-version` / `x-ext-secrets-version`; runner errors are surfaced as 502 with request-id tagged logs. - Added a configurable (default 5s) per-install in-memory cache that keys on tenant+extension and refreshes on expiry or missing rows. - [x] Implement host-side proxy routes (UI → gateway → runner handler) so iframe code can trigger backend actions without receiving secrets directly. - `/api/ext-proxy/[extensionId]/[...path]` mirrors gateway auth, invokes runner `ui-proxy:*` handlers, and streams responses after header filtering; shares install-config cache to avoid duplicate lookups. - Maintains tenant RBAC checks and logs missing bundle/config scenarios for audit. - [x] Validate manifest/provider declarations during publish and record provider enablement per install. - Version publish continues to reject unknown capabilities; install paths normalize requested scopes against the known provider catalog before persisting. - Default provider set (`cap:context.read`, `cap:log.emit`) is enforced server-side so installs inherit baseline capabilities even if manifests omit them. ### Phase 2 — Runner Component Host - Detailed execution steps live in [extension-runtime-wasmtime-plan.md](extension-runtime-wasmtime-plan.md); keep both documents aligned as milestones land. - [x] Upgrade Runner to Wasmtime component APIs and instantiate components for `/v1/execute`. - [x] Implement host functions per WIT (context, secrets, http, storage, logging) with capability checks. - [x] Add secret envelope redemption via Vault transit decryption (token mounted as Docker secret) plus short-lived cache; ensure no secret leakage in logs/traces. - [x] Wire provider registry inside the runner so each capability (secrets, ui_proxy, etc.) can be swapped/extended without touching core execute logic. ### Phase 3 — SDK & Tooling (Componentize-JS Pipeline) - [x] Build the `componentize-js` project template and integrate it into `alga-cli`. - [x] Generate JS/TS bindings from WIT, publish `@alga-psa/extension-runtime`, and document usage. *(Manual first-pass bindings plus proxy helpers shipped under `sdk/extension-runtime`.)* - [x] Provide sample extension + automated test to validate secrets retrieval through the new host interface. *(See `sdk/samples/component/secrets-demo` with Vitest example.)* - [x] Enforce component artifact validation in the publishing pipeline (reject raw Wasm uploads). *(Pack step now requires `dist/component.wasm` + metadata before producing bundles.)* - [x] Add UI SDK helpers demonstrating the proxy pattern (UI calling gateway endpoints backed by runner handlers). *(Available via `callProxyJson` in `@alga-psa/extension-runtime`.)* - [x] Deliver local dev commands (`alga-cli dev`) that spin up mocked capability providers to mirror wasmCloud’s `wash` workflow. *(New `alga component dev` task rebuilds components on file changes.)* ### Phase 4 — Rollout - [ ] Ship an internal extension end-to-end (control plane → gateway → runner → component) to validate secrets delivery. - [ ] Open beta to selected partners once SDK and tooling stabilize; iterate on developer feedback. - [ ] Track follow-up work for observability, telemetry, and runbooks separately. ### Phase 5 — UI Delivery - [ ] Activate the "Settings" button in the Extension Management UI. - [ ] Implement/Connect the `ExtensionSettings` component to the `/msp/settings/extensions/[id]/settings` route. - [ ] Ensure proper loading of Enterprise vs. OSS components (graceful degradation or feature stub). - [ ] Verify "Save Changes" correctly persists to the new configuration and secrets tables. - [ ] Verify "Reset to Defaults" clears overrides. ## Dependencies & Coordination - Runner/Wasmtime specialists to handle component host migration. - Platform/infra teams for Vault policies and Docker-secret delivery of runner tokens. - Secrets platform owners maintaining `secretProvider` APIs and Vault transit setup. - DX/docs teams for SDK publishing, tutorials, and developer onboarding. - Registry/publishing pipeline owners to enforce component artifact requirements and provider declarations. - Future capability provider owners (e.g., messaging, analytics) to contribute host implementations once the catalog is seeded. ## Open Questions - Additional guest languages: after JS/TS, should we generate Rust/TinyGo bindings via `wit-bindgen` to broaden support? - WIT versioning: how do we communicate additive changes to developers and keep bindings in sync (semver for WIT packages)? - How do we expand the host proxy catalog over time (e.g., standardized UI → gateway endpoints) to cover common extension UI use cases? - What guardrails are needed in `alga-cli` to detect mismatched `componentize-js` versions or incompatible Wasmtime features? ## Alternatives Considered 1. **Handwritten Wasmtime host imports (status quo)** Pros: minimal change to existing Runner code. Cons: every capability requires manual glue; no generated SDK; developers must handle ABI details. Rejected. 2. **Embed QuickJS or another JS runtime in Runner** Pros: lets developers ship plain JS without compilation. Cons: larger attack surface, dual runtime maintenance, and still requires secret plumbing; diverges from Wasm-component-first direction. 3. **Expose secrets via HTTP endpoints or storage APIs** Pros: simple to implement. Cons: weak capability enforcement, higher leakage risk, and no alignment with future component-based contracts.