# Astra interface contract — W1 / W2 (Helios owns) **Actor:** Helios (Cursor on Icarus, `centaur-macmini-1`) **Composer:** Mitchell Burns **Date:** 2026-09-13 **Status:** contract for later workers. **Not implemented in this freeze.** Design lock: [`ASTRA_DESIGN.md`](ASTRA_DESIGN.md). Helios (integrator) owns these shared interfaces. Later packets may implement against them. They may **not** silently fork them. Historical plumbing contract: [`ASTRA_CONTRACT.md`](ASTRA_CONTRACT.md) (rev 2). This file supersedes it for **host / grant / auth / envelope / persistence**. Packet 0 map, evaluator decision, and packet receipts remain evidence. --- ## 0. Measured baseline (do not pretend otherwise) | Surface | Path | What it is today | |---|---|---| | Host class | `src/lib/korgi/host/project-host.ts` | In-process `ProjectHost`. `execute({ bindingId, actionId, payload, expectedRevision, operationId })`. Bindings in a `Map`. `pack()` / `openPacked()` = project + journal. | | Process singleton | `src/lib/korgi/host/authoritative-host.server.ts` | `let host: ProjectHost \| null`. **One Node process.** Dies with the process. | | HTTP host | `src/lib/korgi/host/host-http.server.ts`, `src/routes/api/korgi/host.ts` | Bind / inspect / pack / execute against that singleton. | | Browser projection | `src/lib/korgi/host/studio-host-bridge.ts` | `fetch /api/korgi/host`. Packet A actions only for projection execute. 800ms inspect poll in `Studio.tsx`. | | Registry | `src/lib/korgi/actions/registry.ts` | `ACTION_REGISTRY_VERSION = 2`. **56** named actions + **13** `korgi.command.*` = **69**. | | MCP list | `src/lib/korgi/mcp/server.ts` `tools/list` | `mcpToolsFromRegistry()` — **full catalog**, not the grant. | | Packet A grant | `PACKET_A_ACTIONS` | `korgi.settings.setName`, `korgi.peg.setFollow` | | Studio grant | `studioGrant()` | permission `studio`, capabilities `*`, scopes `*` — **host-minted origin `studio` only** | | Auth | `src/lib/auth/verify.server.ts` | Auth off + no `DATABASE_URL` → `"dev-user"`. Auth off + `DATABASE_URL` → throw. No `/login` route. | | Envelope destination | `ActionContext` | actor, dryRun, idempotencyKey, permission, capabilities, scopes, confirmed. **No destination field.** Destination today lives in action payloads (`destId`, cel ids). | | Local persist | `src/lib/project-persist.ts` + zustand `persist` | IndexedDB / OPFS snapshot + tab UI. Not hosted multi-device. | --- ## 1. Command envelope Every mutation that can change committed state uses one envelope. Transports (HTTP MCP, stdio MCP, CLI, in-app operator, Studio dispatch) **adapt to it**. They do not grow a second command language. ```text { operationId: string, // idempotency key; required expectedRevision: string, // documentFingerprint; required on mutate actionId: string, // registry id payload: object, // action schema destination: { // required when the action writes picture, audio, timing, or membership projectId: string, shotId?: string, // production cut / column owner as the project already names it celId?: string, layerId?: string }, bindingId: string, // host-minted; required actor: string // authenticated user id; never client-supplied as authority } ``` **Rules** 1. Duplicate `operationId` + same payload → no second mutation (`deduplicated`). Measured: Packet A passing. 2. Duplicate `operationId` + different payload → refuse. Measured: Packet A passing. 3. Stale `expectedRevision` → refuse, production unchanged. Measured: Packet A passing. 4. Missing / invalidated binding → refuse. `replaceProject` / `openPacked` invalidates bindings. Measured: Packet A passing. 5. `destination` that does not match the bound project, or that the grant cannot write, → refuse. **Not implemented** as an envelope field today; W1 adds it without breaking payloads that already name `destId`. 6. Incomplete ingest / upload **cannot** become a saved cel. Packet B staged ingest remains the model: failure before or after byte storage must not earn “Saved” and must not damage the previous revision. 7. Idempotency is **host-side**, keyed by `(projectId, operationId)`, durable with the document — not only the in-memory journal of one process. --- ## 2. Host persistence **Required:** serialize even across processes. A second Node worker, a restarted Railway instance, or a second device opening the same project must see the **same committed revision** or an explicit conflict, not a silent fork. **Prohibited** - Incomplete upload becoming a saved cel. - Background workers independently overwriting the production document (no shop callback writes dest cels; no mux worker packing a different project copy). - Treating `getAuthoritativeHost()` as durable storage. - Two production documents (MCP vs Studio) with a syncer. **Allowed persistence tiers** (W1 chooses one durable store; this freeze does not pick a vendor) 1. Packed `.korgi` bytes as the document (already `packKorgi` / `unpackKorgi`). 2. Host metadata: bindings (hashed), grants, operation-id log, revision, membership — **adjacent** to the pack, same transactional write. 3. Browser projection may cache a snapshot (IndexedDB/OPFS) as **read replica + transient**. “Saved” requires a **durable ack** from the host (revision + operation receipt). Local cache alone is not saved. `ProjectHost.pack()` today does not include bindings or the operation log. W1 extends the packed (or sidecar) document so a new process can `openPacked` and restore **authority**, not only pixels. **W7 integrator close — one `assetBytes` map.** Imported audio, scan PNGs, and generation rasters are retained on `ProjectHost` the same way cels are: incomplete ingest cannot be saved; `pack` / `openPacked` restore original-byte hashes; Studio `assetBytes` is a view of the host map; “saved” is a durable host ack. Export movie / `korgi_movie.wasm` mix PCM from that host map through W5 `importAudio`. --- ## 3. Grant model **Advertise only authorized actions.** `tools/list` / operator tool list / CLI help for a bound session must equal `grant.actions` (or the computed authorized set). Listing 69 while executing 2 is a **refusal-test failure**. **Minting** - Origin `user` or `studio` only (already). - User-minted bindings come from an **authenticated** user id after Better Auth. - Model cannot mint. Operator cannot mint its own `studio *`. - Session revocation **invalidates** delegated access (all bindings for that session / user grant). **Confirmation** Honor `ActionDef.confirm`: `none` | `prompt` | `destructive`. Operator and MCP do not set `confirmed: true` unless the user confirmed. Today `ProjectHost.execute` sets `confirmed: true` when `grant.permission === "studio"` — Studio origin may do that; **delegated grants must not**. **Refusal tests** (W1/W2 must keep green) | Test | Expected | |---|---| | Model-supplied `studio *` | throw; no binding | | Unauthorized action (e.g. `korgi.draw.mintDrawing` on Packet A grant) | refuse; sheet unchanged | | Stale revision | refuse | | Invalidated binding after project replace | refuse | | Duplicate operationId, different payload | refuse | | `llm-propose` / dry-run | no production write | | Unbound MCP `tools/call` | `unbound: Project Host required` | | Revoked session | subsequent execute refuse | | `tools/list` after Packet A bind | **exactly** the two granted tools (contract; **fails today**) | Equal capability: production grants for Mitchell’s operator and for an art-team operator can include drawing, ingest, holds, generation (with spend), audio import, and export **when the user delegates them**. Do not encode equal access as `studio *` on the wire. --- ## 4. Browser projection The browser holds: 1. A **view** of committed host state (revision, name, cels, clock, `celColumns`, audio clips). 2. **Named transient** interaction state (playhead, selection, onion, in-progress stroke, operator draft, unbound file picker). Pull is already sketched: `inspectStudioHostProjection` + `pullAuthoritativeProject`. Push of Packet A actions goes through `/api/korgi/host`. **“Saved”** means the host returned `ok` with a new (or deduplicated) revision. IndexedDB write without that ack is a local cache, not saved. Canvas-first layout is design ([`ASTRA_DESIGN.md`](ASTRA_DESIGN.md) §4). W1/W2 do not restyle Studio. They stop the tab from being a second writer. --- ## 5. Auth | Rule | Detail | |---|---| | Sessions | Better Auth at `/api/auth/*`. Cookie (and preview bearer) only. Never trust a client-supplied user id. | | Production | `VITE_AUTH_ENABLED` must be on when `DATABASE_URL` is set. Keep fail-closed if auth is off against a real DB. | | Fail closed | Signed-out mutate → 401. No `"dev-user"` in production. | | `/login` | Must exist as a real route that can mint a session. `SIGN_IN_PATH` already names it; the route does not exist. | | Membership | A session is not a project grant. Project membership + host binding are separate. Viewer without a write grant cannot mutate. | | Revocation | Sign-out / session revoke invalidates bindings minted for that session. | `dev-user` remains a **local no-database** convenience only. It must not mint production bindings. --- ## 6. Evaluator, film, audio, export (do not fork) | Topic | Contract | |---|---| | Evaluator | [`EVALUATOR_DECISION.md`](EVALUATOR_DECISION.md) stands. `celColumns` govern production shots. **Do not** dual-write into `takes`. | | New Project | 24@12 (`FILM_LEN = 24`, `FPS = 12` in `src/lib/film.ts`). | | `migrateLegacyLoop` | STUDIO_ONLY, confirm=prompt, operator-visible. Never silent. | | Audio | General import; `importWav` compatible; original bytes; matrix in `ASTRA_DESIGN.md` §8. | | Export | First product target valid WebM VP9/Opus; PCM mux stays labeled Matroska; keep cluster timestamp fix. | | Destination apply | Own quality gate. `accepted-frame2` posterize/black is a defect until proven otherwise. | --- ## 7. File ownership (who may edit) Integrator = Helios on Icarus unless Mitchell names a successor. Packet workers implement **behind** these files; they do not restyle the envelope to make a demo green. | File / area | Owner | Later workers | |---|---|---| | `src/lib/korgi/host/project-host.ts` | **Helios / integrator only** | Call `mintBinding` / `execute` / `inspect`. Do not add a second host class. | | `src/lib/korgi/host/authoritative-host.server.ts` | **Helios / integrator only** | May consume `getAuthoritativeHost()`. Durable store lands here or behind it. | | `src/lib/korgi/host/host-http.server.ts` | **Helios / integrator only** | Projection transport. | | `src/lib/korgi/host/studio-host-bridge.ts` | **Helios / integrator only** | Browser adapter. | | `src/lib/korgi/actions/registry.ts` | **Helios / integrator only** for grants, permissions, new ids that change the catalog contract. Packet workers may **propose** a new action id in a brief; Helios lands the row. | | `src/lib/korgi/actions/types.ts` | **Helios / integrator only** | Envelope / `ActionContext` destination field. | | `src/lib/korgi/actions/execute.ts` | **Helios / integrator** for shared execute; packet workers may add **branches for their action ids** with Helios review. | | `src/lib/korgi/mcp/server.ts` + `from-registry.ts` | **Helios / integrator only** for `tools/list` filtering by grant. | | `src/lib/korgi/evaluate-frame.ts` | **Helios / integrator only** | Evaluator decision. Do not add a second reader. | | `src/lib/auth/*` | **Helios / integrator** for fail-closed production and `/login` wiring. | | `src/lib/korgi/wav.ts` + `korgi.film.importWav` | W5 implements; **must stay importWav-compatible**; Helios reviews the action id. | | `src/lib/korgi/mux-webm-av.ts` | W6 implements VP9/Opus; **must preserve cluster timestamp fix**; Helios reviews container labeling. | | `src/lib/store.ts` Studio dispatch | Packet workers may wire UI to **existing** actions. New production writes go through the host. | | `docs/oss/ASTRA_DESIGN.md` | Helios / Mitchell. Do not rewrite the verdict. | | `docs/oss/ASTRA_INTERFACE_CONTRACT.md` | **Helios only** (this file). | | `docs/oss/EVALUATOR_DECISION.md` | Locked. Do not reopen dual-write. | | Packet receipts A/B/C/D/F | Historical. Append; do not restamp as product-green. | --- ## 8. What W1 vs W2 land **W1 — envelope + durable host.** Operation ID, expected revision, destination, persistence across processes, incomplete upload cannot save, workers cannot overwrite production, “saved” = durable ack. Keep Packet A refusal tests green. Start `tools/list` filter (may complete in W2). **W2 — grants + identity + projection.** User-minted bindings from Better Auth; advertise only authorized actions; confirmation; revocation; fail-closed production; no silent `dev-user`; browser is view + transient. Destination-render quality gate is **named here** and proven with apply fixtures (accepted-frame2 class). Audio (W5) and export (W6) **start in parallel**. They consume this envelope; they do not invent another. --- Helios, Icarus, 2026-09-13. Workers build against **this** file and [`ASTRA_DESIGN.md`](ASTRA_DESIGN.md), not against mixed gallery manifests.