# Walle Relay Phone Access Design

Status: Draft for implementation review
Date: 2026-05-09
Owner: CTM / Wall-E
Related docs:
- `claude-task-manager/docs/phone-access-design.md`
- `claude-task-manager/docs/phone-setup.md`
- `claude-task-manager/docs/remote-desktop-access-design.md`
- `claude-task-manager/docs/microsoft-dev-tunnel-phone-access-design.md`
- `claude-task-manager/docs/session-management-architecture.md`

External reference:
- Claude Code Remote Control:
  `https://code.claude.com/docs/en/remote-control`

## Summary

Walle Relay is the product-grade phone access path for CTM. It replaces "bring
your own network" setup with a Claude Code-style control plane: CTM keeps
running locally, makes an outbound TLS connection to a hosted Walle relay, and
the phone uses a Walle-authenticated web/mobile surface to steer local sessions.

2026-05-27 update: this doc now describes the typed Walle Remote mobile-relay
path only. It is not the primary direct browser-access plan. Full desktop-style
browser access for phone and another computer is covered by the unified
client-device architecture in `remote-desktop-access-design.md`, with private
Microsoft Dev Tunnel as the default transport and Tailscale as the private
network alternative. Cloudflare Tunnel is out of scope.

This is not a generic localhost tunnel. The relay carries a narrow typed
protocol for session review, messaging, and approvals. It must not proxy the
entire CTM web app or arbitrary local HTTP requests in the MVP.

Day-one security requirement: Walle Relay messages are end-to-end encrypted
between the paired phone and local CTM instance. The hosted relay authenticates
accounts, routes sockets, rate-limits abuse, and records minimal metadata, but
must not see CTM prompt text, transcript content, approval command bodies, Wall-E
messages, provider tokens, or local CTM secrets.

Reference model: Claude Code Remote Control documents the same architectural
shape: a local process keeps running on the user's machine, uses outbound-only
networking to the vendor API over TLS, and the phone/browser is a remote control
surface rather than a public tunnel into localhost. It also sets the right UX
expectation: the local process must keep running, remote devices can reconnect,
and web/mobile controls are for continuing a local session rather than moving
execution to cloud infrastructure.

## Product Goals

1. Let the owner continue CTM work from an iPhone without installing Tailscale,
   buying a domain, configuring DNS, or exposing a public port.
2. Keep local execution local: provider CLIs, MCP servers, files, and Wall-E
   memory stay on the Mac unless the user explicitly sends content through the
   relay UI.
3. Keep remote payloads private from the relay by requiring app-layer E2E
   encryption in addition to TLS.
4. Make remote actions safer than a raw tunnel by limiting the protocol surface
   and requiring step-up confirmation for high-risk commands.
5. Give the phone a purpose-built triage UI: what needs attention, what changed,
   what can I safely approve, and what should I send next.
6. Preserve existing Microsoft Dev Tunnel and Tailscale paths as direct
   browser transports outside the typed relay.

## Success Metrics

- First-time setup to paired phone: under 2 minutes after Walle account sign-in.
- Phone notification to agent reply delivered: under 10 seconds when Mac is
  online.
- Pairing failure recovery: user can generate a new code without restarting CTM.
- No high-risk remote action can execute without local registry authorization
  and step-up confirmation.
- Revoked phone cannot send a message after revocation, even if its relay socket
  is still connected.
- Walle account takeover alone cannot control CTM without a locally approved
  paired device key.
- Relay compromise does not reveal decrypted CTM payloads.
- Relay logs contain no provider API keys, local CTM token, raw command approval
  secrets, or prompt bodies by default.

## Non-Goals

- No generic TCP or HTTP proxy in MVP.
- No full desktop CTM UI mirroring in MVP.
- No remote file browser or source-code editor.
- No multi-user collaboration.
- No cloud execution of local agents.
- No relay storage of provider API keys, local secrets, CTM auth tokens, or raw
  terminal credential material.
- No relay-visible plaintext CTM payloads in MVP.

## UX Principles

1. **Setup should feel like pairing a device, not configuring infrastructure.**
   The user should see "Connect phone" and a QR code, not DNS terminology.
2. **Phone is for steering, not IDE work.** The phone UI must prioritize status,
   waiting prompts, concise transcript context, and replies.
3. **Dangerous actions must look dangerous.** Restarting CTM, changing network
   settings, exposing transports, killing sessions, and approving shell/file
   permissions need an explicit confirmation surface.
4. **Offline states are first-class.** A sleeping laptop, expired relay session,
   or weak mobile network should produce clear recovery steps.
5. **Trust must be visible.** The owner should know which phone is paired, when
   it was last used, and be able to revoke it quickly.

## Primary User Journeys

### Journey A: First-Time Pairing

1. User opens CTM on the Mac and goes to `Setup -> Access`.
2. CTM shows three access methods:
   - Walle Remote, recommended
   - Microsoft Dev Tunnel private browser access
   - Tailscale private URL
3. User clicks `Set up Walle Remote`.
4. CTM checks relay prerequisites:
   - Walle account signed in or sign-in available
   - outbound HTTPS/WebSocket to relay reachable
   - local CTM has device-claim support enabled
5. CTM opens a pairing panel with:
   - QR code
   - short pairing code
   - expiration countdown
   - device list preview
6. User scans QR on iPhone.
7. Phone opens `https://m.walle.sh/pair/<claim>` and asks the user to sign in.
8. Phone shows the pairing target:
   - Mac name
   - CTM instance name
   - approximate location/IP metadata
   - requested scopes
9. After phone sign-in, CTM on the Mac shows a local confirmation:
   - "Pair Jun's iPhone?"
   - scopes
   - time limit
   - `Approve` / `Deny`
10. After approval, phone lands on the mobile CTM home screen.

### Journey B: Resume Work From Phone

1. User opens Walle mobile home or taps a push notification.
2. Home shows:
   - connection status to Mac
   - sessions needing attention
   - recent active sessions
   - Wall-E chat entry
3. User taps a session.
4. Session screen shows:
   - compact transcript / terminal summary
   - live agent status
   - pending approvals as separate cards
   - composer pinned at bottom
5. User replies or approves a prompt.
6. For low-risk reply: action sends immediately.
7. For high-risk approval: phone shows a confirmation sheet with exact command,
   affected path/session, and a Face ID/passkey step if available.
8. CTM receives the typed action through the relay and records it in the audit
   log.

### Journey C: Laptop Sleeps

1. Phone detects relay route is unavailable.
2. Home changes to `Mac offline`.
3. UI explains:
   - last seen timestamp
   - current sessions are not running remotely
   - actions cannot be sent until Mac reconnects
4. If a message was drafted, it remains local on the phone and is not queued as
   an implicit remote command.

### Journey D: Revoke Phone

1. User opens CTM `Setup -> Access -> Walle Remote`.
2. Device list shows paired phones with last-used timestamps.
3. User clicks `Revoke`.
4. CTM deletes the device key, notifies relay, and rejects future messages from
   that device.
5. Phone is redirected to a signed-out/revoked state.

## Information Architecture

### Desktop Setup IA

`Setup -> Access`

- Access method selector
  - Walle Remote `Recommended`
  - Microsoft Dev Tunnel
  - Tailscale
- Walle Remote panel
  - Status card
  - Pair phone
  - Paired devices
  - Relay connection diagnostics
  - Security/audit link
- Microsoft Dev Tunnel and Tailscale remain collapsible direct-browser access
  groups.

### Phone IA

`m.walle.sh`

- Home
  - Needs attention
  - Active sessions
  - Wall-E chat
  - Devices/settings
- Session
  - Live output summary
  - Pending approvals
  - Composer
  - Session actions
- Approval detail
  - Exact action
  - Risk level
  - Scope
  - Confirm/deny
- Device settings
  - Current device
  - Revoke this device
  - Sign out
  - Notification settings

## Screen Designs

### Desktop: Walle Remote Setup Panel

Layout: use the same access-method pattern as Microsoft Dev Tunnel and
Tailscale. Walle Remote is the typed mobile-relay option, not the default full
browser-access option.

Primary states:

1. **Not signed in**
   - Title: `Walle Remote`
   - Badge: `Sign in`
   - Body: `Use your Walle account to reach this CTM from your phone without a VPN or domain.`
   - Primary button: `Sign in to Walle`

2. **Ready to pair**
   - Badge: `Ready`
   - Body: `This Mac can connect to Walle Relay. Pair your phone to continue.`
   - Primary button: `Pair phone`
   - Secondary: `Diagnostics`

3. **Pairing**
   - Large QR code
   - Short code with copy button
   - Countdown, e.g. `Expires in 04:59`
   - Security note: `Approve the phone on this Mac before it can control CTM.`

4. **Paired**
   - Badge: `Connected`
   - Body: `1 phone paired. Last used 2m ago.`
   - Buttons: `Open phone URL`, `Pair another phone`
   - Device list with revoke controls

5. **Relay blocked**
   - Badge: `Blocked`
   - Body: `CTM cannot reach relay.walle.sh over HTTPS.`
   - Actions: `Retry`, `Copy diagnostics`

Visual treatment:
- Compact operational panel, not a marketing hero.
- Status badge color:
  - green: connected/ready
  - blue: pairing
  - yellow: degraded/offline
  - red: security/revoked/error
- Minimum touch/click target: 44px height.
- Device rows should be scan-friendly: device name, platform, last seen, scopes,
  revoke.

### Phone: Home

Purpose: answer "does anything need me?"

Structure:

```text
Top bar
  Mac status pill: Connected / Offline / Reconnecting
  device menu

Needs attention
  Approval cards
  Waiting-for-input sessions

Active sessions
  Session rows with provider, title, status, last activity

Wall-E
  Chat row
```

Cards:
- Approval card:
  - provider icon/name
  - command/action summary
  - risk label
  - `Review`
- Waiting card:
  - last agent question
  - quick reply affordance
- Session row:
  - title
  - provider
  - state: running / waiting / done / crashed
  - last update time

Mobile layout:
- 16px horizontal page padding.
- 8px radius max for cards.
- Use sticky bottom composer only on session detail, not home.
- Avoid dense terminal output on home.

### Phone: Session Detail

Purpose: give enough context to respond safely.

Sections:
- Header:
  - session title
  - provider/model
  - state
  - overflow menu
- Activity:
  - summarized recent output by default
  - expand to raw output if needed
- Pending approvals:
  - first-class cards above composer
- Composer:
  - multiline input
  - send button
  - attachment/file mentions are not MVP

Interaction:
- New output should stream, but the view must not steal scroll if the user has
  scrolled up.
- When a permission prompt arrives, pin a card above the composer.
- If connection drops while composing, keep text locally and disable send.

### Phone: Approval Sheet

Approval is the highest-risk mobile interaction.

Fields:
- Action type: shell / file write / restart / network / session kill
- Exact command or operation
- Working directory / target session
- Risk level
- Why agent requested it, if available
- `Deny` and `Approve` buttons

Rules:
- `Approve` requires step-up for high-risk actions.
- Never hide command text behind truncation; allow horizontal wrap/scroll.
- Default focus should not be on approve.
- Deny should allow optional reason.

## Relay Architecture

```text
iPhone browser/app
  |
  | HTTPS + Walle account session + device key
  v
Walle Relay
  |
  | outbound WebSocket route to live CTM instance
  v
Local CTM relay client
  |
  | internal typed calls
  v
CTM session manager / Wall-E / device auth DB
```

### Components

1. **Relay service**
   - Owns account login session.
   - Maintains live socket registry: account -> CTM instance -> connection.
   - Routes typed messages.
   - Enforces rate limits and coarse authorization.
   - Stores minimal metadata and audit records.

2. **CTM relay client**
   - Runs inside CTM.
   - Maintains outbound WebSocket to relay.
   - Authenticates with an instance key minted during setup.
   - Converts relay messages into local CTM operations only through an allowlist.

3. **Phone web app**
   - Hosted by Walle, likely `https://m.walle.sh`.
   - Authenticates user and device.
   - Presents mobile-first CTM control surfaces.

4. **Device registry**
   - Local CTM table remains source of truth for paired devices and scopes.
   - Relay may cache device status but local CTM must validate device IDs for
     each command.

## Protocol Design

Use encrypted JSON envelopes over WebSocket. The relay-visible outer envelope is
only for routing, replay prevention, and coarse rate limiting:

```json
{
  "id": "msg_...",
  "kind": "ctm.e2e",
  "version": 1,
  "account_id": "acct_...",
  "instance_id": "ctm_...",
  "device_id": "dev_...",
  "key_id": "key_...",
  "nonce": "...",
  "created_at": "2026-05-09T12:00:00Z",
  "ciphertext": "..."
}
```

The encrypted inner payload is decrypted only by CTM or the paired phone:

```json
{
  "id": "msg_...",
  "type": "session.send_message",
  "version": 1,
  "created_at": "2026-05-09T12:00:00Z",
  "body": {}
}
```

Required outer envelope fields:
- `id`: unique, idempotency key.
- `kind`: generic relay message kind, not the CTM operation type.
- `version`: schema version.
- `account_id`: relay ownership.
- `instance_id`: CTM instance target.
- `device_id`: paired phone identity.
- `key_id`: E2E key version for rotation.
- `nonce`: unique per encrypted message.
- `created_at`: replay window.
- `ciphertext`: encrypted inner payload.

Encryption rules:
- Use standard WebCrypto-compatible primitives, such as X25519 or P-256 key
  agreement plus AES-GCM, or sealed boxes from an audited library.
- Authenticate the outer routing fields as associated data so ciphertext cannot
  be replayed under another account, instance, device, or key version.
- Never send CTM operation type, session ID, prompt text, approval command, or
  Wall-E message outside the encrypted payload.
- CTM decrypts first, then applies the local typed-message authorization
  registry. Relay authorization is advisory only.

### MVP Message Types

Read:
- `instance.status`
- `sessions.list`
- `sessions.get`
- `sessions.subscribe` (disabled until the relay has a true streaming transport)
- `wall_e.status`

Low-risk write:
- `session.send_message`
- `session.cancel_stream`
- `wall_e.send_message`

High-risk write:
- `approval.respond`
- `session.kill`
- `session.close`
- `network.setup_change`
- `ctm.restart`

MVP implements the read and low-risk write set first. `approval.respond` is the
first high-risk write allowed through the relay, and only after local CTM
authorization, idempotency, audit logging, and step-up have accepted the typed
message. Other high-risk writes can be represented in the UI before they are
enabled, but must fail closed until the same controls are in place.

`sessions.subscribe` is intentionally not a request/response wrapper around
`sessions.get`. The local phone app uses the host WebSocket as the foreground
stream: lifecycle messages update status and `subscribe-stream` hydrates the
active detail view before HTTP history finishes. Hosted Wall-E Remote needs the
same shape before this relay command is enabled:

```
phone WebSocket -> relay -> CTM outbound WebSocket -> SessionStateBus
```

Until that path exists, hosted relay actions can send typed commands, but live
status and transcript freshness remain a local-host/tailnet WebSocket feature.

### Message Handling Rules

- Decryption failure: reject and audit.
- Unknown message type: reject.
- Known type with invalid schema: reject.
- Known type missing device registration: reject.
- Message older than replay window: reject.
- Duplicate message ID: return previous result, do not execute again.
- Duplicate nonce for the same key: reject.
- High-risk message without step-up proof: reject.
- Relay-side authorization is advisory; local CTM authorization is mandatory.

## Authentication and Pairing

### Identities

- Account identity: Walle cloud account.
- Instance identity: one local CTM installation.
- Device identity: one paired phone/browser.
- Session identity: CTM session ID, not provider-native session ID.

### Login

Use passwordless account login for Walle cloud:

1. User opens `https://m.walle.sh`.
2. User enters email.
3. Walle sends a one-time code or magic link.
4. After first login, Walle prompts for passkey registration.
5. Returning phone login defaults to passkey, with email code as recovery.

Do not introduce username/password for MVP. Email is the universal bootstrap
identity; passkey gives the expected iPhone Face ID / Touch ID recovery-free
daily flow.

Mac CTM login should use a device-code style flow:

1. CTM shows `Sign in to Walle`.
2. Browser opens `https://walle.sh/activate?code=...`.
3. User signs into Walle in the browser.
4. User approves `Connect this Mac`.
5. CTM receives an instance credential and creates local E2E keys.

The instance credential proves the CTM process to the relay. The local E2E
private key stays on the Mac and is not uploaded to Walle.

### Pairing Claim

Pairing claim contains:
- claim ID
- relay instance ID
- local CTM nonce
- CTM public E2E key or key ID
- requested scopes
- expiration, target 5 minutes
- one-time secret

The QR should not contain a long-lived device credential. It should only start
the pairing ceremony.

For hosted Walle Remote, put the one-time secret and CTM public key material in
the URL fragment where possible:

```text
https://m.walle.sh/pair#claim=...&secret=...&instance=...&ctm_pubkey=...
```

Fragments are read by the phone-side app but are not sent to the relay in the
HTTP request. The relay may see the claim ID and routing metadata, but should
not receive the one-time secret in request logs.

### Pairing Security

Pairing requires three checks:

1. Phone signs into Walle account.
2. Phone presents short-lived pairing claim.
3. Local CTM user approves the device on the Mac.

After approval:
- CTM creates local device record.
- Phone generates a device E2E key pair.
- CTM accepts and stores the device public key.
- Relay stores route metadata, not local CTM secrets.
- Phone receives a device credential scoped to that account and instance.
- Phone stores its private device key locally. The relay never stores it.

### Device Scopes

MVP scopes:
- `sessions:read`
- `sessions:message`
- `approvals:review`
- `approvals:respond`
- `wall_e:chat`
- `devices:self_revoke`

Do not grant:
- `settings:write`
- `network:write`
- `files:read`
- `terminal:raw`
- `secrets:read`

## Security Model

### Attacker Goals

Assume attackers will try to:

1. Read CTM transcripts, prompts, Wall-E messages, or approval command bodies.
2. Send messages or approvals as a paired phone.
3. Pair their own device to the user's CTM instance.
4. Trick the user into approving a dangerous local action.
5. Abuse recovery or account flows to bypass local CTM authorization.

### Threats

| Threat | Risk | Mitigation |
| --- | --- | --- |
| Walle account takeover | Attacker logs into `m.walle.sh` | Walle login alone cannot control CTM; local CTM must already trust a paired device key |
| Pairing QR leak | Attacker photographs or receives the QR and tries to pair first | 5-minute expiry, one-time secret in URL fragment, one-time use, local Mac approval showing account/device/scopes |
| Relay compromise | Attacker controls Fly app, DB, logs, or relay process | E2E payload encryption, no plaintext payload logging, local CTM authorization, replay checks, route fields authenticated as encryption AAD |
| Malicious `m.walle.sh` JavaScript | Hosted web app reads plaintext before encryption or misuses device key | Strict CSP, no inline scripts, minimal dependencies, protected deploys, audit logs, non-extractable WebCrypto keys where possible; native/signed client is stronger later |
| XSS on phone web app | Attacker-controlled script acts as the paired phone | Escape all user content, no raw HTML transcript rendering, CSP, Trusted Types if practical, no third-party scripts in privileged app shell |
| Stolen phone | Attacker has the paired device | Device revocation, short Walle sessions, local CTM revoke remains authoritative, passkey/Face ID step-up for high risk |
| Relay message replay | Dangerous command repeats or old response is reused | Message IDs, replay window, nonce uniqueness per key, idempotency table |
| Approval replay or substitution | Old approval response is applied to a different pending command | Approval response must include approval ID and hash of the exact command/request; CTM verifies hash before executing |
| Cross-account routing bug | Phone reaches wrong CTM | Account/instance/device binding checked by relay and again by CTM after decrypting authenticated payload |
| Generic proxy abuse | Remote path exposes local CTM APIs, files, or secrets | No generic proxy; no arbitrary `/api/*`; typed allowlist only |
| Prompt injection via phone | Agent or transcript text tricks user into unsafe approval | Exact command display, cwd/session/risk labels, no approve from push notification, passkey step-up, audit log |
| CSRF from phone browser | Malicious site sends commands with Walle cookies | SameSite cookies, CSRF token, device-key message signing, encrypted command payloads |
| Account recovery bypass | Cloud recovery mints a new CTM device without Mac consent | New CTM device requires local Mac approval or a Mac-generated recovery code; Walle cloud cannot create local device credentials alone |
| Denial of service | Attacker floods relay, account login, or CTM socket | Relay rate limits, per-account/device quotas, exponential backoff, CTM decrypt/auth failure throttling |
| Supply-chain compromise | Malicious package steals phone keys or plaintext | Lockfile review, dependency minimization, production build provenance, npm audit/security review before release |

### Non-Negotiable Security Invariants

- E2E encryption is required for MVP.
- Relay never sees plaintext CTM payloads.
- Walle login alone is insufficient to control CTM.
- Pairing requires local Mac approval.
- Local CTM is the final authorization authority.
- No generic localhost tunnel or arbitrary HTTP proxy.
- High-risk actions fail closed until passkey step-up works.
- Approval responses bind to the exact pending command/request hash.
- Revocation on local CTM takes effect even if relay state is stale.

### Web Client Trust Caveat

Browser E2E protects against passive relay logging, database leaks, and normal
relay operators reading payloads. It does not fully protect against a malicious
or compromised `m.walle.sh` deploy, because the browser executes JavaScript
served by that origin before encryption.

MVP should still implement WebCrypto E2E, because it removes relay plaintext and
raises the bar significantly. For a stronger threat model, ship a native iOS app
or a signed/pinned static client bundle so the encryption client is not solely
trusted through the current web deploy.

### Approval-Specific Controls

Approval is the highest-risk phone action. CTM should require:

- approval ID;
- hash of the exact command or operation;
- target session ID;
- working directory or affected resource;
- risk level;
- fresh step-up token for high-risk approvals;
- idempotency key.

CTM must reject approval responses if the pending approval changed, expired, was
already answered, or does not hash to the operation displayed on the phone.

Push notifications must never approve directly. They can only open the approval
screen.

### Local Authorization Registry

CTM must maintain an allowlist mapping message type to:

- risk level
- required device scope
- required step-up
- target handler
- audit category
- rate limit

Example:

```js
{
  "session.send_message": {
    "risk": "low",
    "scope": "sessions:message",
    "stepUp": false,
    "handler": "sendSessionMessage",
    "rateLimit": "30/min"
  },
  "approval.respond": {
    "risk": "high",
    "scope": "approvals:respond",
    "stepUp": true,
    "handler": "respondToApproval",
    "rateLimit": "10/min"
  }
}
```

No handler should be reachable if it is not in the registry.

### Audit Events

Record locally and in relay metadata:

- device paired
- device revoked
- session opened from phone
- message sent
- approval reviewed
- approval accepted/denied
- session killed/closed
- relay connected/disconnected
- auth failure / replay rejection

Audit event fields:
- event ID
- account ID
- instance ID
- device ID
- CTM session ID, when applicable
- action type
- risk level
- timestamp
- result
- IP / user-agent at relay
- local CTM version

## Privacy Model

MVP requires app-layer E2E encryption between the paired phone and local CTM.
TLS to Walle Relay is still required, but TLS is not the privacy boundary for
CTM payload content.

Relay may see:
- account ID;
- instance ID;
- device ID;
- key ID;
- message ID;
- timestamp;
- ciphertext size;
- coarse connection status.

Relay must not see:
- CTM operation type;
- CTM session ID;
- prompt text;
- transcript/output content;
- approval command bodies;
- Wall-E message content;
- provider credentials;
- local CTM tokens or secrets.

MVP privacy rules:
- Relay does not persist encrypted payloads by default unless needed for a
  bounded retry queue.
- Relay stores only short audit summaries and routing metadata.
- Push notifications should not include sensitive command text by default.
- Logs must redact prompt bodies, tokens, and command arguments unless debug is
  explicitly enabled by the user.
- Debug logging must not decrypt payloads at the relay. Payload debugging should
  happen locally in CTM with an explicit user-controlled switch.

## Data Model

### Local CTM Tables

`remote_instances`
- `id`
- `display_name`
- `relay_account_id`
- `relay_url`
- `instance_auth_public_key`
- `e2e_public_key`
- `e2e_key_version`
- `created_at`
- `last_connected_at`
- `enabled`

`remote_devices`
- `id`
- `display_name`
- `platform`
- `device_e2e_public_key`
- `device_signing_public_key`
- `e2e_key_version`
- `scopes_json`
- `paired_at`
- `last_seen_at`
- `revoked_at`

`remote_pairing_claims`
- `id`
- `secret_hash`
- `requested_scopes_json`
- `expires_at`
- `approved_at`
- `denied_at`
- `device_metadata_json`

`remote_message_idempotency`
- `message_id`
- `device_id`
- `message_type`
- `nonce`
- `payload_hash`
- `result_json`
- `created_at`

`remote_audit_log`
- `id`
- `device_id`
- `session_id`
- `action`
- `risk`
- `result`
- `metadata_json`
- `created_at`

### Relay Tables

`accounts`
- cloud auth owner

`instances`
- relay-visible CTM instances
- no local CTM token
- no CTM E2E private key

`connections`
- ephemeral live WebSocket state

`devices`
- cloud-visible paired device metadata

`audit_events`
- minimal event metadata
- no plaintext CTM payloads

`push_subscriptions`
- device push endpoint / token

## API Sketch

### Local CTM

`POST /api/remote/pairing-claims`
- local-only or existing admin auth
- creates QR claim

`GET /api/remote/status`
- shows relay connection and paired devices

`POST /api/remote/devices/:id/revoke`
- revokes device

`POST /api/remote/relay/connect`
- internal client setup, not phone-facing

### Relay

`POST /v1/instances/register`
- CTM registers or refreshes instance auth and E2E public key metadata

`GET /v1/pair/:claimId`
- phone fetches non-secret pairing metadata

`POST /v1/pair/:claimId/complete`
- phone completes claim after Walle sign-in and sends device public key material

`WS /v1/instances/:instanceId/connect`
- CTM outbound socket, receiving encrypted envelopes only

`WS /v1/mobile/:instanceId`
- phone socket, sending encrypted envelopes only

## Visual and Interaction Guidelines

### Desktop

- Keep the setup card quiet and operational.
- Do not introduce a large marketing-style hero inside setup.
- Put Walle Remote first and mark it `Recommended`.
- Use icons only where familiar:
  - phone/device
  - shield/security
  - refresh/retry
  - trash/revoke
- Use buttons for commands, badges for state, and disclosure rows for
  diagnostics.

### Phone

- Minimum touch target: 44x44px.
- Use persistent bottom composer only on session detail.
- Use risk-colored approval cards:
  - blue: informational
  - yellow: needs review
  - red: high-risk / destructive
- Avoid terminal-sized typography. Use compact transcript blocks with expansion.
- Do not put explanatory help text in the primary flow unless the user is stuck.
- Every empty state needs a next action:
  - `No sessions need attention`
  - `Open CTM on your Mac to start a session`
  - `Mac offline. Keep CTM running and connected to the internet`

## Accessibility

- Pairing QR must have a short code alternative.
- All buttons must be reachable by keyboard on desktop.
- Phone confirmation sheets must announce risk and command text to screen
  readers.
- Color cannot be the only signal for risk. Include labels: `High risk`,
  `Needs approval`, `Offline`.
- Use `aria-live="polite"` for connection status and new pending approvals.
- Avoid sub-12px body text on phone.

## Failure States

| State | User-facing copy | Recovery |
| --- | --- | --- |
| Relay unreachable from Mac | `CTM cannot reach Walle Relay.` | Retry, copy diagnostics, use Tailscale fallback |
| Phone signed into wrong account | `This pairing code belongs to another Walle account.` | Sign out / switch account |
| Claim expired | `Pairing code expired.` | Generate a new code on Mac |
| Mac approval timeout | `Pairing was not approved on the Mac.` | Re-scan or retry |
| Mac asleep | `Mac offline. CTM is not reachable.` | Wake Mac, keep CTM running |
| Device revoked | `This phone was revoked.` | Pair again from Mac |
| Step-up failed | `Confirmation failed. No action was sent.` | Retry |
| E2E key mismatch | `This phone is no longer trusted by this Mac.` | Re-pair from Mac |
| Message decrypt failed | `Secure message failed. Nothing was sent.` | Refresh, then re-pair if it persists |
| Unknown command type | `This CTM version does not support that action.` | Update CTM or use desktop |

## Implementation Plan

### Phase 0: Product Contract

- Write message registry and risk taxonomy.
- Decide Walle account provider.
- Decide relay deployment host and domain.
- Require app-layer E2E encryption for MVP.
- Decide web-client hardening baseline: CSP, dependency policy, deploy
  controls, and whether signed/static client verification is required.

### Phase 1: Local CTM Foundations

- Add local remote-device tables.
- Add pairing claim creation.
- Add CTM instance E2E key generation and key rotation metadata.
- Add local approval UI for pairing.
- Add remote authorization registry.
- Add audit log.
- Add Walle Remote setup panel.

### Phase 2: Relay MVP

- Implement relay account auth.
- Implement instance registration.
- Implement CTM outbound WebSocket.
- Implement phone WebSocket.
- Route encrypted envelopes only.
- Add basic rate limits and replay protection.

### Phase 3: Phone MVP

- Add WebCrypto key generation and encrypted envelope handling.
- Build mobile home.
- Build session list and session detail.
- Build send-message flow.
- Build approval review UI, deny-only first if needed.
- Build device revoke/self-signout.

### Phase 4: Push and Step-Up

- Add service worker / push subscriptions.
- Add push for waiting approvals and long task completion.
- Add WebAuthn/passkey or platform authenticator step-up for high-risk actions.
- Enable approval responses remotely after step-up is verified.

### Phase 5: Hardening

- Add key rotation and old-device migration.
- Add signed/static web client or native iOS client evaluation.
- Add abuse monitoring.
- Add relay admin dashboards.
- Add account deletion/export flow.
- Add security review checklist and end-to-end tests.

## MVP Cut Line

The first shippable MVP should include:

- Walle Remote setup panel.
- QR pairing with local Mac approval.
- E2E encrypted phone-to-CTM message envelopes.
- Device registry and revoke.
- CTM outbound relay connection.
- Phone home with session list.
- Session detail with stream summary.
- Send message to session.
- Step-up-gated approval response.
- Audit log.

Remote approval responses are allowed only with step-up, idempotency, and local
audit in place. That is the line between a useful remote companion and a risky
remote admin surface.

## Open Questions

1. What exact Walle account system ships first: email magic link/code only, or
   email plus passkey registration on first login?
2. Which audited WebCrypto-compatible E2E construction should we use for MVP?
3. Should Walle Relay be free for all CTM users or tied to a hosted Walle plan?
4. Should paired phone state live primarily in local CTM with relay cache, or
   should relay become authoritative for device metadata?
5. How much transcript content may push notifications include by default?
6. Do we need a native iOS app or signed/static web client before public beta,
   or is hardened browser E2E acceptable for private beta?

## Decision Recommendation

Proceed with Walle Relay as the preferred long-term phone access path, but build
it as an E2E encrypted typed remote-control protocol, not a tunnel. The relay
should route ciphertext and metadata, while local CTM remains the authorization
authority. The MVP should allow phone triage and safe message sending first.
Approval responses, restarts, network changes, and destructive session actions
should ship only after device keys, step-up confirmation, idempotency, replay
protection, command-hash binding, and audit logging are all in place.
