# WebUI Fleet Claude Bridge Implementation Plan

> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
>
> **Conditional instruction:** The line above is dormant until Fred approves every Section 2 item, all `[FRED-GATE]` markers are replaced by recorded decisions, and `IMPLEMENTATION_BASE_SHA` is recorded. Until then: review and planning only.

**Goal:** Prove one authenticated browser conversation backed by one native Claude Code OAuth session launched through the authoritative Mosaic launcher on one enrolled Agent Host.

**Architecture:** Keep provider OAuth and process ownership on an outbound-connected Agent Host. The Gateway authorizes and durably binds the browser conversation to a normalized runtime provider, while a host-side adapter invokes the installed `mosaic claude` command and converts Claude stream-json frames into redacted, ordered Mosaic events. Reuse the existing Web chat event contract and durable interaction session instead of creating a second prompt-composition or transcript path.

**Tech Stack:** TypeScript, Node.js 22+, pnpm/Turborepo, NestJS 11 + Socket.IO, Next.js 16 + React 19, Vitest, Playwright, native Claude Code stream-json, existing `@mosaicstack/{types,agent,mosaic}` packages.

---

> **STATUS: DRAFT FOR FRED STRUCTURE REVIEW — DO NOT IMPLEMENT YET.**
>
> Fred must approve the launcher/seat-home and one-host enrollment contract below before code. This plan deliberately does not read the lease broker, alter the local roster schema, start Gateway/Web around the database hold, or touch the legacy deployed WebUI.
>
> **Review status:** Fred authorized Task 0's initial docs-only decision PR on the `next` lane in `comms/20260809T094952Z__from-fred__ec0e85.md`. That authorization does **not** extend to Task 1. This revision becomes an executable coding handoff only after the PR records Fred's complete answers, removes every `[FRED-GATE]`, and names an `IMPLEMENTATION_BASE_SHA` containing the W-F-owned launcher. That implementation base is currently `BLOCKED-ON-W-F`.

### Required execution load order, skills, and conventions

Before implementation, load completely and in this exact order:

1. `~/.config/mosaic/SOUL.md`
2. `~/.config/mosaic/STANDARDS.md`
3. `~/.config/mosaic/AGENTS.md`
4. `~/.config/mosaic/guides/E2E-DELIVERY.md`
5. repository `AGENTS.md`
6. `~/.config/mosaic/runtime/claude/RUNTIME.md`

Then load `executing-plans`, `test-driven-development`, `nestjs-best-practices`, `next-best-practices`, `vercel-react-best-practices`, `webapp-testing`, `lint`, `requesting-code-review`, and `verification-before-completion`. Follow ESM `.js` imports, explicit Nest `@Inject()` decorators, dedicated `*.dto.ts` boundaries, typed Socket.IO events, strict TypeScript, and headless-only browser automation.

Tasks 1–8 use unit tests, fake transports, and already-approved test fixtures only. They must not start Gateway/Web, connect PostgreSQL, apply DDL, or invoke a migration. The first real Gateway/Web startup is Task 9 and remains blocked until Fred/Scooby certify that path.

## 1. Reconciled baseline and hard gates

### Evidence pins, not yet the implementation pin

| Surface                   | Verified state on 2026-08-09                                        |
| ------------------------- | ------------------------------------------------------------------- |
| Stack `main`              | `b0f7d26dd9c14d91eaaefc35d6c9fd6618a0bd92`                          |
| Stack `next`              | `4df478cdd150fdf8d52ea109f02ade5d85017acd`                          |
| Branch relation           | Diverged: `main` has 11 unique commits; `next` has 13               |
| Required `next` fix       | #689 / `56787fab`: disable Redis consumers on local tier            |
| Web chat                  | Authenticated Socket.IO chat backed by an in-process Pi SDK session |
| Runtime provider registry | Hermes only                                                         |
| Provider lifecycle        | list/tree/stream/send/attach/detach/terminate; **no create/start**  |
| Framework structure       | Three-root seat-home target is approved but not yet built           |
| Live WebUI                | Legacy v0.0.20, reference-only                                      |

Neither current branch contains the complete required state. **No implementation commit may be based on either evidence SHA.** Before Task 1, Fred must identify one immutable reconciled commit that contains or cleanly contracts with:

1. current `main` Gateway/Web/runtime-provider behavior;
2. #689 or an equivalent certified local-tier queue fix;
3. the Node 22/resolved-path launcher requirement;
4. the Fred-approved generated seat-home launch seam;
5. a certified Gateway/Web startup path that does not bypass the KBN/database hold.

Record that SHA as `IMPLEMENTATION_BASE_SHA` in this plan and the task scratchpad. If no such SHA exists, implementation remains blocked; do not cherry-pick an ad hoc private baseline.

### Verified no-migration storage facts and limitation

At the evidence pin, `packages/db/src/schema.ts` provides:

- `interaction_sessions.id` as the stable primary key with required owner, tenant, provider, and runtime-session fields;
- `interaction_outbox` with `kind`, `content`, digest, correlation/channel IDs, and `pending | processing | delivered` status;
- a unique `(session_id, idempotency_key)` outbox index;
- no create-operation, policy, enrollment, terminal-state, or approval-acceptance columns.

`apps/gateway/src/agent/durable-session.repository.ts` currently updates provider/runtime identity on same-scope `create()`, so baseline behavior is not safe for M1. Section 4.3 defines an explicit pending-reference/outbox encoding and CAS repository port without DDL. `[FRED-GATE]` Task 0 must certify that this encoding and transaction are acceptable. If it cannot, Stop Condition 9 applies; workers may not invent a migration.

### Ownership boundaries

**Fred/framework owns and must freeze:**

- `~/.config/mosaic` system root, `~/.mosaic` user root, and untouched stock runtime homes;
- generated seat home and `CLAUDE_CONFIG_DIR` behavior;
- roster/profile/seat identity and `MOSAIC_AGENT_NAME` binding;
- absolute resolved Node/Mosaic/runtime paths;
- the machine-facing launch boundary that produces a native process with the correct generated home;
- any future read-only lease/VERIFIED status adapter.

**Velma owns after Fred approves the contract:**

- one-host Gateway connection and authorization;
- normalized runtime create/send/stream/stop routing;
- host-side Claude stream adapter around the authoritative launcher;
- Web host readiness/session launch/chat rendering;
- tests, redaction, reconnect behavior, and documentation.

**Velma must not implement:**

- direct broker socket, daemon state, or broker file reads;
- independent roster or seat-home semantics;
- framework prompt composition;
- Gateway-owned provider OAuth;
- tmux pane scraping;
- Matrix/federation, generalized enrollment, broad agent management, or other runtimes in M1.

## 2. Decisions proposed for Fred approval

Fred should mark each item **APPROVE** or return one replacement contract before implementation. Each unresolved item is a `[FRED-GATE]`; workers must not choose for themselves.

1. **One normalized operation `[FRED-GATE]`:** add `session.create` / `createSession`; “start” is the HTTP/CLI lifecycle verb, not a second provider operation.
2. **W-F-bound launch descriptor `[FRED-GATE]`:** Section 4.1 proposes `ResolvedSeatLaunchDescriptorV1` as a candidate **consumer** contract to Fred's W-F harness-homes/launcher work. Its exact field list, artifact pinning, commitment mechanism, custody, and generation checks are not settled requirements; Fred may accept, replace, simplify, or defer them at D2-binding time. The invariant proposed by this plan is narrower: Agent Host consumes the W-F-produced resolved contract and neither searches ambient `PATH` nor trusts ambient `HOME`, `MOSAIC_HOME`, `CLAUDE_CONFIG_DIR`, or runtime paths.
3. **Server-resolved target `[FRED-GATE]`:** the browser request carries only the durable conversation ID and an idempotency key. It cannot submit host URLs/IDs, shell commands, argv, workspace paths, runtime credentials, native session IDs, or policy fields.
4. **One pinned launch policy `[FRED-GATE]`:** M1 resolves one host, roster seat/persona, opaque workspace ID, runtime `claude`, and policy ID `read-only-chat`. Fred provisionally bound the W-C read-only tool-label carve-out to exactly `{Read, Grep, Glob}`; no other tool label may be inferred. Policy remains non-YOLO, with no arbitrary extra args or mutating-tool approval response. If Claude emits `approval.required`, Web shows the state and allows Stop; it does not approve the provider action in M1.
5. **Outbound transport `[FRED-GATE]`:** dedicated authenticated Socket.IO namespace `/agent-host` over WSS. Matrix remains a follow-on provider transport.
6. **Bounded enrollment manifest `[FRED-GATE]`:** one operator-owned `AgentHostEnrollmentManifestV1` binds credential ID, generation, host ID, owner ID, tenant ID, launch-policy digest, issued/expiry timestamps, and revoked state. Gateway revalidates it before every command and accepted event. Productized pairing/UI remains M2.
7. **Credential custody `[FRED-GATE]`:** Fred supplies exact private Agent Host enrollment credential/manifest paths under the user root plus framework-manifest ownership. This credential is entirely separate from Claude OAuth. Claude OAuth stays in the native runtime location selected only by the descriptor's generated `CLAUDE_CONFIG_DIR`. Enrollment token is separate from non-secret config, regular, no-follow, owner-only, read once per validated generation, and never argv/log/event data.
8. **Connection replacement `[FRED-GATE]`:** Gateway assigns a random connection epoch. A higher valid enrollment generation replaces and disconnects the old connection; equal/lower duplicate generations are refused. Late events from an old epoch are rejected.
9. **Single active launch `[FRED-GATE]`:** Gateway reserves the stable conversation before host dispatch; host also enforces one active launch per stable conversation independent of browser idempotency key. Different concurrent keys cannot spawn two processes.
10. **No M1 schema migration `[FRED-GATE]`:** reuse `interaction_sessions` plus durable `interaction_outbox` entries of kind `provider.create` / `provider.stop` for reservations and reconciliation. Host uses an exclusive, atomic private operation journal. If existing tables cannot safely provide these semantics, stop at Task 0 rather than adding an unauthorized migration.
11. **M1 authorization `[FRED-GATE]`:** M1 is single-operator: Jason is the only tenant. Preserve a server-derived `userId`→`tenantId` seam without building multi-tenant machinery. Only the manifest-bound admin owner/tenant may list the host or create/stop sessions; Gateway derives the mapping from validated operator config, never host hello or browser fields.
12. **Provenance visibility `[FRED-GATE]`:** browser receives the Section 4.2 safe target DTO only. Runtime audit stores allowlisted IDs, versions, launch ID, enrollment/contract generation, and only the path-free provenance fields selected by D2/D12. Path strings are neither logged nor hashed. Host-private evidence follows the final W-F contract; live QA emits only approved booleans/digests.
13. **No lease display `[FRED-GATE]`:** host reports launcher/auth readiness and sanitized launch failures only. Agent Host has no broker API/path/status logic; it merely delegates to Fred’s sealed launcher, which may internally enforce broker policy. VERIFIED/TTL display remains F-V3/Fred-owned.
14. **Gateway restart `[FRED-GATE]`:** never auto-launch. Pending create/stop operations reconcile from durable outbox plus host operation journal. An in-flight transcript turn without a durable accepted cursor is marked indeterminate and is not auto-persisted/resumed. Full process adoption after host-daemon restart remains M2.
15. **Stop `[FRED-GATE]`:** browser first obtains a server-derived exact-action approval and `stopOperationId`. Gateway durably accepts that exact approval once, journals dispatch, and reconciles ambiguous responses by the same operation ID without accepting another approval. Host exact-target termination is idempotent.

### Fred decision return packet

Fred's response must fill every value below; “approve direction” alone does not close Task 0.

| Decision                    | Required recorded value                                                                                                                                                                                                                                                                            |
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| D1 normalized create        | approve `session.create` as the only provider operation and HTTP/CLI `start` as naming only                                                                                                                                                                                                        |
| D2 resolved launcher        | W-F binding verdict for the Section 4.1 candidate: final producer/API and accepted/replaced descriptor, custody, commitment/pinning, generation, and Node→Mosaic→interpreter→Claude rules; no candidate field is binding before that verdict                                                       |
| D3 pinned target            | exact host label/ID, seat, persona, byte-equal `MOSAIC_AGENT_NAME`, workspace ID/label, absolute host-only working directory, generated `HOME`, `CLAUDE_CONFIG_DIR`, and `MOSAIC_HOME`                                                                                                             |
| D4 launch policy            | policy ID/digest, exact read-only tool-label allowlist, final non-YOLO Claude flags, and V1 environment allowlist/no inherited extras                                                                                                                                                              |
| D5 transport                | exact Gateway WSS URL, `/agent-host` namespace, TLS termination, heartbeat, reconnect/backoff, and maximum frame values                                                                                                                                                                            |
| D6 enrollment manifest      | host/Gateway manifest sources, final W-F-bound schema/generation/expiry values, any descriptor commitments selected by D2, policy digest, reload signal authority, and systemd owner                                                                                                               |
| D7 credential custody       | host/Gateway token and any D2-selected private commitment-key sources/formats, owner/mode/no-follow rules, rotation installer/authority, and proof Claude OAuth is separate under generated `CLAUDE_CONFIG_DIR`                                                                                    |
| D8 connection generations   | approve higher-generation replace/equal-lower refuse, config-epoch swap order, old-event rejection, and expiry/revocation timer                                                                                                                                                                    |
| D9 one active launch        | approve owner/tenant/durable-session uniqueness on Gateway and host, including rotation and concurrent distinct browser keys                                                                                                                                                                       |
| D10 existing storage only   | certify Section 4.3 encodings/repository transitions/outbox statuses with no DDL, or declare M1 blocked                                                                                                                                                                                            |
| D11 authorization           | exact authenticated Web `userId`, derived `tenantId`, deployment `agentName`, and server-owned `interaction-http` channel mapping                                                                                                                                                                  |
| D12 presentation/provenance | exact safe host/workspace/seat/persona labels, browser readiness/connection fields, D2-selected safe contract provenance, and audit artifact fields; no raw or hashed path strings                                                                                                                 |
| D13 F-V3/cleanup            | approve no bridge broker API/status/read path and give exact Fred-owned cleanup procedure for an indeterminate child                                                                                                                                                                               |
| D14 restart behavior        | approve startup enumeration, create/stop lookup/status actions, no auto-launch, in-flight turn indeterminate behavior, and operator-visible recovery state                                                                                                                                         |
| D15 exact stop              | approve verify→durable accept→claim/`GETDEL`→dispatch state machine, two-minute deadline, retry/status response semantics, and exact child termination                                                                                                                                             |
| Immutable source            | exact `FRED_APPROVED_REF`; full `IMPLEMENTATION_BASE_SHA` containing the W-F-bound launcher and plan, currently `BLOCKED-ON-W-F`                                                                                                                                                                   |
| Certified execution         | safe `CERTIFIED_WEB_URL`; literal private `CERTIFIED_DATABASE_URL_LOADER` that writes only the disposable URL to captured stdout; and KBN-safe startup/post-merge scripts supporting `--worktree`, `--source-sha`, and `--web-url`; smoke stdout is the exact safe JSON schema asserted in Task 10 |
| Versions/readiness          | pinned Node, Mosaic, Claude, adapter/descriptor protocols, exact safe auth-readiness argv/shape—or explicit `unknown-until-launch`                                                                                                                                                                 |

## 3. Target execution spine

```text
Browser (Authentik session)
  │  existing conversation + Socket.IO chat contract
  ▼
Gateway RuntimeChatBridgeService
  │  owner/tenant scope + durable interaction binding
  ▼
MosaicHostRuntimeProvider (`runtime.agent-host`)
  │  authenticated command ID + idempotency + normalized events
  ▼
Outbound-connected Agent Host (`mosaic agent-host connect`)
  │  exact generated seat + workspace policy
  ▼
<absolute node> <absolute mosaic-cli> claude
  │  --print --input-format stream-json --output-format stream-json
  │  --include-partial-messages --replay-user-messages --verbose
  │  --session-id <gateway-generated UUID> + frozen policy flags
  ▼
Claude Code native OAuth + host-local generated `CLAUDE_CONFIG_DIR`
```

The exact Claude flags remain subject to Task 1’s red-first compatibility certificate against the pinned target version. No flag is production-authoritative merely because Claude Code 2.1.226 advertised it during discovery.

## 4. Contract freeze

### 4.1 Candidate consumer descriptor proposed to Fred's W-F work

`[FRED-GATE]` The following `ResolvedSeatLaunchDescriptorV1` is **non-binding design input to W-F**, not a plan-imposed framework requirement. Fred owns the producer and final shape. W-F may accept, replace, simplify, or defer any listed field, artifact digest, or commitment mechanism. The Agent Host implementation must consume only the final D2-bound contract and must not infer omitted values:

```ts
export interface ResolvedSeatLaunchDescriptorV1 {
  protocolVersion: 1;
  generation: number; // integer 1..2_147_483_647
  producerId: string;
  privateDescriptorCommitment: `hmac-sha256:${string}`;
  publicContractDigest: `sha256:${string}`;
  hostLabel: string;
  seatName: string;
  personaLabel: string;
  workspacePolicyId: string;
  workspaceLabel: string;
  launchPolicyDigest: `sha256:${string}`;
  workingDirectory: string;
  nodePath: string;
  mosaicCliPath: string;
  claudePath: string;
  descendantExecutables: readonly Readonly<{
    role: 'lease-launcher' | 'python' | 'runtime-helper';
    path: string;
  }>[];
  expectedArtifacts: Readonly<{
    node: `sha256:${string}`;
    mosaicCli: `sha256:${string}`;
    claude: `sha256:${string}`;
    descendants: readonly Readonly<{
      role: 'lease-launcher' | 'python' | 'runtime-helper';
      digest: `sha256:${string}`;
    }>[];
  }>;
  seatHome: string;
  claudeConfigDir: string;
  mosaicHome: string;
  environment: Readonly<{
    HOME: string;
    CLAUDE_CONFIG_DIR: string;
    MOSAIC_HOME: string;
    MOSAIC_AGENT_NAME: string;
    PATH: string;
    LANG: 'C.UTF-8';
    LC_ALL: 'C.UTF-8';
    TERM: 'dumb';
    TMPDIR: string;
  }>;
}
```

**Candidate mechanics, subject entirely to D2/W-F binding:** `privateDescriptorCommitment` uses HMAC-SHA-256 over RFC 8785/JCS canonical full descriptor JSON with both commitment fields omitted, while `publicContractDigest` covers a safe path-free projection. A separate Fred-owned key and pre-spawn artifact-byte verification are proposed defenses, but W-F may simplify or defer either mechanism for V1. If selected, private values stay out of browser/audit/log data and audit retains only the D2-approved safe projection.

The candidate manifest compares the D2-selected descriptor provenance, generation, and policy before spawn. Candidate byte-exact invariants are `environment.HOME === seatHome`, `CLAUDE_CONFIG_DIR === claudeConfigDir`, `MOSAIC_HOME === mosaicHome`, and `MOSAIC_AGENT_NAME === seatName`; no ambient path/home value survives. These are proposed consumer needs, not claims about an already-built producer.

The final W-F contract defines which secure-open, ownership/mode, symlink/hardlink, generation, commitment, and artifact-pinning checks exist in V1. The approved immutable baseline must contain that bound launcher before Task 1. Task 1 may certify `launch.ts` and add consumer tests against the final contract, but Velma must not patch Fred-owned launcher behavior; a failing descendant certificate returns to Fred and blocks code.

**Downstream binding rule:** every descriptor field, commitment, digest, custody check, and descendant-path assertion named later in this plan is a symbolic candidate slot. Before Task 1, Task 0 must replace or remove each slot to match Fred's final D2/W-F contract. No downstream DTO, test, audit field, or acceptance criterion may preserve a Section 4.1 mechanism that W-F did not bind.

### 4.2 Runtime create and compatibility contract

Add `session.create` and a required `createSession()` method in `packages/types/src/agent/agent-runtime-provider.ts`. Keep browser input separate from the server-resolved provider command. Descriptor-derived names in the following DTO/protocol examples are candidate symbolic slots included to make the consumer flow reviewable; Task 0 must conform them to the final D2/W-F shape before implementation:

```ts
export interface BrowserRuntimeSessionStartInput {
  idempotencyKey: string;
}

export interface RuntimeSessionCreateRequest {
  durableSessionId: string;
  idempotencyKey: string;
  createOperationId: string;
  nativeSessionId: string;
  requestDigest: `sha256:${string}`;
  expectedPrivateDescriptorCommitment: `hmac-sha256:${string}`;
  expectedPublicContractDigest: `sha256:${string}`;
  expectedLaunchPolicyDigest: `sha256:${string}`;
  expectedEnrollmentGeneration: number;
}

export interface RuntimeLaunchProvenance {
  hostId: string;
  hostEnrollmentId: string; // internal/audit only
  launcher: 'mosaic';
  runtime: 'claude';
  launchId: string;
  launchedAt: string;
  launcherArtifactDigest: `sha256:${string}`;
  launcherVersion: string;
  runtimeArtifactDigest: `sha256:${string}`;
  runtimeVersion: string;
  adapterVersion: string;
  publicContractDigest: `sha256:${string}`;
  contractGeneration: number;
}

export interface RuntimeSessionCreateResult {
  session: RuntimeSession;
  disposition: 'created' | 'replayed';
  resumeCursor?: string;
  provenance: RuntimeLaunchProvenance;
}

export interface BrowserRuntimeTargetPresentationV1 {
  hostLabel: string;
  workspaceLabel: string;
  seatLabel: string;
  personaLabel: string;
  launcher: 'mosaic';
  runtime: 'claude';
  launcherVersion: string;
  runtimeVersion: string;
  adapterVersion: string;
  policyId: string;
  authReadiness: 'ready' | 'not-ready' | 'unknown';
  connectionState: 'connected' | 'disconnected' | 'reconnecting';
}

export interface BrowserAgentHostReadinessV1 {
  ready: boolean;
  target: BrowserRuntimeTargetPresentationV1;
  reasons: SafeReadinessCode[];
}

export interface BrowserRuntimeSessionStartResultV1 {
  durableSessionId: string;
  state: RuntimeSessionState;
  disposition: 'created' | 'replayed';
  target: BrowserRuntimeTargetPresentationV1;
}
```

The controller maps browser input to a fully server-resolved request. Before spawn, the host requires the exact equality/validation checks selected by the final D2/W-F contract among request, enrollment, policy, and resolved-launch inputs. The browser serializer emits only `BrowserRuntimeSessionStartResultV1`; it cannot serialize enrollment ID, operation/launch/native IDs, artifact/private/public contract digests, paths, or internal provenance.

Expand `RuntimeSessionState` additively to:

```ts
export type RuntimeSessionState =
  | 'enrolling'
  | 'ready'
  | 'starting'
  | 'running'
  | 'waiting-for-input'
  | 'waiting-for-approval'
  | 'stopping'
  | 'disconnected'
  | 'reconnecting'
  | 'active' // legacy provider mapping
  | 'idle' // legacy provider mapping
  | 'stopped'
  | 'failed';
```

Hermes, Matrix, and tmux implement `createSession()` but do not advertise `session.create`; they throw `capability_unsupported` before transport. Existing state mappings remain unchanged.

### 4.3 Reservation, idempotency, and create reconciliation

Gateway generates an opaque `createOperationId`. Hash input is RFC 8785/JCS canonical JSON with named fields—never delimiter concatenation:

```json
{
  "operation": "session.create",
  "ownerId": "...",
  "tenantId": "...",
  "durableSessionId": "...",
  "idempotencyKey": "...",
  "hostEnrollmentId": "...",
  "enrollmentGeneration": 1,
  "privateDescriptorCommitment": "hmac-sha256:...",
  "publicContractDigest": "sha256:...",
  "launchPolicyDigest": "sha256:..."
}
```

Two identities are enforced:

1. **Active-launch identity** is exactly owner/tenant/durable session; it excludes idempotency key, enrollment generation, and policy. Only one non-terminal runtime may exist for that stable conversation, including during credential rotation.
2. **Request identity** additionally binds idempotency key, enrollment ID/generation, and launch-policy digest; an identical retry returns the same operation/result, while changed semantics return `conflict` until the prior runtime is explicitly terminal/recovered.

Use these exact existing-column encodings:

- reserved `runtimeSessionId`: `pending:v1:<createOperationId-uuid>`;
- definitive failed create: `failed:v1:<createOperationId-uuid>`;
- create outbox idempotency key: `create:v1:<64-lowercase-hex-request-digest>`;
- stop outbox idempotency key: `stop:v1:<stopOperationId-uuid>`;
- `providerId`: `runtime.agent-host`.

`RuntimeCreateOperationRecordV1` in outbox `content` contains only operation ID, request digest, expected private/public contract and policy digests, enrollment generation, accepted/dispatched/result timestamps, and disposition `reserved | dispatched | created | failed | indeterminate`; repository updates content and `contentDigest` together. Existing outbox status means: `pending` = durable reservation accepted but not claimed; `processing` = host may have accepted the effect and status must be reconciled; `delivered` = definitive success or failure recorded in content. No prompt, credential, workspace path, env, or raw provenance is stored.

Add exact repository operations `reserveCreate()`, `activateCreate()`, `failCreate()`, `reserveStop()`, `completeStop()`, and `findRuntimeOperation()`. `reserveCreate()` is one DB transaction: lock/select the stable session ID; insert or CAS it to the pending reference only when absent, `failed:v1:*`, or bound to a runtime with a matching delivered `provider.stop`; and insert the unique create outbox. Active or indeterminate bindings conflict. `activateCreate()` changes only the matching pending reference to the returned runtime ID and completes that outbox. `failCreate()` changes only that pending reference to `failed:v1:*` and records a delivered definitive failure. Existing `create()` must never replace a binding.

The host journal independently locks owner/tenant/durable session. Lookup, active check, spawn, journal commit, and response occur under a private single-daemon lock. Journal and parent directory are owner-only, no-follow regular files with secure ancestor checks, atomic rename, file-and-directory `fsync`, and a single-instance lock. It stores operation/request/private/public contract/policy digests, safe provenance, child handle, and disposition—never prompts, output, env, or credentials.

Identical retries return the same IDs/provenance as `replayed`; changed semantics conflict. A post-spawn lost response stays `processing/indeterminate`; Gateway calls only `session.lookup` with the same operation ID. If spawn succeeds before Gateway CAS, pending row + processing outbox + host journal prevent another launch. Task 0 statically certifies this use of current columns/indexes; Task 5 proves repository transitions with fakes, and Task 9 must run the real repository transaction test only through the KBN-safe path.

### 4.4 Typed host protocol and readiness

Every envelope is versioned and schema-validated before dispatch:

```ts
export type AgentHostOperation =
  | 'host.readiness'
  | 'session.create'
  | 'session.lookup'
  | 'session.send'
  | 'session.stream'
  | 'session.replay'
  | 'session.stop'
  | 'session.stopStatus';

export type AgentHostOperationPayloadV1 =
  | { operation: 'host.readiness' }
  | ({ operation: 'session.create' } & RuntimeSessionCreateRequest)
  | { operation: 'session.lookup'; createOperationId: string; durableSessionId: string }
  | {
      operation: 'session.send';
      runtimeSessionId: string;
      turnId: string;
      idempotencyKey: string;
      content: string; // UTF-8, 1..65_536 bytes
    }
  | { operation: 'session.stream'; runtimeSessionId: string; afterCursor?: string }
  | { operation: 'session.replay'; runtimeSessionId: string; afterCursor: string }
  | {
      operation: 'session.stop';
      runtimeSessionId: string;
      stopOperationId: string;
      actionDigest: `sha256:${string}`;
    }
  | { operation: 'session.stopStatus'; runtimeSessionId: string; stopOperationId: string };

export interface SafeRuntimeError {
  code: RuntimeErrorCode;
  boundary: RuntimeFailureBoundary;
  message: string; // fixed code-mapped text, max 256 UTF-8 bytes
  retryable: boolean;
}

type AgentHostEnvelopeV1 =
  | {
      type: 'hello';
      protocolVersion: 1;
      credentialId: string;
      generation: number;
      hostId: string;
      privateDescriptorCommitment: `hmac-sha256:${string}`;
      publicContractDigest: `sha256:${string}`;
      launchPolicyDigest: `sha256:${string}`;
    }
  | {
      type: 'command';
      protocolVersion: 1;
      commandId: string;
      connectionEpoch: string;
      payload: AgentHostOperationPayloadV1;
    }
  | {
      type: 'result';
      protocolVersion: 1;
      commandId: string;
      connectionEpoch: string;
      ok: boolean;
      payload?: AgentHostOperationResultV1;
      error?: SafeRuntimeError;
    }
  | { type: 'event'; protocolVersion: 1; connectionEpoch: string; event: MosaicRuntimeEventV1 };

export type AgentHostOperationResultV1 =
  | { operation: 'host.readiness'; readiness: HostReadinessV1 }
  | { operation: 'session.create'; result: RuntimeSessionCreateResult }
  | {
      operation: 'session.lookup';
      disposition: 'created' | 'failed' | 'indeterminate' | 'not_found';
      result?: RuntimeSessionCreateResult;
    }
  | { operation: 'session.send'; accepted: true; turnId: string }
  | { operation: 'session.stream' | 'session.replay'; accepted: true; afterCursor?: string }
  | { operation: 'session.stop'; disposition: 'stopped' | 'replayed' | 'indeterminate' }
  | {
      operation: 'session.stopStatus';
      disposition: 'stopped' | 'failed' | 'indeterminate' | 'not_found';
    };
```

Successful result payload must validate as exactly one `AgentHostOperationResultV1` variant matching the correlated command. Failed result has no payload and requires `SafeRuntimeError`.

No arbitrary RPC method exists. DTO validators cap every identifier at 128 safe characters, cursor at 512 bytes, envelope at 1 MiB, and content as shown; unknown fields are rejected. WSS and Gateway session auth are mandatory; credential is in Socket.IO auth, never query. Heartbeat is 20 seconds, unavailable threshold 60 seconds, reconnect is capped exponential backoff with jitter, and old epochs cannot receive commands or publish accepted events.

Internal `host.readiness` returns only approved labels/versions plus `{ ready, hostLabel, workspaceLabel, seatLabel, personaLabel, authReadiness, enrollmentGeneration, launcherVersion, runtimeVersion, adapterVersion, policyId, reasons }`. Gateway adds connection state and serializes only `BrowserAgentHostReadinessV1`; enrollment generation is internal. It never returns IDs, paths, env, credential identity/content, lease state, or native errors.

### 4.5 Ordered lifecycle and transcript effects

Legal host-path transitions are:

```text
enrolling → ready → starting → running → waiting-for-input
                         ↘ waiting-for-approval
                         ↘ disconnected → reconnecting → previous live state
                         ↘ stopping → stopped
                         ↘ failed
```

Do not add an arbitrary metadata bag. Define the event as a validated base plus a closed payload union:

```ts
interface MosaicRuntimeEventBaseV1 {
  protocolVersion: 1;
  durableSessionId: string;
  runtimeSessionId: string;
  providerId: 'runtime.agent-host';
  hostId: string;
  runtime: 'claude';
  correlationId: string;
  turnId: string;
  messageId?: string;
  sequence: number; // positive safe integer, process scoped
  cursor: string;
  timestamp: string; // validated UTC RFC 3339
  eventDigest: `sha256:${string}`;
}

type MosaicRuntimeEventPayloadV1 =
  | { type: 'session.state'; state: RuntimeSessionState }
  | { type: 'message.delta'; text: string }
  | { type: 'message.complete'; text: string }
  | { type: 'tool.started'; toolLabel: SafeToolLabel; callId: string }
  | { type: 'tool.completed'; callId: string; status: 'succeeded' | 'failed' | 'cancelled' }
  | { type: 'approval.required'; approvalId: string; toolLabel: SafeToolLabel }
  | { type: 'runtime.error'; error: SafeRuntimeError };

type MosaicRuntimeEventV1 = MosaicRuntimeEventBaseV1 & MosaicRuntimeEventPayloadV1;
```

`SafeToolLabel` is the frozen policy allowlist; unknown labels become fixed `other`, never raw native names. Text events are individually capped at 64 KiB after streaming redaction; tool args/output are forbidden.

`eventDigest` is SHA-256 over RFC 8785/JCS canonical **redacted** event with `eventDigest` omitted. Host computes after validation/redaction and before ring append. Gateway validates schema, recomputes digest, and timing-safely compares before any sequence handling, persistence, audit, or browser emission.

Host sequence starts at 1 per runtime process. Gateway tracks `expectedNext` plus a map keyed by future sequence. For a lower accepted sequence, equal digest is duplicate/drop and differing digest is `protocol_mismatch`/disconnect. For a sequence already buffered in the future map, equal digest is duplicate/drop; differing digest immediately clears buffer and fails/disconnects. A new future sequence enters the 64-event/2-second buffer and triggers replay from `expectedNext`; no buffered event is persisted. Unresolved gaps fail visibly. `message.complete` and browser `agent:end` are withheld until every prior turn sequence is accepted.

Assistant persistence uses a UUIDv5 message ID derived from `(runtimeSessionId, turnId, 'assistant.complete')` and `INSERT ... ON CONFLICT DO NOTHING`; the event's stable message ID is stored in bounded message metadata. Duplicate completion or Gateway retry cannot create a second assistant message. Gateway restart with no durable accepted cursor marks the in-flight turn indeterminate and refuses automatic completion persistence or resume.

### 4.6 Error, replay, and host-daemon restart

Add failure boundary `gateway | transport | host | launcher | runtime | authentication` and codes `host_unavailable`, `protocol_mismatch`, `authentication_required`, `launch_failed`, `cursor_expired`, `indeterminate`, plus existing normalized codes. Safe errors contain code, stable message, boundary, and retryability only.

Host replay ring is bounded to 4,096 events and 8 MiB, whichever comes first; completed replay expires after 30 minutes. Resume is strictly after cursor. Missing/expired cursor reloads persisted transcript and warns without create. Browser disconnect leaves provider stream alive.

Host-daemon restart adoption is excluded. On restart, journal entries lacking a live registered child handle become `indeterminate`; readiness is false for them and no command except status/stop reconciliation is accepted. The host must not infer/adopt a process by PID, tmux, broker state, or process scanning. Operator cleanup uses the Fred-approved exact launcher/host procedure.

Gateway implements `RuntimeOperationRecoveryService implements OnApplicationBootstrap`. Repository method `listRecoverableRuntimeOperations(['pending', 'processing'])` enumerates only typed `provider.create`/`provider.stop` records for the manifest-bound owner/tenant:

- pending create: expose `pending`; never dispatch/launch until the same browser/CLI idempotency retry;
- processing create: call `session.lookup` only, then CAS to active/failed or remain indeterminate;
- pending accepted stop: call `session.stopStatus`; if `not_found` and within deadline, atomically claim and dispatch that same accepted operation;
- processing stop: call status first, then same-operation recovery from Section 4.7;
- unavailable/revoked host: retain indeterminate state and surface safe recovery status; never select another host;
- in-flight transcript without durable accepted cursor: mark indeterminate and never persist/auto-resume completion.

Startup recovery completes before host readiness/start routes report ready. Tests reconstruct service/repository, enumerate mixed owner/kind/status records, and assert only the transitions above; no auto-create occurs.

### 4.7 Retry-safe exact stop

Extend existing `InteractionController` routes; do not invent a second base path:

```text
GET  /api/interaction/:agentName/host-readiness
POST /api/interaction/:agentName/sessions/:sessionId/start
  body { idempotencyKey }
POST /api/interaction/:agentName/sessions/:sessionId/stop-approval
  body {}
  -> { stopOperationId, approvalRef, expiresAt }
POST /api/interaction/:agentName/sessions/:sessionId/stop
  body { stopOperationId, approvalRef }
  -> { status: stopped | replayed | failed | indeterminate, stopOperationId, error?: SafeRuntimeError }
GET  /api/interaction/:agentName/sessions/:sessionId/stop-operations/:stopOperationId
  -> { status: pending | stopped | failed | indeterminate, stopOperationId, error?: SafeRuntimeError }
```

Existing `apps/gateway/src/agent/interaction.controller.ts` owns all five routes; Task 3 has no HTTP controller. `:agentName` must equal deployment `MOSAIC_AGENT_NAME`; owner/tenant come from `scopeFromUser()` and byte-equal manifest owner/tenant. Add `operationContext(user)` for these new routes: channel is fixed `interaction-http`, and correlation is server-generated UUID (`createOperationId` or `stopOperationId`). It neither requires nor trusts client `X-Correlation-Id`. Preserve current header requirement only for untouched legacy interaction routes. Browser supplies no provider, host, runtime, action correlation, or policy.

Redis and PostgreSQL cannot share a transaction, so stop uses durable acceptance before destructive consumption:

1. Approval issuance generates `stopOperationId` and creates the existing Redis exact-action approval bound to actor, tenant, channel, provider, runtime session, and that operation ID.
2. Stop POST calls a new non-destructive `verifyRuntimeTerminationApproval()`; invalid/expired/mismatched approval writes nothing.
3. One DB transaction inserts unique `provider.stop` outbox `stop:v1:<operationId>` with an `authorizationAccepted` record containing only approval digest, action digest, accepted/expires timestamps, and server-derived IDs. This commit is the one durable authorization acceptance.
4. An atomic outbox claim changes `pending → processing`. The claimant atomically `GETDEL`s (or Lua compare-and-deletes) the exact Redis approval, then dispatches. Duplicate requests cannot claim twice.
5. Crash before DB commit leaves approval usable and no authorization record. Crash after DB commit is safe: the exact durable acceptance authorizes only this operation. If the token is gone or expires later, recovery first queries `session.stopStatus`; `not_found` permits dispatch of this same accepted operation before a two-minute operation deadline. It cannot authorize another operation or target.
6. Host journals disposition before reply; Gateway updates outbox content/digest and marks `delivered`. `processing` always reconciles status before any resend. Definitive timeout records delivered `failed`; a new action requires a newly issued approval.

Invalid/expired/mismatched approval is HTTP 403 and creates no operation. Once durably accepted, POST/status returns HTTP 200 with the operation status; definitive host/timeout failure is `failed` with only `SafeRuntimeError`. Tests inject failure before/after verify, DB commit, outbox claim, Redis `GETDEL`, host accept, graceful signal, force kill, host journal commit, and Gateway completion. Host binds stop to its registered child handle. Already stopped is success only for the same runtime/operation target.

### 4.8 Enrollment revocation and command authorization

```ts
export interface AgentHostEnrollmentManifestV1 {
  protocolVersion: 1;
  credentialId: string;
  generation: number; // integer 1..2_147_483_647
  hostId: string;
  ownerId: string;
  tenantId: string;
  expectedPrivateDescriptorCommitment: `hmac-sha256:${string}`;
  expectedPublicContractDigest: `sha256:${string}`;
  allowedLaunchPolicyDigest: `sha256:${string}`;
  issuedAt: string;
  expiresAt?: string;
  revoked: boolean;
}
```

Token digest is held by Gateway secret config, not the non-secret manifest. Gateway validates schema, integer range, UTC timestamps, manifest/digest equality, and credential at connect and immediately before every command **and event acceptance**. Generation ordering is numeric and strictly greater; equal/lower duplicate connection is refused.

Operator installs host and Gateway copies of generation N+1 atomically, then sends Gateway `SIGHUP`. `AgentHostConfigService.reload()` parses into a new immutable config epoch before swap; on success it removes old generation from registry/readiness first, rejects subsequent old-epoch events, disconnects its socket, and then allows N+1 registration. Parse failure retains prior config and reports safe not-ready reason. Expiry/revocation timer invokes the same path. Tests call `reload()` directly and via the signal adapter, including revocation during create/stream and events racing the swap. No product UI or DB migration is required.

### 4.9 Streaming redaction and provenance

Extract a shared bounded streaming redactor in `packages/log` before host event work. It retains at most 4 KiB suffix across chunks and recognizes split private keys/tokens/OAuth-like values/emails plus **all** absolute path classes: POSIX (including workspace/executable/working/config/home paths), Windows drive paths, UNC paths, `file://` URIs, and tilde-home forms. It emits fixed `[REDACTED_PATH]`/overflow markers and never releases a partial candidate before delimiter or bounded decision. Approved labels/versions travel in typed fields, never by exempting arbitrary text paths. Host applies before ring/journal/transport; Gateway applies again before audit/transcript/browser. Hostile NDJSON, stdout, stderr, errors, provenance, and split boundaries use the same component.

Extend `RuntimeAuditOperation` with `session.create` and typed outcomes/error codes. Audit projection contains safe host/launch IDs, launcher/runtime/adapter versions, enrollment generation, contract generation, and only D2/D12-approved path-free provenance (which may include artifact-content digests if W-F binds them). Do not hash path strings: predictable paths are dictionary-recoverable. No raw paths, arbitrary metadata, prompts, output, approval references, or credentials enter audit.

Host performs the final D2/W-F-bound launch-input checks and records only the corresponding D12-approved boolean/digest evidence in controlled QA. Fake-process tests may assert literal fixture paths; live QA never emits an absolute home/config/executable path. Browser receives only the explicit DTO in Section 4.2.

### 4.10 F-V3 boundary

Agent Host code has no lease-broker imports, socket/path constants, daemon-file reads, VERIFIED/TTL parsing, or status UI. Calling Fred's sealed authoritative launcher is allowed even if that launcher internally contacts the broker. The bridge treats launch success/failure as the only boundary result and does not claim system-wide absence of broker interaction.

## 5. Implementation tasks

### 5.0 Orchestrator task manifest and delivery loop

Task 0 replaces `$ISSUE_NUMBER`, `$IMPLEMENTATION_BASE_SHA`, and branch placeholders, then Fred/orchestrator writes these rows under a new WebUI Fleet Bridge section in root `docs/TASKS.md`. The table uses the superset schema from `~/.config/mosaic/templates/docs/TASKS.md.template` while retaining every repo-required field. Workers never edit rows or model assignments.

| id              | status      | description                                                 | issue         | repo              | branch                                       | depends_on      | blocks          | agent  | started_at | completed_at | estimate | used | notes                               |
| --------------- | ----------- | ----------------------------------------------------------- | ------------- | ----------------- | -------------------------------------------- | --------------- | --------------- | ------ | ---------- | ------------ | -------- | ---- | ----------------------------------- |
| web-fleet-m1-00 | blocked     | Fred contract, storage feasibility, plan PR, immutable base | $ISSUE_NUMBER | mosaicstack/stack | docs/webui-fleet-claude-bridge-plan          |                 | web-fleet-m1-01 | opus   |            |              | 18K      |      | Fred decision; Velma docs           |
| web-fleet-m1-01 | not-started | Claude/Mosaic descendant protocol certificate               | $ISSUE_NUMBER | mosaicstack/stack | feat/$ISSUE_NUMBER-webui-fleet-claude-bridge | web-fleet-m1-00 | web-fleet-m1-02 | codex  |            |              | 20K      |      | Velma; Fred reviews launcher        |
| web-fleet-m1-02 | not-started | shared provider/event/audit/redaction contracts             | $ISSUE_NUMBER | mosaicstack/stack | feat/$ISSUE_NUMBER-webui-fleet-claude-bridge | web-fleet-m1-01 | web-fleet-m1-03 | codex  |            |              | 24K      |      | Velma; haiku review                 |
| web-fleet-m1-03 | not-started | host enrollment/revocation/transport                        | $ISSUE_NUMBER | mosaicstack/stack | feat/$ISSUE_NUMBER-webui-fleet-claude-bridge | web-fleet-m1-02 | web-fleet-m1-04 | codex  |            |              | 32K      |      | Velma; sonnet security review       |
| web-fleet-m1-04 | not-started | host daemon/journal/replay/stop                             | $ISSUE_NUMBER | mosaicstack/stack | feat/$ISSUE_NUMBER-webui-fleet-claude-bridge | web-fleet-m1-03 | web-fleet-m1-05 | codex  |            |              | 35K      |      | Velma; sonnet security review       |
| web-fleet-m1-05 | not-started | durable create/stop operations                              | $ISSUE_NUMBER | mosaicstack/stack | feat/$ISSUE_NUMBER-webui-fleet-claude-bridge | web-fleet-m1-04 | web-fleet-m1-06 | codex  |            |              | 35K      |      | Velma; sonnet security review       |
| web-fleet-m1-06 | not-started | ordered ChatGateway bridge                                  | $ISSUE_NUMBER | mosaicstack/stack | feat/$ISSUE_NUMBER-webui-fleet-claude-bridge | web-fleet-m1-05 | web-fleet-m1-07 | codex  |            |              | 28K      |      | Velma; haiku review                 |
| web-fleet-m1-07 | not-started | minimal Web runtime UI                                      | $ISSUE_NUMBER | mosaicstack/stack | feat/$ISSUE_NUMBER-webui-fleet-claude-bridge | web-fleet-m1-06 | web-fleet-m1-08 | codex  |            |              | 24K      |      | Velma; haiku + accessibility review |
| web-fleet-m1-08 | not-started | no-service adversarial integration                          | $ISSUE_NUMBER | mosaicstack/stack | feat/$ISSUE_NUMBER-webui-fleet-claude-bridge | web-fleet-m1-07 | web-fleet-m1-09 | codex  |            |              | 28K      |      | Velma; sonnet security review       |
| web-fleet-m1-09 | blocked     | certified isolated live E2E/docs                            | $ISSUE_NUMBER | mosaicstack/stack | feat/$ISSUE_NUMBER-webui-fleet-claude-bridge | web-fleet-m1-08 | web-fleet-m1-10 | sonnet |            |              | 24K      |      | Fred/Scooby startup; Jason login    |
| web-fleet-m1-10 | not-started | exact-head review/PR/merge/post-merge proof                 | $ISSUE_NUMBER | mosaicstack/stack | feat/$ISSUE_NUMBER-webui-fleet-claude-bridge | web-fleet-m1-09 |                 | sonnet |            |              | 20K      |      | coordinator + independent reviewers |

After Task 0, use:

```bash
: "${ISSUE_NUMBER:?Task 0 must export the recorded provider issue number}"
: "${FRED_APPROVED_REF:?Task 0 must export the immutable approved ref}"
: "${IMPLEMENTATION_BASE_SHA:?Task 0 must export Fred's certified full SHA}"
: "${CERTIFIED_WEB_URL:?Task 0 must export the KBN-safe Web URL}"
: "${CERTIFIED_DATABASE_URL_LOADER:?Task 0 must export the approved private DB URL loader path}"
: "${CERTIFIED_STARTUP_SCRIPT:?Task 0 must export the exact approved startup script path}"
: "${CERTIFIED_POST_MERGE_SMOKE_SCRIPT:?Task 0 must export the exact smoke script path}"
test -x "$CERTIFIED_DATABASE_URL_LOADER"
CERTIFIED_DATABASE_URL="$("$CERTIFIED_DATABASE_URL_LOADER")"
: "${CERTIFIED_DATABASE_URL:?DB URL loader returned an empty value}"
IMPLEMENTATION_BRANCH="feat/${ISSUE_NUMBER}-webui-fleet-claude-bridge"
export ISSUE_NUMBER FRED_APPROVED_REF IMPLEMENTATION_BASE_SHA IMPLEMENTATION_BRANCH
export CERTIFIED_WEB_URL CERTIFIED_DATABASE_URL CERTIFIED_STARTUP_SCRIPT
export CERTIFIED_POST_MERGE_SMOKE_SCRIPT
```

Task 0 records the non-secret values/loader path in the reviewed scratchpad; every fresh implementation session repeats this block. The loader output exists only in that process environment and must never be echoed, placed in argv, committed, or included in evidence.

**Mandatory loop before every Task 1–9 logical commit:** run focused GREEN; run `git diff --check`; freeze `git diff HEAD`; dispatch the task's independent reviewer (never the author) with `requesting-code-review`; write `docs/reports/code-review/${ISSUE_NUMBER}-task-NN.md`; remediate every blocker; rerun tests; re-review the new exact diff. Then commit only the exact task paths, run `ci-queue-wait.sh --purpose push -B "$IMPLEMENTATION_BRANCH"`, and push immediately. A non-ready guard blocks. Every pushed head runs PR CI; every source-head change invalidates the prior review.

Orchestrator-only tracker writes use the same mechanics: current delivery branch, full 14-column row update (`blocks`, timestamps, estimate/used included), Prettier/diff-check, independent haiku review, separate tracking commit, push queue guard, push, and terminal CI. A tracker commit changes the PR head and therefore requires exact-head review again; it is never pushed directly to `next`.

Task 1's first push creates the implementation PR with `pr-create.sh -i "$ISSUE_NUMBER" -B next -H "$IMPLEMENTATION_BRANCH"` (not draft, so no undocumented ready-state transition is needed). Subsequent task pushes update it. Record `IMPLEMENTATION_PR_NUMBER`; review reports are committed with their task. Task 10 takes the exact reviewed head through final CI/merge/closure.

### Task 0: Fred contract gate, storage certificate, plan PR, tracking, and immutable base

**Files:**

- Modify after review: `docs/plans/2026-08-09-webui-fleet-claude-bridge.md`
- Modify: `docs/scratchpads/webui-fleet-bridge-plan.md`
- Modify with orchestrator ownership: `docs/PRD.md`
- Modify with orchestrator ownership: `docs/TASKS.md`
- Create: `docs/reports/code-review/${ISSUE_NUMBER}-task-00-plan.md`

Fred/orchestrator—not a worker—writes the Section 5.0 rows to `docs/TASKS.md` in the separately reviewed tracking commit defined below.

**Authorized preliminary scope:** Fred authorized an initial docs-only decision PR on `next` in `comms/20260809T094952Z__from-fred__ec0e85.md`, with the lane/descriptor corrections in `comms/20260809T095437Z__from-fred__4ee79d.md`. That PR contains only this plan and its discovery scratchpad. Opening it before the complete D1–D15 packet is the sole exception to the Task 0 close gates; it does not close Task 0, authorize Task 1, create an implementation issue, or permit PRD/tracker/runtime changes.

**Step 0: Open the exact docs-only decision PR on `next`**

Create `docs/webui-fleet-claude-bridge-plan` from exact `origin/next`, add only the two decision documents, run Prettier/diff/scope/credential checks, obtain independent exact-diff review, and commit. Immediately before push, re-fetch `next` and require it still equals the recorded base. Run the required push queue guard; a non-ready result stops. Open the PR against `next`, then report `PLAN_PR_NUMBER` and the exact full `PLAN_HEAD_SHA` to Fred. His full contract set lands as provider review plus git comms on that exact head. Any source-head change invalidates that review.

**Step 1: Obtain and record Fred's complete decision packet**

Send Sections 1–4 through git comms. Record every field from Section 2's return table, including `FRED_APPROVED_REF`, safe Web URL, private DB URL loader path (never its output), and startup/post-merge executable paths. Task 0 validates the loader and performs the capture/export block in Section 5.0 without echoing the URL. Replace every `[FRED-GATE]` with the decision and decision-message commit/path. No marker may remain.

**Step 2: Certify no-migration feasibility and source baseline**

Fred/Scooby must explicitly accept the Section 4.3 encodings against the verified primary key, outbox unique index, and three existing status values. Record accepted repository transitions and KBN-safe real transaction test path in the scratchpad. This is static design review only; do not boot DB or write a migration.

```bash
git fetch origin main next
git rev-parse "$FRED_APPROVED_REF"
```

Expected: one full 40-character `IMPLEMENTATION_BASE_SHA`, not either discovery SHA unless Fred explicitly certifies reconciliation. If storage or source baseline is not certified, stop.

**Step 3: Create and record the provider issue**

```bash
MOSAIC_GIT_IDENTITY=velma GITEA_LOGIN=mosaicstack-velma \
  ~/.config/mosaic/tools/git/issue-create.sh \
  -t "WebUI: native Claude Agent Host vertical slice" \
  -b "Deliver the approved docs/plans/2026-08-09-webui-fleet-claude-bridge.md contract."
```

Record numeric `ISSUE_NUMBER`, verify creator `velma`, replace all issue/branch placeholders, and have Fred/orchestrator create every canonical task row from Section 5.0 with exact dependencies/models/estimates.

**Step 4: Add normative PRD requirements**

Add `WEB-FLEET-M1-*` requirements and Section 7 acceptance evidence. No `ASSUMPTION`, placeholder, or `[FRED-GATE]` may remain at Task 0 close.

**Step 5: Independent plan review before commit**

Freeze `git diff HEAD`; an independent architecture reviewer plus Fred reviews the exact plan/PRD/scratchpad delta using `requesting-code-review`. Separately, a haiku reviewer checks the complete 14-column `docs/TASKS.md` delta. Write `docs/reports/code-review/${ISSUE_NUMBER}-task-00-plan.md`, remediate blockers, rerun Prettier and `git diff --check`, and re-review any changed delta.

**Step 6: Commit, push, and merge the docs-only plan PR**

```bash
git add docs/PRD.md docs/plans/2026-08-09-webui-fleet-claude-bridge.md \
  docs/scratchpads/webui-fleet-bridge-plan.md \
  "docs/reports/code-review/${ISSUE_NUMBER}-task-00-plan.md"
git commit -m "docs(#${ISSUE_NUMBER}): plan native Claude WebUI bridge"
# Fred/orchestrator executes this reviewed tracker-only commit:
git add docs/TASKS.md
git commit -m "chore(#${ISSUE_NUMBER}): track native Claude WebUI bridge"
PLAN_BRANCH="docs/webui-fleet-claude-bridge-plan"
test "$(git branch --show-current)" = "$PLAN_BRANCH"
~/.config/mosaic/tools/git/ci-queue-wait.sh --purpose push -B "$PLAN_BRANCH"
git push origin "$PLAN_BRANCH"
# The existing Step 0 PR is updated; do not create a second plan PR.
```

Record `PLAN_PR_NUMBER`, require terminal-green `pr-ci-wait.sh -n "$PLAN_PR_NUMBER"`, coordinator go-ahead, full `PLAN_HEAD_SHA`, then:

```bash
~/.config/mosaic/tools/git/ci-queue-wait.sh --purpose merge \
  -B "$PLAN_BRANCH" -R mosaicstack/stack --sha "$PLAN_HEAD_SHA"
~/.config/mosaic/tools/git/pr-merge.sh \
  -n "$PLAN_PR_NUMBER" -m squash --expect-head "$PLAN_HEAD_SHA"
~/.config/mosaic/tools/git/pr-ci-wait.sh -n "$PLAN_PR_NUMBER"
```

Do not close the implementation issue.

**Step 7: Create implementation branch from the certified immutable base**

After the plan merge and any Fred-owned baseline work, re-record Fred's final full SHA if it changed, then:

```bash
git fetch origin
git switch --detach "$IMPLEMENTATION_BASE_SHA"
git switch -c "$IMPLEMENTATION_BRANCH"
test "$(git rev-parse HEAD)" = "$IMPLEMENTATION_BASE_SHA"
```

Expected: clean implementation branch and all task rows unblocked only as dependencies permit. Task 1 is still forbidden if any gate/value/evidence is missing.

### Task 1: Certify Claude’s machine protocol through `mosaic claude`

**Files:**

- Create: `packages/mosaic/src/agent-host/claude-stream-adapter.ts`
- Create: `packages/mosaic/src/agent-host/claude-stream-adapter.spec.ts`
- Create: `packages/mosaic/src/agent-host/resolved-seat-launch-descriptor.ts`
- Create: `packages/mosaic/src/agent-host/resolved-seat-launch-descriptor.spec.ts`
- Create: `packages/mosaic/src/agent-host/fixtures/claude-stream-v1.ndjson`
- Create: `packages/mosaic/src/agent-host/fixtures/claude-stream-malformed.ndjson`
- Inspect/certify only (Fred-owned implementation): `packages/mosaic/src/commands/launch.ts`
- Modify: `packages/mosaic/src/commands/launch.spec.ts`
- Create: `docs/reports/code-review/${ISSUE_NUMBER}-task-01.md`

**Step 1: Write RED exact-boundary tests**

Generate the host-adapter certificate from Fred's final D2/W-F contract, not from the candidate interface in Section 4.1. It must prove the approved launcher chain, generated-home identity, non-YOLO flags, and no ambient path/home override. Commitment, artifact-byte, descendant-path, owner/mode, symlink, and hardlink rejection cases are required only to the extent W-F binds those mechanisms. Independently reject shell strings, direct Agent Host `claude`, `mosaic yolo claude`, unknown flags, unapproved policy, and mismatched native session ID. A failure in Fred-owned descendant behavior blocks and returns to Fred; do not patch `launch.ts` in this task.

```ts
expect(spawnRequest.command).toBe(resolved.nodePath);
expect(spawnRequest.args.slice(0, 2)).toEqual([resolved.mosaicCliPath, 'claude']);
expect(spawnRequest.args).toContain('--input-format');
expect(spawnRequest.args).toContain('stream-json');
expect(spawnRequest.args).not.toContain('yolo');
```

**Step 2: Run RED**

```bash
pnpm --filter @mosaicstack/mosaic exec vitest run \
  src/agent-host/claude-stream-adapter.spec.ts src/commands/launch.spec.ts
```

Expected: FAIL because the adapter does not exist.

**Step 3: Implement the minimal adapter**

- Use `spawn`, never a shell.
- Pipe stdin/stdout/stderr; cap a single frame and buffered stderr.
- Runtime-validate NDJSON before mapping it.
- Map only allowlisted fields; never forward raw native objects.
- Keep the process open for stream-json input until explicit stop.
- Use `--session-id <gateway UUID>` and assert Claude's initialization frame agrees.
- Pin the M1 permission policy to Fred/Jason-approved non-YOLO behavior.
- Certify a non-secret native auth-readiness command through Mosaic; if the pinned Claude version has no safe machine-readable status, readiness must be `unknown` until launch rather than inspecting OAuth files.

**Step 4: Run GREEN and negative mutation controls**

Delete `--output-format stream-json` in a temporary test subject and confirm the compatibility test fails, then restore it.

**Step 5: Offline pinned-version certificate**

Inside the implementation Distrobox, record only the Fred-approved binaries' `--version`/`--help` output and run certified NDJSON fixtures through the adapter. Do not log in, read OAuth files, or send a live prompt in Tasks 1–8. Any mismatch from the pinned version/flags is a stop condition and returns to Fred. Native login and the only live prompt occur in Task 9.

**Step 6: Commit**

```bash
git add packages/mosaic/src/agent-host/claude-stream-adapter.ts \
  packages/mosaic/src/agent-host/claude-stream-adapter.spec.ts \
  packages/mosaic/src/agent-host/resolved-seat-launch-descriptor.ts \
  packages/mosaic/src/agent-host/resolved-seat-launch-descriptor.spec.ts \
  packages/mosaic/src/agent-host/fixtures/claude-stream-v1.ndjson \
  packages/mosaic/src/agent-host/fixtures/claude-stream-malformed.ndjson \
  packages/mosaic/src/commands/launch.spec.ts \
  "docs/reports/code-review/${ISSUE_NUMBER}-task-01.md"
git commit -m "test(#${ISSUE_NUMBER}): certify Claude stream protocol"
```

### Task 2: Add normalized create, host event, audit, and redaction contracts

**Files:**

- Modify: `packages/types/src/agent/agent-runtime-provider.ts`
- Modify: `packages/types/src/agent/agent-runtime-provider.spec.ts`
- Create: `packages/types/src/agent/agent-host-protocol.dto.ts`
- Create: `packages/types/src/agent/agent-host-protocol.dto.spec.ts`
- Modify: `packages/types/src/agent/index.ts`
- Modify: `packages/agent/src/hermes-runtime-provider.ts`
- Modify: `packages/agent/src/hermes-runtime-provider.test.ts`
- Modify: `packages/agent/src/matrix-native-runtime-provider.ts`
- Modify: `packages/agent/src/matrix-native-runtime-provider.test.ts`
- Modify: `packages/agent/src/tmux-fleet-runtime-provider.ts`
- Modify: `packages/agent/src/tmux-fleet-runtime-provider.test.ts`
- Modify: `packages/agent/src/runtime-provider-parity.test.ts`
- Modify: `packages/agent/src/runtime-provider-registry.test.ts`
- Modify: `packages/log/src/runtime-audit.ts`
- Modify: `packages/log/src/runtime-audit.test.ts`
- Create: `packages/log/src/streaming-redactor.ts`
- Create: `packages/log/src/streaming-redactor.spec.ts`
- Modify: `packages/log/src/index.ts`
- Create: `docs/reports/code-review/${ISSUE_NUMBER}-task-02.md`

**Step 1: Write RED contract tests**

Cover every field and descriptor-provenance mechanism retained by the final D2/W-F contract, plus positive integer sequence, UTC timestamps, exact state/error unions, legal transitions, safe error boundaries, field limits, unsupported create denial, protocol rejection, JCS event-digest recomputation, browser DTO exclusion, typed safe audit provenance, all split path classes/secrets, 4-KiB carry cap, and fail-closed overflow.

**Step 2: Run RED**

```bash
pnpm --filter @mosaicstack/types test -- agent-runtime-provider agent-host-protocol
pnpm --filter @mosaicstack/agent test -- runtime-provider
pnpm --filter @mosaicstack/log test -- runtime-audit streaming-redactor
```

Expected: FAIL on missing capability, DTOs, and provider methods.

**Step 3: Implement the minimal shared contract**

After D2 binding, define the exact versioned hello/command/result/event DTOs from Section 4 with every rejected candidate descriptor slot removed. Every external frame enters as `unknown` and is validated before use. Do not add arbitrary metadata maps. Extract the shared bounded streaming redactor and extend runtime audit with `session.create`, normalized error codes, and the D2/D12-approved allowlisted projection from Section 4.9.

**Step 4: Make legacy providers fail closed**

Legacy providers return `capability_unsupported` from `createSession` and do not call their transports. Preserve all existing advertised capabilities.

**Step 5: Run GREEN, typecheck, lint**

```bash
pnpm --filter @mosaicstack/types test
pnpm --filter @mosaicstack/agent test
pnpm --filter @mosaicstack/log test
pnpm --filter @mosaicstack/types typecheck
pnpm --filter @mosaicstack/agent typecheck
pnpm --filter @mosaicstack/log typecheck
pnpm --filter @mosaicstack/types lint
pnpm --filter @mosaicstack/agent lint
pnpm --filter @mosaicstack/log lint
```

**Step 6: Commit**

```bash
git add packages/types/src/agent/agent-runtime-provider.ts \
  packages/types/src/agent/agent-runtime-provider.spec.ts \
  packages/types/src/agent/agent-host-protocol.dto.ts \
  packages/types/src/agent/agent-host-protocol.dto.spec.ts packages/types/src/agent/index.ts \
  packages/agent/src/hermes-runtime-provider.ts packages/agent/src/hermes-runtime-provider.test.ts \
  packages/agent/src/matrix-native-runtime-provider.ts packages/agent/src/matrix-native-runtime-provider.test.ts \
  packages/agent/src/tmux-fleet-runtime-provider.ts packages/agent/src/tmux-fleet-runtime-provider.test.ts \
  packages/agent/src/runtime-provider-parity.test.ts packages/agent/src/runtime-provider-registry.test.ts \
  packages/log/src/runtime-audit.ts packages/log/src/runtime-audit.test.ts \
  packages/log/src/streaming-redactor.ts packages/log/src/streaming-redactor.spec.ts packages/log/src/index.ts \
  "docs/reports/code-review/${ISSUE_NUMBER}-task-02.md"
git commit -m "feat(#${ISSUE_NUMBER}): define Agent Host runtime contracts"
```

### Task 3: Implement one-host authentication, revocation, and provider transport

**Files:**

- Create: `apps/gateway/src/agent-host/agent-host.config.ts`
- Create: `apps/gateway/src/agent-host/agent-host.config.spec.ts`
- Create: `apps/gateway/src/agent-host/agent-host.module.ts`
- Create: `apps/gateway/src/agent-host/agent-host.gateway.ts`
- Create: `apps/gateway/src/agent-host/agent-host.gateway.spec.ts`
- Create: `apps/gateway/src/agent-host/agent-host-auth.service.ts`
- Create: `apps/gateway/src/agent-host/agent-host-auth.service.spec.ts`
- Create: `apps/gateway/src/agent-host/agent-host-connection.service.ts`
- Create: `apps/gateway/src/agent-host/agent-host-connection.service.spec.ts`
- Create: `packages/agent/src/mosaic-host-runtime-provider.ts`
- Create: `packages/agent/src/mosaic-host-runtime-provider.test.ts`
- Modify: `packages/agent/src/index.ts`
- Modify: `apps/gateway/src/app.module.ts`
- Modify: `apps/gateway/src/agent/agent.module.ts`
- Create: `docs/reports/code-review/${ISSUE_NUMBER}-task-03.md`

**Step 1: Write RED authentication and scope tests**

Prove missing/wrong/expired/revoked credentials disconnect before registration; manifest binding beats hello/browser fields; wrong owner/tenant cannot list or command; equal/lower duplicate generation is refused; a higher generation replaces the old epoch; revocation while connected/during create/during stream removes readiness and rejects commands/events; command envelopes are bounded and typed; and provider registration occurs only after validated config.

Also assert logs/audit never contain credential, authorization header, workspace path, OAuth metadata, or raw manifest/token values.

**Step 2: Run RED**

```bash
env -u DATABASE_URL pnpm --filter @mosaicstack/gateway exec vitest run \
  src/agent-host/agent-host.config.spec.ts \
  src/agent-host/agent-host.gateway.spec.ts \
  src/agent-host/agent-host-auth.service.spec.ts \
  src/agent-host/agent-host-connection.service.spec.ts
pnpm --filter @mosaicstack/agent test -- mosaic-host-runtime-provider
```

Expected: FAIL because Agent Host config, namespace, auth, connection registry, and provider do not exist.

**Step 3: Implement operator config and outbound registry**

- Dedicated `/agent-host` namespace; WSS required outside loopback.
- Parse `AgentHostEnrollmentManifestV1` from the Fred-approved operator source and token from secret config.
- Timing-safe credential validation at connect and before every command.
- Server-derived host/owner/tenant/policy binding and random connection epoch.
- Higher-generation replace; equal/lower refuse; force-disconnect revoked/superseded epoch.
- 20-second heartbeat, 60-second unavailable threshold, bounded correlation map/timeouts.
- Internal readiness projection exactly as Section 4.4; no HTTP controller and no DB write in this task. Task 5's existing `InteractionController` owns the sole browser/CLI readiness route.

**Step 4: Implement `MosaicHostRuntimeProvider`**

Delegate normalized create/stream/send/terminate through the exact current connection epoch. Implement required list/tree/attach/detach interface methods as fail-closed `capability_unsupported` methods and do not advertise them in M1. Validate every result/event DTO, enforce scope, reject sequence/epoch violations, and normalize safe errors.

**Step 5: Run GREEN and existing security regressions**

```bash
env -u DATABASE_URL pnpm --filter @mosaicstack/gateway exec vitest run \
  src/agent-host/agent-host.config.spec.ts \
  src/agent-host/agent-host.gateway.spec.ts \
  src/agent-host/agent-host-auth.service.spec.ts \
  src/agent-host/agent-host-connection.service.spec.ts \
  src/agent/__tests__/runtime-provider-registry.service.test.ts
pnpm --filter @mosaicstack/agent test -- mosaic-host-runtime-provider
```

Expected: PASS with no live Gateway/Web/PostgreSQL startup.

**Step 6: Commit**

```bash
git add apps/gateway/src/agent-host/agent-host.config.ts \
  apps/gateway/src/agent-host/agent-host.config.spec.ts \
  apps/gateway/src/agent-host/agent-host.module.ts \
  apps/gateway/src/agent-host/agent-host.gateway.ts \
  apps/gateway/src/agent-host/agent-host.gateway.spec.ts \
  apps/gateway/src/agent-host/agent-host-auth.service.ts \
  apps/gateway/src/agent-host/agent-host-auth.service.spec.ts \
  apps/gateway/src/agent-host/agent-host-connection.service.ts \
  apps/gateway/src/agent-host/agent-host-connection.service.spec.ts \
  apps/gateway/src/app.module.ts apps/gateway/src/agent/agent.module.ts \
  packages/agent/src/mosaic-host-runtime-provider.ts \
  packages/agent/src/mosaic-host-runtime-provider.test.ts packages/agent/src/index.ts \
  "docs/reports/code-review/${ISSUE_NUMBER}-task-03.md"
git commit -m "feat(#${ISSUE_NUMBER}): add authenticated Agent Host provider"
```

### Task 4: Implement host daemon, exclusive operation journal, replay, and exact stop

**Files:**

- Create: `packages/mosaic/src/agent-host/agent-host-client.ts`
- Create: `packages/mosaic/src/agent-host/agent-host-client.spec.ts`
- Create: `packages/mosaic/src/agent-host/session-registry.ts`
- Create: `packages/mosaic/src/agent-host/session-registry.spec.ts`
- Create: `packages/mosaic/src/agent-host/operation-journal.ts`
- Create: `packages/mosaic/src/agent-host/operation-journal.spec.ts`
- Create: `packages/mosaic/src/agent-host/event-ring.ts`
- Create: `packages/mosaic/src/agent-host/event-ring.spec.ts`
- Create: `packages/mosaic/src/commands/agent-host.ts`
- Create: `packages/mosaic/src/commands/agent-host.spec.ts`
- Modify: `packages/mosaic/src/cli.ts`
- Create: `docs/reports/code-review/${ISSUE_NUMBER}-task-04.md`

**Step 1: Write RED process, crash, and idempotency tests**

Prove one spawn for concurrent same-key and concurrent distinct-key creates for one stable session; conflict on changed semantics; same-operation lookup after dropped response; exact stop/status replay; dropped responses after graceful signal and force kill; no second daemon lock; refusal of every unsafe/stale launch input defined by final D2/W-F; cursor order/expiry/caps; and restart behavior that marks an unregistered child indeterminate without PID/process scanning.

Inject crashes before spawn, after spawn/before journal commit, after journal commit/before response, and during stop. Every recovery path must use the same operation ID and never infer a target.

**Step 2: Run RED**

```bash
pnpm --filter @mosaicstack/mosaic exec vitest run \
  src/agent-host/agent-host-client.spec.ts \
  src/agent-host/session-registry.spec.ts \
  src/agent-host/operation-journal.spec.ts \
  src/agent-host/event-ring.spec.ts src/commands/agent-host.spec.ts
```

Expected: FAIL because host components and command do not exist.

**Step 3: Implement `mosaic agent-host connect`**

Read only the Fred-approved enrollment credential/manifest and final D2/W-F descriptor contract; validate its bound ownership/generation rules; connect outbound; report the exact safe readiness DTO; send 20-second heartbeats; use capped jittered reconnect; and wait for typed commands. No arbitrary RPC or config path is accepted from Gateway.

**Step 4: Implement exclusive launch/session ownership**

Under one private single-instance lock, resolve the frozen policy/descriptor, reject a second active launch identity, spawn through Task 1, and durably journal operation/request digests, IDs, versions, child handle, timestamps, and disposition. Use no-follow secure opens, atomic rename, and file/directory `fsync`. Never journal prompt/output/env/credential content. Apply the shared streaming redactor before ring, journal-safe error, or transport.

**Step 5: Implement ordered replay and exact stop**

Assign process-scoped sequence before ring/send, retain capped events, and support strict-after-cursor replay. Bind stop operation to the registered child handle. Signal graceful termination, wait a bounded interval, then force-kill that child only. Journal disposition before replying. `session.stopStatus` and same-operation retry reconcile; never fuzzy-match PID, tmux, broker, process list, or argv.

**Step 6: Run GREEN and shell-injection mutation tests**

```bash
pnpm --filter @mosaicstack/mosaic exec vitest run \
  src/agent-host/agent-host-client.spec.ts \
  src/agent-host/session-registry.spec.ts \
  src/agent-host/operation-journal.spec.ts \
  src/agent-host/event-ring.spec.ts src/commands/agent-host.spec.ts
pnpm --filter @mosaicstack/mosaic typecheck
pnpm --filter @mosaicstack/mosaic lint
```

Expected: PASS with fake child processes and fake transport only.

**Step 7: Commit**

```bash
git add packages/mosaic/src/agent-host/agent-host-client.ts \
  packages/mosaic/src/agent-host/agent-host-client.spec.ts \
  packages/mosaic/src/agent-host/session-registry.ts \
  packages/mosaic/src/agent-host/session-registry.spec.ts \
  packages/mosaic/src/agent-host/operation-journal.ts \
  packages/mosaic/src/agent-host/operation-journal.spec.ts \
  packages/mosaic/src/agent-host/event-ring.ts \
  packages/mosaic/src/agent-host/event-ring.spec.ts \
  packages/mosaic/src/commands/agent-host.ts packages/mosaic/src/commands/agent-host.spec.ts \
  packages/mosaic/src/cli.ts "docs/reports/code-review/${ISSUE_NUMBER}-task-04.md"
git commit -m "feat(#${ISSUE_NUMBER}): run native Claude sessions on Agent Host"
```

### Task 5: Add durable create/start reservation and retry-safe stop operations

**Files:**

- Modify: `apps/gateway/src/agent/agent.module.ts`
- Modify: `apps/gateway/src/agent/runtime-provider-registry.service.ts`
- Modify: `apps/gateway/src/agent/__tests__/runtime-provider-registry.service.test.ts`
- Create: `apps/gateway/src/agent/runtime-operation.service.ts`
- Create: `apps/gateway/src/agent/runtime-operation.service.test.ts`
- Create: `apps/gateway/src/agent/runtime-operation-recovery.service.ts`
- Create: `apps/gateway/src/agent/runtime-operation-recovery.service.test.ts`
- Modify: `apps/gateway/src/agent/interaction.controller.ts`
- Modify: `apps/gateway/src/agent/interaction.controller.test.ts`
- Modify: `apps/gateway/src/agent/durable-session.dto.ts`
- Modify: `apps/gateway/src/agent/durable-session.service.ts`
- Modify: `apps/gateway/src/agent/durable-session.repository.ts`
- Create: `apps/gateway/src/agent/durable-session.repository.unit.test.ts`
- Modify: `apps/gateway/src/commands/command-authorization.service.ts`
- Modify: `apps/gateway/src/commands/command-authorization.service.spec.ts`
- Modify: `apps/gateway/src/commands/runtime-approval-verifier.ts`
- Modify: `packages/agent/src/durable-session.ts`
- Modify: `packages/agent/src/durable-session.test.ts`
- Modify: `packages/mosaic/src/commands/interaction.ts`
- Modify: `packages/mosaic/src/commands/interaction.test.ts`
- Modify: `packages/mosaic/src/tui/gateway-api.ts`
- Modify: `packages/mosaic/src/tui/gateway-api.interaction-errors.test.ts`
- Create: `docs/reports/code-review/${ISSUE_NUMBER}-task-05.md`

**Step 1: Write RED reservation, crash, audit, and stop tests**

Cover admin scope, server-resolved policy, same-key replay, changed-semantics conflict, concurrent distinct-key single launch, and refusal to replace an active runtime binding. Inject failures before host dispatch, after host spawn, after host journal result, before Gateway compare-and-set, and after response creation.

For stop, cover server-derived operation correlation, duplicate clicks, wrong/expired/mismatched approval, and every Section 4.7 crash boundary around Redis verification/DB acceptance/atomic claim/`GETDEL`/host dispatch/reconciliation. Also cover dropped response after graceful/force termination, disconnect, Gateway reconstruction, already-stopped status, and prohibition on a new operation while pending. Assert `InteractionController` owns every new route, caller bodies contain no provider/host/path/command/correlation fields, missing/forged `X-Correlation-Id` cannot select the operation correlation, and audit is allowlisted only.

**Step 2: Run RED**

```bash
env -u DATABASE_URL pnpm --filter @mosaicstack/gateway exec vitest run \
  src/agent/__tests__/runtime-provider-registry.service.test.ts \
  src/agent/runtime-operation.service.test.ts \
  src/agent/runtime-operation-recovery.service.test.ts \
  src/agent/interaction.controller.test.ts \
  src/agent/durable-session.repository.unit.test.ts \
  src/commands/command-authorization.service.spec.ts
pnpm --filter @mosaicstack/agent test -- durable-session
pnpm --filter @mosaicstack/mosaic exec vitest run \
  src/commands/interaction.test.ts src/tui/gateway-api.interaction-errors.test.ts
```

Expected: FAIL on missing create reservation, stop approval/status routes, and operation reconciliation.

**Step 3: Implement durable create reservation before dispatch**

`RuntimeOperationService.start()` creates one Gateway UUID operation, resolves descriptor/policy/enrollment values server-side, JCS hashes Section 4.3, and calls exact `reserveCreate()`. Persist the specified pending/failed runtime references and typed `provider.create` outbox content. Dispatch the fully resolved request, then exact pending-reference CAS to runtime ID. A processing outbox is never automatically requeued; reconciliation calls `session.lookup` for that operation.

Implement all six Section 4.3 repository methods plus `listRecoverableRuntimeOperations()`. Same-scope `create()` cannot replace active identity. New reservation after stop requires a matching delivered stop record for current runtime. Task 5 tests through domain/in-memory and mocked repository transactions only; do not run existing PGlite repository tests. No schema or DDL change.

**Step 4: Implement Gateway startup recovery**

Register `RuntimeOperationRecoveryService` in `AgentModule` and implement the exact `OnApplicationBootstrap` enumeration/transitions from Section 4.6. Recovery gates readiness and never auto-launches a pending create.

**Step 5: Add create audit and HTTP/CLI start**

`RuntimeProviderService.createSession()` performs frozen scope/capability checks and requested/succeeded/denied/failed audit with typed internal provenance. Add exact `POST /api/interaction/:agentName/sessions/:sessionId/start`, body `{ idempotencyKey }` only, returning only `BrowserRuntimeSessionStartResultV1`. `mosaic interaction start <session-id> --idempotency-key <uuid>` calls it with no host/path/runtime/command flags.

**Step 6: Add stop approval, dispatch, and status routes**

Implement the five Section 4.7 interaction routes: readiness, start, stop approval, stop dispatch, and stop status. Add non-destructive approval verification and atomic compare-delete to `CommandAuthorizationService`/verifier. Persist durable authorization acceptance before delete/dispatch exactly as Section 4.7; a processing retry queries host first. Browser/CLI never supplies action correlation.

**Step 7: Run GREEN**

```bash
env -u DATABASE_URL pnpm --filter @mosaicstack/gateway exec vitest run \
  src/agent/__tests__/runtime-provider-registry.service.test.ts \
  src/agent/runtime-operation.service.test.ts \
  src/agent/runtime-operation-recovery.service.test.ts \
  src/agent/interaction.controller.test.ts \
  src/agent/durable-session.repository.unit.test.ts \
  src/commands/command-authorization.service.spec.ts
pnpm --filter @mosaicstack/agent test -- durable-session
pnpm --filter @mosaicstack/mosaic exec vitest run \
  src/commands/interaction.test.ts src/tui/gateway-api.interaction-errors.test.ts
```

Expected: PASS with DB repository fakes only; do not connect PostgreSQL/PGlite.

**Step 8: Commit**

```bash
git add apps/gateway/src/agent/agent.module.ts \
  apps/gateway/src/agent/runtime-provider-registry.service.ts \
  apps/gateway/src/agent/__tests__/runtime-provider-registry.service.test.ts \
  apps/gateway/src/agent/runtime-operation.service.ts \
  apps/gateway/src/agent/runtime-operation.service.test.ts \
  apps/gateway/src/agent/runtime-operation-recovery.service.ts \
  apps/gateway/src/agent/runtime-operation-recovery.service.test.ts \
  apps/gateway/src/agent/interaction.controller.ts \
  apps/gateway/src/agent/interaction.controller.test.ts \
  apps/gateway/src/agent/durable-session.dto.ts \
  apps/gateway/src/agent/durable-session.service.ts \
  apps/gateway/src/agent/durable-session.repository.ts \
  apps/gateway/src/agent/durable-session.repository.unit.test.ts \
  apps/gateway/src/commands/command-authorization.service.ts \
  apps/gateway/src/commands/command-authorization.service.spec.ts \
  apps/gateway/src/commands/runtime-approval-verifier.ts \
  packages/agent/src/durable-session.ts packages/agent/src/durable-session.test.ts \
  packages/mosaic/src/commands/interaction.ts packages/mosaic/src/commands/interaction.test.ts \
  packages/mosaic/src/tui/gateway-api.ts \
  packages/mosaic/src/tui/gateway-api.interaction-errors.test.ts \
  "docs/reports/code-review/${ISSUE_NUMBER}-task-05.md"
git commit -m "feat(#${ISSUE_NUMBER}): add durable Agent Host operations"
```

### Task 6: Route existing Web chat through an ordered runtime bridge

**Files:**

- Create: `apps/gateway/src/chat/runtime-chat-bridge.service.ts`
- Create: `apps/gateway/src/chat/runtime-chat-bridge.service.spec.ts`
- Create: `apps/gateway/src/chat/runtime-message-id.ts`
- Create: `apps/gateway/src/chat/runtime-message-id.spec.ts`
- Create: `apps/gateway/src/chat/chat.gateway-runtime-provider.spec.ts`
- Modify: `apps/gateway/src/chat/chat.module.ts`
- Modify: `apps/gateway/src/chat/chat.gateway.ts`
- Modify: `apps/gateway/src/agent/durable-session.service.ts`
- Modify: `packages/brain/src/conversations.ts`
- Create: `packages/brain/src/conversations.idempotency.test.ts`
- Create: `docs/reports/code-review/${ISSUE_NUMBER}-task-06.md`

**Step 1: Write RED routing, ordering, and persistence tests**

Given an authenticated socket and authorized durable binding, assert Gateway sends through `RuntimeProviderService`, not `AgentService.prompt()`; owns one provider stream independent of sockets; double-redacts events; and maps only safe typed browser events.

Cover Gateway digest recomputation failure, accepted duplicate sequence, differing-digest accepted duplicate, equal/differing digest for the same future-buffer sequence, future gap, late-gap recovery, gap timeout, complete-before-delta, duplicate completion, transient reconnect/replay, cursor expiry, browser refresh, and service reconstruction. No `message.complete`/`agent:end` may persist/emit before prior turn sequences. UUIDv5 completion ID plus conflict-ignore must persist one assistant message across duplicate/retry; an in-flight turn after Gateway restart becomes visibly indeterminate.

**Step 2: Run RED**

```bash
env -u DATABASE_URL pnpm --filter @mosaicstack/gateway exec vitest run \
  src/chat/runtime-chat-bridge.service.spec.ts \
  src/chat/runtime-message-id.spec.ts \
  src/chat/chat.gateway-runtime-provider.spec.ts
pnpm --filter @mosaicstack/brain test -- conversations.idempotency
```

Expected: FAIL on missing bridge, reorder/gap handling, and deterministic message persistence.

**Step 3: Implement `RuntimeChatBridgeService`**

Keep provider stream ownership outside sockets. Track expected-next sequence, a 64-event/2-second reorder buffer, accepted cursor, per-turn completion, and event digests. Request strict replay on a gap and fail closed if unresolved. Apply shared streaming redaction before persistence/browser even though host already redacted.

Persist completed assistant content through the existing conversations repository with deterministic UUIDv5 ID and conflict-ignore. Store only bounded runtime/turn/message IDs in metadata; never native frames or tool data.

**Step 4: Add the narrow ChatGateway branch**

If an authorized durable runtime binding exists, route through the bridge. Otherwise preserve current Pi behavior. The client cannot choose provider or target. Map delta/tool/state/error/end into existing typed events only after ordering checks.

**Step 5: Implement refresh/reconnect semantics**

Refresh reloads persisted conversation messages and rejoins the same bridge stream. Transient host disconnect emits `disconnected`/`reconnecting` and resumes after accepted cursor. Cursor expiry reloads persisted transcript, warns, and never calls create. Gateway service reconstruction marks any uncompleted turn indeterminate rather than auto-resuming/persisting it.

**Step 6: Run GREEN and legacy regressions**

```bash
env -u DATABASE_URL pnpm --filter @mosaicstack/gateway exec vitest run \
  src/chat/runtime-chat-bridge.service.spec.ts \
  src/chat/runtime-message-id.spec.ts \
  src/chat/chat.gateway-runtime-provider.spec.ts \
  src/chat/chat.gateway-redaction.spec.ts \
  src/chat/chat.gateway-command-approval.spec.ts \
  src/chat/__tests__/chat-security.test.ts
pnpm --filter @mosaicstack/brain test -- conversations.idempotency
```

Expected: PASS; current Pi chat, auth, ownership, redaction, Discord, and durable-session behavior remain green. No service or database starts.

**Step 7: Commit**

```bash
git add apps/gateway/src/chat/runtime-chat-bridge.service.ts \
  apps/gateway/src/chat/runtime-chat-bridge.service.spec.ts \
  apps/gateway/src/chat/runtime-message-id.ts \
  apps/gateway/src/chat/runtime-message-id.spec.ts \
  apps/gateway/src/chat/chat.gateway-runtime-provider.spec.ts \
  apps/gateway/src/chat/chat.module.ts apps/gateway/src/chat/chat.gateway.ts \
  apps/gateway/src/agent/durable-session.service.ts \
  packages/brain/src/conversations.ts packages/brain/src/conversations.idempotency.test.ts \
  "docs/reports/code-review/${ISSUE_NUMBER}-task-06.md"
git commit -m "feat(#${ISSUE_NUMBER}): bridge runtime events into Web chat"
```

### Task 7: Add minimal Web host/session UI and working stop control

**Files:**

- Create: `apps/web/src/components/chat/runtime-target-card.tsx`
- Create: `apps/web/src/components/chat/runtime-target-card.test.tsx`
- Create: `apps/web/src/components/chat/runtime-session-status.tsx`
- Create: `apps/web/src/components/chat/runtime-session-status.test.tsx`
- Create: `apps/web/src/lib/runtime-session.ts`
- Create: `apps/web/src/lib/runtime-session.test.ts`
- Modify: `apps/web/src/app/(dashboard)/chat/page.tsx`
- Modify: `apps/web/src/components/chat/chat-input.tsx`
- Modify: `apps/web/src/lib/api.ts`
- Create: `docs/reports/code-review/${ISSUE_NUMBER}-task-07.md`

**Step 1: Write RED pure-state and server-rendered component tests**

Cover ready/not-ready, create replay, boundary errors, every lifecycle label, disabled send while starting/stopping, refresh state that does not request start, semantic labels/focus hooks, safe metadata only, and Stop calling approval issuance then stop with the returned operation ID/reference.

```bash
pnpm --filter @mosaicstack/web test -- runtime-session runtime-target-card runtime-session-status
```

Expected: FAIL because runtime state/components and API methods do not exist. Do not start Next.js or Playwright in this task.

**Step 2: Implement one pinned target card**

Display safe host label, Mosaic/Claude versions, auth readiness, opaque workspace label, seat/persona label, and connection state. Never display filesystem paths, credential state, enrollment ID, or lease/TTL state.

**Step 3: Start once and refresh safely**

Generate one UUID idempotency key per unbound conversation, call start, and retain it for retries. Refresh loads conversation/durable binding first and never generates/calls create when already pending or active. Disable duplicate clicks while request/reconciliation is pending.

**Step 4: Wire existing chat events and exact stop**

Reuse typed `agent:start`, `agent:text`, safe tool/state events, `agent:end`, and `error`; retain Pi fallback. Confirmation calls `stop-approval`, then sends exactly the server-returned `stopOperationId` and `approvalRef`. Browser never creates correlation/action fields. Poll same-operation status after an indeterminate result; already stopped is success; definitive `failed` renders only `SafeRuntimeError` and requires a newly confirmed stop action if retryable.

**Step 5: Repair touched chat theming only**

Use existing design tokens and semantic controls. Ensure opaque backgrounds, visible text/caret/borders, focus rings, disabled/loading states, and reduced-motion-safe status changes. Do not redesign the dashboard. Browser light/dark/WCAG evidence is deferred to certified Task 9.

**Step 6: Run GREEN**

```bash
pnpm --filter @mosaicstack/web test
pnpm --filter @mosaicstack/web typecheck
pnpm --filter @mosaicstack/web lint
```

Expected: PASS without starting Web/Gateway/PostgreSQL.

**Step 7: Commit**

```bash
git add apps/web/src/components/chat/runtime-target-card.tsx \
  apps/web/src/components/chat/runtime-target-card.test.tsx \
  apps/web/src/components/chat/runtime-session-status.tsx \
  apps/web/src/components/chat/runtime-session-status.test.tsx \
  apps/web/src/lib/runtime-session.ts apps/web/src/lib/runtime-session.test.ts \
  'apps/web/src/app/(dashboard)/chat/page.tsx' \
  apps/web/src/components/chat/chat-input.tsx apps/web/src/lib/api.ts \
  "docs/reports/code-review/${ISSUE_NUMBER}-task-07.md"
git commit -m "feat(#${ISSUE_NUMBER}): chat with an enrolled Claude host"
```

### Task 8: Prove service-level security, crash, ordering, replay, and provenance

**Files:**

- Create: `apps/gateway/src/__tests__/integration/agent-host-claude.service.test.ts`
- Create: `packages/mosaic/src/agent-host/agent-host-security.spec.ts`
- Modify: `apps/gateway/src/agent-host/agent-host.gateway.spec.ts`
- Modify: `apps/gateway/src/agent/runtime-operation.service.test.ts`
- Modify: `apps/gateway/src/chat/runtime-chat-bridge.service.spec.ts`
- Modify: `packages/log/src/streaming-redactor.spec.ts`
- Create: `docs/reports/code-review/${ISSUE_NUMBER}-task-08.md`

**Step 1: Build a no-service/no-model fixture**

Compose Gateway services, fake authenticated Socket.IO transport, host client, and fake absolute Mosaic launcher in-process. Launcher emits certified Claude NDJSON. Still assert exact `mosaic claude` descendant argv/environment; do not mock around protocol, reservation, journal, event ordering, or redaction. Do not bind ports, start Nest/Next/PostgreSQL, or apply DDL.

**Step 2: Add abuse, crash, and ordering cases**

Cover forged/revoked/superseded/wrong-host credential; wrong actor/tenant/session; unapproved policy; shell metacharacters/oversized frames; malformed event and same-sequence/different-digest; gap rejection, late-gap recovery, complete-before-delta, duplicate completion; disconnect before dispatch, after spawn, after journal commit, before Gateway compare-and-set, and during stream; replay across reconnect; Gateway reconstruction with no duplicate message; stale cursor; same-key replay versus concurrent distinct-key conflict; stop response loss after graceful/force kill; and split OAuth-like values/private keys/tokens/emails/home paths across NDJSON/stdout/stderr/error/provenance chunks.

**Step 3: Assert typed provenance and secret absence**

Safe event/readiness/audit identify only D2/D12-approved host label and contract provenance, versions, adapter, enrollment generation, and launch ID. Fake-process assertions verify absolute fixture paths match the descriptor, but audit/browser/transcript assertions require the Section 4.2 projection only. Scan every captured frame, audit entry, transcript row, error, and log for canaries and raw argv/env/OAuth bytes.

**Step 4: Run focused GREEN**

```bash
env -u DATABASE_URL pnpm --filter @mosaicstack/gateway exec vitest run \
  src/__tests__/integration/agent-host-claude.service.test.ts \
  src/agent-host/agent-host.gateway.spec.ts \
  src/agent/runtime-operation.service.test.ts \
  src/chat/runtime-chat-bridge.service.spec.ts
pnpm --filter @mosaicstack/mosaic exec vitest run \
  src/agent-host/agent-host-security.spec.ts
pnpm --filter @mosaicstack/log test -- streaming-redactor
```

Expected: PASS using fakes only, with all crash points producing one launch and one assistant completion at most.

**Step 5: Run migration-free static baselines**

```bash
pnpm typecheck
pnpm lint
pnpm format:check
pnpm build
```

Expected: PASS. Root `pnpm test` is explicitly forbidden here because it includes PGlite migration and framework-shell/lease-broker suites. Do not invoke Playwright, package-wide Gateway tests, `dev`, PostgreSQL/PGlite integration targets, Compose, systemd tests, or migrations in Task 8. Focused GREEN commands above are the complete permitted dynamic allowlist until Task 9's certified path.

**Step 6: Commit**

```bash
git add apps/gateway/src/__tests__/integration/agent-host-claude.service.test.ts \
  apps/gateway/src/agent-host/agent-host.gateway.spec.ts \
  apps/gateway/src/agent/runtime-operation.service.test.ts \
  apps/gateway/src/chat/runtime-chat-bridge.service.spec.ts \
  packages/mosaic/src/agent-host/agent-host-security.spec.ts \
  packages/log/src/streaming-redactor.spec.ts \
  "docs/reports/code-review/${ISSUE_NUMBER}-task-08.md"
git commit -m "test(#${ISSUE_NUMBER}): prove Agent Host failure boundaries"
```

### Task 9: Certify isolated Distrobox and one live OAuth browser flow

**Files:**

- Create: `docs/guides/agent-host-claude-m1.md`
- Create: `docs/reports/qa/${ISSUE_NUMBER}-agent-host-live-e2e.md`
- Modify: `docs/openapi-tess.yaml`
- Modify: `docs/tess/USER-GUIDE.md`
- Modify: `docs/SITEMAP.md`
- Modify: `.env.example` with names only and no secret defaults
- Modify: `apps/web/e2e/chat.spec.ts`
- Modify: `apps/web/playwright.config.ts`
- Create: `apps/gateway/src/agent/durable-session.repository.certification.test.ts`
- Create: `docs/reports/documentation/${ISSUE_NUMBER}-agent-host-checklist.md`
- Create: `docs/reports/code-review/${ISSUE_NUMBER}-task-09.md`

**Step 1: Freeze the candidate and prepare greenfield environment**

Record base separately, then freeze remote implementation head:

```bash
git fetch origin "$IMPLEMENTATION_BRANCH"
IMPLEMENTATION_PR_HEAD_SHA="$(git rev-parse "origin/$IMPLEMENTATION_BRANCH")"
test "$IMPLEMENTATION_PR_HEAD_SHA" = "$(git rev-parse HEAD)"
```

Create Debian Distrobox with dedicated home. Do not inherit/overwrite host `~/.config/mosaic`, `~/.mosaic`, stock `~/.claude`, Codex, Pi, or OpenCode state. Inside it, create an **attached** clean implementation branch—not detached base/head:

```bash
GREENFIELD_REPO="$HOME/src/stack-web-fleet-${ISSUE_NUMBER}"
git clone https://git.mosaicstack.dev/mosaicstack/stack.git "$GREENFIELD_REPO"
cd "$GREENFIELD_REPO"
git fetch origin "$IMPLEMENTATION_BRANCH"
git switch -c "$IMPLEMENTATION_BRANCH" --track "origin/$IMPLEMENTATION_BRANCH"
test "$(git rev-parse HEAD)" = "$IMPLEMENTATION_PR_HEAD_SHA"
```

Record base/head/image versions. Develop, review, commit, and push Task 9's e2e/docs delta from this attached branch via Section 5.0. Task 10 repeats evidence after that delta is committed/pushed.

**Step 2: Respect the Gateway startup gate**

Use only the exact Fred/Scooby-certified startup script recorded in Task 0:

```bash
"$CERTIFIED_STARTUP_SCRIPT" --worktree "$PWD" \
  --source-sha "$IMPLEMENTATION_PR_HEAD_SHA" --web-url "$CERTIFIED_WEB_URL"
```

Expected: exit 0 and deployed/test artifact reports the exact candidate SHA. Do not run root `pnpm dev`, ad hoc Gateway/Web, Compose PostgreSQL, a migration runner, or manual image build around KBN.

Create a certification test that connects only to the pre-provisioned disposable certified database, uses unique owner/session IDs plus transaction cleanup, and imports/calls no migration or DDL function. It proves real `reserveCreate/activate/fail/reserveStop/completeStop/listRecoverable` CAS and concurrency:

```bash
! rg -n 'run.*Migration|migrate|CREATE TABLE|ALTER TABLE' \
  apps/gateway/src/agent/durable-session.repository.certification.test.ts
: "${CERTIFIED_DATABASE_URL:?Load the certified disposable DB URL from Task 0's secret source}"
MOSAIC_KBN_CERTIFIED_DB_TEST=1 DATABASE_URL="$CERTIFIED_DATABASE_URL" \
  pnpm --filter @mosaicstack/gateway exec vitest run \
  src/agent/durable-session.repository.certification.test.ts
```

Expected: PASS with no DDL/migration. If exact DB/startup path is absent, this task and PR completion are blocked.

**Step 3: Authenticate Claude locally**

Jason performs native Claude login inside the isolated home. The report records only `ready/not-ready`, account classification approved for testing, CLI version, and success/failure. Never copy or print the OAuth store.

**Step 4: Run the live slice and record bounded evidence**

1. Sign into WebUI through Authentik and record authenticated user/tenant classification, not cookies.
2. Confirm the pinned host readiness DTO and versions.
3. Capture Agent Host registered-child count `0`; start one conversation; capture count `1`, internal launch/runtime IDs, and only the D2-approved private match/provenance evidence. None of these internal fields may appear in the browser response.
4. Send a harmless deterministic prompt: request the configured Mosaic seat name plus one non-secret contract marker. Compare output with Fred's descriptor/contract. Treat this only as behavioral corroboration, never an authorization/security signal.
5. Verify ordered streamed response, one persisted assistant message, sanitized provenance, and no raw path in browser/API.
6. Refresh during/after the session; capture the same launch/runtime IDs and registered-child count `1`.
7. Send a second prompt; verify same native session and incremented sequence.
8. Issue exact stop approval, repeat the same Stop click/request safely, reconcile the same operation, and capture terminal child count `0`. Dropped-response fault injection remains Task 8; do not manipulate the certified live network.
9. Exercise one sanitized host, runtime, auth, and cursor failure; verify each reports the correct boundary without create.
10. Run the approved repository secret scanner against captured browser network/logs, Gateway logs/audit fixture, transcript query, host event capture, QA report, and git diff. Record command names, exit codes, and canary counts only.

**Step 5: Run certified headless Web checks**

Update `apps/web/playwright.config.ts` with exactly four Chromium projects: desktop-light (1440×900, `colorScheme: light`), desktop-dark, mobile-light (390×844), and mobile-dark. Keep `webServer` absent and require external `PLAYWRIGHT_BASE_URL`. Update `apps/web/e2e/chat.spec.ts` with deterministic CSS contrast calculations and runtime flow assertions.

```bash
PLAYWRIGHT_BASE_URL="$CERTIFIED_WEB_URL" \
  pnpm --filter @mosaicstack/web exec playwright test e2e/chat.spec.ts
```

Expected: all four projects PASS headlessly. Assert keyboard order, visible focus, status announcements, disabled/loading states, opaque surfaces, and WCAG AA contrast. Store screenshot artifact paths/test exit code in QA report; do not commit images containing personal content.

**Step 6: Document exact operator inputs and scan evidence**

Document Fred-approved sources and these canonical names only. Host: `MOSAIC_AGENT_HOST_GATEWAY_URL`, `MOSAIC_AGENT_HOST_MANIFEST`, `MOSAIC_AGENT_HOST_TOKEN_FILE`, `MOSAIC_AGENT_HOST_LAUNCH_DESCRIPTOR`. Gateway: `MOSAIC_GATEWAY_AGENT_HOST_MANIFEST`, `MOSAIC_GATEWAY_AGENT_HOST_TOKEN_FILE`. No aliases, secret defaults, or raw path values. Run reviewed secret scanner; inspect key names/status only and never print credential-bearing files.

**Step 7: Complete documentation checklist and commit**

```bash
git add docs/guides/agent-host-claude-m1.md \
  "docs/reports/qa/${ISSUE_NUMBER}-agent-host-live-e2e.md" docs/openapi-tess.yaml \
  docs/tess/USER-GUIDE.md docs/SITEMAP.md .env.example apps/web/e2e/chat.spec.ts \
  apps/web/playwright.config.ts \
  apps/gateway/src/agent/durable-session.repository.certification.test.ts \
  "docs/reports/documentation/${ISSUE_NUMBER}-agent-host-checklist.md" \
  "docs/reports/code-review/${ISSUE_NUMBER}-task-09.md"
git commit -m "docs(#${ISSUE_NUMBER}): certify native Claude Agent Host"
```

### Task 10: Exact-head review, CI, merge, post-merge proof, and issue closure

**Files:**

- Update: `docs/scratchpads/webui-fleet-bridge-plan.md`
- Create: `docs/reports/code-review/${ISSUE_NUMBER}-agent-host.md`
- Create: `docs/reports/security/${ISSUE_NUMBER}-agent-host.md`
- Complete: `docs/reports/documentation/${ISSUE_NUMBER}-agent-host-checklist.md`

**Step 1: Freeze and self-review the full implementation diff**

```bash
git fetch origin next "$IMPLEMENTATION_BRANCH"
PR_HEAD_SHA="$(git rev-parse HEAD)"
test "$PR_HEAD_SHA" = "$(git rev-parse "origin/$IMPLEMENTATION_BRANCH")"
git diff --check "origin/next...$PR_HEAD_SHA"
```

Check scope creep, duplicated prompt assembly, direct broker imports/reads, raw secret/path surfaces, untrusted target selection, process ambiguity, ordering/replay, Redis/DB stop boundaries, and Pi regressions. Repeat Task 8 focused/static commands and Task 9 certified DB/browser evidence on this exact SHA.

**Step 2: Write independent review reports**

Author cannot review own code. Fred reviews structure/launcher/F-V3; an independent security reviewer covers enrollment/revocation, OAuth isolation, create/stop crash safety, ordering, redaction, and provenance. Write both report files with reviewed full SHA and zero unresolved blockers. Remediate, rerun tests/evidence, and regenerate both whenever source changes.

**Step 3: Commit reports/checklist/scratchpad and push through guard**

```bash
git add docs/scratchpads/webui-fleet-bridge-plan.md \
  "docs/reports/code-review/${ISSUE_NUMBER}-agent-host.md" \
  "docs/reports/security/${ISSUE_NUMBER}-agent-host.md" \
  "docs/reports/documentation/${ISSUE_NUMBER}-agent-host-checklist.md"
git commit -m "docs(#${ISSUE_NUMBER}): record Agent Host delivery evidence"
~/.config/mosaic/tools/git/ci-queue-wait.sh --purpose push -B "$IMPLEMENTATION_BRANCH"
git push origin "$IMPLEMENTATION_BRANCH"
FINAL_HEAD_SHA="$(git rev-parse HEAD)"
```

A non-ready guard blocks; never bypass. Require terminal-green:

```bash
~/.config/mosaic/tools/git/pr-ci-wait.sh -n "$IMPLEMENTATION_PR_NUMBER"
```

**Step 4: Exact-final-head provider reviews**

Because the report commit changed the head, Fred and the independent security reviewer each inspect `FINAL_HEAD_SHA` and post a provider review naming that full SHA through their own verified principals:

```bash
~/.config/mosaic/tools/git/pr-review.sh \
  -n "$IMPLEMENTATION_PR_NUMBER" -a approve \
  -c "Structure review APPROVE exact head $FINAL_HEAD_SHA"
~/.config/mosaic/tools/git/pr-review.sh \
  -n "$IMPLEMENTATION_PR_NUMBER" -a approve \
  -c "Security review APPROVE exact head $FINAL_HEAD_SHA"
```

These are executed by the respective reviewers, not Velma impersonating them. Any request-changes or head change returns to Steps 1–4.

**Step 5: Coordinator go-ahead, exact-head merge guard, and squash merge**

After coordinator explicitly authorizes merge:

```bash
git fetch origin next
PRE_MERGE_NEXT_SHA="$(git rev-parse origin/next)"
~/.config/mosaic/tools/git/ci-queue-wait.sh --purpose merge \
  -B "$IMPLEMENTATION_BRANCH" -R mosaicstack/stack --sha "$FINAL_HEAD_SHA"
~/.config/mosaic/tools/git/pr-merge.sh \
  -n "$IMPLEMENTATION_PR_NUMBER" -m squash --expect-head "$FINAL_HEAD_SHA"
~/.config/mosaic/tools/git/pr-ci-wait.sh -n "$IMPLEMENTATION_PR_NUMBER"
git fetch origin next
MERGE_SHA="$(git rev-parse origin/next)"
test "$MERGE_SHA" != "$PRE_MERGE_NEXT_SHA"
git merge-base --is-ancestor "$PRE_MERGE_NEXT_SHA" "$MERGE_SHA"
test "$(git rev-parse "$MERGE_SHA^")" = "$PRE_MERGE_NEXT_SHA"
~/.config/mosaic/tools/git/ci-queue-wait.sh --purpose merge \
  -B next -R mosaicstack/stack --sha "$MERGE_SHA" --require-status
```

Every command/assertion must succeed. Queue serialization, the exact-head merge precondition, and direct parent assertion identify `MERGE_SHA` as the squash result produced from the reviewed head.

**Step 6: Exact merged-SHA smoke, issue closure, and tracking closure**

```bash
POST_MERGE_WORKTREE="$(mktemp -d)"
SMOKE_REPORT="$(mktemp)"
git worktree add --detach "$POST_MERGE_WORKTREE" "$MERGE_SHA"
"$CERTIFIED_POST_MERGE_SMOKE_SCRIPT" \
  --worktree "$POST_MERGE_WORKTREE" \
  --source-sha "$MERGE_SHA" --web-url "$CERTIFIED_WEB_URL" > "$SMOKE_REPORT"
test "$(git -C "$POST_MERGE_WORKTREE" rev-parse HEAD)" = "$MERGE_SHA"
python3 - "$SMOKE_REPORT" "$MERGE_SHA" <<'PY'
import json, sys
with open(sys.argv[1], encoding='utf-8') as handle:
    raw = handle.read(4097)
if len(raw.encode('utf-8')) > 4096:
    raise SystemExit('post-merge smoke report exceeded 4 KiB')
def strict_object(pairs):
    result = {}
    for key, value in pairs:
        if key in result:
            raise ValueError(f'duplicate key: {key}')
        result[key] = value
    return result
report = json.loads(raw, object_pairs_hook=strict_object)
expected = sys.argv[2]
allowed = {
    'sourceSha', 'worktreeSha', 'deployedSha', 'status',
    'readiness', 'headlessChat', 'maxChildCount',
}
if not isinstance(report, dict) or set(report) != allowed:
    raise SystemExit('post-merge smoke used a non-exact or extended schema')
for field in ('sourceSha', 'worktreeSha', 'deployedSha'):
    if report[field] != expected:
        raise SystemExit(f'{field} did not match exact merged SHA')
if report['status'] != 'passed' or report['readiness'] is not True:
    raise SystemExit('post-merge readiness did not pass')
if report['headlessChat'] is not True:
    raise SystemExit('post-merge headless chat assertion failed')
if type(report['maxChildCount']) is not int or report['maxChildCount'] != 1:
    raise SystemExit('post-merge exact integer child-count assertion failed')
PY
git worktree remove "$POST_MERGE_WORKTREE"
~/.config/mosaic/tools/git/issue-close.sh -i "$ISSUE_NUMBER" \
  -c "Delivered by PR #$IMPLEMENTATION_PR_NUMBER at $MERGE_SHA; merged-next CI and certified smoke green."
~/.config/mosaic/tools/git/issue-list.sh \
  --state closed --limit 1000 --repo mosaicstack/stack \
  > /tmp/web-fleet-closed-issues.txt
python3 - /tmp/web-fleet-closed-issues.txt "$ISSUE_NUMBER" <<'PY'
import re, sys
text = open(sys.argv[1], encoding='utf-8', errors='replace').read().splitlines()
issue = re.escape(sys.argv[2])
if not any(re.match(rf'^\s*#?{issue}(?:\s|\|)', line) for line in text):
    raise SystemExit('exact issue was absent from provider closed-state query')
PY
```

Smoke stdout must be one exact-key JSON object: three SHA fields equal `MERGE_SHA`, `status: passed`, `readiness: true`, `headlessChat: true`, and `maxChildCount: 1`. Unknown/extra keys fail, so credentials, paths, prompts, or personal content cannot hide in the report. It must not expose provider OAuth or paths.

Fred/orchestrator then closes tracking through the same wrappers (the comment marks where the single-writer edit occurs):

```bash
TRACKING_BRANCH="docs/${ISSUE_NUMBER}-close-webui-fleet-tracking"
git fetch origin next
git switch --detach origin/next
git switch -c "$TRACKING_BRANCH"
# Fred/orchestrator marks all rows done, fills started_at/completed_at/used/notes.
pnpm exec prettier --check docs/TASKS.md
git diff --check
# Independent haiku review of the exact docs/TASKS.md delta must pass here.
git add docs/TASKS.md
git commit -m "chore(#${ISSUE_NUMBER}): close WebUI Fleet bridge tracking"
~/.config/mosaic/tools/git/ci-queue-wait.sh --purpose push -B "$TRACKING_BRANCH"
git push -u origin "$TRACKING_BRANCH"
~/.config/mosaic/tools/git/pr-create.sh \
  -t "chore(#${ISSUE_NUMBER}): close WebUI Fleet bridge tracking" \
  -b "Post-delivery tracker closure for merged PR #${IMPLEMENTATION_PR_NUMBER}." \
  -B next -H "$TRACKING_BRANCH"
# Record TRACKING_PR_NUMBER, then:
~/.config/mosaic/tools/git/pr-ci-wait.sh -n "$TRACKING_PR_NUMBER"
TRACKING_HEAD_SHA="$(git rev-parse HEAD)"
git fetch origin next
TRACKING_PRE_NEXT_SHA="$(git rev-parse origin/next)"
~/.config/mosaic/tools/git/ci-queue-wait.sh --purpose merge \
  -B "$TRACKING_BRANCH" -R mosaicstack/stack --sha "$TRACKING_HEAD_SHA"
~/.config/mosaic/tools/git/pr-merge.sh \
  -n "$TRACKING_PR_NUMBER" -m squash --expect-head "$TRACKING_HEAD_SHA"
~/.config/mosaic/tools/git/pr-ci-wait.sh -n "$TRACKING_PR_NUMBER"
git fetch origin next
TRACKING_MERGE_SHA="$(git rev-parse origin/next)"
test "$TRACKING_MERGE_SHA" != "$TRACKING_PRE_NEXT_SHA"
test "$(git rev-parse "$TRACKING_MERGE_SHA^")" = "$TRACKING_PRE_NEXT_SHA"
~/.config/mosaic/tools/git/ci-queue-wait.sh --purpose merge \
  -B next -R mosaicstack/stack --sha "$TRACKING_MERGE_SHA" --require-status
```

Any failure leaves delivery blocked. Do not report completion before tracking PR, issue closure, merged-next CI, and post-merge smoke all succeed.

## 6. Explicit M1 non-goals

- Codex, Pi CLI, or OpenCode adapters;
- arbitrary host/workspace/persona selection;
- generalized enrollment/pairing UI;
- host failover or process adoption after host-daemon restart;
- Gateway restart stream recovery beyond fail-closed preservation;
- lease/VERIFIED/TTL display;
- Matrix transport or federation;
- tmux streaming/scraping;
- task/project/Mosaic Brain integration;
- broad dashboard redesign;
- provider approval-response UI beyond visible state and exact session stop;
- any database migration.

## 7. Acceptance criteria to evidence map

| Acceptance criterion                                          | Planned evidence                                                                                                                |
| ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| Browser response came from native Claude OAuth                | Task 9 native login classification, runtime version, launch ID, ordered event capture, and transcript assertion                 |
| Launched through Mosaic, not direct Claude                    | Task 1 descendant argv/environment certificate + Task 8 fake-process assertion + Task 9 descriptor-match/artifact/process proof |
| Current framework identity reached the session                | Descriptor generation/digest + sanitized provenance + Task 9 exact seat/contract-marker prompt (behavioral corroboration only)  |
| Registered durable Fleet session                              | Task 5 `interaction_sessions` pending→active CAS + `provider.create` outbox evidence + host journal/audit projection            |
| Browser ↔ Gateway ↔ Host ↔ Claude stream                      | Task 8 in-process full protocol fixture + Task 9 live E2E                                                                       |
| Lifecycle visible and correlated                              | Typed Task 2 contract + Tasks 6/7 tests + Task 9 launch/runtime/turn IDs                                                        |
| Refresh reuses process                                        | Task 6 socket-independent stream + Task 9 same IDs and registered-child count remains `1`                                       |
| Exact idempotent stop                                         | Host operation journal + Gateway stop outbox/approval tests + Task 9 same-operation reconciliation and child count `0`          |
| OAuth absent from browser/Gateway/DB/argv/events              | Task 8 split canaries + Task 9 scans of network/log/audit/transcript/event/report/diff artifacts                                |
| Light/dark chat usable                                        | Task 7 semantic/unit checks + Task 9 headless screenshots, focus, and contrast assertions                                       |
| Boundary-specific errors                                      | Typed Task 2 taxonomy + Tasks 3/4/7 tests + Task 9 sanitized failure samples                                                    |
| Authorization/order/redaction/reconnect/failed launch covered | Task 8 explicit abuse/crash/gap matrix and pass output                                                                          |
| Agent Host has no direct broker/daemon read path              | Static import/string scan + Fred structure review + independent security diff review                                            |
| No unsafe Gateway startup bypass                              | Task 0 baseline gate + exact Fred/Scooby startup command/reference and Task 9 execution log                                     |

## 8. Stop conditions

Stop and report to Fred before code or before proceeding when:

1. no reconciled immutable baseline exists;
2. Fred has not approved the seat-home/launcher/enrollment contract;
3. Claude’s pinned version cannot sustain structured stream-json input/output through `mosaic claude`;
4. `/user` or git/provider actions stop resolving to `velma`;
5. a required queue guard is non-ready;
6. a safe Gateway/Web startup path is unavailable;
7. M1 requires a DB migration before KBN migration authority lands;
8. any design requires browser/Gateway access to provider OAuth, raw workspace paths, or direct broker state;
9. existing durable tables cannot atomically reserve one active launch without migration;
10. enrollment revocation cannot be revalidated per command or stale connection epochs cannot be rejected;
11. exact stop cannot preserve durable one-time approval plus same-operation reconciliation;
12. ordered completion cannot prevent a gap/duplicate from persisting a false or duplicate transcript;
13. a live test would require overwriting an existing runtime home.

## 9. Execution handoff

After Fred approves this plan, replaces every `[FRED-GATE]`, records `IMPLEMENTATION_BASE_SHA`, and Task 0 closes, execute serially with the external `executing-plans` skill at `~/.config/mosaic/skills/executing-plans/SKILL.md`. Do not parallelize changes that touch the shared runtime contract, Gateway provider registry, durable operation coordinator, or ChatGateway. Independent reviewers may work in parallel only after each implementation head is frozen.
