# 21 · Host Coordination, Sidecars, and Native

Read this whenever an extension declares native behavior, non-Wasm server
surfaces, or heavyweight sidecar
services that need live executable host wiring. It prepares the
host-coordination handoff for extension-owned runtime behavior that is not
covered by the published Wasm server runtime. Local ZIP admin runtime is
covered by the admin package contract; this document covers native execution,
managed providers, heavyweight sidecars, and any server code that cannot run
as the published Wasm component.

## What is still host-coordinated after the wasm runtime

> **Now self-serve via the wasm runtime** (no handoff needed — see
> [`07-wasm-server-backends.md`](07-wasm-server-backends.md)):
> server **routes**, **jobs**, **database** (read/count/write/delete/batch/compare-and-set/migrate,
> including comparison filters, ordering, and atomic transactions), **settings**,
> **secrets**, **telemetry** (analytics events **and** site-brain records),
> **customer-tags**, **crm-source-events** (granted on any `crmSourceAccess[]`;
> third-party payment attribution uses `cedros_pay`; reserved first-party
> `cedros_login` is accepted only for the bundled `cedros-login` extension, so a
> third-party extension cannot declare its own source kind without a host change),
> **providers** (storage/intelligence/image/voice/email, plus **blockchain** via
> `blockchain.config.read`), and
> **outbound HTTP** (`http.fetch`, restricted to an operator-configured host
> allowlist — e.g. OIDC/JWKS for federated login).

That is the implemented, self-serve path: upload a `.wasm`, hot-load,
capability-gated, no restart. The wasm runtime is the consumer that binds
routes into the host route registry, so the old "awaiting a consumer" caveat
no longer applies to wasm extensions.

Still host-coordinated — keep a handoff row (see
[`06-runtime-sdk-reference.md`](06-runtime-sdk-reference.md)):

- The managed **domains** provider for third-party extensions.
- Scheduling and Shop provisioning where the owning-system binding is not
  published.
- Heavyweight sidecar services — any full standalone service that cannot run
  inside the Wasm component.
- First-party in-process Rust-crate bindings compiled into cedros-data.

## Current model

- Server packages expose `register_extension_family()` for manifest/package
  metadata.
- Third-party server execution is self-serve through the package archive's
  `cedros-extension.server.wasm` component. Manifest route/job/settings/
  content-model/event ids scope what the Wasm component can export or call.
  `surfaces.server.migrations[]` remains ownership/review metadata because the
  public Wasm import has no migration-id argument.
- The legacy in-process Rust-crate registry is host-coordinated and reserved for
  first-party/lightweight code compiled into cedros-data (e.g.
  managed-providers).
- Native package and registration are required family members. Extension ZIP
  intake retains the executable React Native package as a trusted runtime
  surface. Hosted App Builder releases vendor enabled native packages directly
  from those immutable installed artifacts; private packages are never fetched
  from npm during a build.
- **Mobile extension content blocks (self-serve).** The private native host:
  `loadCedrosNativeExtensions(extensionBootstraps)` composes the cedros-data
  native surface with the host app's extension react-native bootstraps (each
  carrying `contentBlockRenderers` — runtime renderers for its custom block
  types), and `createCedrosNativeExtensionBlockRenderer(installedExtensions)`
  returns a `renderBlock` you pass to `AuthoredEntryView` / `AuthoredDocumentView`.
  The render path then renders an extension's custom blocks on mobile instead of
  the unsupported-block placeholder. Only block types with no built-in native
  renderer are delegated, so core blocks are never overridden. The same
  `contentBlockRenderers` contract is shared with web: the public web hydration
  path already consumes it (extension renderers take precedence over
  `contentBlocks[].Component`), so this is one contract live on both surfaces.
- **Native screen/stack/provider loading.** The private host resolves executable
  `native.screens`, `native.stacks`, and `native.providers` from trusted React
  Native bootstraps. Every contribution must match a manifest-declared ID;
  missing, undeclared, and duplicate contributions fail closed. App Builder
  mounts the configured `metadata.nativePrimaryStackId`, applies declared
  providers in registration order, and stops before queueing when a required
  native artifact is missing.
- Use the route/job hook envelope fields and host handoff table shapes in this
  document. If a needed envelope field is not listed, document it as
  host-coordinated context instead of adding it to the SDK shape.

## Host seam status (what exists vs. what an author can rely on)

The host-side seam **contracts** exist in cedros-data and are unit-tested. Read
this before assuming any of them is end-to-end ready — maturity is uneven, and
the in-process Rust-crate registry is bound only by first-party host code (the
self-serve path for third parties is the wasm runtime above).

- **Site database seam — implemented and verified.** The host-internal Rust
  site-database client (`CedrosExtensionSiteDatabaseClient`; not an SDK type)
  has a concrete implementation backed by cedros-data's Postgres content
  store (`read` → query entries, `count` → aggregate, `write` → upsert entry,
  `delete` → remove entry, `batch` → atomic multi-op transaction, `migrate` →
  register the content-model collection), wrapped by the manifest-scope-enforcing
  validated client, and verified with a live Postgres round-trip. Extensions
  never receive a raw pool (see [`08-site-database.md`](08-site-database.md)).
  Third parties reach it only through the WIT `database` import inside a
  Wasm-backed route/job handler.
- **Route/job registry + context envelope — implemented contract.** The
  host-internal registry (`CedrosExtensionHostRuntimeRegistry`, Rust only)
  binds a route handler to a manifest-declared `route_id` and dispatches
  inbound requests by `(method, path)`; each call carries the context envelope
  (`actorId`, `authenticated`, `systemAdmin`, `permissionClaims`, `requestId`,
  `idempotencyKey`, `csrfVerified`, `siteId`, `environment`, `authScheme`,
  `principalId`, `capabilityClaims`, and Assistant run ids). For third-party
  extensions, the Wasm runtime binds manifest-declared route/job ids from the
  installed component. Binding a Rust handler directly is **in-process host
  code**: the extension's server crate must be compiled into the host. Mount
  route families under
  `official_extension_route_prefix(extension_id)` → `/extensions/{extension_id}`;
  ownership is enforced at bind time. Handlers must fail closed on absent auth,
  permission, and CSRF context.

## Host-owned surfaces — do not rebuild

Use this matrix before adding admin sections, tabs, routes, or setup flows. If a
proposed extension surface collides with a host-owned concern, do not rebuild it
inside the extension; declare the relevant seam, consume the published host
service, or write a host handoff row.

| Surface or concern | Owner | What the extension does instead |
|---|---|---|
| Email provider setup, sender identity, delivery webhooks, bounce handling, deliverability | Host email/providers runtime | Declare `providerAccess[]` for `email.send`, call the email provider seam, and show feature-specific disabled states. Do not ship an Email config or webhook tab. |
| Storage, image, voice, or intelligence provider credentials/configuration | Host Settings > Providers and provider runtime | Declare the needed provider access and call the provider seam. Do not collect API keys, model-router settings, storage buckets, or provider credentials. |
| Team members, admin permissions, invites, page access, and role assignment | Host users/admin access runtime | Rely on host admin permissions and consume a published team-directory/access service when Cedros exposes one. Do not ship Team, Permissions, or Invites sections. |
| Account/profile/admin session shell | Host AdminShell and cedros-login contracts | Expose extension-owned login settings only. Do not rebuild host account, profile, or admin-session management. |
| Activity, audit history, logs, metrics, server health, and operational diagnostics | Host observability/history/runtime logs | Emit redacted telemetry/site-brain records through published seams. Do not ship Activity, Logs, Metrics, or Server tabs as a replacement for host observability. |
| CORS, CSRF, rate limits, security headers, and request hardening | Host HTTP/security runtime | Document route auth, CSRF, idempotency, and retry behavior. Do not ship a CORS/security-header settings UI. |
| Domains, DNS, registrar, and managed domain provider operations | Host-managed domain provider | Use the managed provider contract only when Cedros grants it, or add a handoff row. Do not build registrar/provider UI. |
| Public page rows, publishing, route conflicts, and page lifecycle | Host content/page runtime | Declare `pageSeeds[]` with `autoCreateOnEnable`/`publishOnEnable` when appropriate. Do not fake page rows or use page seeds for executable route handlers. |

## Required handoff table

Every host-coordinated item must appear in this table.

| Item | Manifest ID | Type | Runtime package | Host hook required | Status | Owner | Blocking? | Launch gate |
|---|---|---|---|---|---|---|---|---|
| Payment sidecar sync | `acme-demo:payment-sidecar-sync` | sidecar service | server | service deployment/reverse proxy | deferred | Cedros backend | yes | production |
| Native lead detail | `acme-demo:lead-detail` | native screen | react-native | native screen hook | unsupported | Cedros mobile | yes | production |
| Managed domains | `acme-demo:domains` | provider access | server | managed domains provider grant | host-hook-requested | Cedros backend | yes | production |

Status values:

- `metadata-only`
- `host-hook-requested`
- `bound-in-staging`
- `bound-in-production`
- `deferred`
- `blocked`
- `unsupported`

## Host handoff status to release status

| Host handoff status | Typical release status |
|---|---|
| `bound-in-production` | `ready` |
| `bound-in-staging` | `host-coordinated` until production-bound |
| `host-hook-requested` | `host-coordinated` |
| `metadata-only` | `metadata-only` |
| `deferred` | `deferred` |
| `blocked` | `blocked` |
| `unsupported` | `blocked` or `not applicable`, depending on whether the feature remains in scope |

Database has two common levels. `databaseAccess[]` declaration plus Wasm
`database.read/count/write/delete/batch/compare-and-set/migrate` is self-serve for
extension-owned JSONB content models. Bespoke typed SQL/schema still needs a
handoff row and is never executed from the ZIP by declaration alone.

Customer-tag access is fully self-serve. `customerTagAccess[]`
declaration is self-serve metadata; the WIT `customer-tags` import (backed by
a host-internal validated client) is linked on compatible Cedros Data hosts
and is reachable from any Wasm route/job without a separate host binding.
Treat customer-tag rows in the handoff table as install-gate items, not
production-gate.

Example filled row:

| Item | Manifest ID | Type | Runtime package | Host hook required | Status | Owner | Blocking? | Launch gate |
|---|---|---|---|---|---|---|---|---|
| Checkout sidecar proxy | `acme-demo:checkout-sidecar` | sidecar route | external service | reverse proxy + service deployment | host-hook-requested | Cedros backend | yes | production |

## What you declare and build

Manifest metadata:

- `surfaces.server.routes[]`
- `surfaces.server.migrations[]`
- `surfaces.server.settingsSchemas[]`
- `surfaces.server.contentModels[]`
- `databaseAccess[]`
- `surfaces.server.jobs[]`
- `surfaces.server.events[]`
- `surfaces.native.screenIds[]`
- `surfaces.native.stackIds[]`
- `surfaces.native.providerIds[]`
- required services/capabilities

Host handoff:

- executable package coordinates
- server route hook contract
- request/response schemas
- auth/permission model
- job executor hook contract
- migration order and rollback plan
- settings schema and secret storage plan
- host-provider call context for intelligence, image, voice, domains, storage, or email
  calls
- the WIT `providers.execute` request/response JSON when a route or job calls a
  site-owned provider ([`10-host-providers.md`](10-host-providers.md))
- the WIT `database` request/response JSON when a route or job reads or writes
  extension-owned site data ([`08-site-database.md`](08-site-database.md))
- notification event publishing plan when `setupSeeds.notifications[]`
  references `surfaces.server.events[]`; the setup seed exposes Settings UI,
  while `telemetry.emit-event` remains analytics/best-effort only and durable
  notification acceptance needs a separate published host seam
- native host integration checklist
- logs/site-brain records for operational events
- test/staging verification plan
- one host handoff row for every native id targeting a host without the App
  Builder loader, and every non-Wasm/sidecar server id

For Wasm server code, use `telemetry.emit-record` with the required capability
scope. For non-Wasm/server-handoff code, produce the durable record plan and add
runtime emission to the host handoff instead of inventing an emitter.

## Host ticket template

Create one ticket per coherent binding, not one giant release ticket.

```md
Title: Bind <extensionId> <route/job/native/migration/settings/content-model>

Extension:
- id:
- version:
- package/artifact:
- manifest id:
- runtime export:

Requested host binding:
- type:
- method/path or schedule/native target:
- auth and permission requirement:
- required host services/providers/database access ids:
- settings/secrets needed:

Safety:
- input validation:
- idempotency/replay strategy:
- rate limits/timeouts/retries:
- redaction rules:
- rollback/disable behavior:

Verification:
- local smoke command:
- staging test URL or run command:
- expected analytics/site-brain/log output:
- production launch gate:
```

Before binding exists, test what can be tested locally: manifest validation,
package import/build, handler unit tests with fake requests, and failure-state
rendering. Mark end-to-end execution as pending host binding until staging
proves the actual Cedros hook.

## Route handoff

For each route, provide:

| Field | Required | Notes |
|---|---:|---|
| route id | yes | Must be declared in `surfaces.server.routes[]`. |
| handler/export | yes | Server package export or binding name Cedros should connect. |
| method | yes | GET, HEAD, OPTIONS, POST, PUT, PATCH, or DELETE. |
| path | yes | Relative to the extension namespace (the host mounts at `/extensions/{extensionId}`); no `//`, backslashes, or control chars. |
| auth requirement | yes | Public, authenticated, admin, or service. |
| permission requirement | yes | Exact permission/capability or `none`. |
| request schema | yes | JSON Schema or explicit field table. |
| response schema | yes | Status/body table. |
| validation rules | yes | External input validation and failure behavior. |
| error codes | yes | Include auth, validation, rate limit, downstream failure. |
| rate limits | yes | Bucket, limit, retry-after behavior. |
| idempotency key strategy | yes for writes/webhooks | Replay and duplicate-submit guard. |
| webhook signature source | yes for webhooks | Header, algorithm, secret handle, replay window. |
| host provider calls | no | Provider access id and call context. |
| `http.fetch` allowlist | yes when `http.fetch` is used | Exact hosts the operator must add to `CEDROS_EXTENSION_HTTP_ALLOWLIST`; an empty allowlist denies all egress. |
| database calls | no | Database access id, content model id, operation, and rollback behavior. |
| analytics/site-brain/logs | yes | Redacted operational records. |
| staging test URL | yes | Concrete staging path or pending owner. |
| rollback behavior | yes | Disable, unbind, redirect, or compatibility path. |
| launch gate | yes | install-only, staging, or production. |

Declared route `path` values are RELATIVE to the extension namespace. The host
mounts every extension route under `/extensions/{extensionId}` and strips that
prefix before invoking the handler — declare `/webhook-ingest`, not
`/extensions/acme-demo/webhook-ingest`.

Route hook metadata shape:

```json
{
  "extensionId": "acme-demo",
  "routeId": "acme-demo:webhook-ingest",
  "method": "POST",
  "path": "/webhook-ingest",
  "description": "Receives validated Acme webhook events."
}
```

Route handler envelope:

```json
{
  "extensionId": "acme-demo",
  "routeId": "acme-demo:webhook-ingest",
  "method": "POST",
  "path": "/webhook-ingest",
  "siteContext": "host-coordinated",
  "actor": "host-coordinated",
  "authContext": "host-coordinated",
  "requestId": "host-coordinated",
  "headers": {},
  "query": {},
  "body": {}
}
```

External routes need CORS, CSRF, webhook signature verification, replay
protection, and idempotency rules. Reserve Cedros-owned host prefixes such as
`/api`, `/admin`, `/.well-known`, `/ai/capabilities`, and `/auth` unless the
host owner explicitly assigns a path.

Route paths must be checked against host routes and other extension route
assignments during host coordination.

## Job handoff

For each job, provide:

| Field | Required | Notes |
|---|---:|---|
| job id | yes | Must be declared in `surfaces.server.jobs[]`. |
| executor package/export | yes | Server package and exported executor binding. |
| schedule examples | yes | Cedros automation settings examples. |
| metadata schema | yes | Safe JSON only. |
| timeout | yes | Seconds; justify long-running work. |
| retry behavior | yes | Max retries, retryable conditions, backoff. |
| concurrency behavior | yes | Single-flight, per-site, per-account, or concurrent. |
| idempotency key strategy | yes | Required for writes/sends/syncs. |
| downstream APIs | no | Rate limits, credentials, failure modes. |
| failure records | yes | Site-brain/logging/notification behavior. |
| disable behavior | yes | Pause schedule, stop executor, or fail closed. |
| staging verification | yes | Manual or automated test evidence. |

Job execution input:

```json
{
  "extensionId": "acme-demo",
  "jobId": "acme-demo:nightly-sync",
  "siteContext": "host-coordinated",
  "runId": "host-coordinated",
  "scheduleInstanceId": "host-coordinated",
  "triggeredBy": "host-coordinated",
  "metadata": {},
  "attempt": 1
}
```

Job outcome:

```json
{
  "status": "succeeded",
  "details": {
    "summary": "Synced 12 records"
  }
}
```

Current status values are `succeeded`, `failed`, and `retryable`. If a feature
needs `skipped`, `timed_out`, `cancelled`, or `retry_scheduled`, document that
as an SDK/platform extension request.

## Migration handoff

For each migration:

- migration id
- package/export or host-reviewed SQL source
- ordering
- target content/data model
- reversible, forward-only, or no-data migration
- rollback or forward-fix plan
- dry-run/staging evidence
- data backup expectation

Wasm `database.migrate` can register a declared extension-owned content model
as a host collection. It cannot consume a manifest migration id. Bespoke SQL
source in a migration handoff is never automatically executed from the ZIP.

## Settings schema handoff

For each settings schema:

- settings schema id
- admin page that edits it
- storage owner
- default values
- validation schema
- secret references, if any
- migration behavior
- disable/remove behavior

Settings schemas grant the Wasm `settings` import for server routes/jobs. Admin
UI persistence still needs a Wasm-backed route or published admin settings
helper.

## Content model handoff

For each content model:

- content model id
- records owned
- read/write permissions
- retention/deletion behavior
- import/export behavior
- privacy classification
- APIs or hooks that access it
- migration and rollback behavior

Content models require host data-layer integration.

## Native handoff

For each native screen, stack, or provider:

- native id
- package/export
- required mobile host version
- permissions/capabilities
- offline behavior
- deep links or navigation entry points
- unsupported fallback
- staging device verification

Generic native screen, stack, and provider loading is self-serve for hosted App
Builder releases: it packages enabled extensions' compiled native exports,
validates declared IDs, composes providers, and mounts the configured or first
published stack. For any other native host, the same declarations remain
metadata-first until that host publishes an equivalent loader contract.

## Lightweight in-process vs. heavyweight service — the decisive choice

The in-process route/job registry is only appropriate for **lightweight,
host-aligned** server logic that compiles cleanly into the cedros-data binary.
A **heavyweight** extension — one with its own relational schema, a large or
conflicting dependency tree, private/transitive sub-crates, or a full standalone
server (auth, payments, wallet, etc.) — must **not** be mounted in-process.
It integrates as a **host-coordinated service** that Cedros runs alongside
cedros-data (sharing cedros-data's Postgres instance) and that cedros-data calls
server-to-server and/or reverse-proxies. Vendoring such a platform into the CMS
binary is an anti-pattern (build/conflict/blast-radius cost); do not do it.

## Native fallback matrix

| Native id | Unsupported fallback | Owner-visible behavior |
|---|---|---|
| hosted App Builder + complete compiled native export | fail the build on a missing archive, entrypoint, or declared contribution | render through the packaged native host registry, then verify on a staging device |
| other native host + screen id, no equivalent loader | hide entry point or show unavailable state | launch remains `metadata-only` or `blocked` until that host publishes a loader |
| other native host + stack/provider id, no binding | keep package bootstrap importable | mark launch `host-coordinated` and list the target host owner in the handoff |

## Rules

- Do not assume uploaded ZIP metadata alone makes server hooks or React Native
  screens executable. Server execution requires `cedros-extension.server.wasm`;
  native execution requires hosted App Builder or another published loader plus
  the compiled native export.
- Do not assume id declarations mount routes, jobs, migrations, or screens.
- Pair every executable server route with a matching Wasm route export or a
  documented sidecar/first-party host hook.
- Pair every executable server job with a matching Wasm job export or a
  documented sidecar/first-party host hook.
- Pair every database operation with a declared `databaseAccess[]` id.
- Route paths must avoid reserved host prefixes unless Cedros assigns them.
- External webhook routes require signature verification, replay protection,
  and idempotency.
- Keep host integration notes explicit and separate from manifest metadata.
- Never include raw secrets in manifest, logs, analytics, or site-brain records.
- Validate external input at every host-integrated route.
- Fail closed for auth and permission checks.
- Treat server events as metadata labels until Cedros identifies whether they
  are event-bus topics, analytics events, site-brain records, or another
  published seam.
- Do not hide arbitrary SQL in content model or migration plans unless the host
  migration handoff explicitly requests reviewed SQL.

## Acceptance checks

- Every host-coordinated manifest id appears in the handoff table.
- Every declared server/native id has an owner and runtime integration status.
- Every blocking item has an owner and next action.
- Every executable route/job/screen has a host binding plan.
- Every executable server route/job binding references a manifest-declared id.
- Migrations are reversible or have a documented rollback/forward-fix plan.
- Secrets are stored through host-approved secret storage, never manifest
  metadata.
- Staging verification is concrete or explicitly pending, and verifies the
  actual host wiring, not only manifest validation.
- Release readiness distinguishes installable metadata from live executable
  behavior.

Next: [`22-packaging-and-upload.md`](22-packaging-and-upload.md).
