Auth / tenant (C3–C5)
- Dev login issues a console session. Switch tenant when membership has more than one.
+ Dev login issues a console session. Switch tenant when membership has more than one. Register host workspaces (admin) for Pilot binds.
+
diff --git a/contracts/workspace.py b/contracts/workspace.py
new file mode 100644
index 0000000..fe7cd4e
--- /dev/null
+++ b/contracts/workspace.py
@@ -0,0 +1,25 @@
+"""Registered host workspaces for Pilot / API workdir binding (RW0).
+
+See ``docs/specifications/registered-workspaces.md``.
+"""
+
+from __future__ import annotations
+
+from datetime import datetime
+
+from pydantic import BaseModel, ConfigDict, Field
+
+
+class RegisteredWorkspace(BaseModel):
+ """Allowlisted host directory a tenant may bind as a run workdir."""
+
+ model_config = ConfigDict(extra="forbid")
+
+ workspace_id: str = Field(min_length=1, max_length=64)
+ tenant_id: str = Field(min_length=1, max_length=64)
+ display_name: str = Field(min_length=1, max_length=128)
+ host_root: str = Field(min_length=1, description="Absolute host path (Windows drive-letter)")
+ enabled: bool = True
+ created_at: datetime | None = None
+ created_by: str = Field(min_length=1)
+ notes: str | None = None
diff --git a/docs/architecture/go-live.md b/docs/architecture/go-live.md
index b16218c..affe42b 100644
--- a/docs/architecture/go-live.md
+++ b/docs/architecture/go-live.md
@@ -136,17 +136,43 @@ python -m uvicorn recertia.api.app:app --host 127.0.0.1 --port 8080
| Surface | Use |
| --- | --- |
-| Pilot | Goal form, templates, sync/async submit, live event stream |
+| Pilot | Goal form, templates, sync/async submit, live event stream; workspace select |
| Runs / Skills | Browse transcripts, promote (golden-gated) |
| Tower | Proposals, jobs (`dry_run` default), practice / pressure panels |
| Metrics | `MetricReport` + canary (unavailable reasons preserved) |
-| Auth | Dev login / OIDC session; tenant switcher (Phase-4 gated) |
+| Auth | Dev login / OIDC session; tenant switcher; **register workspaces** (admin) |
Issue an API key with `runs` (+ `metrics` / `exec` as needed) for the sidebar. Browser
sessions (`RECERTIA_CONSOLE_AUTH=dev` or `oidc`) carry human roles; do not embed long-lived
keys in frontend source. Specs: [`../specifications/product-console.md`](../specifications/product-console.md).
Plan: [`../implementation-plan-console.md`](../implementation-plan-console.md).
+### Registered workspaces (real repo bind)
+
+Pilot cannot take raw absolute `workdir` paths. Register an allowlisted host root first
+(API process must resolve Windows drive-letter paths — run uvicorn on Windows for
+`D:\…` roots):
+
+```powershell
+# Admin key (or console role admin + API key with runs)
+recertia keys issue --tenant default --scopes runs,admin,metrics --actor dev
+
+recertia workspaces register `
+ --id recertia `
+ --name "quantrobs/recertia" `
+ --host-root D:\src\recertia `
+ --tenant default `
+ --runs-root .recertia
+
+# CLI sugar
+recertia run --goal evals/golden/repo-chore/add-editorconfig/goal.json `
+ --workspace-id recertia --local-exec --runs-root .recertia
+```
+
+In the console: **Auth / Tenant → Register workspace**, then Pilot → Run → Workspace
+select → Submit. Spec:
+[`../specifications/registered-workspaces.md`](../specifications/registered-workspaces.md).
+
## Soak and durability (operator GA)
Durability unit: the entire `.recertia/` tree (checkpoints, operations, ledger,
diff --git a/docs/architecture/product-console.md b/docs/architecture/product-console.md
index 1dbb4d0..810ab6c 100644
--- a/docs/architecture/product-console.md
+++ b/docs/architecture/product-console.md
@@ -62,8 +62,9 @@ quota and identity, not necessarily self-serve key minting.
- **Programs board** — durable migration programs (`/v1/programs`): ordered steps,
freeze/mutate hints, per-step preview/run/bind (see [goal-packs.md](goal-packs.md)). Distinct
from Tower **ReplayPack** evidence.
-- **Workdir picker** — path or registered workspace; never accept arbitrary host escapes
- beyond the existing API workdir rules.
+- **Workdir picker** — sandbox (default) or **registered workspace** (allowlisted Windows
+ host root + optional subpath). Never accept raw absolute `workdir` on create-run; see
+ [registered-workspaces.md](../specifications/registered-workspaces.md).
- **Runs browser** — list/filter by tenant, task class, terminal, time; open detail.
- **Run detail** — route log, spend, manifest pins, failure class, links to transcript and
trajectory.
diff --git a/docs/implementation-plan-console.md b/docs/implementation-plan-console.md
index 48d18b8..9a46122 100644
--- a/docs/implementation-plan-console.md
+++ b/docs/implementation-plan-console.md
@@ -60,6 +60,11 @@ Each milestone lists **engineering gates** (merge requirements) and **explicit n
Conformance: `tests/unit/test_product_console.py` (PC-1…PC-6). C5 UI must not be marketed as
multi-tenant-safe until production-readiness criteria pass.
+**Registered workspaces (Pilot real-repo bind):** planned as RW0–RW2 in
+[`implementation-plan-registered-workspaces.md`](implementation-plan-registered-workspaces.md)
+(spec: [`specifications/registered-workspaces.md`](specifications/registered-workspaces.md)).
+Does not reopen absolute `workdir` on create-run.
+
---
## C0 — Read-only console (Pilot + Ops)
diff --git a/docs/implementation-plan-registered-workspaces.md b/docs/implementation-plan-registered-workspaces.md
new file mode 100644
index 0000000..7a1f229
--- /dev/null
+++ b/docs/implementation-plan-registered-workspaces.md
@@ -0,0 +1,149 @@
+# Registered workspaces — implementation plan
+
+Build order for Pilot workdir binding via allowlisted Windows host roots.
+Normative contracts: [`specifications/registered-workspaces.md`](specifications/registered-workspaces.md).
+Architecture note: [`architecture/product-console.md`](architecture/product-console.md) §3.1 Workdir picker.
+
+## Guiding rules
+
+1. **No raw absolute `workdir` on create-run.** Registry is the only API path to host trees.
+2. **Preserve sandbox default.** Omitting `workspace_id` keeps today’s
+ `workspaces///` behaviour and existing tests.
+3. **Windows drive-letter roots first.** Reject UNC / extended-length until a later revision.
+4. **Admin registers; runners bind.** `runs` may use enabled workspaces; `admin` (or console
+ `admin`) creates/disables them.
+5. **Resume is strict.** Disabled, missing, or drifted `host_root` → hard fail (409), never
+ silent sandbox fallback.
+
+## Milestones
+
+```text
+RW0 Registry + create/resume resolution + tests
+RW1 Pilot UI (select + subpath) + register form + Programs wire-up
+RW2 Docs/go-live polish + optional CLI --workspace-id sugar
+```
+
+| Milestone | Status | Notes |
+| --- | --- | --- |
+| RW0 | Implemented | Backend contracts, store, API, workdir.json kind |
+| RW1 | Implemented | `/console` Pilot + Auth/Ops registration |
+| RW2 | Implemented | go-live.md, README index, CLI `workspaces` + `run --workspace-id` |
+
+Depends on: existing console C0–C3 (API keys, `/v1/runs`, Pilot SPA, console auth).
+
+---
+
+## RW0 — Registry and run binding
+
+### Scope
+
+- Contract `RegisteredWorkspace` in `contracts/workspace.py`; regenerate schemas.
+- Store: `src/recertia/workspaces/registry.py` (SQLite under api root), mirroring
+ `programs/store.py` patterns.
+- Path helpers: extend [`src/recertia/paths.py`](../src/recertia/paths.py) with
+ `normalize_windows_host_root()` / `split_rel_subpath()` (split on `/` and `\`).
+- Resolve path in [`src/recertia/api/__init__.py`](../src/recertia/api/__init__.py):
+ - Extend `RunCreate` with `workspace_id: str | None`.
+ - Branch `_resolve_create_workdir` (or replace with `_resolve_run_workdir`) per spec §5.2.
+ - Persist/load `workdir.json` with `kind`.
+ - Resume enforcement per §5.3.
+- Routes in [`src/recertia/api/console_routes.py`](../src/recertia/api/console_routes.py)
+ (or small `workspace_routes.py` registered from `create_app`): CRUD from §5.1.
+- Wire Programs step `/run` envelope to pass `workspace_id` through `run_create`.
+
+### Acceptance
+
+- RW-1…RW-6, RW-8, RW-9 green in `tests/unit/test_registered_workspaces.py` (+ extend
+ `test_api_runs.py` so absolute-without-id still fails).
+- `pytest -v` full suite green; no Docker required.
+
+### Out of scope
+
+- Console HTML/JS (RW1).
+- UNC paths.
+
+---
+
+## RW1 — Pilot and registration UI
+
+### Scope
+
+- [`console/static/index.html`](../console/static/index.html): Workspace `