Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz Source: /opt/alga-psa on psa.joliet.tech
174 lines
9.4 KiB
Markdown
174 lines
9.4 KiB
Markdown
# Extension Runner (EE)
|
||
|
||
This document explains the Extension Runner’s role in the Enterprise Extension System and how it integrates with the Gateway, Registry, signing, and UI delivery.
|
||
|
||
References:
|
||
- Runner project (Rust): [ee/runner/Cargo.toml](ee/runner/Cargo.toml)
|
||
- Iframe bootstrap and URL builder in host UI: [bootstrapIframe()](../../../server/src/lib/extensions/ui/iframeBridge.ts:45), [buildExtUiSrc()](../../../server/src/lib/extensions/ui/iframeBridge.ts:38)
|
||
- Registry v2 service (types and scaffold): [ExtensionRegistryServiceV2](../../server/src/lib/extensions/registry-v2.ts:48)
|
||
- Gateway handler (current implementation): [server/src/app/api/ext/[extensionId]/[[...path]]/route.ts](../../../server/src/app/api/ext/%5BextensionId%5D/%5B%5B...path%5D%5D/route.ts)
|
||
|
||
## Purpose and responsibilities
|
||
|
||
- Execute extension server handlers (Wasmtime components produced by `componentize-js`) with strict isolation.
|
||
- Serve static UI assets for extensions by immutable content hash (content-addressed).
|
||
- Enforce capability-based host APIs and guardrails (quotas, timeouts, egress policies).
|
||
- Provide a stable HTTP interface for the Gateway to invoke extension handlers.
|
||
- Maintain a pod-local cache of verified bundles for performance and reliability.
|
||
|
||
## High-level architecture
|
||
|
||
- Process and runtime: Rust + Wasmtime (Component Model).
|
||
- Wasmtime is used to load/execute precompiled or interpreted WASM components with resource limits.
|
||
- HTTP server: Axum + Tower layers (tracing, headers, static files), see dependencies in [ee/runner/Cargo.toml](ee/runner/Cargo.toml).
|
||
- Storage and caching:
|
||
- Immutable content-addressed bundles (tar/zstd or similar) stored in object storage.
|
||
- Pod-local cache keyed by content hash for hot assets and module bytes.
|
||
- Security:
|
||
- Signature verification at publish/install (via Registry) and prior to execution/load.
|
||
- Capability-based host API; deny-by-default for egress and privileged operations.
|
||
- Strict header and size limits on inbound/outbound HTTP.
|
||
|
||
## Interfaces
|
||
|
||
### 1) Execute endpoint (Runner)
|
||
|
||
- HTTP: `POST /v1/execute`
|
||
- Caller: Gateway
|
||
- Request (example):
|
||
- Actual payload shape (2025-11-12):
|
||
```json
|
||
{
|
||
"context": {
|
||
"request_id": "3d2c3f27-5b9c-4ad0-90f5-4c5ec8e844be",
|
||
"tenant_id": "tenant-123",
|
||
"extension_id": "com.example.sales",
|
||
"version_id": "ver_abc123",
|
||
"content_hash": "sha256:012345...abcd"
|
||
// install_id is currently omitted; tracked in Workstream A1.
|
||
},
|
||
"http": {
|
||
"method": "POST",
|
||
"path": "/agreements/sync",
|
||
"query": { "force": "true" },
|
||
"headers": {
|
||
"x-request-id": "3d2c3f27-...",
|
||
"x-alga-tenant": "tenant-123",
|
||
"x-alga-extension": "com.example.sales",
|
||
"content-type": "application/json"
|
||
},
|
||
"body_b64": "eyAiZm9vIjogImJhciIgfQ=="
|
||
},
|
||
"limits": { "timeout_ms": 5000 },
|
||
"config": { "region": "emea" },
|
||
"providers": ["cap:http.fetch","cap:storage.kv","cap:secrets.get"],
|
||
"secret_envelope": { "ciphertext_b64": "vault:v1:....", "algorithm": "vault-transit:v1" }
|
||
}
|
||
```
|
||
|
||
Earlier versions included an `endpoint` field pointing at `dist/handlers/...`. Componentized extensions no longer require the gateway to select a handler file; the component inspects `request.http` directly.
|
||
- Response (example):
|
||
```json
|
||
{
|
||
"status": 200,
|
||
"headers": {
|
||
"content-type": "application/json",
|
||
"x-ext-request-id": "3d2c3f27-5b9c-4ad0-90f5-4c5ec8e844be"
|
||
},
|
||
"body_b64": "eyAic3RhdHVzIjogIm9rIiB9"
|
||
}
|
||
```
|
||
- The Gateway normalizes the inbound Next.js request, attaches install metadata, and forwards the envelope. See [server/src/app/api/ext/[extensionId]/[[...path]]/route.ts](../../../server/src/app/api/ext/%5BextensionId%5D/%5B%5B...path%5D%5D/route.ts).
|
||
|
||
### 2) Static UI asset hosting (Runner)
|
||
|
||
- Purpose: Serve iframe UI assets for a given extension by content hash.
|
||
- URL shape (recommended):
|
||
- `${RUNNER_PUBLIC_BASE}/ext-ui/{extensionId}/{content_hash}/[...]`
|
||
- Behavior:
|
||
- Content-addressed path ensures immutability. Set `Cache-Control: public, max-age=31536000, immutable`.
|
||
- Assets validated (existence and content hash) before serving.
|
||
- MIME type is derived safely (e.g., via `mime_guess`); only static file types served.
|
||
- Host bootstrap:
|
||
- The host uses [buildExtUiSrc()](../../../server/src/lib/extensions/ui/iframeBridge.ts:38) to construct the iframe URL for the Runner’s public base and [bootstrapIframe()](../../../server/src/lib/extensions/ui/iframeBridge.ts:45) to perform the secure initialization (sandbox, origin checks, postMessage protocol).
|
||
|
||
## Execution model
|
||
|
||
- Module resolution:
|
||
- `version_id` + `content_hash` → fetch component artifact (`dist/main.wasm`) from object storage/cache. The handler selection happens inside the component; manifest endpoint data is advisory today.
|
||
- Isolation and limits:
|
||
- Memory/time/fuel limits enforced per invocation (configurable).
|
||
- Concurrency controls per tenant/extension (global caps, per-request rate limits).
|
||
- Capability-based host APIs (examples):
|
||
- `http.fetch` with tenant/extension egress allowlists.
|
||
- `storage.kv` with tenant-namespaced keys.
|
||
- `secrets.get` returning handles/tokens; plaintext minimized.
|
||
- `ui_proxy.call_route` bridging from components to host-approved UI proxy endpoints.
|
||
- `invoicing.create_manual_invoice` to create draft manual invoices (requires `cap:invoice.manual.create`).
|
||
- `log`, `metrics`, and live debug events emitting structured telemetry.
|
||
|
||
## Security and signing
|
||
|
||
- Content-addressed bundles:
|
||
- Hash: `sha256:<hex64>`.
|
||
- Bundles contain `manifest.json` (v2), `dist/` (WASM handlers), `ui/` (static assets), and optional `precompiled` artifacts.
|
||
- Signatures:
|
||
- Detached signature persisted alongside bundle metadata.
|
||
- Verification occurs on publish/install and before load/serve.
|
||
- Trust roots provisioned via Runner/Registry environment (e.g., PEM chain).
|
||
- Origin and iframe safety:
|
||
- The host enforces sandbox defaults (`allow-scripts`, no implicit `allow-same-origin`) and validates target origins in the bootstrap flow (see [bootstrapIframe()](../../../server/src/lib/extensions/ui/iframeBridge.ts:45)).
|
||
- Header policy:
|
||
- Gateway strips end-user `authorization` and injects service-level headers (`x-request-id`, tenant/extension IDs).
|
||
- Runner enforces response header allowlist (`content-type`, safe `cache-control`, custom `x-ext-*`).
|
||
|
||
## Configuration (env)
|
||
|
||
- `RUNNER_BASE_URL`: Gateway’s internal URL to call Runner (e.g., `http://runner:8080`).
|
||
- `RUNNER_DOCKER_HOST`: Override Runner base URL when using the Docker backend (e.g., `http://localhost:8085`).
|
||
- `RUNNER_PUBLIC_BASE`: Public base used in iframe src for UI assets. Accepts absolute URLs or relative paths (e.g., `/runner`) when the gateway proxies Runner assets.
|
||
- `SIGNING_TRUST_BUNDLE`: Path or value for trusted publisher certificates/keys.
|
||
- `BUNDLE_STORE_BASE` / `BUNDLE_STORAGE_*`: Object storage configuration for content-addressed bundle retrieval (S3 or equivalent).
|
||
- `REGISTRY_BASE_URL`, `ALGA_AUTH_KEY`: Used to fetch install metadata/signature info from the EE server.
|
||
- `EXT_EGRESS_ALLOWLIST`: Comma-separated list of hostnames allowed for `alga.http.fetch`.
|
||
- `RUNNER_DEBUG_REDIS_URL`, `RUNNER_DEBUG_REDIS_STREAM_PREFIX`, `RUNNER_DEBUG_REDIS_MAXLEN`, `RUNNER_DEBUG_MAX_EVENT_BYTES`: Enable Redis-backed debug streaming (stdout/stderr/log fan-out).
|
||
- `UI_PROXY_BASE_URL`, `UI_PROXY_AUTH_KEY`, `UI_PROXY_TIMEOUT_MS`: Configure the UI proxy host capability.
|
||
- `WASM_POOL_*` / `EXT_CACHE_ROOT`: Tune Wasmtime pooling and cache directories.
|
||
|
||
## Gateway → Runner flow (summary)
|
||
|
||
1. Client calls host `/api/ext/{extensionId}/{...}`.
|
||
2. Gateway resolves tenant install → (`version_id`, `content_hash`, config, provider grants, sealed secret envelope).
|
||
3. Gateway builds normalized request (currently without `install_id`, see plan A1) and `POST /v1/execute` to Runner with timeouts and service auth.
|
||
4. Runner executes the handler in a sandbox and returns `{status, headers, body_b64}`.
|
||
5. Gateway returns filtered headers/body to the client.
|
||
|
||
## Observability
|
||
|
||
- Structured logs per request with correlation IDs.
|
||
- Metrics exposed by Runner:
|
||
- Invocation duration, memory usage, fuel, egress bytes, error counts.
|
||
- Live debug stream:
|
||
- When `RUNNER_DEBUG_REDIS_URL` is set, stdout/stderr/log events are published to Redis Streams (`ext-debug:{tenant}:{extension}`) and consumed by `/api/ext-debug/stream`.
|
||
- `RUNNER_DEBUG_MAX_EVENT_BYTES` truncates noisy messages; the UI shows a `[truncated]` marker.
|
||
- Execution logs persisted via Registry or a logging backend keyed by tenant/extension.
|
||
|
||
## Local development
|
||
|
||
- Run Runner locally (Cargo) and point the host `RUNNER_BASE_URL` to it.
|
||
- Use `RUNNER_PUBLIC_BASE` to serve UI assets directly from Runner’s static asset host.
|
||
- During early development of UI, authors may serve assets via a local dev server, but production must use content-addressed assets hosted by Runner.
|
||
|
||
## Error mapping (guidance)
|
||
|
||
- 404: Unknown endpoint in manifest or missing asset (by content hash/path).
|
||
- 413: Request/response size exceeded configured limits.
|
||
- 502: Runner internal error or non-OK upstream.
|
||
- 504: Timeout reached (Gateway or Runner).
|
||
- Always include `x-request-id` and `x-ext-*` headers where appropriate.
|
||
|
||
## Registry integration
|
||
|
||
- The Runner relies on Registry to supply metadata: versions, manifests, content hashes, and signatures (publish/install workflows).
|
||
- See [ExtensionRegistryServiceV2](../../server/src/lib/extensions/registry-v2.ts:48) for service scaffolding used by the Gateway and Registry layer.
|