diff --git a/docs/adr/0001-deck-as-panel-host-for-mystira-ops-tooling.md b/docs/adr/0001-deck-as-panel-host-for-mystira-ops-tooling.md
new file mode 100644
index 0000000..7963f66
--- /dev/null
+++ b/docs/adr/0001-deck-as-panel-host-for-mystira-ops-tooling.md
@@ -0,0 +1,163 @@
+# ADR-0001: deck as panel-host for Mystira ops tooling in a generic shell
+
+## Status
+
+Proposed
+
+## Date
+
+2026-07-11 (proposed). Re-grounded against `dev` @ `bc0d1c5` on 2026-08-10 before commit — the
+evidence in §Context was refreshed for PR #3 (Camera panel) and PR #5 (CI); the decision itself is
+unchanged and still awaiting ratification.
+
+## Context
+
+deck was extracted from `mystira-workspace/apps/devhub` (March 2026) with the explicit goal of
+becoming a **generic phoenixvc ops shell** — a VSCode-style Tauri desktop app with a Service
+Manager, an Infrastructure panel, a Dashboard, and a .NET sidecar, reusable across the org. During
+extraction the Mystira-specific panels (Cosmos Explorer, Migration Manager) were stripped so the
+base shell would not carry Mystira coupling.
+
+R13 now wants deck to be the **operator cockpit over the Mystira AI-content pipeline**
+(`sluice → story-generator → publisher`; `docket` meters spend), across three facets (see
+`docs/specs/deck-ops-cockpit.md`):
+
+1. a Dashboard **cost/ops** section (sluice health + docket spend),
+2. **re-porting** the Cosmos Explorer + Migration Manager panels, and
+3. a Service-Manager **batch-run monitor** over the story-generator nightly Batch API.
+
+This reintroduces Mystira-specific surfaces into the very shell that was just made generic. Two
+observations sharpen the decision:
+
+- The extraction was **incomplete**: the panel directories and their `VIEWS` entries are gone, but
+ the Rust `cosmos.rs` backend and the `.NET` `MigrationService` remained, `Dashboard.tsx` still
+ offers three Cosmos/Migration quick-actions that now silently land on the Service Manager, and
+ `StatisticsPanel.tsx` / `ExportPanel.tsx` still invoke `cosmos_stats` / `cosmos_export` while
+ nothing renders them. So Mystira coupling was never fully removed — it was left **half-wired**,
+ with live backends under no UI. (When first drafted this bullet also cited dangling
+ `AppContent.tsx` / `AppSidebar.tsx` imports that broke the build; PR #3 removed those, and CI is
+ green. The residue above is what survives, and the conclusion is unchanged.)
+- Every R13 facet is fundamentally **Mystira-shaped** (Mystira Cosmos containers, the Mystira
+ story-gen Batch API, Mystira's spend in docket). None of it is generic ops.
+
+The question this ADR settles: **should deck host Mystira-specific ops panels, and if so, how does
+it stay a reusable generic shell rather than collapsing back into "Mystira DevHub"?**
+
+Alternatives considered:
+
+1. **Keep deck strictly generic; build the Mystira cockpit elsewhere** (e.g. a new Mystira-only
+ desktop app, or back inside `mystira-workspace`). Rejected: it discards deck's entire reason for
+ existing (it *is* the extracted DevHub), duplicates the Tauri/Service-Manager/exec machinery,
+ and the ecosystem docs (README, ecosystem-provisioning design) already designate **deck** as the
+ operator window into sluice/docket/story-gen.
+2. **Fold the Mystira panels into the generic shell as first-class, always-present features.**
+ Rejected: it re-Mystira-fies the shell — a second org adopting deck would inherit Cosmos/Migration/
+ batch panels it does not want, and the "generic" claim becomes false.
+3. **deck is a generic panel-host; Mystira panels are pluggable, clearly-bounded modules.** The
+ shell (VSCode layout, activity bar, Service Manager, Infrastructure panel, exec primitives,
+ .NET sidecar bridge) stays generic and org-agnostic; Mystira-specific surfaces are added through
+ the existing four-file panel recipe as **cohesive, self-contained modules** that consume
+ external Mystira/phoenixvc contracts and can be omitted without touching the shell.
+
+## Decision
+
+**deck is a generic panel-host, and Mystira ops tooling is hosted as bounded panel modules on top
+of it (Alternative 3).**
+
+Concretely:
+
+- The **shell is the generic asset**: `VSCodeLayout`, the activity bar / `VIEWS` routing, the
+ Service Manager, the Infrastructure panel, the Rust exec primitives (`Command::new`, argv-only),
+ and the `cli.rs` ↔ .NET-sidecar JSON bridge carry **no Mystira semantics** and remain reusable by
+ any phoenixvc project.
+- **Mystira-coupled surfaces are panels/modules**, added via deck's existing recipe (a `VIEWS`
+ entry + `ACTIVITY_BAR_ITEMS` + an `AppContent` route + an `AppSidebar` block) plus a dedicated
+ backend module (`cost.rs`, `story_gen.rs`, the re-ported `cosmos`/`migration` panels over the
+ existing `cosmos.rs` + `MigrationService`). Each module owns its Mystira coupling; the shell does
+ not.
+- Panels **consume external contracts, they do not embed Mystira domain logic**: Facet 1 reads
+ sluice `/health`,`/metrics` and docket's MCP tools; Facet 3 reads the story-gen Batch operation
+ model (`BatchGenerationItem` / `BatchSubmission`, operation-id + status); Facet 2 talks to Mystira
+ Cosmos via the operator's `az` session. deck holds **no** Mystira business rules — it is a viewer
+ and a trigger.
+- **Secrets never enter the repo.** The docket `MCP_SECRET`, Mystira-Cosmos connection strings, and
+ the story-gen API token are session/local-only (env or machine-local app-config), consistent with
+ deck's `security.md` and the org MCP-bearer rule.
+- **The interrupted extraction is completed, not perpetuated.** Re-porting Facet 2 means finishing
+ the half-removal cleanly (re-add the two `VIEWS` entries + panels, restore the stripped
+ `cosmos.stats`/`cosmos.export` .NET CLI handlers, fix the DevHub→Deck naming/CLI-path residue) so
+ the tree is consistent and the coupling is explicit and bounded — not dangling.
+
+The full technical design is `docs/specs/deck-ops-cockpit.md`.
+
+## Consequences
+
+### Positive
+
+- **deck stays reusable.** A future adopter gets the generic shell without Mystira panels; the
+ Mystira modules are additive and omissible.
+- **One cockpit, one payoff.** Health, spend, data tooling, and batch ops live in a single operator
+ window, and the modules can cross-link (e.g. a batch run → its docket spend slice) precisely
+ because they are co-located but individually bounded.
+- **Clear ownership.** The shell is deck's; each panel owns its upstream contract; Mystira domain
+ logic stays in `mystira-workspace`. No repo crosses that line.
+- **Honest dependency posture.** Because panels consume external contracts, deck degrades
+ gracefully when an upstream is only specced (docket MCP, story-gen Batch API) instead of pretending
+ data exists.
+
+### Negative / Costs
+
+- **Coupling creep risk.** "Generic shell + Mystira modules" is a discipline, not a mechanism —
+ nothing stops a future change from leaking Mystira assumptions into the shell. Mitigation: keep
+ Mystira strings/types inside the panel modules and their backend files; the shell's `VIEWS`
+ registry is the only shared touch-point.
+- **Multi-tenant panel selection is unsolved.** Today all `VIEWS` are compiled in. If a second org
+ adopts deck, panel visibility should become config-driven (feature flags / a panel registry).
+ This ADR does not build that; it only keeps the coupling bounded so it is a later, tractable step.
+- **Restores Mystira-specific maintenance to deck.** deck now tracks Mystira upstreams (sluice
+ endpoints, docket tool names, the Batch API contract) and must follow their changes.
+
+### Boundary summary
+
+| Layer | Generic (org-agnostic) | Mystira-coupled (panel modules) |
+| --- | --- | --- |
+| Shell / layout / routing | ✅ `VSCodeLayout`, activity bar, `VIEWS` mechanism | — |
+| Exec + sidecar bridge | ✅ `Command::new` argv-only, `cli.rs`, .NET sidecar protocol | — |
+| Service Manager / Infrastructure | ✅ generic panels | Batch-run **section** consumes story-gen |
+| Dashboard | ✅ generic panel | Cost/Ops **sections** consume sluice + docket |
+| Cosmos / Migration | — | ✅ Mystira Cosmos panels + `MigrationService` |
+
+## Notes on placement of this ADR
+
+Before this ADR, deck had **no** ADR directory. The only breadcrumb was a dangling reference in
+`docs/guides/contracts-migration.md` (lines 20 and 267) to
+`../adr/0020-package-consolidation-strategy.md` — an artifact inherited from mystira-workspace's
+4-digit ADR numbering (that file does not exist in deck, and this ADR does not create it). deck is
+**not** onboarded to AgentKit/Retort, so there is no generated `decisions/` registry to collide
+with (unlike docket's ADR-09, which had to skip reserved numbers).
+
+Decision on location and number:
+
+- **Location:** `docs/adr/` — matches the single existing breadcrumb (`../adr/…`) and keeps ADRs
+ beside the existing `docs/guides/` and the proposed `docs/specs/`. (The phoenixvc/docket
+ convention `docs/architecture/decisions/NN-*.md` was considered; `docs/adr/` was chosen to honour
+ deck's own inherited breadcrumb and avoid inventing an `architecture/` tree deck doesn't have.)
+- **Number:** **`0001`** — deck's first hand-authored ADR, 4-digit to match the inherited breadcrumb
+ style.
+- **Format:** the org/mystira **narrative** ADR shape (Status / Context / Decision / Consequences /
+ placement note), not a weighted decision-matrix — this records a decision narrative, not a tool
+ bake-off.
+
+This establishes `docs/adr/NNNN-*.md` as deck's ADR home for future decisions.
+
+## References
+
+- `docs/specs/deck-ops-cockpit.md` — deck ops cockpit technical spec (this ADR's companion).
+- `mystira-workspace .agents/roadmaps/video-generation/design-ecosystem-provisioning.md` — deck's
+ ecosystem role (Models panel + sluice hybrid routing; §1 fit-with-what-deck-already-is).
+- `phoenixvc/sluice docs/planning/sluice-go-live-audit-2026-07.md` — sluice LIVE verification;
+ `/health`, `/metrics`, `/spend/logs` surfaces + the master-key caveat.
+- `phoenixvc/docket docs/architecture/specs/07_docket_mcp_server.md` + `decisions/09-docket-mcp-domain-service.md`
+ — docket MCP tool surface, phasing, and the domain-service-MCP shape deck consumes.
+- `mystira-workspace apps/story-generator/docs/specs/BATCH_API_GENERATION_ROUTING.md` — the batch
+ operation-id + status model deck's Facet 3 renders against.
diff --git a/docs/specs/deck-ops-cockpit.md b/docs/specs/deck-ops-cockpit.md
new file mode 100644
index 0000000..b280db5
--- /dev/null
+++ b/docs/specs/deck-ops-cockpit.md
@@ -0,0 +1,474 @@
+# Deck Ops Cockpit — Cost/Ops Panel + Re-Ported Mystira Ops Tooling (R13)
+
+- **Status:** Draft (design spec — no implementation)
+- **Repo:** `phoenixvc/deck` (this repo). Companion doc lives in mystira-workspace for cross-repo review only.
+- **Last updated:** 2026-08-10 — authored 2026-07-11; §1.1/§1.2/§1.4 re-grounded against `dev`
+ @ `bc0d1c5` before commit (PR #3 added the Camera panel and cleaned part of the extraction
+ residue; PR #5 added CI). The design and the three facets are unchanged.
+- **Scope:** Turn deck from the current generic ops shell into the **operator cockpit over the
+ R13 Mystira AI-content pipeline** (`sluice → story-generator → publisher`; `docket` meters spend).
+ Three owner-selected facets: (1) a **cost/ops cockpit** in the Dashboard, (2) **re-port** the
+ Cosmos Explorer + Migration Manager panels stripped on the devhub→deck extraction, (3)
+ **Service-Manager batch-run ops** over the specced nightly story-gen Batch API.
+- **Docs-convention note:** before this spec landed, deck had only `docs/guides/`. This spec
+ establishes `docs/specs/` as the home for deck design specs (mirrors story-generator's
+ `docs/specs/` and docket's `docs/architecture/specs/`). The ADR lands at `docs/adr/0001-*.md`
+ — see §9.
+- **ADR:** `docs/adr/0001-deck-as-panel-host-for-mystira-ops-tooling.md` (this spec's companion
+ decision — deck-as-panel-host-in-a-generic-shell).
+
+---
+
+## 0. One-line summary
+
+deck hosts three Mystira-facing surfaces inside its existing VSCode-style shell — a **Dashboard
+cost/ops section** (sluice health + docket spend), a **re-ported Cosmos Explorer + Migration
+Manager**, and a **Service-Manager batch-run monitor** — each added through deck's existing
+panel-host recipe, each consuming a **real, specced upstream contract**, and each degrading
+gracefully when its upstream is only specced (not yet built).
+
+---
+
+## 1. Current-state grounding (deck as-is, 2026-08-10 — `dev` @ `bc0d1c5`)
+
+### 1.1 The shell and the panel-host recipe
+
+deck is a Tauri 2.0 + React 18/TS5/Vite5/Tailwind app (`app/`) over a Rust backend
+(`app/src-tauri/`), a .NET 9 CLI sidecar (`Deck.CLI/` + `Deck.Services/`), and two Rust crates —
+`crates/deck-contracts/` (shared Rust↔TS contracts) and `crates/deck-camera/` (a deck-owned local
+RTSP transport, added in PR #3). Adding a panel is a fixed four-file recipe (confirmed in
+`CLAUDE.md` §Architecture notes and the code):
+
+1. `app/src/types/constants.ts` — add a `VIEWS` entry.
+2. `app/src/App.tsx` — add an `ACTIVITY_BAR_ITEMS` entry.
+3. `app/src/components/app/AppContent.tsx` — add a route `case`.
+4. `app/src/components/app/AppSidebar.tsx` — add a description + quick-actions block.
+
+The **Camera panel (PR #3) is the recipe's most recent worked example**, and a useful precedent for
+this spec: a self-contained panel over its own backend crate (`crates/deck-camera/`), added by
+*using* the four-file recipe rather than changing the routing mechanism it plugs into — which is
+exactly the shape ADR-0001 prescribes for the Mystira panels.
+
+Backend commands are Rust `#[tauri::command]` functions registered in
+`app/src-tauri/src/main.rs` (`invoke_handler![…]`). Heavy data work (Cosmos SDK, migrations) is
+delegated to the .NET sidecar over JSON-stdin/stdout via `app/src-tauri/src/cli.rs`
+(`execute_devhub_cli`), which the sidecar's `Deck.CLI/Program.cs` dispatches by `command` name.
+
+CI (PR #5, `.github/workflows/ci.yml`) gates every PR on `cargo fmt --check`, `cargo clippy
+--workspace --all-targets`, `cargo test --workspace`, and — for `app/` — `tsc --noEmit`, `vitest
+run`, and `npm run build`. Any work under this spec must keep all six green.
+
+### 1.2 Finding: the devhub→deck extraction is **incomplete** — half-wired, not merely stripped
+
+The README claims the Cosmos Explorer + Migration Manager panels "were removed during
+extraction." The reality is a **half-removal**: the UI is gone, the backends are not, and a few
+now-dead frontend callers survive. This is load-bearing for Facet 2:
+
+| Layer | State today | Evidence |
+| --- | --- | --- |
+| Frontend panels | **Removed** — `app/src/components/cosmos/` and `.../migration/` do not exist | directories absent |
+| Frontend routing/sidebar | **Clean** — `AppContent.tsx` routes only `SERVICES`/`DASHBOARD`/`INFRASTRUCTURE`/`CAMERA`/`TEST`; `AppSidebar.tsx` has no Cosmos/Migration block | `AppContent.tsx:12-33`; `AppSidebar.tsx` (no matches) |
+| Dashboard quick-actions | **Dead links** — three quick-actions still call `onNavigate("cosmos")` / `onNavigate("migration")`. Neither view exists, so `AppContent`'s `default:` silently renders `` — the buttons look live and go to the wrong panel | `Dashboard.tsx:66,74,82`; `AppContent.tsx:31-32` |
+| Orphaned Cosmos callers | **Present but unrendered** — `StatisticsPanel.tsx` invokes `cosmos_stats` and `ExportPanel.tsx` invokes `cosmos_export`; nothing in the tree renders either component | `StatisticsPanel.tsx:38`; `ExportPanel.tsx:52` |
+| `VIEWS` enum | **Missing** `COSMOS` / `MIGRATION` — `SERVICES, DASHBOARD, INFRASTRUCTURE, CAMERA, TEST` | `app/src/types/constants.ts:2-8` |
+| Rust backend | **Present + registered** — `cosmos.rs` exposes `cosmos_export`, `cosmos_stats`, `migration_run`, `fetch_environment_connections`, `check_azure_cli_login`, all in `main.rs` `invoke_handler![]` | `app/src-tauri/src/cosmos.rs`; `main.rs:42-43,86-90` |
+| .NET migration backend | **Present** — `Deck.Services/Migration/MigrationService.cs` + `Deck.CLI/Commands/MigrationCommands.cs` (14 known containers) | files exist |
+| .NET Cosmos stats/export | **Removed** — `Deck.CLI/Program.cs` routes **only** `"migration.run"`; `cosmos.export` / `cosmos.stats` (invoked by `cosmos.rs`) return `"Unknown command"` | `Deck.CLI/Program.cs:126-127` |
+
+> **Consequence:** the tree **builds and is green** — CI (PR #5) runs `tsc --noEmit`, `vitest run`
+> and `npm run build` on every PR. The residue is **behavioural, not compile-time**: three Dashboard
+> quick-actions navigate to views that no longer exist and land on the Service Manager instead; two
+> orphaned components call Cosmos commands whose .NET handlers were stripped; and the Rust + .NET
+> halves of both features sit fully wired with no UI above them. Facet 2 is therefore not a
+> from-scratch build — it is **finishing an interrupted extraction**: re-add the two panels, re-add
+> the two `VIEWS` entries, re-point (or delete) the dead Dashboard quick-actions, adopt or remove
+> the two orphaned components, and restore the two stripped `.NET` CLI handlers. See §4.
+>
+> **Drift note (2026-08-10).** As authored on 2026-07-11 this section recorded a stronger finding:
+> that `AppContent.tsx` and `AppSidebar.tsx` still imported the removed `CosmosExplorer` /
+> `MigrationManager` components and switched on undefined `VIEWS` members, so the frontend could not
+> typecheck. **PR #3 removed that dangling wiring**, and PR #5's CI now proves the build is clean.
+> The finding is retained in its corrected form because the *conclusion* is unchanged — the
+> extraction is still unfinished, just less visibly so, and the surviving residue (dead nav,
+> orphaned callers, headless backends) is now the evidence for it.
+
+### 1.3 Secret-handling posture (deck's existing conventions — normative)
+
+From `app/security.md` and the code, deck already has a coherent secret model this spec extends
+rather than replaces:
+
+- **No token capture.** deck relies on the operator's already-authenticated `az` / `gh` CLI
+ sessions; it never stores auth tokens. `cosmos.rs::fetch_environment_connections` fetches Cosmos
+ and Storage connection strings **on demand** via `az cosmosdb keys list` / `az storage account
+ show-connection-string`, using the operator's `az login`.
+- **Connection strings are session-only** — entered in the UI, held in component state, never
+ persisted to disk (`security.md` §Credential Management). Future: system keychain via
+ `tauri-plugin-keytar` (deferred).
+- **argv-only host exec.** deck runs host commands via `Command::new` with an argument vector — no
+ `sh -c`, no shell interpolation (see the ecosystem-provisioning design doc §1.4 for the
+ normative statement). Every new backend command in this spec follows the same rule.
+- **Minimal Tauri capabilities** — `app/src-tauri/capabilities/default.json` grants only
+ `core:default`, `shell:allow-open`, `opener:allow-open-url`, `dialog:default` and a scoped
+ `fs:default`. Any new *plugin* scope is an explicit capability addition.
+ > **Caveat — capabilities do not gate this spec's HTTP.** deck's outbound calls use **native
+ > `reqwest` from Rust**, not the Tauri HTTP plugin, so nothing in `capabilities/default.json`
+ > constrains them. Every new outbound command in §3.6 and §5.4 must therefore enforce its own
+ > allowlist **in Rust** — require `https`, and permit only the configured sluice / docket /
+ > story-gen hosts — rather than relying on the capability system. Related: the existing
+ > `services/status.rs:44` health client sets `danger_accept_invalid_certs(true)` for localhost
+ > self-signed certs; the cockpit's clients must **not** reuse that setting against remote
+ > endpoints.
+
+### 1.4 Naming residue (must be cleaned as part of any re-port)
+
+The extraction left DevHub identifiers that will break the re-ported paths: `main.rs:69` logs
+`"Mystira DevHub starting…"`; `cli.rs:58,74,77` error text points the operator at
+`tools/Mystira.DevHub.CLI`, and `helpers.rs:111-123` (`get_cli_executable_path()`) resolves the
+sidecar binary by joining `Mystira.DevHub.CLI[.exe|.dll]` — the sidecar is now `Deck.CLI`.
+`Deck.CLI/Program.cs` doc-comments say "DevHub" too. Facet 2 must reconcile the CLI-executable path
+so `execute_devhub_cli` actually locates the `Deck.CLI` build output, or the re-ported
+Cosmos/Migration commands will fail to spawn.
+
+---
+
+## 2. Cockpit panel map (how the three facets land in the shell)
+
+| Facet | Lands in | Mechanism | New view? |
+| --- | --- | --- | --- |
+| **1 — Cost/Ops** | Existing **Dashboard** view | New "Pipeline Health" + "AI Spend" sections in `Dashboard.tsx`; new `costStore` (zustand, mirrors `connectionStore`); new Rust `cost.rs` Tauri module | No — extends Dashboard |
+| **2 — Cosmos Explorer + Migration Manager** | New **Cosmos** + **Migration** views | Re-add `VIEWS.COSMOS`/`VIEWS.MIGRATION` + `ACTIVITY_BAR_ITEMS` + panels; backend already present (§1.2) | Yes — re-adds two views |
+| **3 — Batch-run ops** | Existing **Services** view | New "Story-Gen Batch Runs" section in the Service Manager; new Rust `story_gen.rs` Tauri module | No — extends Service Manager |
+
+This keeps the cockpit inside deck's existing IA: **spend/health is a Dashboard concern, data
+tooling is its own views, and long-running-job ops is a Service-Manager concern** — no new
+navigation paradigm.
+
+---
+
+## 3. Facet 1 — Dashboard cost/ops panel (sluice health + docket spend)
+
+### 3.1 What it shows
+
+Two new sections in `Dashboard.tsx`, above "Recent Operations":
+
+- **Pipeline Health** — sluice gateway liveness/readiness + throughput, and (later) story-gen
+ batch-queue depth. Green/amber/red tiles reusing the existing `getStatusColor` / `StatusBadge`
+ idiom.
+- **AI Spend** — this-month burn, spend-by-service (Azure OpenAI et al.), a 30-day trend
+ sparkline, and budget-vs-actual against sluice's `max_budget`. Sourced from docket.
+
+### 3.2 Data sources and transport (the core design fork)
+
+| Signal | Source | Endpoint | Auth | Transport |
+| --- | --- | --- | --- | --- |
+| Gateway liveness | sluice | `GET https://sluice.phoenixvc.tech/health/liveliness` | **none** | Rust `cost.rs` (reqwest) |
+| Gateway readiness (db, litellm version, callbacks) | sluice | `GET …/health/readiness` → JSON | **none** | Rust `cost.rs` |
+| Throughput / request rollups | sluice | `GET …/metrics` (Prometheus text) | **none** (or scrape-scoped) | Rust `cost.rs` — parse Prometheus |
+| Spend summary / by-service / burn / trend | **docket MCP** | `get_burn_total`, `get_spend_by_service`, `get_cost_trend`, `get_ai_spend` | `MCP_SECRET` Bearer | see §3.3 |
+| Optimization insights | docket MCP | `get_optimization_insights` | `MCP_SECRET` Bearer | see §3.3 |
+| Per-component LLM spend | docket MCP | `get_llm_usage_by_component` | `MCP_SECRET` Bearer | **gated** — §7 of the docket spec |
+
+**sluice is a green dependency** (verified live: `/health/readiness` → 200,
+`litellm_version 1.83.7`, `db:"connected"`, Prometheus + OTel callbacks loaded — sluice go-live
+audit §2.4). deck reaches it over the **stable custom domain** `https://sluice.phoenixvc.tech`,
+never the raw ACA FQDN (which changes on CAE rebuild). The health/throughput half needs **no
+secret**, so it is buildable immediately.
+
+> **Do NOT put the LiteLLM master key in deck.** sluice's row-level `/spend/logs` requires the
+> LiteLLM master/admin key (`pvc-prod-sluice-kv/gateway-key`, sluice audit §4.3). That is a
+> high-privilege, org-wide credential; a desktop app is the wrong place to hold it. deck reads
+> **aggregated** spend through docket (which already ingests sluice spend as its domain job) and
+> only touches sluice directly for **unauthenticated** health/metrics. If a future need forces
+> direct `/spend/logs` access, it must be a deliberate, separately-reviewed KV-access decision —
+> out of scope here.
+
+### 3.3 The MCP-from-a-desktop-app problem (must be resolved before Facet 1's spend half)
+
+docket's cost surface is specced as an **MCP server** (`07_docket_mcp_server.md`, ADR-09):
+FastMCP mounted at `/mcp` on docket's FastAPI process, JSON-RPC over HTTP + `MCP_SECRET` Bearer,
+deployed on Railway, discovered by **agents** via `claude mcp add --scope user`. That registration
+model is agent-facing (Claude Code reads `~/.claude.json`). **deck is not an LLM agent** — it
+cannot "discover and call MCP tools" the way Claude Code does. Two viable options; pick one before
+building the spend half:
+
+- **Option A (recommended) — deck speaks MCP JSON-RPC directly.** A Rust `cost.rs` MCP client
+ performs the MCP `initialize` handshake against docket's `/mcp` and issues `tools/call` for
+ `get_burn_total` et al., sending `Authorization: Bearer `. Pro: consumes the exact
+ specced surface, no new docket work. Con: deck must implement a minimal MCP-over-HTTP client and
+ manage the bearer (see §3.4).
+- **Option B — request docket expose a plain REST cost facade.** The docket MCP is mounted
+ *alongside* the existing FastAPI REST app; docket could expose the same six Phase-1 queries as
+ REST (`GET /cost/burn`, `/cost/by-service`, …) that deck calls with a simpler read token. Pro:
+ trivial for a desktop client. Con: **new docket work not in the current spec** — must be
+ requested of the docket team and is a net-new cross-repo dependency.
+
+**Recommendation:** Option A. It aligns with the ecosystem's "MCP over HTTP + Bearer is the wire
+contract" stance (ADR-09) and needs nothing docket hasn't already specced. Treat the MCP client
+as a small Rust module; the bearer is handled per §3.4.
+
+### 3.4 Auth / secret handling (never commit bearers)
+
+- The docket `MCP_SECRET` bearer is a per-developer secret. It must **never** enter the deck repo
+ (matches deck's `security.md` "never commit credentials" and the org rule that MCP bearers live
+ only in `~/.claude.json`).
+- **Where deck reads it:** an env var (`DOCKET_MCP_TOKEN`) or deck's existing app-config store
+ (`config.rs` / `get_app_config`), which is machine-local and git-ignored — the same posture as
+ Cosmos connection strings (session/local only, never persisted to the repo). It is **not** put
+ in `~/.claude.json` (that file is Claude Code's, not deck's).
+- **Endpoint config:** the docket `/mcp` base URL and the sluice base URL are non-secret and can
+ live in `Deck.CLI/appsettings.json` / deck app-config with env-override, defaulting to
+ `https://sluice.phoenixvc.tech` and the docket Railway `/mcp` URL.
+
+### 3.5 Graceful degradation (docket MCP is only specced, not built)
+
+The spend half **must** assume docket is absent and light up incrementally:
+
+- On mount, `costStore.probeCost()` first hits sluice health/metrics (works today), then attempts
+ the docket handshake.
+- If docket is unreachable / unregistered / returns auth failure, the AI-Spend tiles render a
+ neutral **"docket cost API not yet available"** state (not an error, not a fabricated zero) —
+ mirroring how `get_llm_usage_by_component` must report `coverage: none` rather than a silent zero
+ (docket spec §7). The Pipeline-Health section stays fully live off sluice.
+- A single `costStore` flag (`docketAvailable: boolean`) drives the whole spend section, so Facet 1
+ ships **useful on day one** (health/throughput) and the spend tiles activate the moment docket's
+ MCP goes live and a bearer is configured — no deck code change required.
+
+### 3.6 Backend shape (new Rust module, mirrors `cosmos.rs`/`github.rs`)
+
+New `app/src-tauri/src/cost.rs`, registered in `main.rs`:
+
+```rust
+#[tauri::command] pub async fn sluice_health() -> Result; // GET /health/readiness
+#[tauri::command] pub async fn sluice_metrics() -> Result; // GET /metrics (Prometheus parse)
+#[tauri::command] pub async fn docket_burn_total(since: String) -> Result; // MCP tools/call get_burn_total
+#[tauri::command] pub async fn docket_spend_by_service(since: String, top: Option) -> Result;
+#[tauri::command] pub async fn docket_cost_trend(since: String, granularity: String) -> Result;
+```
+
+The docket-facing commands reuse the crate's existing `CommandResponse` envelope
+(`deck-contracts::devhub`) so the frontend contract is uniform with the rest of deck. A new
+`crates/deck-contracts/src/cost.rs` (or a `docket` module) can hold the spend/health DTOs so the
+Rust↔TS types are generated the same way as the existing contracts.
+
+---
+
+## 4. Facet 2 — Cosmos Explorer + Migration Manager re-port
+
+### 4.1 What to bring back (and what is already here)
+
+Per the devhub audit lineage, these were Mystira-specific panels over Cosmos data + the
+account-to-account migration flow. The **backend is mostly intact** (§1.2), so the re-port is
+frontend-heavy:
+
+- **Migration Manager panel** — a React panel over the **already-present** `migration_run` Tauri
+ command → `Deck.CLI` `migration.run` → `Deck.Services.MigrationService.MigrateContainerAsync` /
+ `MigrateBlobStorageAsync`. The 14 known containers + partition keys already live in
+ `MigrationCommands.cs`; the dry-run, source/dest connection, and per-container selection UX is
+ documented in `docs/guides/cosmos-db-migration.md`. **No new backend needed** beyond the
+ naming-path fix (§1.4).
+- **Cosmos Explorer panel** — a React panel for stats/export/browse. The `cosmos_stats` /
+ `cosmos_export` Tauri commands exist and are registered, **but** their `.NET` handlers
+ (`cosmos.stats`, `cosmos.export`) were stripped from `Deck.CLI/Program.cs` (only `migration.run`
+ survives). Re-port therefore also requires **restoring those two CLI handlers** in the sidecar
+ (a `CosmosCommands` class dispatched from `Program.cs`), or the Explorer's stats/export buttons
+ return `"Unknown command"`.
+
+### 4.2 Fit with deck's Tauri/React/.NET-9 shell
+
+- **Frontend:** re-create `app/src/components/cosmos/` (exporting `CosmosExplorer`) and
+ `app/src/components/migration/` (exporting `MigrationManager`), matching the existing panel
+ structure (`ServiceManager`, `InfrastructurePanel`, and the `CameraPanel` added in PR #3 — the
+ closest and most recent template). Re-add, following the §1.1 four-file recipe in full:
+ - `VIEWS.COSMOS = "cosmos"` and `VIEWS.MIGRATION = "migration"` in `constants.ts`;
+ - `ACTIVITY_BAR_ITEMS` entries (e.g. `🗄️ Cosmos`, `🔄 Migration`) in `App.tsx`;
+ - **new** `AppContent.tsx` route cases and `AppSidebar.tsx` description/quick-action blocks. PR #3
+ deleted the originals, so these are written fresh rather than reactivated;
+ - re-point the three dead `Dashboard.tsx` quick-actions (§1.2), which currently fall through to
+ the Service Manager, and decide whether the orphaned `StatisticsPanel.tsx` / `ExportPanel.tsx`
+ become the Explorer's stats/export surfaces or are deleted as dead code.
+- **Connection UX:** reuse `connectionStore` (already lists a "Cosmos DB" connection, `type:
+ "cosmos"`, `🗄️`) and `fetch_environment_connections(cosmosAccountName)` for auto-discovery of the
+ resource group + storage account from the Cosmos account name.
+- **.NET-9 sidecar:** the migration path already flows through `execute_devhub_cli`; restore the
+ Cosmos stats/export handlers alongside it. Note `Program.cs` currently **requires an `AuthToken`
+ with the `admin` role on every command** (`AuthService.IsAuthorizedAsync`) — the re-ported
+ panels must supply this token exactly as the migration flow does.
+
+### 4.3 Mystira-Cosmos connection / secret model
+
+- deck **never stores** Cosmos connection strings in the repo or on disk (§1.3). The operator
+ either pastes a connection string into the panel (session-only) or lets
+ `fetch_environment_connections` mint one on demand from their `az login`.
+- Canonical Mystira Cosmos accounts (from `cosmetics_pr_sequence` / prod-identity lineage and the
+ migration guide): destination `mys-{env}-core-cosmos` (the guide still shows the legacy
+ `-san`-suffixed names and the retired `prodwusappmystiracosmos` source — the re-ported panel's
+ presets should be updated to the ADR-0027 canonical names, no region suffix).
+- **COPPA/PII caveat:** Cosmos containers hold child-facing data (`UserProfiles`, `Accounts`,
+ `GameSessions`). The Explorer is an operator tool behind the `admin` role; it must not export
+ PII to arbitrary local paths without the existing confirm-dialog + audit-log posture
+ (`security.md` §Audit and Logging). Export destinations stay operator-chosen and session-scoped.
+
+### 4.4 Re-port is independent of the AI pipeline
+
+Facet 2 has **no upstream on sluice/docket/story-gen**. Its only dependency is Mystira-Cosmos
+access via the operator's own `az` session — which is already deck's model. It is the one facet
+buildable end-to-end today (modulo the extraction-completion + naming-path fixes).
+
+---
+
+## 5. Facet 3 — Service-Manager batch-run ops (story-gen nightly Batch API)
+
+### 5.1 What it does
+
+A "Story-Gen Batch Runs" section in the Service Manager view that lets an operator:
+
+1. **Trigger** a bulk/nightly generation run (enqueue a batch workload).
+2. **List/monitor** batch operations by **operation id + status**.
+3. **Surface failures** (errored / expired / re-queued items) and the kill-switch state.
+
+### 5.2 Design against the specced operation-id + status model
+
+From `BATCH_API_GENERATION_ROUTING.md` (R13, Draft — **not yet built**), the model deck renders
+against:
+
+- **Enqueue → operation id.** A bulk request returns an **operation id immediately (202)**, exactly
+ like the continuity async pattern. `custom_id == BatchGenerationItem.Id`.
+- **Item status ladder:** `Queued → Submitted → Succeeded | Errored | Expired | Failed` (with
+ `BatchId msgbatch_…`, `SubmittedAt`, `CompletedAt`, `Attempts`).
+- **Batch status:** `BatchSubmission { BatchId, WindowId (batch-YYYYMMDD), ItemCount,
+ ProcessingStatus (in_progress|canceling|ended), Counts { processing, succeeded, errored,
+ canceled, expired } }`.
+- **Two-phase cycle:** submit ~02:00 UTC, poll/retrieve every ~15 min.
+- **Kill-switch:** `Ai:Sluice:Batch:Enabled` (default `false`). deck should **display** this flag's
+ state (read-only) so the operator knows whether batch is even active in that environment.
+
+### 5.3 Endpoints deck consumes (proposed by the story-gen spec — verify before building)
+
+| deck action | story-gen endpoint | Status |
+| --- | --- | --- |
+| Monitor one operation | `GET api/stories/batch/{operationId}` | **Proposed** (spec Appendix), not built |
+| Enqueue a bulk workload | `POST api/stories/batch` | **Proposed**, not built |
+| Trigger the submit phase **on demand** | *(none)* | **Does not exist** — nightly cron only |
+
+> **Two blockers to flag.** (a) The endpoints are a **proposed implementation checklist**, not
+> shipped API. (b) There is **no on-demand "run the submit phase now" trigger** — the submit phase
+> is a scheduled job (Container App Job or in-process `BackgroundService`, a deferred
+> mystira-mason decision). So deck's "trigger a run" realistically means **enqueue items** via
+> `POST api/stories/batch` and let the next nightly cycle submit them — deck **cannot** force an
+> immediate LLM submission unless story-gen adds an ad-hoc trigger endpoint. deck should either
+> (i) scope "trigger" to enqueue-only, or (ii) request an explicit on-demand submit endpoint from
+> the story-gen team.
+
+### 5.4 Transport, auth, backend shape
+
+- New `app/src-tauri/src/story_gen.rs` Tauri module (mirrors `github.rs`'s workflow-dispatch/status
+ pattern), reqwest over HTTP to the story-gen App Service:
+
+```rust
+#[tauri::command] pub async fn batch_enqueue(payload: BatchEnqueueRequest) -> Result; // POST api/stories/batch
+#[tauri::command] pub async fn batch_status(operation_id: String) -> Result; // GET api/stories/batch/{id}
+#[tauri::command] pub async fn batch_kill_switch() -> Result; // read Ai:Sluice:Batch:Enabled (via a story-gen status endpoint)
+```
+
+- **Contract placement (open):** `BatchOperation` above is a placeholder — it is *not* defined
+ anywhere yet, and it must not silently conflate `BatchGenerationItem` (one item's status ladder)
+ with `BatchSubmission` (a whole batch's rollup); §5.2 keeps them distinct and the DTOs should
+ too. deck already ships `crates/deck-contracts/src/story_generator.rs`, which is the natural home
+ for these types and should be reused/extended rather than duplicated in `story_gen.rs`. Settle
+ the exact request/response shapes — plus `POST` idempotency on `custom_id ==
+ BatchGenerationItem.Id`, and whether the kill-switch is read-only or writable — with the
+ story-gen team when their endpoints move from proposed to built (§5.3).
+- **Auth:** story-gen's batch endpoints sit behind `[Authorize]` + rate-limiting (spec §8.3). deck
+ needs a story-gen API credential/token for the target environment — **held session/local only**,
+ never in the repo (same posture as §3.4). The exact scheme (bearer, magic-link-derived, service
+ principal) is a story-gen decision to confirm.
+- **Attribution visibility (nice-to-have):** batched requests are tagged `Workflow="nightly-batch"`
+ for docket attribution (story-gen spec §7). Once Facet 1's docket path is live, deck can
+ cross-link a batch run to its `get_llm_usage_by_component` slice — closing the loop between "I
+ ran this batch" and "it cost this much." This is the cockpit's payoff and the reason all three
+ facets belong in one tool.
+
+### 5.5 Batch ops is design-ready but non-functional until upstream ships
+
+deck can build the monitor UI now against the specced status model (it is stable and detailed),
+but **nothing works until** story-gen ships `POST/GET api/stories/batch`, `Ai:Sluice:Batch:Enabled`
+is flipped, and (upstream of *that*) sluice exposes the Anthropic `/v1/messages/batches*` passthrough
+(story-gen spec §3c blocker, sluice task `5dc28591`). Build the UI behind a feature flag; keep it
+dark in every environment until the endpoints return real data.
+
+---
+
+## 6. Cross-cutting concerns
+
+- **Never commit secrets.** Three new credentials appear across the facets: docket `MCP_SECRET`
+ (Facet 1), Mystira-Cosmos connection strings (Facet 2), a story-gen API token (Facet 3). All are
+ session/local-only, sourced from env or machine-local app-config, and **must never enter the
+ deck repo** — consistent with `security.md` and the org's MCP-bearer rule.
+- **argv-only + minimal capabilities.** New backend commands use `Command::new`/reqwest with no
+ shell interpolation; outbound HTTP to sluice/docket/story-gen is added as an explicit Tauri
+ capability, not a blanket network grant.
+- **Naming residue.** The DevHub→Deck cleanup (§1.4) is a prerequisite for Facet 2 (CLI path
+ resolution) and good hygiene for the rest; do it in the same PR as the extraction-completion.
+- **Sibling future panel (out of scope, noted for coherence).** The ecosystem-provisioning design
+ proposes a **deck Models panel** (local-model detect/install → report capability to sluice's
+ registry). It shares this cockpit's exec + Service-Manager machinery and the same argv-only/
+ allowlist/consent posture, but is a separate proposal — not part of this spec.
+
+---
+
+## 7. Per-facet readiness (build-now vs blocked-on-upstream)
+
+| Facet | Sub-capability | Readiness | Blocking upstream |
+| --- | --- | --- | --- |
+| **1 — Cost/Ops** | sluice **health + throughput** tiles | ✅ **Build now** | none — sluice LIVE (audit §2.4), unauthenticated |
+| **1 — Cost/Ops** | docket **spend / budget / trend** tiles | ⛔ **Blocked** | docket MCP built + deployed + registered + per-dev bearer; **and** the MCP-consumption fork resolved (§3.3) |
+| **2 — Cosmos Explorer + Migration Manager** | Migration Manager panel | ✅ **Build now** | none — backend present; needs frontend re-add + dead-nav re-point (§1.2) + naming-path fix + `az` login |
+| **2 — Cosmos Explorer + Migration Manager** | Cosmos Explorer panel | ✅ **Build now** (small backend restore) | none upstream — but must **restore** the stripped `cosmos.stats`/`cosmos.export` .NET CLI handlers |
+| **3 — Batch-run ops** | Monitor UI (against specced status model) | 🟡 **Design/build now, dark** | story-gen Batch API not built |
+| **3 — Batch-run ops** | Functional trigger + live status | ⛔ **Blocked** | story-gen `POST/GET api/stories/batch` + `Ai:Sluice:Batch:Enabled`; on-demand trigger endpoint (absent); sluice batch passthrough (task `5dc28591`) |
+
+**Net:** Facet 2 is fully deliverable now (it is really *finishing the extraction*). Facet 1 is
+**half deliverable now** (health), half blocked (spend). Facet 3 is **design-ready but
+non-functional** until the story-gen Batch API ships. Recommended build order: **Facet 2 → Facet 1
+health half → Facet 1 spend half (when docket lands) → Facet 3 (when Batch API lands).**
+
+---
+
+## 8. Cross-repo dependency list (what must become real for the cockpit to function)
+
+1. **docket MCP** (`phoenixvc/docket`) — Phase-1 tools built (`get_burn_total`,
+ `get_spend_by_service`, `get_cost_trend`, `get_ai_spend`, `get_spend_by_resource_group`,
+ `get_spend_by_region`), deployed on Railway behind `MCP_SECRET`, and **registered in the org
+ MCP** (`register_project`). Status: **specced, not built** (spec `07_docket_mcp_server.md`,
+ ADR-09; baton `91ca9c86-4057-49ad-8d2e-4744188daee1`).
+ - **Sub-dependency (deck-specific):** resolve how a **non-agent desktop app** consumes the MCP —
+ either deck ships an MCP JSON-RPC client (Option A, recommended) **or** docket exposes a REST
+ cost facade (Option B, net-new docket work). The current spec provides only the MCP surface.
+ - **Sub-dependency:** a **per-developer docket bearer** provisioned to deck via env/app-config
+ (never the repo, never `~/.claude.json`).
+2. **sluice** (`phoenixvc/sluice`) — `https://sluice.phoenixvc.tech` `/health/*` + `/metrics`
+ reachable. Status: **LIVE ✓** (audit §2.4). *Do not* wire deck to `/spend/logs` (needs the
+ LiteLLM master key `pvc-prod-sluice-kv/gateway-key`) — route spend through docket instead.
+3. **story-gen Batch API** (`mystira-workspace/apps/story-generator`) — endpoints
+ `POST api/stories/batch` (enqueue) + `GET api/stories/batch/{operationId}` (status) exposed with
+ an auth scheme deck can hold; the `Ai:Sluice:Batch:Enabled` flag flipped per environment; and —
+ if deck is to trigger runs on demand — an **on-demand submit trigger endpoint that does not yet
+ exist** (nightly cron only). Status: **specced, not built** (`BATCH_API_GENERATION_ROUTING.md`;
+ baton `944e357c-6491-47fc-be80-058e13ddcee3`).
+ - **Transitive blocker:** the Batch API itself is blocked on **sluice exposing the Anthropic
+ `/v1/messages/batches*` passthrough** with auth + metadata attribution (story-gen spec §3c;
+ sluice task `5dc28591`). Until that holds, Facet 3 cannot function even if deck + story-gen
+ are ready.
+4. **deck internal (this repo)** — complete the interrupted devhub extraction (§1.2), fix the
+ DevHub→Deck naming/CLI-path residue (§1.4), and restore the stripped `cosmos.stats`/`cosmos.export`
+ .NET CLI handlers. No external dependency; prerequisite for Facet 2.
+
+---
+
+## 9. ADR
+
+The generic-shell-vs-Mystira-coupling decision is captured in the companion ADR:
+`docs/adr/0001-deck-as-panel-host-for-mystira-ops-tooling.md`. It records **why** deck hosts
+Mystira-specific ops panels in a generic phoenixvc shell, the boundary that keeps the shell
+reusable (panels are Mystira-coupled; the shell, exec primitives, and Service-Manager machinery
+stay generic), and the placement/numbering choice for deck's first ADR.