diff --git a/AUDIT.md b/AUDIT.md
index 0158e80c..1284085a 100644
--- a/AUDIT.md
+++ b/AUDIT.md
@@ -1,260 +1,93 @@
-# AUDIT.md — wecanjustbuildthings.dev architecture & Path-A audit
-
-**Mode:** read-only / plan. No source files were modified to produce this report.
-**Branch:** `claude/kind-albattani-tnycq4`
-**Scope:** the four builder components (Build Studio, Mentor Engine, Skills Creator,
-Catalog), the shared build-session, the Content-Layer catalog, all LLM-inference
-pathways, existing Goose wiring, framework/host config, and vendor-exclusion posture.
-**Goal context:** wire the platform to drive **Goose** as a user-facing agent runtime,
-while the deployed platform itself stays "Model-free Path A" (zero inference, one
-documented exception). Findings only — **no solutions proposed.**
-
-> Note: the project's PreToolUse guard blocks writing the literal excluded-vendor
-> API host strings (OpenAI/xAI/Meta). Where those hosts are relevant below, they are
-> cited by `file:line` rather than reproduced verbatim. Permitted hosts (Anthropic,
-> OpenRouter, DeepSeek, Google) are shown normally.
-
----
-
-## 1. Architecture map — the four components
-
-All four are Svelte 5 islands (or inline logic within one) mounted into Starlight
-`.mdx` pages with `client:load`. Pure logic is factored into `src/lib/*.ts` and
-`src/modules/cost-estimator/`. Everything client-side is deterministic.
-
-### Build Studio
-The orchestrator; hosts Movements 1–4 and the Skills Creator inline.
-
-| File | Role |
-|---|---|
-| `src/components/BuildStudio.svelte` (~1,525 ln) | The island: intent → blueprint → skills → handoff. Reads/writes the build session. |
-| `src/content/docs/build.mdx:18` | Mount: `` (`/build/`); es at `src/content/docs/es/build.mdx:18`, ar under `src/content/docs/ar/…` |
-| `src/lib/studio-stack.ts` | Stack eligibility, advisory ranking, `pinnedDependencies` (#3/#4). |
-| `src/lib/chemistry.ts` | Tool "chemistry" — capability role-graph pairs/conflicts/order. |
-| `src/lib/mentor-engine.ts` | Movement 1 signals/questions/constraints (see below). |
-| `src/lib/skill-doc.ts` | Movement 3 `slugifySkill` / `skillToMd` (SKILL.md renderer). |
-| `src/lib/build-session.ts` | The shared session object (see §2). |
-| `enforcement/matcher.ts` | Imported into the island to **re-verify** the assembled stack in-browser. |
-
-**State:** reads the shared build session on mount (`BuildStudio.svelte:232-252`),
-persists every change via a `$effect` (`:283-310`). The downloadable starter
-(zip / GitHub / Goose recipe) is assembled in `starterFiles()` (`:806-822`).
-
-### Mentor Engine
-| File | Role |
-|---|---|
-| `src/lib/mentor-engine.ts` | **Pure, deterministic, stateless.** `detectSignals`, `pickQuestions`, `reflect` over a curated trilingual lexicon (`:33-42`) + protocol signals (`:45-50`). No model, no I/O. |
-| `src/lib/mentor-engine.test.ts` | Unit tests incl. structural invariants. |
-
-**State:** holds none itself. BuildStudio computes `reflect(signals, answers)`
-(`BuildStudio.svelte:547`) and persists the result into `session.converged`
-(`:295-296`). The deterministic `reflect()` is what CLAUDE.md calls the
-"structured-reflection step"; it makes **no** inference call (see §4 nuance).
-
-### Skills Creator
-| File | Role |
-|---|---|
-| `src/components/BuildStudio.svelte:962-981` | **Inline** Movement 3 UI + capture/dedupe (`captureSkill`, `authoredSkills`, `removeAuthored`). |
-| `src/lib/skill-doc.ts` | Pure `slugifySkill` + `skillToMd` (YAML-frontmatter SKILL.md). |
-| (external) `github.com/MartinMontero/knowledge-to-skills-pipeline` | Referenced by generated README/skills folder (`BuildStudio.svelte:780`); not in this repo. |
-
-**State:** authored skills live in component state (`authoredSkills`) and are
-persisted into `session.skills` (`BuildStudio.svelte:297`). There is **no** standalone
-Skills Creator component or route — it is part of Build Studio.
-
-### Catalog
-| File | Role |
-|---|---|
-| `src/content/docs/catalog/**/*.md(x)` | The entries themselves (Content-Layer docs; see §3). |
-| `src/content.config.ts` | Collection definition (`docsLoader` + `docsSchema({ extend: catalogExtend })`). |
-| `src/schema/catalog.ts` | The Zod frontmatter schema. |
-| `src/pages/catalog.json.ts` | Build-time static JSON API at `/catalog.json` (filters to `INDEXED_TYPES`). |
-| `src/components/CatalogExplorer.svelte` | Interactive island (search/facets/sort) at `/catalog/` (`catalog/index.mdx:21`). |
-| `src/lib/catalog-filter.ts` | Pure filter/facet/sort logic used by the island. |
-| `src/components/CatalogList.astro` | Static server-rendered lists (e.g. PIE pages). |
-| `data/*.json` + `scripts/build-catalog.ts` | Build-time seeds → catalog md generator (`package.json:19`). |
-
-**State:** the island fetches `/catalog.json` once on mount
-(`CatalogExplorer.svelte:237`) and filters entirely client-side. It does **not** use
-the shared build session — its only handoff to Build Studio is a URL param
-(`?seed=`, read at `BuildStudio.svelte:271-277`).
-
----
-
-## 2. The shared client-side build-session object
-
-**Defined in:** `src/lib/build-session.ts` — `interface BuildSession` (`:54-90`).
-
-**Persistence:** **`localStorage`** (not IndexedDB, not a framework store).
-- Key: `wcb.build-session.v1` (`build-session.ts:92`).
-- Change notification: same-tab `CustomEvent('wcb:session-change')` (`:93`, dispatched in `saveSession` `:161`) + cross-tab `storage` event (`subscribeSession` `:194-206`).
-- Defensive load/coerce via `migrate()` (`:120-181`); silent degradation if storage is unavailable (`saveSession` `:158-160`).
-
-**Fields** (`build-session.ts:54-90`):
-| Field | Line | Owner / meaning |
+# AUDIT.md — SHIP Phase-1 findings (2026-07-11)
+
+Read-only research pass for the backlog-ship + design-elevation effort.
+Placed at `audit/AUDIT.md` because root `AUDIT.md` is the (historical,
+completed) Goose-pivot audit — promotion/renaming is a gate decision.
+Companion docs: `BACKLOG.md` (consolidated ledger), `DESIGN.md` (direction),
+`audit/screens/` (39 screenshots, 13 flows × 3 widths).
+
+## Baselines (executed this session)
+
+- `npm run verify:all` — **green**: astro check 0 errors/0 warnings, both
+ typechecks clean, **352/352 tests**, enforce + enforce:skills green,
+ production build 6,653 pages.
+- `npx tsx enforcement/cli.ts all --tree .` — exits 1, but **every hit is
+ self-referential**: the engine's own signal-definition YAMLs and its
+ negative-test fixtures. **Zero hits in `src/`, `worker/`, `scripts/`,
+ `public/`.** Refinement (SHIP-GATE-R1 2c): CI **already dogfoods Layers 1–2
+ full-tree** (`verify.yml:42-43`, green); only Layer 3 full-tree is
+ unsatisfiable by construction. Exact exempt paths, enumerated:
+ `enforcement/excluded-organizations.yaml`,
+ `enforcement/excluded-provider-signals.yaml`, `enforcement/tests/**`.
+ DoD-prose fix suffices — no engine change needed — gate item G3.
+- `npm audit --omit=dev` — 4 low (esbuild via astro; Windows-dev-server-only,
+ prod unaffected). Full audit: 14 (7 low/6 moderate/1 high) — the high is
+ `tmp` via `@lhci/cli`, CI-tooling only.
+- Enforcement full-tree + verify baselines contradict nothing in CI (10
+ workflows, none disabled; the only two `continue-on-error` are
+ documented-deliberate: `security-pr.yml:42`, `security-cron.yml:58`).
+
+## P0 — security / broken / binding-doc conflicts
+
+| # | Finding | Evidence |
|---|---|---|
-| `v: 1`, `updatedAt` | 55-56 | schema version + timestamp |
-| `movement: 1\|2\|3\|4` | 58 | wayfinding |
-| `intent{projectName,problem,goal,success,protocols,answers}` | 60-67 | Movement 1 raw intent + Socratic answers |
-| `converged` | 69 | Movement 1 deterministic reflection |
-| `adjustments{swaps,removed,extra}` | 71-75 | Movement 2 blueprint edits |
-| `stack: SessionStackItem[]` | 77 | Movement 2 resolved tools + receipts |
-| `skills: SessionSkill[]` | 79 | Movement 3 authored skills |
-| `seededTool` | 81 | Catalog "Build with this" seed |
-| `handoff` | 83 | Movement 4 method (`zip\|github\|goose\|kickoff`) |
-| `usage?`, `costEstimate?` | 88-89 | Hosting Cost Estimator I/O |
-
-**Readers/writers** (only three modules import it):
-- `src/components/BuildStudio.svelte` — read + write (primary).
-- `src/modules/cost-estimator/ui/CostEstimator.svelte` — read.
-- `src/modules/cost-estimator/core/usage-profile.ts` — write (`usage` / `costEstimate`).
-
-**Components NOT wired to the build session (flagged):**
-- `src/components/CatalogExplorer.svelte` — independent; hands off only via `?seed=` URL param, **not** the session object. (So a Catalog→Studio handoff carries a single tool id, nothing else.)
-- `src/components/PolicyChecker.svelte` (`/check/`) — fully standalone; no session read/write.
-- `src/components/AccountWidget.astro` — uses a **separate** server-side auth session (HttpOnly cookie → KV → D1), unrelated to the client build session.
-
----
-
-## 3. Catalog inventory (Astro Content Layer)
-
-**Content Layer:** confirmed. `src/content.config.ts` uses Starlight's
-`docsLoader()` (a glob/file loader over `src/content/docs/`) with
-`docsSchema({ extend: catalogExtend })` — i.e. the Zod schema in
-`src/schema/catalog.ts` layered onto Starlight docs frontmatter.
-
-**Counts** (English content; `es`/`ar` catalog dirs contain only an `index.mdx` —
-translations not yet generated):
-- **2,182** entry files under `src/content/docs/catalog/`.
-- **827** of those are datasets (`src/content/docs/catalog/datasets/`).
-- **1,354** tool-class entries — authoritative count from `npm run enforce:layer1`
- (`✓ 1354 catalog entries clean`). (1,354 + 827 + 1 index ≈ 2,182.)
-- `/catalog.json` ships `INDEXED_TYPES` = tools + datasets (`catalog.json.ts:9-15`).
-
-**Schema fields** (`src/schema/catalog.ts:80-134`, all optional at top level;
-conditionally required via `superRefine` `:140-207`):
-- Discriminator: `entry_type` (`:82`) ∈ `ENTRY_TYPES` (`:28-40`).
-- Dependency: `dependency_name, ecosystem, category, what_it_does, homepage_url, repo_url, registry_url, protocols` (`:85-92`).
-- License (accountability core): `license_spdx, license_source_url, license_source_commit_sha` (`:95-97`).
-- Maintenance: `maintenance_status, last_release_at, version` (`:100-105`).
-- AOS adoption: `aos_repos_using, aos_repos_list` (`:108-109`).
-- Nav/i18n: `pie_anchor` (`:112`), `machine_translated` (`:117`).
-- Enforcement: `provider_agnostic, verification_status, verification_blocked_reason, origin_advisory, verified_at` (`:120-126`).
-- **Recipe contract** (required when `entry_type==='recipe'`): `recipe_type, target_entry, target_entry_slug, excluded_providers_unreachable_when[], verification_steps[]` (`:129-133`, refined `:181-206`).
-
-**How datasets / extensions / skills are represented:**
-- **Datasets** — first-class: `entry_type: 'dataset'` (`schema:34`), 827 files, surfaced in `/catalog.json` as `kind: 'dataset'` (`catalog.json.ts:21`). Seed: `data/datasets.json`.
-- **Extensions (Goose/MCP)** — **NOT a first-class type.** `ENTRY_TYPES` (`schema:28-40`) has no `extension`. MCP servers exist only as generic catalog **tools** (e.g. `data/agentic-tools.json:1173-1230`, "MCP Ecosystem"). Relevant to the Goose pivot, where extensions are a distinct concept.
-- **Skills** — **NOT a catalog type.** No `skill` in `ENTRY_TYPES`. Skills are a build-flow artifact (authored in Build Studio → `skill-doc.ts` → SKILL.md) and a build-time seed (`data/skills-seed.json`); the authoring how-to lives in the external knowledge-to-skills-pipeline.
-- **Recipes** — first-class `entry_type: 'recipe'` (`schema:35`) but currently only **2** files under `src/content/docs/recipes/`. These are *provider-lockdown policy recipes* (exclusion exceptions), **distinct** from the Goose run-recipe YAML the Build Studio generates (§5).
+| P0-1 | **CLAUDE.md documents an i18n freshness/security-gate layer that does not exist** — incl. a 2-review gate for `security_sensitive` pages. One page carries the flag today (`guides/encrypted-group-messaging-marmot.mdx:10`) and gets **no extra protection**; the schema doesn't even define the field. CLAUDE.md is binding session instructions, so this is the highest-risk doc/reality conflict. | No `scripts/i18n-freshness.mjs`, `scripts/i18n-security-gate.mjs`, `i18n:stamp` script, `TranslationStatus.astro`, `src/config/i18n.mjs`, `TRANSLATING.md`, `.github/CODEOWNERS`, or i18n workflows (verified absent) |
+| P0-2 | **PolicyChecker island ignores its `lang` prop** — the verdict surface (the product's thesis) renders English-only inside `/es/check/` and `/ar/check/`. | `src/components/PolicyChecker.svelte` (no `$props()`); pages pass `lang="es|ar"` (`es/check.mdx:18`, `ar/check.mdx:17`); hardcoded strings `:83-127` |
----
+(P0-3 in the first revision — CLI jargon in the checker verdict — is
+**reclassified to P1-15** per the prompt's severity ladder: P0 is
+security/broken/policy; copy register is UX. Reclassification requested and
+ruled at SHIP-GATE-R1 2b.)
-## 4. LLM inference calls (Path-A check)
+## P1 — correctness / UX / design debt
-**Result: NOT zero.** There is exactly **one** server-side inference pathway,
-reached from two client call sites. It is BYOK (the user's key), allowlist-gated,
-and appears to be the documented "one permitted exception" — but see the nuance.
+Kill-list hits (each with file:line; full table in the design-audit record):
-**The inference call (server-side, in the Worker):**
-- `worker/index.ts:199` — `const res = await fetch(p.url, { ...p.build(model, prompt, apiKey), … })` inside `kickoffHandler` (`:182-207`), routed at `/api/agent/kickoff` (`:463`).
-- Provider hosts it can reach (`PROVIDERS`, `worker/index.ts:68-99`):
- - `:70` Anthropic Messages API (`api.anthropic.com`)
- - `:80` OpenRouter chat-completions (`openrouter.ai`)
- - `:90` DeepSeek chat-completions (`api.deepseek.com`)
-
-**Client call sites (same-origin → the Worker brokers; the browser never calls a model directly):**
-- `src/components/BuildStudio.svelte:1012` — builds the **MENTOR structured-reflection** prompt (returns JSON `proposals` + `skills`).
-- `src/components/BuildStudio.svelte:904` — the **"Try a step with AI" kickoff** handoff (`handoff: 'kickoff'`).
-
-**Excluded-vendor host strings elsewhere are NOT calls** — they are the enforcement
-engine's *negative signals* and tests (the OpenAI/xAI host literals live at
-`enforcement/excluded-provider-signals.yaml:11,31`, and in
-`enforcement/tests/recipe.test.ts:46`, `enforcement/tests/layer3.test.ts:25,33`).
-
-**Nuance / discrepancy to surface:**
-- CLAUDE.md's "exactly one permitted exception: the single **deterministic**
- structured-reflection step" is worded ambiguously. In code, the *deterministic*
- reflection is `mentor-engine.reflect()` (`BuildStudio.svelte:547`) and makes **no**
- model call. The actual *inference* exception is the `/api/agent/kickoff` broker,
- which is used for **two** purposes: structured reflection (`:1012`) **and** a
- free-form "try a step" kickoff (`:904`). So the single permitted endpoint backs
- two product features, only one of which is "structured reflection."
-- This sits in tension with the new goal ("the platform makes zero inference calls;
- Goose runs inference on the user's machine"). The broker is the lone exception
- today. (Flagged in Gaps & Risks; no fix proposed here.)
-
----
-
-## 5. Existing Goose integration
-
-**State: partial, recipe-file level — already wired, no deeplink/MCP.**
-
-- **Handoff option:** `handoff` includes `'goose'` (`BuildStudio.svelte:200`), with UI strings "Run it with Goose" / "Download the Goose recipe" / "Copy the run command" (`:43,58-59`, es `:96,111-112`, ar `:149,164-165`).
-- **Recipe generation:** `gooseRecipe` derived (`BuildStudio.svelte:733-742`) emits a Goose recipe YAML — `version: "1.0.0"`, `title`, `description`, `instructions: |`, `prompt: |` (the full agent prompt). Bundled into the starter zip as `${slug}.goose-recipe.yaml` (`:814`) and individually downloadable (`:1360`).
-- **Run command:** `goose run --recipe ${slug}.goose-recipe.yaml`, shown and copyable (`:1361-1362`).
-- **Catalog presence:** Goose is a catalog entry — `data/agentic-tools.json:693-699`, `source_url: https://github.com/aaif-goose/goose`.
-- **Docs:** Goose is referenced throughout prose (`astro.config.mjs:99,148`; README `:14,47,171,184`; guides incl. `guides/get-started-with-goose`, `TRANSLATIONS.md:44`).
-
-**Absent today (relevant to the pivot):**
-- **No `goose://` deeplink** anywhere (searched `src/`, none).
-- **No MCP / extension wiring** in the platform — MCP appears only as catalog *content* (`data/agentic-tools.json` "MCP Ecosystem"), not as integration code.
-- The generated recipe is a **static template** (`instructions` + `prompt`); it does not yet encode `extensions:`, `activities:`, `parameters:`, or provider/settings blocks from the Goose recipe schema.
-- The provider-lockdown **policy** recipes (`src/content/docs/recipes/`, `entry_type:'recipe'`) are unrelated to the generated **run** recipe — two different "recipe" concepts share the word.
-
----
-
-## 6. Framework / adapter / build config
-
-- **Astro:** `^6.4.6` (`package.json:44`). *(Note: the task brief said "Astro 5"; the repo is on Astro 6.)*
-- **Starlight:** `@astrojs/starlight ^0.40.0` (`package.json:41`). Svelte `^5.56.3`; `@astrojs/svelte ^8.1.2`.
-- **Build output mode:** `output: 'static'` (`astro.config.mjs:92`), `trailingSlash: 'ignore'` (`:93`).
-- **Cloudflare adapter:** **none.** There is no `@astrojs/cloudflare` adapter and no `adapter:` in `astro.config.mjs`. Instead the site is a **static build served by a hand-rolled Worker**: `wrangler.jsonc` sets `main: worker/index.ts` with a Static-Assets binding (`assets.directory: ./dist`, `binding: ASSETS`, `not_found_handling: "404-page"`), KV (`SESSIONS`, `ATPROTO`), and D1 (`DB`). Static HTML is served by the asset layer (which bypasses the Worker); `/api/*` is handled in `worker/index.ts`.
-- **Security headers:** emitted at build into `dist/_headers` via a custom integration (`astro.config.mjs:44-69`); hash-based CSP ships **Report-Only** by default (`:63`), `CSP_MODE=enforce` to enforce.
-- **i18n:** root locale `en`, plus `es` and `ar` (RTL) (`astro.config.mjs:106-111`).
-
----
-
-## 7. AOS vendor-exclusion posture (Meta / OpenAI / xAI)
-
-**Posture: clean — no Meta/OpenAI/xAI used as an inference/model provider; Google permitted.**
-
-- **Dependencies:** no Meta/OpenAI/xAI SDK in `package.json` (`:39-66`). LLM-adjacent deps are only `@atproto/*` and `nostr-tools` (not model SDKs). No `openai`, `@anthropic-ai/sdk` (the broker calls Anthropic over raw `fetch`, not an SDK), `@ai-sdk`, or `langchain`.
-- **Broker allowlist** (`worker/index.ts`):
- - Direct providers offered: `anthropic`, `openrouter`, `deepseek` (`:68-99`). **OpenAI and xAI are not offered**, and the error message says so (`:189`).
- - OpenRouter (a gateway to *all* vendors) is constrained by a **default-deny allowlist** `PERMITTED_ROUTER_VENDORS` (`:106-109`) and a hard block `isExcludedRouterModel` (matches the excluded-org vendor segments, `:118-121`), enforced before the call (`:195-197`).
- - **Google is explicitly permitted** — `'google'` ∈ the allowlist (`:107`).
-- **Catalog-level exclusion** is enforced separately by the three-layer engine (`enforcement/`, blocking CI gate `verify.yml`) and the `EXCLUDED_PROVIDER_IDS = ['meta','openai','xai','aws','oracle']` registry (`src/modules/cost-estimator/registry/providers.ts:16`), shared by the Model Compass registry.
-- **Google services** (Fonts/Analytics/etc.): the only analytics is **Plausible** (opt-in, `astro.config.mjs:74-88`) — not Google. No Google Fonts/Maps/reCAPTCHA found. (Per policy Google is permitted; none is currently wired, so nothing to flag.)
-- **Anthropic / DeepSeek** as offered providers are **not** policy violations — only Meta/OpenAI/xAI are excluded.
-
----
-
-## Gaps & Risks (ranked by severity)
-
-1. **[High · Path-A definition vs. code] The lone inference exception backs two features and overlaps with the Goose pivot.**
- `/api/agent/kickoff` (`worker/index.ts:182-207`, fetch `:199`) is the only inference call, but it serves both the structured-reflection (`BuildStudio.svelte:1012`) and a free-form "Try a step with AI" kickoff (`:904`). CLAUDE.md authorizes *one* "deterministic structured-reflection step," yet the deterministic reflection (`reflect()`) makes no model call — so the wording and the code don't line up, and the "kickoff" is arguably a second use of the exception. As Goose takes over user-facing inference (on the user's machine), this server-side broker's necessity should be re-examined. *(Observation only.)*
-
-2. **[High · Goose readiness] Extensions and skills are not first-class catalog types; the generated Goose recipe is a minimal template.**
- `ENTRY_TYPES` (`schema:28-40`) has no `extension` or `skill`; MCP servers are only generic `tool` entries (`data/agentic-tools.json`). The emitted recipe (`BuildStudio.svelte:733-742`) carries only `instructions`+`prompt` — no `extensions`, `parameters`, `activities`, or provider/settings blocks from the Goose recipe schema. Driving Goose richly will need a representation for extensions and a fuller recipe.
-
-3. **[Medium · Docs accuracy] Stated facts diverge from the code.**
- - Brief/CLAUDE.md imply **Astro 5**; repo is **Astro `^6.4.6`** (`package.json:44`).
- - CLAUDE.md says "~842 YAML entries"; actual is **1,354 tool entries + 827 datasets** (`enforce:layer1`; `find` counts). Entries are Markdown/MDX, not raw YAML.
- - These don't break anything but undermine trust in the written invariants.
-
-4. **[Medium · State coupling] Three islands are not on the shared session.**
- `CatalogExplorer` hands off only a single `?seed=` id (`BuildStudio.svelte:271`), and `PolicyChecker` / `AccountWidget` are fully independent. Any future flow that wants Catalog selections, a policy-check result, or the signed-in identity to travel into the Build Studio has no channel today. (`AccountWidget` is deliberately a *separate* auth session — not a defect, but worth noting it is unrelated to the build session.)
-
-5. **[Medium · CSP] The site-wide CSP is still Report-Only.**
- `astro.config.mjs:63` ships `report-only` unless `CSP_MODE=enforce`. Until promoted, the hash-based CSP is observational. (Collection was made durable earlier in this branch.)
-
-6. **[Low · i18n] Catalog translations are effectively absent.**
- `src/content/docs/es/catalog/` and `…/ar/catalog/` contain only an `index.mdx`; the 1,354+827 entries exist in English only. The Build Studio / Catalog UIs are translated, but entry bodies are not (a `translate-catalog` script exists, `package.json:27`).
-
-7. **[Low · naming] Two distinct "recipe" concepts share the word.**
- Policy provider-lockdown recipes (`entry_type:'recipe'`, `src/content/docs/recipes/`) vs. the generated Goose run-recipe YAML (`${slug}.goose-recipe.yaml`). A reader can easily conflate them; worth disambiguating in any Goose-facing work.
-
----
-
-*End of audit. No source files were modified. Awaiting direction before proposing changes.*
+| # | Finding | Evidence |
+|---|---|---|
+| P1-1 | Landing feature sections are stock Starlight `CardGrid`/`Card icon=…` + 3 `LinkCard` grids | `src/content/docs/index.mdx:37-99` |
+| P1-2 | Footer chrome is untouched Starlight default (only un-overridden chrome left) | `astro.config.mjs:146-153` |
+| P1-3 | Catalog explorer: 1,359 entries as a single-column card wall, 60/page (desktop page renders 20,562px tall); native checkboxes in a scroll box; no `accent-color` anywhere in src | `CatalogExplorer.svelte:513,505-510,298`; screenshot `catalog-explorer-desktop.png` |
+| P1-4 | Checker verdict = text line + default-styled table | `PolicyChecker.svelte:101-123` |
+| P1-5 | Island style dialects split: token-native vs raw `--sl-*`/hex/off-scale rems. Worst: ModelCompass hex ramp (`#d33 #e80 …`), CostEstimator all-`--sl-*`, BuildStudio mixes both in one stylesheet, lone raw box-shadow | `ModelCompass.svelte:144-152`; `CostEstimator.svelte:240-276`; `BuildStudio.svelte:1355` vs `:1360-1364`; `CatalogExplorer.svelte:536` |
+| P1-6 | `TODO: confirm` is user-visible product copy (zero-fabrication semantics, placeholder register) | `model-compass/ui/i18n.ts:73`; rendered `CostEstimator.svelte:220`, `ModelCompass.svelte:48,53,62`; advertised `build/cost.mdx:13`, `build/models.mdx:13` |
+| P1-7 | Dev-jargon first-contact copy on organizer surfaces (landing, /build, /check, /build/models, /start) | `index.mdx:87,89`; `build.mdx:13-16`; `check.mdx:12-14`; `models.mdx:10-12`; `start/index.mdx:11-13` |
+| P1-8 | i18n strays: BuildStudio hardcoded English beside its own STR table (`:1074,1027,1036,1084,1265,1272,1314-1322`); catalog CTA hardlinks English `/build/` from es/ar (`CatalogExplorer.svelte:358` — locale-aware `buildBase` exists at `:287` unused); AccountWidget ar placeholder (`:79`); English data-layer prose rendered in localized UIs (compass `models.ts:57`, estimator `providers.ts:64-68`, `CatalogList.astro:55-62`) | as cited |
+| P1-9 | All 16 island mounts `client:load`; zero `client:visible/idle`. ModelCompass ships its whole registry as eager JS with no no-JS render | grep `client:`; `build/models.mdx:16` |
+| P1-10 | `/console/` styled with raw hex outside the token system (own dark-mode media query) | `AdminConsole.svelte:470-523`; `console/index.astro:33-44` |
+| P1-11 | Arabic gets no display face (identity absent for RTL readers) | `tokens.css:12-13` |
+| P1-12 | CSP still Report-Only (mechanism complete; operator flip pending) | `astro.config.mjs:63`; `security-headers.ts:112-180` |
+| P1-13 | Cost Estimator: 12× `unitPrice: null` + 3× `lastVerified: null`; tier bands/scale placeholders. Model Compass: 7 models `score: null` (runbook undercounts as 3) | `providers.ts:62-97`; `tiers.ts:26-30`; `models.ts:37-182` |
+| P1-14 | Admin panel phases 4–8 unbuilt (per spec + wrangler binding comments); staging follow-ups (publish→PR, enforcement wiring, expiry job) logged | `docs/admin-panel-spec.md:73-136`; `wrangler.jsonc:48-62` |
+| P1-15 | CLI jargon inside the checker verdict aimed at non-developers: "run `npx tsx enforcement/cli.ts all --tree .` on a real project." (reclassified from P0 at SHIP-GATE-R1 2b) | `PolicyChecker.svelte:126-127` |
+
+## P2 — polish / docs / deferred
+
+- Doc drift set: ROADMAP Done stops at #35 (repo at #55); admin-spec header
+ says "not yet built"; PLAN.md footer says "no feature code written"; README
+ describes Pages-dashboard deploy vs actual Workers Builds topology
+ (`README.md:127-148`); `TRANSLATING.md` referenced but file is
+ `TRANSLATIONS.md`; catalog-count disagreement (CLAUDE.md 1,355 / TRANSLATIONS
+ ~2,182 / disk 1,360); runbook §B2 undercount; MOBILE_FIXES header cites a
+ merged branch; `operational_advisory` name→workflow mapping lives only in
+ guard.py.
+- `/build/models/` color-contrast: 89 axe nodes (en/es) from opacity + hex
+ fallbacks (a11y score still ≥ budget) — folds into P1-5's tokenization.
+- Catalog prose translation run (pipeline built, needs `ANTHROPIC_API_KEY` +
+ waves); native-speaker review of ~25 machine-translated pages.
+- Dependabot remainder (5 dev-only alerts); the **Astro 7 coupled upgrade**
+ (astro 7.0.7 + starlight 0.41.3 + @astrojs/svelte 9 — 0.40 is the newest
+ Starlight compatible with Astro 6; breaking-change list verified, none of
+ the removed APIs used in-repo) — a decision, not an obligation.
+- NIP-07 one-line note in public docs; Plausible remains the only sanctioned
+ (opt-in, off-by-default) third-party origin.
+- Stack otherwise current: wrangler `ratelimits` GA (not beta),
+ `run_worker_first` current, no deprecated Astro/nostr-tools API usage found.
+
+## What is NOT broken (verified, for the record)
+
+Intentional identity system exists and is substantial (tokens/theme/components
++ 5 overrides + Build Plate + self-hosted subset fonts). No purple gradients,
+no glassmorphism, no lorem, no stock illustration, no font CDNs. Path A holds
+(broker retired; CI-gated). Mobile fixes 1–8 merged. `/api/health`,
+`/api/license`, auth, admin auth+staging all live and tested (352 tests).
diff --git a/BACKLOG.md b/BACKLOG.md
index 87b5dd68..fb9ac691 100644
--- a/BACKLOG.md
+++ b/BACKLOG.md
@@ -14,9 +14,9 @@ check. Nothing found was dropped. **Scope key** — the column the gate decides:
| ID | Item | Source/evidence | Status | Pri | Acceptance criteria |
|---|---|---|---|---|---|
-| D1 | Receipt/verdict component system (the signature): shared visual language for checker verdicts, catalog verification badges, Studio policy gate, enforcement page | DESIGN §1; audit P1-4 | absent | P1 | One shared component/token set renders clean/blocked/chain verdicts; used by ≥4 surfaces; axe clean |
+| D1 | Receipt/verdict component system (the signature): shared visual language for checker verdicts, catalog verification badges, Studio policy gate, enforcement page | DESIGN §1; audit P1-4 | absent | P1 | One shared component/token set renders clean/blocked/chain verdicts; used by ≥4 surfaces; axe clean; **status never encoded by color alone (WCAG 1.4.1 — icon + word + color; reads correct in grayscale)** [SHIP-GATE-R2 rider] |
| D2 | Checker verdict redesign + island i18n (fixes P0-2/P0-3) | `PolicyChecker.svelte` | broken i18n | **P0** | Island honors `lang` (house STR pattern, es/ar shipped); verdict = receipt band; CLI jargon behind a disclosure; localized empty/error states |
-| D3 | Catalog explorer density redesign: responsive 1→2→3-col receipt cards, designed facets w/ counts, `accent-color`, designed zero-result/empty/error states at full 1,359-entry dataset; locale-aware CTA (`:358`) | audit P1-3, P1-8 | default-ish | P1 | Desktop page no longer a 20k-px column; facets on-token; all states designed; CTA locale-aware |
+| D3 | Catalog explorer density redesign: responsive 1→2→3-col receipt cards, designed facets w/ counts, `accent-color`, designed zero-result/empty/error states at the full dataset (count = the E6 generated figure, never a literal) [SHIP-GATE-R2 rider]; locale-aware CTA (`:358`) | audit P1-3, P1-8 | default-ish | P1 | Desktop page no longer a 20k-px column; facets on-token; all states designed at the full dataset; CTA locale-aware |
| D4 | Token-only island rule: retire `--sl-*`/raw-hex/off-scale rems from BuildStudio, CostEstimator, ModelCompass, CatalogExplorer; add `--shadow-1`, verdict washes, global `accent-color` | audit P1-5 | split dialects | P1 | grep: zero `--sl-color` refs in island styles; zero raw hex; `/build/models/` axe color-contrast = 0 (closes the 89-node finding) |
| D5 | Landing: replace stock CardGrid/LinkCard sections with bespoke WorkOrderGrid; copy-register pass | `index.mdx:37-99`; audit P1-1, P1-7 | stock | P1 | No Starlight Card/LinkCard on the landing; copy passes the register rules (§4) |
| D6 | Footer override (last stock chrome) | audit P1-2 | stock | P1 | Designed, token-native, locale-aware footer on all pages |
@@ -24,7 +24,7 @@ check. Nothing found was dropped. **Scope key** — the column the gate decides:
| D8 | `TODO: confirm` → designed provenance chip ("unverified — awaiting human check"), same semantics | audit P1-6 | placeholder register | P1 | No user-visible "TODO"; zero-fabrication semantics preserved; localized |
| D9 | BuildStudio stray strings → STR table; AccountWidget ar placeholder; data-layer prose i18n keys (compass cautions, estimator labels) | audit P1-8 | strays | P1 | grep finds no hardcoded user-facing English in islands with i18n tables |
| D10 | `/console/` joins the token layer (raw hex → tokens, `.btn`/`.field`); stays standalone-CSP, stays English (documented operator exception) | audit P1-10 | off-system | P1 | Zero raw hex in console surfaces; CSP wss: rule unchanged (invariance test stays green) |
-| D11 | Arabic display face: self-hosted subset OFL face (Readex Pro candidate) wired via `:lang(ar)` | audit P1-11; DESIGN §2/§7 | absent | P1 | ≤45 KB woff2, OFL license committed, `/ar/` headings render the face; perf budgets hold |
+| D11 | Arabic display face: self-hosted subset OFL face (Readex Pro candidate) wired via `:lang(ar)` | audit P1-11; DESIGN §2/§7 | absent | P1 | Final face pick + measured subset size recorded as a PLAN.md checkpoint BEFORE implementation [SHIP-GATE-R2 rider]; ≤45 KB woff2, OFL license committed, `/ar/` headings render the face; perf budgets hold |
| D12 | Hydration pass: `client:visible` for below-fold islands; ModelCompass considered for static render + filter-only island | audit P1-9 | all-eager | P1 | No LCP regression on /catalog/; JS transferred on /build/models/ drops; e2e stays green |
| D13 | Page-weight ceilings recorded + measured (reading ≤250 KB, tool ≤450 KB, landing ≤350 KB) | DESIGN §8 | new | P2 | Measured per page type in VERIFICATION.md; over-ceiling pages fixed |
diff --git a/DESIGN.md b/DESIGN.md
index f726898e..76f8894b 100644
--- a/DESIGN.md
+++ b/DESIGN.md
@@ -23,8 +23,10 @@ surfaces are the product's thesis, so that is exactly backwards.
(clean / blocked / chain-to-excluded), the Studio's policy gate, the
enforcement page's verdict band, admin audit views. A receipt is: a stamped
edge (2px structural rule), a status color that is never decorative
-(`--ok/--warn/--danger`), the evidence inline (commit SHA, org chain, license
-id), and a plain-verb sentence a non-developer can act on. Boldness is spent
+(`--ok/--warn/--danger`) **and never the only carrier — icon + word + color,
+correct in grayscale (WCAG 1.4.1; SHIP-GATE-R2 rider)** — the evidence inline
+(commit SHA, org chain, license id), and a plain-verb sentence a non-developer
+can act on. Boldness is spent
here — the blocked-dependency moment should feel like a customs stamp coming
down — and everywhere else stays quiet: paper, ink, structure.
diff --git a/PLAN.md b/PLAN.md
index 4f1d4940..3b0eb2a5 100644
--- a/PLAN.md
+++ b/PLAN.md
@@ -1,453 +1,148 @@
-# PLAN.md — drive Goose as a user-facing agentic runtime via ONE build session
-
-**Source:** `AUDIT.md` (read-only audit, branch `claude/kind-albattani-tnycq4`).
-**Mode:** PLAN ONLY — no feature code. Contracts below are *specifications* (types +
-signatures, no bodies). Execution happens slice-by-slice after review.
-**Deliverable of the pivot:** the four components (Build Studio, Mentor Engine, Skills
-Creator, Catalog) converge on ONE typed client-side build session, which serializes to
-a Goose recipe and launches via a `goose://recipe?config=` deeplink — all pure
-client-side, model-free.
-
----
-
-## 0. Encoded constraints, reconciliations & out-of-scope
-
-**Hard constraints (carried into every slice):**
-1. **Stack:** Astro (Starlight) + Cloudflare Pages; Catalog on Astro Content Layer
- (`docsLoader` + Zod), per `src/content.config.ts` + `src/schema/catalog.ts`.
-2. **ONE shared client-side build session** wired across the four components
- (`src/lib/build-session.ts`).
-3. **Model-free Path A:** ZERO inference API calls from the platform. Exactly ONE
- deterministic structured-reflection exception that performs **no LLM call** — it
- *reads* structured JSON the user brings back from their own Goose run
- (`response.json_schema` output).
-4. **Goose = STABLE primitive only:** client-side recipe deeplinks
- `goose://recipe?config=` via `btoa(JSON.stringify(recipe))` behind a
- UTF-8-safe wrapper (reuse the pattern in `src/components/AccountWidget.astro:86-95`).
-5. **Mentor Engine** = Yoda/Morpheus/J.A.R.V.I.S./Neo persona shipped as a Goose
- **SKILL** + deterministic reflection over the recipe's `response.json_schema`
- output (`src/lib/mentor-engine.ts`).
-6. **Vendor exclusions = Meta/OpenAI/xAI ONLY** as inference providers; Google
- permitted. Goose is model-agnostic — **never hardcode a provider**; omit
- `settings.goose_provider`/model so the user's own Goose config selects it.
-7. **MCP safety:** the **Catalog is the trust boundary**. Only allowlisted, vetted
- extensions/skills may appear in a generated recipe. Lean on Goose "Trust &
- Execute" consent + extension allowlist + least privilege. Never surface raw MCP
- server config to non-devs. Treat tool descriptions as untrusted unless sourced
- from a verified Catalog entry.
-
-**Reconciliations with `AUDIT.md` (facts the brief mis-stated — plan honors reality):**
-- Framework is **Astro `^6.4.6`**, not 5 (`AUDIT.md §6`, `package.json:44`). Content
- Layer API is identical for this work; the plan is version-agnostic and pins nothing
- to a major version.
-- Catalog is **1,354 tool-class + 827 dataset** entries (`AUDIT.md §3`), not "~842".
- Entries are MD/MDX with YAML frontmatter. Counts only matter to Slice B's allowlist
- scope.
-- There is **no `@astrojs/cloudflare` adapter**; static build + hand-rolled Worker
- (`AUDIT.md §6`, `wrangler.jsonc`). All new logic is **pure client-side**, so the
- serializer/deeplink never touch the Worker.
-
-**Out of scope (note as FUTURE ENHANCEMENT only — do not build now):**
-- `goose serve` / ACP-over-HTTP / Goose TS SDK (experimental mid-2026). Recorded in
- §6 "Future" so a later slice can pick them up; no design dependency on them.
-- Retiring the existing `/api/agent/kickoff` broker is IN scope (Slice D) because it is
- the lone inference call standing between us and Path-A zero.
-
----
-
-## 1. Cross-cutting invariants & global gates (apply to ALL slices)
-
-These are the exit gates every slice is measured against, in addition to its own:
-
-- **G-PathA (zero inference):** no platform code fetches a model host. Enforced by a
- new additive test/CI check (Slice D) — extends the negative-signal approach already
- in `enforcement/excluded-provider-signals.yaml` and `enforcement/tests/layer3.test.ts`.
-- **G-Vendor:** no Meta/OpenAI/xAI as a provider; recipe emits no provider/model.
- Reuse `EXCLUDED_PROVIDER_IDS` (`src/modules/cost-estimator/registry/providers.ts:16`)
- + the broker allowlist logic pattern (`worker/index.ts:106-130`) as the vetting source.
-- **G-Trust (MCP):** every extension in a generated recipe ∈ the Catalog allowlist;
- property-tested so a non-allowlisted extension is unrepresentable.
-- **G-Gate:** the blocking `operational_advisory` CI gate is never weakened; all new CI
- is ADDITIVE in new workflow files (CLAUDE.md #4; `AUDIT.md §6`). The project guard
- (`.claude/hooks/guard.py`) stays in force.
-- **G-Determinism:** all new serializer/deeplink/reflection code is pure and
- deterministic — same session ⇒ byte-identical recipe & deeplink.
-- **G-Green:** the existing **185-test** suite stays green (`npm test`), `astro check`
- 0 errors, and a production `npm run build` succeeds, after every slice.
-- **G-NonDev:** no raw MCP/server config, env vars, or shell strings shown to the
- builder; all attacker-influenceable text rendered via `textContent` (reuse the
- XSS-safe pattern already used in `src/components/AccountWidget.astro`).
-
----
-
-## 2. Sequencing / gating
-
-```
-A (typed session + Catalog wiring)
-└─> B (recipe serializer + Catalog extension allowlist)
- ├─> C (deeplink + "Open in Goose" UX)
- ├─> D (Mentor persona SKILL + structured-reflection; retire broker → Path-A zero)
- └─> E (Skills Creator → session + Catalog sub-recipes)
-```
-A is the foundation. B depends on A. C, D, E each depend on B; C/D/E are independent of
-one another and may land in any order once B is in. Each slice is its own PR, gated on
-its benchmark + the global gates in §1.
-
----
-
-## SLICE A — Formalize the shared build session; wire all four components
-
-**Goal.** Turn `BuildSession` into a single typed, runtime-validated, versioned schema
-and ensure all four components read/write the *same* object — closing `AUDIT.md` Gap #4
-for the Catalog. (PolicyChecker/AccountWidget are intentionally NOT among "the four"
-and stay independent — `AUDIT.md §2`.)
-
-**Gate (entry):** none (foundation).
-
-**Files to change.**
-- `src/lib/build-session.ts` — formalize the schema (see contract); bump `v: 1 → 2`
- with a `migrate` v1→v2 path (extend the existing `migrate()` at `:120-181`, same
- defensive style); add session fields the recipe needs: `extensions`, `mentorReflection`.
-- `src/lib/build-session.test.ts` — extend (round-trip, v1→v2 migrate, coercion).
-- `src/components/CatalogExplorer.svelte` — wire to the session: write selected
- tools/extensions; on mount read prior selections (reuse the subscribe pattern from
- `src/components/BuildStudio.svelte:232-252` and `src/modules/cost-estimator/ui/CostEstimator.svelte:33-35`).
-- `src/components/BuildStudio.svelte` — consume the new typed fields where it already
- reads/writes the session (`:232-310`); no behavior change beyond new fields.
-- (Mentor Engine + Skills Creator already persist through BuildStudio — formalize their
- field ownership in the schema doc, no new component file.)
-
-**Contract / interface.**
-```ts
-// src/lib/build-session.ts (Zod, mirroring the astro/zod pattern in src/schema/catalog.ts)
-export const SESSION_VERSION = 2 as const;
-
-export interface SessionExtension { // an allowlisted Goose extension ref (Slice B/G-Trust)
- catalogId: string; // slug of the verified Catalog entry it came from
- name: string;
- kind: 'builtin' | 'mcp_stdio' | 'mcp_sse'; // resolved from the Catalog, never user-typed
-}
-export interface SessionMentorReflection { // deterministic output of Slice D
- schemaVersion: 1;
- constraints: string[]; // ConstraintId[]; from mentor-engine
- proposals: Array<{ action: 'add'|'swap'|'remove'; name: string; why: string }>;
-}
-export interface BuildSession { // EXTENDS the current interface at :54-90
- /* …all existing fields… */
- v: 2;
- extensions: SessionExtension[]; // NEW — selected, allowlisted extensions
- mentorReflection: SessionMentorReflection | null; // NEW — Slice D writes; null until run
-}
-export function migrate(parsed: unknown): BuildSession; // v1 docs upgrade to v2 (extensions:[], mentorReflection:null)
-```
-Component contract (unchanged API surface): all four use only
-`loadSession/saveSession/updateSession/subscribeSession` (`build-session.ts:143-206`).
-
-**Test plan.**
-- *Unit:* extend `build-session.test.ts` — v1→v2 migrate fills `extensions: []` /
- `mentorReflection: null`; Zod rejects malformed extensions; round-trip + cross-tab
- event still fire (reuse the fake-DOM harness already in that file).
-- *Integration:* a jsdom-free node test that simulates Catalog write → BuildStudio read
- through `localStorage` + `subscribeSession` (extend the existing pub/sub tests).
-
-**Rollback.** Single revert of the slice commit; `migrate` is forward-only and
-v1-compatible, so reverting to v1 readers ignores the new fields (they were additive &
-optional in storage). No data migration to undo.
-
-**Benchmark / threshold (proves it works).**
-- All four components demonstrably read+write the one object in an integration test
- (Catalog selection observable in a BuildStudio-side read): **pass = 1 object, 4
- participants**.
-- v1→v2 migrate property test: **100%** of randomly-mutated v1 blobs coerce to a valid
- v2 (no throw, schema-valid).
-- `npm test` stays green at **≥185** tests; `astro check` 0 errors.
-
----
-
-## SLICE B — Pure-client recipe serializer (session → Goose recipe)
-
-**Goal.** Deterministically turn a `BuildSession` into a Goose recipe object
-(`extensions` allowlisted from the Catalog, `parameters`, `activities`,
-`response.json_schema`, `instructions`, `prompt`) — superseding the minimal template at
-`src/components/BuildStudio.svelte:733-742`. Model-agnostic (no provider/model).
-
-**Gate (entry):** Slice A merged (needs `session.extensions` + typed schema).
-
-**Files to change.**
-- **New** `src/lib/goose-recipe.ts` — the pure serializer (extraction pattern of
- `src/lib/catalog-filter.ts` / `src/lib/skill-doc.ts`).
-- **New** `src/lib/goose-recipe.test.ts`.
-- `src/schema/catalog.ts` — add a first-class **extension** representation (closes
- `AUDIT.md` Gap #2): either `entry_type: 'extension'` in `ENTRY_TYPES` (`:28-40`) or a
- `goose_extension` block, gated by `verification_status === 'verified'` in the existing
- `superRefine` (`:140-207`).
-- **New** `src/pages/extensions.json.ts` — build-time static allowlist of vetted
- extensions for the client serializer (same Content-Layer query pattern as
- `src/pages/catalog.json.ts`).
-- `src/components/BuildStudio.svelte` — replace the inline `gooseRecipe` derived
- (`:733-742`) with a call to `buildGooseRecipe(session, allowlist)`.
-- `enforcement/` — extend the layer-3 recipe contract (`enforcement/cli.ts` +
- `enforcement/types.ts:92` `RecipeFinding`) to assert generated recipes reference only
- allowlisted extensions; add fixtures under `enforcement/tests/`.
-
-**Contract / interface.**
-```ts
-// src/lib/goose-recipe.ts
-export interface GooseRecipe { // the stable recipe primitive we emit
- version: string; // "1.0.0"
- title: string;
- description: string;
- instructions: string;
- prompt: string;
- extensions: GooseExtensionRef[]; // ONLY from the allowlist
- parameters: GooseParameter[]; // typed inputs the user fills in Goose
- activities: string[]; // suggested next actions
- response: { json_schema: JsonSchema }; // forces structured output (Slice D reads it)
- // NOTE: no `settings.goose_provider` / model — model-agnostic by construction (G-Vendor)
-}
-export interface ExtensionAllowlist { // fetched from /extensions.json
- byId: Record;
-}
-export function buildGooseRecipe(
- session: BuildSession,
- allow: ExtensionAllowlist,
-): GooseRecipe; // pure; drops any extension not in `allow`
-export const RESPONSE_JSON_SCHEMA: JsonSchema; // the shared schema Slice D reflects over
-```
-
-**Test plan.**
-- *Unit:* `goose-recipe.test.ts` — determinism (same session ⇒ deep-equal recipe);
- **G-Trust property test**: for arbitrary `session.extensions`, every emitted
- extension ∈ `allow` and any non-allowlisted ref is dropped (cannot appear);
- no provider/model field ever present (G-Vendor); recipe validates against the Goose
- recipe JSON-schema (vendored as a test fixture).
-- *Integration:* `enforcement` layer-3 run over a generated recipe fixture passes;
- a recipe built from a representative session validates end-to-end.
-
-**Rollback.** Revert the slice; BuildStudio's `gooseRecipe` derived returns to the
-`:733-742` template (kept in git history). Schema addition is additive/optional, so
-existing catalog entries still validate.
-
-**Benchmark / threshold.**
-- **0** non-allowlisted extensions emitted across a 1,000-case property test.
-- **0** recipes carrying a provider/model field.
-- Recipe schema-validation: **100%** of generated recipes valid against the vendored
- Goose recipe schema.
-- `enforcement` layer-3 passes on generated recipes; `npm test` green.
-
----
-
-## SLICE C — Deeplink generator + "Open in Goose" UX
-
-**Goal.** Encode a recipe into `goose://recipe?config=` (UTF-8-safe), launch via
-an "Open in Goose" action with an **explain-before-launch** panel and a **copy-link**
-(and recipe-file) fallback when the deeplink exceeds the length budget.
-
-**Gate (entry):** Slice B merged (needs `GooseRecipe`).
-
-**Files to change.**
-- **New** `src/lib/goose-deeplink.ts` — pure encode/decode + length guard. The
- UTF-8-safe base64 reuses the wrapper pattern in
- `src/components/AccountWidget.astro:86-95` (encode) and its inverse for round-trip.
-- **New** `src/lib/goose-deeplink.test.ts`.
-- `src/components/BuildStudio.svelte` — the `handoff: 'goose'` branch (`:1357-1363`):
- add "Open in Goose" (anchor to the deeplink), the explain panel, and the existing
- copy/download fallbacks (`copy()` `:825`, `blobDownload()` `:828`). Keep the
- `goose run --recipe …` file path as the >budget fallback.
-- (Optional) `src/components/CatalogExplorer.svelte` — a single-tool "Open in Goose"
- using a minimal one-extension recipe (only if Slice A wired selection; else defer).
-
-**Contract / interface.**
-```ts
-// src/lib/goose-deeplink.ts
-export const DEEPLINK_MAX_BYTES = 8192; // budget; above this → fallback (tune in benchmark)
-export function encodeRecipeConfig(recipe: GooseRecipe): string; // UTF-8-safe base64
-export function decodeRecipeConfig(b64: string): GooseRecipe; // inverse (for tests)
-export function recipeDeeplink(recipe: GooseRecipe): {
- url: string; // `goose://recipe?config=…`
- bytes: number;
- withinBudget: boolean; // false ⇒ UX shows copy-link/file fallback
-};
-export interface ExplainModel { // what the explain-before-launch panel renders
- title: string;
- extensions: Array<{ name: string; why: string }>; // rendered via textContent (G-NonDev)
- willDoNothingUntilUserConsents: true; // copy: Goose "Trust & Execute" prompts at runtime
-}
-export function explainRecipe(recipe: GooseRecipe): ExplainModel;
-```
-
-**Test plan.**
-- *Unit:* `goose-deeplink.test.ts` — round-trip `decode(encode(r)) === r` incl.
- **non-ASCII** (es/ar/emoji) titles/prompts (proves the UTF-8-safe wrapper); URL shape
- `goose://recipe?config=`; `withinBudget` flips correctly at the boundary;
- `explainRecipe` lists every extension (no omissions).
-- *Integration:* build a recipe (Slice B) → deeplink → decode → recipe equals input; a
- jsdom/Playwright check that the explain panel renders every extension via `textContent`
- (no HTML injection) and the fallback appears when over budget.
-
-**Rollback.** Revert; the `goose` handoff falls back to the existing recipe-file +
-`goose run` command (`:1360-1362`), which is unaffected.
-
-**Benchmark / threshold.**
-- Round-trip fidelity: **100%** over a fuzz corpus including multibyte text.
-- A representative full-stack recipe encodes to **< `DEEPLINK_MAX_BYTES`**; over-budget
- recipes deterministically take the fallback path (asserted).
-- Explain panel enumerates **100%** of recipe extensions; **0** raw MCP config strings
- in the DOM (asserted).
-
----
-
-## SLICE D — Mentor persona SKILL + deterministic structured-reflection; retire the broker
-
-**Goal.** Ship the Yoda/Morpheus/J.A.R.V.I.S./Neo guidance as a Goose **persona SKILL**
-inside the recipe, and make the platform's ONLY model-output consumption a
-**deterministic** read of the recipe's `response.json_schema` JSON the user brings back
-— then **retire `/api/agent/kickoff`** so the platform makes zero inference calls
-(Path-A zero; `AUDIT.md §4`, Risk #1).
-
-**Gate (entry):** Slice B merged (needs `RESPONSE_JSON_SCHEMA` + recipe).
-
-**Files to change.**
-- **New** `src/lib/mentor-persona.ts` — builds the persona SKILL.md via the existing
- `skillToMd` (`src/lib/skill-doc.ts`); included by the serializer (Slice B) as a recipe
- skill/instruction. Pure, deterministic, no model.
-- `src/lib/mentor-engine.ts` — add `reflectFromResponse(json)` that deterministically
- maps a `RESPONSE_JSON_SCHEMA`-conformant object → `SessionMentorReflection` (reuses
- the existing `reflect()` constraint ordering `:155-186`). **No fetch, no LLM.**
-- `src/lib/mentor-engine.test.ts` — extend.
-- `src/components/BuildStudio.svelte` — replace the two broker calls
- (`:904`, `:1012`) with: (a) generate recipe+deeplink (Slices B/C), (b) an
- "import structured result" affordance that pastes/loads the Goose `response` JSON and
- runs `reflectFromResponse` into `session.mentorReflection`.
-- `worker/index.ts` — **remove** `kickoffHandler` (`:182-207`), its route (`:463`), the
- `PROVIDERS` table + router-allowlist (`:68-130`). (Keep `coerceUsageProfile`/pricing.)
-- `worker/tests/worker.test.ts` — drop the now-removed broker tests; keep the rest.
-- **New** `.github/workflows/path-a.yml` — ADDITIVE CI asserting zero model-host fetches
- in `src/` + `worker/` (extends the signal set in
- `enforcement/excluded-provider-signals.yaml`). Does NOT touch `verify.yml` (G-Gate).
-
-**Contract / interface.**
-```ts
-// src/lib/mentor-engine.ts (additive)
-export function reflectFromResponse(
- response: unknown, // a RESPONSE_JSON_SCHEMA-shaped object (validated)
-): SessionMentorReflection; // deterministic; null-safe; never calls a model
-
-// src/lib/mentor-persona.ts
-export function mentorPersonaSkill(lang: 'en'|'es'|'ar'): DraftSkill; // → skillToMd → recipe
-```
-
-**Test plan.**
-- *Unit:* `reflectFromResponse` is deterministic and total (malformed input → safe empty
- reflection, mirroring `coerceUsageProfile`'s defensive style); persona SKILL.md
- validates (parse frontmatter with `yaml`, as in `src/lib/skill-doc.test.ts`).
-- *Integration:* recipe (Slice B) → its `response.json_schema` → a conformant sample →
- `reflectFromResponse` → stable `SessionMentorReflection` written to the session.
-- *Gate test:* `path-a.yml` check fails if any model host appears in platform code.
-
-**Rollback.** The broker removal is its own commit; revert restores `/api/agent/kickoff`
-and the two client calls. The persona/reflection additions are independent and need not
-be reverted with it.
-
-**Benchmark / threshold.**
-- **G-PathA = 0**: zero model-host fetches in `src/` + `worker/` (new CI check passes).
-- `reflectFromResponse`: deterministic across a 500-case corpus; **0** throws on
- malformed input.
-- No reachable `/api/agent/kickoff` route remains (worker route test asserts 404).
-
----
-
-## SLICE E — Skills Creator authoring → session + Catalog sub-recipes
-
-**Goal.** Make the inline Skills Creator (Movement 3, `src/components/BuildStudio.svelte:962-981`)
-write authored skills into the session AND emit them as Goose **sub-recipes** the main
-recipe references, with an optional contribution path into the Catalog as vetted,
-reusable artifacts.
-
-**Gate (entry):** Slice B merged (sub-recipes are emitted by the serializer).
-
-**Files to change.**
-- `src/lib/skill-doc.ts` — add a sub-recipe projection of a `DraftSkill` (a skill →
- a minimal `GooseRecipe` fragment / sub-recipe ref); pure.
-- `src/lib/skill-doc.test.ts` — extend.
-- `src/lib/goose-recipe.ts` (Slice B) — include `session.skills` as recipe
- sub-recipes/activities.
-- `src/components/BuildStudio.svelte` — Skills Creator writes to `session.skills`
- (already does `:297`) and now also surfaces "include as sub-recipe."
-- `src/schema/catalog.ts` — optional **skill** representation (parallel to the extension
- work in Slice B) so a contributed skill can become a verified Catalog entry; gated by
- `verification_status`.
-- `public/admin/config.yml` — add a skills collection for the contribution path (mirrors
- the existing `recipes` collection at `:41-52`), so non-devs contribute via Sveltia CMS
- → PR → enforcement.
-
-**Contract / interface.**
-```ts
-// src/lib/skill-doc.ts (additive)
-export function skillToSubRecipe(s: DraftSkill): GooseSubRecipe; // pure; no model
-export interface GooseSubRecipe { name: string; instructions: string; activities: string[] }
-```
-
-**Test plan.**
-- *Unit:* `skillToSubRecipe` deterministic; SKILL.md still valid YAML (existing
- `skill-doc.test.ts` harness); a skill with hostile text stays safe (reuse the
- `yamlDoubleQuoted` coverage already there).
-- *Integration:* author skill → `session.skills` → serializer emits it as a sub-recipe →
- recipe still schema-valid (Slice B benchmark) and deeplink round-trips (Slice C).
-- *Contribution:* a CMS-authored skill file passes `enforcement` layer-3.
-
-**Rollback.** Revert; `session.skills` continues to flow into the starter zip as today
-(`src/components/BuildStudio.svelte:806-822`); no sub-recipe emission.
-
-**Benchmark / threshold.**
-- Authored skill round-trips session ⇄ SKILL.md ⇄ sub-recipe with **0** data loss.
-- Recipes including N authored sub-recipes stay schema-valid and **within the deeplink
- budget** (else fallback) for N up to a documented cap.
-- `enforcement` layer-3 green on contributed skills.
-
----
-
-## 3. Risks carried forward (from `AUDIT.md`) + new
-
-| # | Risk | Source | Handled in |
-|---|---|---|---|
-| R1 | Lone inference exception overlaps the pivot | AUDIT Risk #1 | **Slice D** (retire broker → Path-A zero) |
-| R2 | Extensions/skills not first-class; recipe too thin | AUDIT Risk #2 | **Slice B/E** (schema + serializer) |
-| R3 | Deeplink length limits truncate large recipes | new | **Slice C** budget + fallback |
-| R4 | Untrusted MCP tool descriptions / raw config leak | constraint #7 | **G-Trust + G-NonDev** (allowlist, textContent) |
-| R5 | Hardcoding a provider breaks model-agnosticism | constraint #6 | **Slice B** (no settings/provider; G-Vendor) |
-| R6 | Catalog translations absent (es/ar recipes) | AUDIT Risk #6 | recipe i18n via `lang` param (Slice C/D), tracked |
-| R7 | CSP still Report-Only; `goose://` scheme + new UX | AUDIT Risk #5 | verify CSP allows the scheme before enforce (note for CSP-promote work) |
-| R8 | "recipe" overload (policy recipe vs run recipe) | AUDIT Risk #7 | naming: `GooseRecipe`/sub-recipe vs `entry_type:'recipe'` policy recipes |
-
----
-
-## 4. Definition of Done (whole pivot)
-
-- One typed build session; the four components read/write it (Slice A).
-- Pure client serializer → schema-valid, model-agnostic, allowlist-only Goose recipe
- (Slice B).
-- `goose://recipe?config=` deeplink with explain-before-launch + fallback
- (Slice C).
-- Mentor persona SKILL + deterministic structured-reflection; **`/api/agent/kickoff`
- retired ⇒ platform inference calls = 0** (Slice D).
-- Skills Creator → session + sub-recipes + Catalog contribution path (Slice E).
-- All global gates (§1) hold: Path-A=0, vendor-clean, MCP-trust-boundary, additive CI,
- determinism, suite green, build green.
-
----
-
-## 5. Future enhancements (explicitly deferred — do NOT build now)
-
-- `goose serve` / ACP-over-HTTP / Goose TS SDK for a hosted, live agent loop
- (experimental mid-2026). Would replace the bring-back-JSON step in Slice D with a live
- session — design Slice D's `reflectFromResponse` so it can later accept a streamed
- response without changing its deterministic contract.
-- Server-side recipe signing / shareable recipe links (needs the Worker; out of the
- pure-client scope here).
-- Full catalog translation so es/ar recipes ship localized extension copy.
-
----
-
-*Plan only — no feature code written. Awaiting review of `PLAN.md` before executing
-Slice A.*
+# PLAN.md — ship the approved backlog + design elevation (Phase 3)
+
+Authority: SHIP-GATE-R1/R2 rulings — G1 approved as marked (+riders a/b),
+G2 = Option 2 (+riders), G3 confirmed (paths named), G4 executed (see
+`docs/archive/`), G5 confirmed (Astro 7 out), G6 approved (+riders a/b).
+Scope = BACKLOG.md SHIP column only: **D1–D13, B1-Option-2, B10, E1–E10**
+(E10 already executed). OWNER and DEFER items are out of scope. No further
+gates except Rule 9 (destructive/irreversible actions).
+
+Discipline per milestone: vertical slice → its own PR → `npm run verify:all`
++ `npm run enforce` green before close → owner merges (never pushed to main).
+One logical change per commit. Every user-facing string lands en/es/ar
+together (except the documented console exception).
+
+## Milestones
+
+### M0 — Governance truth (B1-Option-2, E5) — first, it is the P0
+- `src/schema/catalog.ts`: add `security_sensitive: z.boolean().optional()`
+ to the docs schema extension so the existing flag on the Marmot guide is
+ real, validated data (English source only; translations inherit by path).
+- CLAUDE.md: rewrite the i18n-governance section to the truth — what exists
+ (locales, translate-catalog pipeline, fallback), what is planned (freshness
+ stamps, banner, roles, TRANSLATING.md), and the minimal gate below. E5: the
+ `TRANSLATING.md` reference is removed/marked planned; no dangling reference
+ survives. E9 rides here: constraint 4 names the protected workflow set
+ {verify, security-pr, quality}.
+- **The minimal gate (mechanism, per G2 rider):** new ADDITIVE workflow
+ `.github/workflows/i18n-security-gate.yml`, triggers `pull_request` +
+ `pull_request_review`. Steps: diff changed files vs base; select files
+ whose frontmatter has `security_sensitive: true` OR that are locale
+ variants (`es/…`, `ar/…`) of a flagged English source (path-mapped); if
+ any selected, count DISTINCT approving reviewers via the REST reviews API;
+ **fail the check while approvals < 2**. It never modifies or weakens
+ `operational_advisory` workflows (additive file, constraint 4).
+- **B4 dependency, stated plainly:** as a workflow this gate goes red but
+ GitHub only *blocks* merge once the check is marked REQUIRED in branch
+ protection (OWNER item B4). Until B4 flips, the gate is a loud advisory —
+ the P0 is fully closed only by this milestone + B4 together. PLAN records
+ this; the owner flips B4.
+- Acceptance: schema validates the flagged page; a test PR touching it shows
+ the gate red at 0–1 approvals, green at 2; CLAUDE.md contains no claim a
+ fresh clone can falsify.
+- Non-goals: freshness stamps, banner, CODEOWNERS, TRANSLATING.md content —
+ the full layer is its own later effort.
+
+### M1 — System layer (parts of D1, D4, D6)
+- tokens.css: `--verdict-ok-bg/--verdict-warn-bg/--verdict-danger-bg` (both
+ themes), `--shadow-1`; global `accent-color: var(--structure)`.
+- theme.css: explicit `@layer` ordering vs Starlight's `starlight` layer.
+- `.workbench` page class (frontmatter-driven) for the five tool pages.
+- **Receipt primitive**: shared component (Astro for static surfaces + a
+ Svelte twin or class-contract for islands) rendering status as
+ icon + word + color (grayscale-correct, WCAG 1.4.1 — G6 rider), stamped
+ edge, inline evidence slot, plain-verb line. Variants: ok / warn / danger /
+ neutral-pending.
+- Footer override (D6): designed, token-native, locale-aware; registered in
+ astro.config components.
+- Acceptance: tokens exist + consumed by the primitive; footer on all pages
+ incl. es/ar/RTL; axe clean; visual check at 3 widths; no `--sl-*` in new code.
+
+### M2 — Receipts on the trust surfaces (D1 complete, D2; closes P0-2, P1-15)
+- PolicyChecker: house i18n pattern (`lang` prop + STR en/es/ar); verdict
+ band via the receipt primitive — clean / blocked / chain-to-excluded with
+ the ownership chain rendered as a chain; CLI detail behind a "for
+ developers" disclosure; designed empty/error states.
+- Catalog entry badges + `/policies/enforcement/` verdict band + Studio
+ policy gate adopt the same primitive.
+- Acceptance: D1's ≥4 surfaces met; `/es/check/` + `/ar/check/` fully
+ localized (grep: no hardcoded English in the island); axe clean; e2e green.
+
+### M3 — Catalog explorer density (D3)
+- Responsive 1→2→3-col receipt-card grid; designed facet groups with counts;
+ designed zero-result/empty/error states at the full dataset (count = E6
+ generated figure); locale-aware CTA (`buildBase` used at `:358`).
+- Acceptance: desktop `/catalog/` no longer a single-column 20k-px page;
+ states verified at full dataset; CTA locale-verified on es/ar; LCP budget
+ holds (explorer is the LCP element).
+
+### M4 — Island tokenization + honesty chips (D4, D8, D9)
+- Retire `--sl-*`/raw hex/off-scale rems from BuildStudio, CostEstimator,
+ ModelCompass, CatalogExplorer (the `#d33…` ramp, `0 6px 24px` shadow, etc.).
+- D8: `TODO: confirm` → provenance chip ("unverified — awaiting human check",
+ localized; semantics unchanged).
+- D9: BuildStudio stray strings → STR; AccountWidget ar placeholder; compass
+ cautions + estimator labels via i18n keys.
+- Acceptance: grep zero `--sl-color` and zero hex literals in island styles;
+ `/build/models/` axe color-contrast = 0 (closes the 89-node finding); grep
+ finds no user-visible "TODO"; 352+ tests stay green.
+
+### M5 — Landing + register (D5, D7, B10)
+- WorkOrderGrid replaces stock CardGrid/LinkCard sections; copy-register pass
+ on landing, /build, /check, /build/models, /start intros (en/es/ar in the
+ same commit); B10: NIP-07 one-liner (nos2x/Alby) near sign-in docs.
+- Acceptance: no Starlight Card/LinkCard on the landing; register rules
+ (DESIGN §4) hold; screenshots at 3 widths vs DESIGN intent.
+
+### M6 — Console tokens, Arabic face, hydration, ceilings (D10–D13)
+- **CHECKPOINT (G6 rider b, recorded here before implementation):** Arabic
+ display face — final pick: _____ ; measured subset woff2 size: _____ KB
+ (budget ≤ 45 KB); OFL text committed. Filled in by the M6 PR itself; the
+ face does not land until this line is filled.
+- D10: console token-compliance only (raw hex → tokens; `.btn`/`.field`);
+ CSP wss: invariance test stays green; PANEL-RESPEC-v2 room untouched.
+- D12: `client:visible` for below-fold islands; ModelCompass static-render
+ evaluated; no `/catalog/` LCP regression (measured).
+- D13: page-weight ceilings measured + recorded in VERIFICATION.md.
+- Acceptance: zero raw hex in console; `/ar/` headings render the face;
+ before/after JS-transfer numbers recorded.
+
+### M7 — Doc-drift closeout (E1–E8; E9 landed in M0, E10 done)
+One commit each: ROADMAP through #56+; admin-spec phase ledger; E4 README
+deploy section → Workers Builds topology (+ CLAUDE.md "Cloudflare Pages"
+phrasing); E6 one generated catalog count everywhere; E7 runbook §B2 = 7
+models + maple-ai; E8 mobile-doc headers.
+- Acceptance: README claims match built reality line by line (DoD); no doc
+ names a file or number a fresh clone can falsify.
+
+### M8 — Verification loop + ship report
+Per the ship prompt Phase 5: two consecutive full passes — clean-state
+`verify:all`, every flow walked at full dataset, 3-width screenshots vs
+DESIGN intent, axe + Lighthouse vs budgets, reduced-motion on/off —
+evidence in VERIFICATION.md; failures fixed and re-run. Then SHIP.md:
+ledger (item → status → evidence), before/after screens, known limitations,
+residual risks, deploy steps. CHANGELOG records MVP → v1 deltas.
+
+## DoD reconciliation (SHIP-GATE-R1 2d — the prompt's DoD, restated against reality)
+
+| Prompt DoD line | Reconciled criterion |
+|---|---|
+| Fresh clone `npm install && npm run verify:all` passes | Unchanged — as written |
+| Backlog ledger 100% shipped-or-BLOCKERS | Scope = BACKLOG SHIP column; OWNER/DEFER items listed in SHIP.md as owner-court, not blockers |
+| Design implemented, zero banned defaults, all values via tokens | Unchanged; deviations documented in SHIP.md |
+| "All three enforcement layers pass on the repo's own tree (`cli.ts all --tree .`)" | **G3 wording:** Layers 1–2 pass `--tree .` (already CI-enforced, `verify.yml:42-43`); Layer 3 passes on every path except its own definitions/fixtures: `enforcement/excluded-organizations.yaml`, `enforcement/excluded-provider-signals.yaml`, `enforcement/tests/**` |
+| Catalog integrity (count, hand-authored untouched, catalog.json consumed) | Unchanged; count asserted via the E6 generated figure |
+| Quality gates ≥ budgets | Unchanged + DESIGN §8 page-weight ceilings |
+| Privacy: zero third-party requests | Unchanged; Plausible documented as the sole opt-in, off-by-default exception |
+| Security: osv-scanner clean/accepted; secrets scan; input validation; CSP+headers; no-secrets build | Unchanged; CSP ENFORCE flip itself = OWNER item B2 (mechanism already shipped) |
+| i18n parity or gaps logged | Parity for all SHIP-scope UI; catalog-entry corpus gap = OWNER item B8, logged not shipped |
+| Worker `/api/license`+`/api/health` verified; error/rate-limit documented | Unchanged (both live; rate-limit binding GA) |
+| Responsive + reduced-motion | Unchanged |
+| Docs: README matches reality; CHANGELOG; CONTRIBUTING accurate | Unchanged (M7/M8) |
+| **"Deploy-ready: Cloudflare Pages per README (build `npm run build`, output `dist`, NODE_VERSION=22)"** | **Reconciled:** deploy = Cloudflare **Workers** (static assets from `dist/` + `run_worker_first: ["/api/*"]`), auto-deployed by **Workers Builds on merge to main only** — branch pushes upload preview versions, verified against the deployments ledger (SHIP-GATE-R2 item 0). `wrangler.jsonc` is canonical; README's Pages section is fixed by E4. Worker deploy path documented in the runbook |
+
+## Non-goals (whole effort)
+Everything in BACKLOG OWNER/DEFER columns; QA-REMEDIATE-v1 and
+PANEL-RESPEC-v2 (external, un-consolidated — BACKLOG §D2); Astro 7 (G5);
+full i18n governance layer (G2); IA changes; new features; any weakening of
+POLICIES.md, the enforcement engine, or CI gates.
diff --git a/ROADMAP.md b/ROADMAP.md
index 7e0444d8..8a153fdd 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -77,7 +77,7 @@ project in a new session or conversation.
- **Required checks** — mark `path-a` / `e2e` / `skills` as required in branch protection.
- **Dependabot remainder** — leave (dev-only) or schedule a framework-upgrade pass (Astro 6→7).
-## 🔭 Deferred — FUTURE (see `docs/OPERATOR-RUNBOOK.md` Part B and `PLAN.md` §5)
+## 🔭 Deferred — FUTURE (see `docs/OPERATOR-RUNBOOK.md` Part B and `docs/archive/PLAN-goose-pivot.md` §5)
Goose live agent loop (`goose serve`/ACP) · server-side recipe signing + shareable links ·
full es/ar recipe-content translation.
diff --git a/audit/AUDIT.md b/audit/AUDIT.md
deleted file mode 100644
index 1284085a..00000000
--- a/audit/AUDIT.md
+++ /dev/null
@@ -1,93 +0,0 @@
-# AUDIT.md — SHIP Phase-1 findings (2026-07-11)
-
-Read-only research pass for the backlog-ship + design-elevation effort.
-Placed at `audit/AUDIT.md` because root `AUDIT.md` is the (historical,
-completed) Goose-pivot audit — promotion/renaming is a gate decision.
-Companion docs: `BACKLOG.md` (consolidated ledger), `DESIGN.md` (direction),
-`audit/screens/` (39 screenshots, 13 flows × 3 widths).
-
-## Baselines (executed this session)
-
-- `npm run verify:all` — **green**: astro check 0 errors/0 warnings, both
- typechecks clean, **352/352 tests**, enforce + enforce:skills green,
- production build 6,653 pages.
-- `npx tsx enforcement/cli.ts all --tree .` — exits 1, but **every hit is
- self-referential**: the engine's own signal-definition YAMLs and its
- negative-test fixtures. **Zero hits in `src/`, `worker/`, `scripts/`,
- `public/`.** Refinement (SHIP-GATE-R1 2c): CI **already dogfoods Layers 1–2
- full-tree** (`verify.yml:42-43`, green); only Layer 3 full-tree is
- unsatisfiable by construction. Exact exempt paths, enumerated:
- `enforcement/excluded-organizations.yaml`,
- `enforcement/excluded-provider-signals.yaml`, `enforcement/tests/**`.
- DoD-prose fix suffices — no engine change needed — gate item G3.
-- `npm audit --omit=dev` — 4 low (esbuild via astro; Windows-dev-server-only,
- prod unaffected). Full audit: 14 (7 low/6 moderate/1 high) — the high is
- `tmp` via `@lhci/cli`, CI-tooling only.
-- Enforcement full-tree + verify baselines contradict nothing in CI (10
- workflows, none disabled; the only two `continue-on-error` are
- documented-deliberate: `security-pr.yml:42`, `security-cron.yml:58`).
-
-## P0 — security / broken / binding-doc conflicts
-
-| # | Finding | Evidence |
-|---|---|---|
-| P0-1 | **CLAUDE.md documents an i18n freshness/security-gate layer that does not exist** — incl. a 2-review gate for `security_sensitive` pages. One page carries the flag today (`guides/encrypted-group-messaging-marmot.mdx:10`) and gets **no extra protection**; the schema doesn't even define the field. CLAUDE.md is binding session instructions, so this is the highest-risk doc/reality conflict. | No `scripts/i18n-freshness.mjs`, `scripts/i18n-security-gate.mjs`, `i18n:stamp` script, `TranslationStatus.astro`, `src/config/i18n.mjs`, `TRANSLATING.md`, `.github/CODEOWNERS`, or i18n workflows (verified absent) |
-| P0-2 | **PolicyChecker island ignores its `lang` prop** — the verdict surface (the product's thesis) renders English-only inside `/es/check/` and `/ar/check/`. | `src/components/PolicyChecker.svelte` (no `$props()`); pages pass `lang="es|ar"` (`es/check.mdx:18`, `ar/check.mdx:17`); hardcoded strings `:83-127` |
-
-(P0-3 in the first revision — CLI jargon in the checker verdict — is
-**reclassified to P1-15** per the prompt's severity ladder: P0 is
-security/broken/policy; copy register is UX. Reclassification requested and
-ruled at SHIP-GATE-R1 2b.)
-
-## P1 — correctness / UX / design debt
-
-Kill-list hits (each with file:line; full table in the design-audit record):
-
-| # | Finding | Evidence |
-|---|---|---|
-| P1-1 | Landing feature sections are stock Starlight `CardGrid`/`Card icon=…` + 3 `LinkCard` grids | `src/content/docs/index.mdx:37-99` |
-| P1-2 | Footer chrome is untouched Starlight default (only un-overridden chrome left) | `astro.config.mjs:146-153` |
-| P1-3 | Catalog explorer: 1,359 entries as a single-column card wall, 60/page (desktop page renders 20,562px tall); native checkboxes in a scroll box; no `accent-color` anywhere in src | `CatalogExplorer.svelte:513,505-510,298`; screenshot `catalog-explorer-desktop.png` |
-| P1-4 | Checker verdict = text line + default-styled table | `PolicyChecker.svelte:101-123` |
-| P1-5 | Island style dialects split: token-native vs raw `--sl-*`/hex/off-scale rems. Worst: ModelCompass hex ramp (`#d33 #e80 …`), CostEstimator all-`--sl-*`, BuildStudio mixes both in one stylesheet, lone raw box-shadow | `ModelCompass.svelte:144-152`; `CostEstimator.svelte:240-276`; `BuildStudio.svelte:1355` vs `:1360-1364`; `CatalogExplorer.svelte:536` |
-| P1-6 | `TODO: confirm` is user-visible product copy (zero-fabrication semantics, placeholder register) | `model-compass/ui/i18n.ts:73`; rendered `CostEstimator.svelte:220`, `ModelCompass.svelte:48,53,62`; advertised `build/cost.mdx:13`, `build/models.mdx:13` |
-| P1-7 | Dev-jargon first-contact copy on organizer surfaces (landing, /build, /check, /build/models, /start) | `index.mdx:87,89`; `build.mdx:13-16`; `check.mdx:12-14`; `models.mdx:10-12`; `start/index.mdx:11-13` |
-| P1-8 | i18n strays: BuildStudio hardcoded English beside its own STR table (`:1074,1027,1036,1084,1265,1272,1314-1322`); catalog CTA hardlinks English `/build/` from es/ar (`CatalogExplorer.svelte:358` — locale-aware `buildBase` exists at `:287` unused); AccountWidget ar placeholder (`:79`); English data-layer prose rendered in localized UIs (compass `models.ts:57`, estimator `providers.ts:64-68`, `CatalogList.astro:55-62`) | as cited |
-| P1-9 | All 16 island mounts `client:load`; zero `client:visible/idle`. ModelCompass ships its whole registry as eager JS with no no-JS render | grep `client:`; `build/models.mdx:16` |
-| P1-10 | `/console/` styled with raw hex outside the token system (own dark-mode media query) | `AdminConsole.svelte:470-523`; `console/index.astro:33-44` |
-| P1-11 | Arabic gets no display face (identity absent for RTL readers) | `tokens.css:12-13` |
-| P1-12 | CSP still Report-Only (mechanism complete; operator flip pending) | `astro.config.mjs:63`; `security-headers.ts:112-180` |
-| P1-13 | Cost Estimator: 12× `unitPrice: null` + 3× `lastVerified: null`; tier bands/scale placeholders. Model Compass: 7 models `score: null` (runbook undercounts as 3) | `providers.ts:62-97`; `tiers.ts:26-30`; `models.ts:37-182` |
-| P1-14 | Admin panel phases 4–8 unbuilt (per spec + wrangler binding comments); staging follow-ups (publish→PR, enforcement wiring, expiry job) logged | `docs/admin-panel-spec.md:73-136`; `wrangler.jsonc:48-62` |
-| P1-15 | CLI jargon inside the checker verdict aimed at non-developers: "run `npx tsx enforcement/cli.ts all --tree .` on a real project." (reclassified from P0 at SHIP-GATE-R1 2b) | `PolicyChecker.svelte:126-127` |
-
-## P2 — polish / docs / deferred
-
-- Doc drift set: ROADMAP Done stops at #35 (repo at #55); admin-spec header
- says "not yet built"; PLAN.md footer says "no feature code written"; README
- describes Pages-dashboard deploy vs actual Workers Builds topology
- (`README.md:127-148`); `TRANSLATING.md` referenced but file is
- `TRANSLATIONS.md`; catalog-count disagreement (CLAUDE.md 1,355 / TRANSLATIONS
- ~2,182 / disk 1,360); runbook §B2 undercount; MOBILE_FIXES header cites a
- merged branch; `operational_advisory` name→workflow mapping lives only in
- guard.py.
-- `/build/models/` color-contrast: 89 axe nodes (en/es) from opacity + hex
- fallbacks (a11y score still ≥ budget) — folds into P1-5's tokenization.
-- Catalog prose translation run (pipeline built, needs `ANTHROPIC_API_KEY` +
- waves); native-speaker review of ~25 machine-translated pages.
-- Dependabot remainder (5 dev-only alerts); the **Astro 7 coupled upgrade**
- (astro 7.0.7 + starlight 0.41.3 + @astrojs/svelte 9 — 0.40 is the newest
- Starlight compatible with Astro 6; breaking-change list verified, none of
- the removed APIs used in-repo) — a decision, not an obligation.
-- NIP-07 one-line note in public docs; Plausible remains the only sanctioned
- (opt-in, off-by-default) third-party origin.
-- Stack otherwise current: wrangler `ratelimits` GA (not beta),
- `run_worker_first` current, no deprecated Astro/nostr-tools API usage found.
-
-## What is NOT broken (verified, for the record)
-
-Intentional identity system exists and is substantial (tokens/theme/components
-+ 5 overrides + Build Plate + self-hosted subset fonts). No purple gradients,
-no glassmorphism, no lorem, no stock illustration, no font CDNs. Path A holds
-(broker retired; CI-gated). Mobile fixes 1–8 merged. `/api/health`,
-`/api/license`, auth, admin auth+staging all live and tested (352 tests).
diff --git a/docs/OPERATOR-RUNBOOK.md b/docs/OPERATOR-RUNBOOK.md
index d250b584..e8586150 100644
--- a/docs/OPERATOR-RUNBOOK.md
+++ b/docs/OPERATOR-RUNBOOK.md
@@ -116,7 +116,7 @@ no `wrangler deploy` in CI, so no `CLOUDFLARE_API_TOKEN` is needed in GitHub Act
- **What:** Replace the current "bring back the JSON your Goose run produced" step with a
live, streamed session.
- **Why deferred:** Experimental (mid-2026), and it would mean the platform drives a live
- loop — a meaningful architecture step (`PLAN.md` §5). `reflectFromResponse` was
+ loop — a meaningful architecture step (`docs/archive/PLAN-goose-pivot.md` §5). `reflectFromResponse` was
deliberately designed to later accept a streamed response without changing its
deterministic contract.
- **What it needs:** A stable Goose serve/ACP endpoint to target, plus a decision on where
@@ -126,14 +126,14 @@ no `wrangler deploy` in CI, so no `CLOUDFLARE_API_TOKEN` is needed in GitHub Act
- **What:** Sign recipes and produce shareable links (today there's only the client-side
`goose://` deeplink + the zip).
- **Why deferred:** Needs the Worker (server-side signing/storage); out of the pure-client
- scope the pivot stayed within (`PLAN.md` §5).
+ scope the pivot stayed within (`docs/archive/PLAN-goose-pivot.md` §5).
- **What it needs:** A Worker endpoint + a signing key + a storage decision (KV/R2), and a
UX for link expiry/revocation.
### 5. Full catalog translation for es/ar recipes
- **What:** Localized extension/recipe **content** in Spanish and Arabic (the UI is
translated; recipe/extension copy is not fully localized).
-- **Why deferred:** Scope (`PLAN.md` §5).
+- **Why deferred:** Scope (`docs/archive/PLAN-goose-pivot.md` §5).
- **What it needs:** Run/extend `npm run translate:catalog` (needs `ANTHROPIC_API_KEY`)
over recipe/extension content, then human review of the machine output (the
`machine_translated` schema flag already exists for surfacing an advisory).
diff --git a/docs/archive/AUDIT-goose-pivot.md b/docs/archive/AUDIT-goose-pivot.md
new file mode 100644
index 00000000..0158e80c
--- /dev/null
+++ b/docs/archive/AUDIT-goose-pivot.md
@@ -0,0 +1,260 @@
+# AUDIT.md — wecanjustbuildthings.dev architecture & Path-A audit
+
+**Mode:** read-only / plan. No source files were modified to produce this report.
+**Branch:** `claude/kind-albattani-tnycq4`
+**Scope:** the four builder components (Build Studio, Mentor Engine, Skills Creator,
+Catalog), the shared build-session, the Content-Layer catalog, all LLM-inference
+pathways, existing Goose wiring, framework/host config, and vendor-exclusion posture.
+**Goal context:** wire the platform to drive **Goose** as a user-facing agent runtime,
+while the deployed platform itself stays "Model-free Path A" (zero inference, one
+documented exception). Findings only — **no solutions proposed.**
+
+> Note: the project's PreToolUse guard blocks writing the literal excluded-vendor
+> API host strings (OpenAI/xAI/Meta). Where those hosts are relevant below, they are
+> cited by `file:line` rather than reproduced verbatim. Permitted hosts (Anthropic,
+> OpenRouter, DeepSeek, Google) are shown normally.
+
+---
+
+## 1. Architecture map — the four components
+
+All four are Svelte 5 islands (or inline logic within one) mounted into Starlight
+`.mdx` pages with `client:load`. Pure logic is factored into `src/lib/*.ts` and
+`src/modules/cost-estimator/`. Everything client-side is deterministic.
+
+### Build Studio
+The orchestrator; hosts Movements 1–4 and the Skills Creator inline.
+
+| File | Role |
+|---|---|
+| `src/components/BuildStudio.svelte` (~1,525 ln) | The island: intent → blueprint → skills → handoff. Reads/writes the build session. |
+| `src/content/docs/build.mdx:18` | Mount: `` (`/build/`); es at `src/content/docs/es/build.mdx:18`, ar under `src/content/docs/ar/…` |
+| `src/lib/studio-stack.ts` | Stack eligibility, advisory ranking, `pinnedDependencies` (#3/#4). |
+| `src/lib/chemistry.ts` | Tool "chemistry" — capability role-graph pairs/conflicts/order. |
+| `src/lib/mentor-engine.ts` | Movement 1 signals/questions/constraints (see below). |
+| `src/lib/skill-doc.ts` | Movement 3 `slugifySkill` / `skillToMd` (SKILL.md renderer). |
+| `src/lib/build-session.ts` | The shared session object (see §2). |
+| `enforcement/matcher.ts` | Imported into the island to **re-verify** the assembled stack in-browser. |
+
+**State:** reads the shared build session on mount (`BuildStudio.svelte:232-252`),
+persists every change via a `$effect` (`:283-310`). The downloadable starter
+(zip / GitHub / Goose recipe) is assembled in `starterFiles()` (`:806-822`).
+
+### Mentor Engine
+| File | Role |
+|---|---|
+| `src/lib/mentor-engine.ts` | **Pure, deterministic, stateless.** `detectSignals`, `pickQuestions`, `reflect` over a curated trilingual lexicon (`:33-42`) + protocol signals (`:45-50`). No model, no I/O. |
+| `src/lib/mentor-engine.test.ts` | Unit tests incl. structural invariants. |
+
+**State:** holds none itself. BuildStudio computes `reflect(signals, answers)`
+(`BuildStudio.svelte:547`) and persists the result into `session.converged`
+(`:295-296`). The deterministic `reflect()` is what CLAUDE.md calls the
+"structured-reflection step"; it makes **no** inference call (see §4 nuance).
+
+### Skills Creator
+| File | Role |
+|---|---|
+| `src/components/BuildStudio.svelte:962-981` | **Inline** Movement 3 UI + capture/dedupe (`captureSkill`, `authoredSkills`, `removeAuthored`). |
+| `src/lib/skill-doc.ts` | Pure `slugifySkill` + `skillToMd` (YAML-frontmatter SKILL.md). |
+| (external) `github.com/MartinMontero/knowledge-to-skills-pipeline` | Referenced by generated README/skills folder (`BuildStudio.svelte:780`); not in this repo. |
+
+**State:** authored skills live in component state (`authoredSkills`) and are
+persisted into `session.skills` (`BuildStudio.svelte:297`). There is **no** standalone
+Skills Creator component or route — it is part of Build Studio.
+
+### Catalog
+| File | Role |
+|---|---|
+| `src/content/docs/catalog/**/*.md(x)` | The entries themselves (Content-Layer docs; see §3). |
+| `src/content.config.ts` | Collection definition (`docsLoader` + `docsSchema({ extend: catalogExtend })`). |
+| `src/schema/catalog.ts` | The Zod frontmatter schema. |
+| `src/pages/catalog.json.ts` | Build-time static JSON API at `/catalog.json` (filters to `INDEXED_TYPES`). |
+| `src/components/CatalogExplorer.svelte` | Interactive island (search/facets/sort) at `/catalog/` (`catalog/index.mdx:21`). |
+| `src/lib/catalog-filter.ts` | Pure filter/facet/sort logic used by the island. |
+| `src/components/CatalogList.astro` | Static server-rendered lists (e.g. PIE pages). |
+| `data/*.json` + `scripts/build-catalog.ts` | Build-time seeds → catalog md generator (`package.json:19`). |
+
+**State:** the island fetches `/catalog.json` once on mount
+(`CatalogExplorer.svelte:237`) and filters entirely client-side. It does **not** use
+the shared build session — its only handoff to Build Studio is a URL param
+(`?seed=`, read at `BuildStudio.svelte:271-277`).
+
+---
+
+## 2. The shared client-side build-session object
+
+**Defined in:** `src/lib/build-session.ts` — `interface BuildSession` (`:54-90`).
+
+**Persistence:** **`localStorage`** (not IndexedDB, not a framework store).
+- Key: `wcb.build-session.v1` (`build-session.ts:92`).
+- Change notification: same-tab `CustomEvent('wcb:session-change')` (`:93`, dispatched in `saveSession` `:161`) + cross-tab `storage` event (`subscribeSession` `:194-206`).
+- Defensive load/coerce via `migrate()` (`:120-181`); silent degradation if storage is unavailable (`saveSession` `:158-160`).
+
+**Fields** (`build-session.ts:54-90`):
+| Field | Line | Owner / meaning |
+|---|---|---|
+| `v: 1`, `updatedAt` | 55-56 | schema version + timestamp |
+| `movement: 1\|2\|3\|4` | 58 | wayfinding |
+| `intent{projectName,problem,goal,success,protocols,answers}` | 60-67 | Movement 1 raw intent + Socratic answers |
+| `converged` | 69 | Movement 1 deterministic reflection |
+| `adjustments{swaps,removed,extra}` | 71-75 | Movement 2 blueprint edits |
+| `stack: SessionStackItem[]` | 77 | Movement 2 resolved tools + receipts |
+| `skills: SessionSkill[]` | 79 | Movement 3 authored skills |
+| `seededTool` | 81 | Catalog "Build with this" seed |
+| `handoff` | 83 | Movement 4 method (`zip\|github\|goose\|kickoff`) |
+| `usage?`, `costEstimate?` | 88-89 | Hosting Cost Estimator I/O |
+
+**Readers/writers** (only three modules import it):
+- `src/components/BuildStudio.svelte` — read + write (primary).
+- `src/modules/cost-estimator/ui/CostEstimator.svelte` — read.
+- `src/modules/cost-estimator/core/usage-profile.ts` — write (`usage` / `costEstimate`).
+
+**Components NOT wired to the build session (flagged):**
+- `src/components/CatalogExplorer.svelte` — independent; hands off only via `?seed=` URL param, **not** the session object. (So a Catalog→Studio handoff carries a single tool id, nothing else.)
+- `src/components/PolicyChecker.svelte` (`/check/`) — fully standalone; no session read/write.
+- `src/components/AccountWidget.astro` — uses a **separate** server-side auth session (HttpOnly cookie → KV → D1), unrelated to the client build session.
+
+---
+
+## 3. Catalog inventory (Astro Content Layer)
+
+**Content Layer:** confirmed. `src/content.config.ts` uses Starlight's
+`docsLoader()` (a glob/file loader over `src/content/docs/`) with
+`docsSchema({ extend: catalogExtend })` — i.e. the Zod schema in
+`src/schema/catalog.ts` layered onto Starlight docs frontmatter.
+
+**Counts** (English content; `es`/`ar` catalog dirs contain only an `index.mdx` —
+translations not yet generated):
+- **2,182** entry files under `src/content/docs/catalog/`.
+- **827** of those are datasets (`src/content/docs/catalog/datasets/`).
+- **1,354** tool-class entries — authoritative count from `npm run enforce:layer1`
+ (`✓ 1354 catalog entries clean`). (1,354 + 827 + 1 index ≈ 2,182.)
+- `/catalog.json` ships `INDEXED_TYPES` = tools + datasets (`catalog.json.ts:9-15`).
+
+**Schema fields** (`src/schema/catalog.ts:80-134`, all optional at top level;
+conditionally required via `superRefine` `:140-207`):
+- Discriminator: `entry_type` (`:82`) ∈ `ENTRY_TYPES` (`:28-40`).
+- Dependency: `dependency_name, ecosystem, category, what_it_does, homepage_url, repo_url, registry_url, protocols` (`:85-92`).
+- License (accountability core): `license_spdx, license_source_url, license_source_commit_sha` (`:95-97`).
+- Maintenance: `maintenance_status, last_release_at, version` (`:100-105`).
+- AOS adoption: `aos_repos_using, aos_repos_list` (`:108-109`).
+- Nav/i18n: `pie_anchor` (`:112`), `machine_translated` (`:117`).
+- Enforcement: `provider_agnostic, verification_status, verification_blocked_reason, origin_advisory, verified_at` (`:120-126`).
+- **Recipe contract** (required when `entry_type==='recipe'`): `recipe_type, target_entry, target_entry_slug, excluded_providers_unreachable_when[], verification_steps[]` (`:129-133`, refined `:181-206`).
+
+**How datasets / extensions / skills are represented:**
+- **Datasets** — first-class: `entry_type: 'dataset'` (`schema:34`), 827 files, surfaced in `/catalog.json` as `kind: 'dataset'` (`catalog.json.ts:21`). Seed: `data/datasets.json`.
+- **Extensions (Goose/MCP)** — **NOT a first-class type.** `ENTRY_TYPES` (`schema:28-40`) has no `extension`. MCP servers exist only as generic catalog **tools** (e.g. `data/agentic-tools.json:1173-1230`, "MCP Ecosystem"). Relevant to the Goose pivot, where extensions are a distinct concept.
+- **Skills** — **NOT a catalog type.** No `skill` in `ENTRY_TYPES`. Skills are a build-flow artifact (authored in Build Studio → `skill-doc.ts` → SKILL.md) and a build-time seed (`data/skills-seed.json`); the authoring how-to lives in the external knowledge-to-skills-pipeline.
+- **Recipes** — first-class `entry_type: 'recipe'` (`schema:35`) but currently only **2** files under `src/content/docs/recipes/`. These are *provider-lockdown policy recipes* (exclusion exceptions), **distinct** from the Goose run-recipe YAML the Build Studio generates (§5).
+
+---
+
+## 4. LLM inference calls (Path-A check)
+
+**Result: NOT zero.** There is exactly **one** server-side inference pathway,
+reached from two client call sites. It is BYOK (the user's key), allowlist-gated,
+and appears to be the documented "one permitted exception" — but see the nuance.
+
+**The inference call (server-side, in the Worker):**
+- `worker/index.ts:199` — `const res = await fetch(p.url, { ...p.build(model, prompt, apiKey), … })` inside `kickoffHandler` (`:182-207`), routed at `/api/agent/kickoff` (`:463`).
+- Provider hosts it can reach (`PROVIDERS`, `worker/index.ts:68-99`):
+ - `:70` Anthropic Messages API (`api.anthropic.com`)
+ - `:80` OpenRouter chat-completions (`openrouter.ai`)
+ - `:90` DeepSeek chat-completions (`api.deepseek.com`)
+
+**Client call sites (same-origin → the Worker brokers; the browser never calls a model directly):**
+- `src/components/BuildStudio.svelte:1012` — builds the **MENTOR structured-reflection** prompt (returns JSON `proposals` + `skills`).
+- `src/components/BuildStudio.svelte:904` — the **"Try a step with AI" kickoff** handoff (`handoff: 'kickoff'`).
+
+**Excluded-vendor host strings elsewhere are NOT calls** — they are the enforcement
+engine's *negative signals* and tests (the OpenAI/xAI host literals live at
+`enforcement/excluded-provider-signals.yaml:11,31`, and in
+`enforcement/tests/recipe.test.ts:46`, `enforcement/tests/layer3.test.ts:25,33`).
+
+**Nuance / discrepancy to surface:**
+- CLAUDE.md's "exactly one permitted exception: the single **deterministic**
+ structured-reflection step" is worded ambiguously. In code, the *deterministic*
+ reflection is `mentor-engine.reflect()` (`BuildStudio.svelte:547`) and makes **no**
+ model call. The actual *inference* exception is the `/api/agent/kickoff` broker,
+ which is used for **two** purposes: structured reflection (`:1012`) **and** a
+ free-form "try a step" kickoff (`:904`). So the single permitted endpoint backs
+ two product features, only one of which is "structured reflection."
+- This sits in tension with the new goal ("the platform makes zero inference calls;
+ Goose runs inference on the user's machine"). The broker is the lone exception
+ today. (Flagged in Gaps & Risks; no fix proposed here.)
+
+---
+
+## 5. Existing Goose integration
+
+**State: partial, recipe-file level — already wired, no deeplink/MCP.**
+
+- **Handoff option:** `handoff` includes `'goose'` (`BuildStudio.svelte:200`), with UI strings "Run it with Goose" / "Download the Goose recipe" / "Copy the run command" (`:43,58-59`, es `:96,111-112`, ar `:149,164-165`).
+- **Recipe generation:** `gooseRecipe` derived (`BuildStudio.svelte:733-742`) emits a Goose recipe YAML — `version: "1.0.0"`, `title`, `description`, `instructions: |`, `prompt: |` (the full agent prompt). Bundled into the starter zip as `${slug}.goose-recipe.yaml` (`:814`) and individually downloadable (`:1360`).
+- **Run command:** `goose run --recipe ${slug}.goose-recipe.yaml`, shown and copyable (`:1361-1362`).
+- **Catalog presence:** Goose is a catalog entry — `data/agentic-tools.json:693-699`, `source_url: https://github.com/aaif-goose/goose`.
+- **Docs:** Goose is referenced throughout prose (`astro.config.mjs:99,148`; README `:14,47,171,184`; guides incl. `guides/get-started-with-goose`, `TRANSLATIONS.md:44`).
+
+**Absent today (relevant to the pivot):**
+- **No `goose://` deeplink** anywhere (searched `src/`, none).
+- **No MCP / extension wiring** in the platform — MCP appears only as catalog *content* (`data/agentic-tools.json` "MCP Ecosystem"), not as integration code.
+- The generated recipe is a **static template** (`instructions` + `prompt`); it does not yet encode `extensions:`, `activities:`, `parameters:`, or provider/settings blocks from the Goose recipe schema.
+- The provider-lockdown **policy** recipes (`src/content/docs/recipes/`, `entry_type:'recipe'`) are unrelated to the generated **run** recipe — two different "recipe" concepts share the word.
+
+---
+
+## 6. Framework / adapter / build config
+
+- **Astro:** `^6.4.6` (`package.json:44`). *(Note: the task brief said "Astro 5"; the repo is on Astro 6.)*
+- **Starlight:** `@astrojs/starlight ^0.40.0` (`package.json:41`). Svelte `^5.56.3`; `@astrojs/svelte ^8.1.2`.
+- **Build output mode:** `output: 'static'` (`astro.config.mjs:92`), `trailingSlash: 'ignore'` (`:93`).
+- **Cloudflare adapter:** **none.** There is no `@astrojs/cloudflare` adapter and no `adapter:` in `astro.config.mjs`. Instead the site is a **static build served by a hand-rolled Worker**: `wrangler.jsonc` sets `main: worker/index.ts` with a Static-Assets binding (`assets.directory: ./dist`, `binding: ASSETS`, `not_found_handling: "404-page"`), KV (`SESSIONS`, `ATPROTO`), and D1 (`DB`). Static HTML is served by the asset layer (which bypasses the Worker); `/api/*` is handled in `worker/index.ts`.
+- **Security headers:** emitted at build into `dist/_headers` via a custom integration (`astro.config.mjs:44-69`); hash-based CSP ships **Report-Only** by default (`:63`), `CSP_MODE=enforce` to enforce.
+- **i18n:** root locale `en`, plus `es` and `ar` (RTL) (`astro.config.mjs:106-111`).
+
+---
+
+## 7. AOS vendor-exclusion posture (Meta / OpenAI / xAI)
+
+**Posture: clean — no Meta/OpenAI/xAI used as an inference/model provider; Google permitted.**
+
+- **Dependencies:** no Meta/OpenAI/xAI SDK in `package.json` (`:39-66`). LLM-adjacent deps are only `@atproto/*` and `nostr-tools` (not model SDKs). No `openai`, `@anthropic-ai/sdk` (the broker calls Anthropic over raw `fetch`, not an SDK), `@ai-sdk`, or `langchain`.
+- **Broker allowlist** (`worker/index.ts`):
+ - Direct providers offered: `anthropic`, `openrouter`, `deepseek` (`:68-99`). **OpenAI and xAI are not offered**, and the error message says so (`:189`).
+ - OpenRouter (a gateway to *all* vendors) is constrained by a **default-deny allowlist** `PERMITTED_ROUTER_VENDORS` (`:106-109`) and a hard block `isExcludedRouterModel` (matches the excluded-org vendor segments, `:118-121`), enforced before the call (`:195-197`).
+ - **Google is explicitly permitted** — `'google'` ∈ the allowlist (`:107`).
+- **Catalog-level exclusion** is enforced separately by the three-layer engine (`enforcement/`, blocking CI gate `verify.yml`) and the `EXCLUDED_PROVIDER_IDS = ['meta','openai','xai','aws','oracle']` registry (`src/modules/cost-estimator/registry/providers.ts:16`), shared by the Model Compass registry.
+- **Google services** (Fonts/Analytics/etc.): the only analytics is **Plausible** (opt-in, `astro.config.mjs:74-88`) — not Google. No Google Fonts/Maps/reCAPTCHA found. (Per policy Google is permitted; none is currently wired, so nothing to flag.)
+- **Anthropic / DeepSeek** as offered providers are **not** policy violations — only Meta/OpenAI/xAI are excluded.
+
+---
+
+## Gaps & Risks (ranked by severity)
+
+1. **[High · Path-A definition vs. code] The lone inference exception backs two features and overlaps with the Goose pivot.**
+ `/api/agent/kickoff` (`worker/index.ts:182-207`, fetch `:199`) is the only inference call, but it serves both the structured-reflection (`BuildStudio.svelte:1012`) and a free-form "Try a step with AI" kickoff (`:904`). CLAUDE.md authorizes *one* "deterministic structured-reflection step," yet the deterministic reflection (`reflect()`) makes no model call — so the wording and the code don't line up, and the "kickoff" is arguably a second use of the exception. As Goose takes over user-facing inference (on the user's machine), this server-side broker's necessity should be re-examined. *(Observation only.)*
+
+2. **[High · Goose readiness] Extensions and skills are not first-class catalog types; the generated Goose recipe is a minimal template.**
+ `ENTRY_TYPES` (`schema:28-40`) has no `extension` or `skill`; MCP servers are only generic `tool` entries (`data/agentic-tools.json`). The emitted recipe (`BuildStudio.svelte:733-742`) carries only `instructions`+`prompt` — no `extensions`, `parameters`, `activities`, or provider/settings blocks from the Goose recipe schema. Driving Goose richly will need a representation for extensions and a fuller recipe.
+
+3. **[Medium · Docs accuracy] Stated facts diverge from the code.**
+ - Brief/CLAUDE.md imply **Astro 5**; repo is **Astro `^6.4.6`** (`package.json:44`).
+ - CLAUDE.md says "~842 YAML entries"; actual is **1,354 tool entries + 827 datasets** (`enforce:layer1`; `find` counts). Entries are Markdown/MDX, not raw YAML.
+ - These don't break anything but undermine trust in the written invariants.
+
+4. **[Medium · State coupling] Three islands are not on the shared session.**
+ `CatalogExplorer` hands off only a single `?seed=` id (`BuildStudio.svelte:271`), and `PolicyChecker` / `AccountWidget` are fully independent. Any future flow that wants Catalog selections, a policy-check result, or the signed-in identity to travel into the Build Studio has no channel today. (`AccountWidget` is deliberately a *separate* auth session — not a defect, but worth noting it is unrelated to the build session.)
+
+5. **[Medium · CSP] The site-wide CSP is still Report-Only.**
+ `astro.config.mjs:63` ships `report-only` unless `CSP_MODE=enforce`. Until promoted, the hash-based CSP is observational. (Collection was made durable earlier in this branch.)
+
+6. **[Low · i18n] Catalog translations are effectively absent.**
+ `src/content/docs/es/catalog/` and `…/ar/catalog/` contain only an `index.mdx`; the 1,354+827 entries exist in English only. The Build Studio / Catalog UIs are translated, but entry bodies are not (a `translate-catalog` script exists, `package.json:27`).
+
+7. **[Low · naming] Two distinct "recipe" concepts share the word.**
+ Policy provider-lockdown recipes (`entry_type:'recipe'`, `src/content/docs/recipes/`) vs. the generated Goose run-recipe YAML (`${slug}.goose-recipe.yaml`). A reader can easily conflate them; worth disambiguating in any Goose-facing work.
+
+---
+
+*End of audit. No source files were modified. Awaiting direction before proposing changes.*
diff --git a/docs/archive/PLAN-goose-pivot.md b/docs/archive/PLAN-goose-pivot.md
new file mode 100644
index 00000000..648b8cd8
--- /dev/null
+++ b/docs/archive/PLAN-goose-pivot.md
@@ -0,0 +1,456 @@
+# PLAN.md — drive Goose as a user-facing agentic runtime via ONE build session
+
+**Source:** `AUDIT.md` (read-only audit, branch `claude/kind-albattani-tnycq4`).
+**Mode:** PLAN ONLY — no feature code. Contracts below are *specifications* (types +
+signatures, no bodies). Execution happens slice-by-slice after review.
+**Deliverable of the pivot:** the four components (Build Studio, Mentor Engine, Skills
+Creator, Catalog) converge on ONE typed client-side build session, which serializes to
+a Goose recipe and launches via a `goose://recipe?config=` deeplink — all pure
+client-side, model-free.
+
+---
+
+## 0. Encoded constraints, reconciliations & out-of-scope
+
+**Hard constraints (carried into every slice):**
+1. **Stack:** Astro (Starlight) + Cloudflare Pages; Catalog on Astro Content Layer
+ (`docsLoader` + Zod), per `src/content.config.ts` + `src/schema/catalog.ts`.
+2. **ONE shared client-side build session** wired across the four components
+ (`src/lib/build-session.ts`).
+3. **Model-free Path A:** ZERO inference API calls from the platform. Exactly ONE
+ deterministic structured-reflection exception that performs **no LLM call** — it
+ *reads* structured JSON the user brings back from their own Goose run
+ (`response.json_schema` output).
+4. **Goose = STABLE primitive only:** client-side recipe deeplinks
+ `goose://recipe?config=` via `btoa(JSON.stringify(recipe))` behind a
+ UTF-8-safe wrapper (reuse the pattern in `src/components/AccountWidget.astro:86-95`).
+5. **Mentor Engine** = Yoda/Morpheus/J.A.R.V.I.S./Neo persona shipped as a Goose
+ **SKILL** + deterministic reflection over the recipe's `response.json_schema`
+ output (`src/lib/mentor-engine.ts`).
+6. **Vendor exclusions = Meta/OpenAI/xAI ONLY** as inference providers; Google
+ permitted. Goose is model-agnostic — **never hardcode a provider**; omit
+ `settings.goose_provider`/model so the user's own Goose config selects it.
+7. **MCP safety:** the **Catalog is the trust boundary**. Only allowlisted, vetted
+ extensions/skills may appear in a generated recipe. Lean on Goose "Trust &
+ Execute" consent + extension allowlist + least privilege. Never surface raw MCP
+ server config to non-devs. Treat tool descriptions as untrusted unless sourced
+ from a verified Catalog entry.
+
+**Reconciliations with `AUDIT.md` (facts the brief mis-stated — plan honors reality):**
+- Framework is **Astro `^6.4.6`**, not 5 (`AUDIT.md §6`, `package.json:44`). Content
+ Layer API is identical for this work; the plan is version-agnostic and pins nothing
+ to a major version.
+- Catalog is **1,354 tool-class + 827 dataset** entries (`AUDIT.md §3`), not "~842".
+ Entries are MD/MDX with YAML frontmatter. Counts only matter to Slice B's allowlist
+ scope.
+- There is **no `@astrojs/cloudflare` adapter**; static build + hand-rolled Worker
+ (`AUDIT.md §6`, `wrangler.jsonc`). All new logic is **pure client-side**, so the
+ serializer/deeplink never touch the Worker.
+
+**Out of scope (note as FUTURE ENHANCEMENT only — do not build now):**
+- `goose serve` / ACP-over-HTTP / Goose TS SDK (experimental mid-2026). Recorded in
+ §6 "Future" so a later slice can pick them up; no design dependency on them.
+- Retiring the existing `/api/agent/kickoff` broker is IN scope (Slice D) because it is
+ the lone inference call standing between us and Path-A zero.
+
+---
+
+## 1. Cross-cutting invariants & global gates (apply to ALL slices)
+
+These are the exit gates every slice is measured against, in addition to its own:
+
+- **G-PathA (zero inference):** no platform code fetches a model host. Enforced by a
+ new additive test/CI check (Slice D) — extends the negative-signal approach already
+ in `enforcement/excluded-provider-signals.yaml` and `enforcement/tests/layer3.test.ts`.
+- **G-Vendor:** no Meta/OpenAI/xAI as a provider; recipe emits no provider/model.
+ Reuse `EXCLUDED_PROVIDER_IDS` (`src/modules/cost-estimator/registry/providers.ts:16`)
+ + the broker allowlist logic pattern (`worker/index.ts:106-130`) as the vetting source.
+- **G-Trust (MCP):** every extension in a generated recipe ∈ the Catalog allowlist;
+ property-tested so a non-allowlisted extension is unrepresentable.
+- **G-Gate:** the blocking `operational_advisory` CI gate is never weakened; all new CI
+ is ADDITIVE in new workflow files (CLAUDE.md #4; `AUDIT.md §6`). The project guard
+ (`.claude/hooks/guard.py`) stays in force.
+- **G-Determinism:** all new serializer/deeplink/reflection code is pure and
+ deterministic — same session ⇒ byte-identical recipe & deeplink.
+- **G-Green:** the existing **185-test** suite stays green (`npm test`), `astro check`
+ 0 errors, and a production `npm run build` succeeds, after every slice.
+- **G-NonDev:** no raw MCP/server config, env vars, or shell strings shown to the
+ builder; all attacker-influenceable text rendered via `textContent` (reuse the
+ XSS-safe pattern already used in `src/components/AccountWidget.astro`).
+
+---
+
+## 2. Sequencing / gating
+
+```
+A (typed session + Catalog wiring)
+└─> B (recipe serializer + Catalog extension allowlist)
+ ├─> C (deeplink + "Open in Goose" UX)
+ ├─> D (Mentor persona SKILL + structured-reflection; retire broker → Path-A zero)
+ └─> E (Skills Creator → session + Catalog sub-recipes)
+```
+A is the foundation. B depends on A. C, D, E each depend on B; C/D/E are independent of
+one another and may land in any order once B is in. Each slice is its own PR, gated on
+its benchmark + the global gates in §1.
+
+---
+
+## SLICE A — Formalize the shared build session; wire all four components
+
+**Goal.** Turn `BuildSession` into a single typed, runtime-validated, versioned schema
+and ensure all four components read/write the *same* object — closing `AUDIT.md` Gap #4
+for the Catalog. (PolicyChecker/AccountWidget are intentionally NOT among "the four"
+and stay independent — `AUDIT.md §2`.)
+
+**Gate (entry):** none (foundation).
+
+**Files to change.**
+- `src/lib/build-session.ts` — formalize the schema (see contract); bump `v: 1 → 2`
+ with a `migrate` v1→v2 path (extend the existing `migrate()` at `:120-181`, same
+ defensive style); add session fields the recipe needs: `extensions`, `mentorReflection`.
+- `src/lib/build-session.test.ts` — extend (round-trip, v1→v2 migrate, coercion).
+- `src/components/CatalogExplorer.svelte` — wire to the session: write selected
+ tools/extensions; on mount read prior selections (reuse the subscribe pattern from
+ `src/components/BuildStudio.svelte:232-252` and `src/modules/cost-estimator/ui/CostEstimator.svelte:33-35`).
+- `src/components/BuildStudio.svelte` — consume the new typed fields where it already
+ reads/writes the session (`:232-310`); no behavior change beyond new fields.
+- (Mentor Engine + Skills Creator already persist through BuildStudio — formalize their
+ field ownership in the schema doc, no new component file.)
+
+**Contract / interface.**
+```ts
+// src/lib/build-session.ts (Zod, mirroring the astro/zod pattern in src/schema/catalog.ts)
+export const SESSION_VERSION = 2 as const;
+
+export interface SessionExtension { // an allowlisted Goose extension ref (Slice B/G-Trust)
+ catalogId: string; // slug of the verified Catalog entry it came from
+ name: string;
+ kind: 'builtin' | 'mcp_stdio' | 'mcp_sse'; // resolved from the Catalog, never user-typed
+}
+export interface SessionMentorReflection { // deterministic output of Slice D
+ schemaVersion: 1;
+ constraints: string[]; // ConstraintId[]; from mentor-engine
+ proposals: Array<{ action: 'add'|'swap'|'remove'; name: string; why: string }>;
+}
+export interface BuildSession { // EXTENDS the current interface at :54-90
+ /* …all existing fields… */
+ v: 2;
+ extensions: SessionExtension[]; // NEW — selected, allowlisted extensions
+ mentorReflection: SessionMentorReflection | null; // NEW — Slice D writes; null until run
+}
+export function migrate(parsed: unknown): BuildSession; // v1 docs upgrade to v2 (extensions:[], mentorReflection:null)
+```
+Component contract (unchanged API surface): all four use only
+`loadSession/saveSession/updateSession/subscribeSession` (`build-session.ts:143-206`).
+
+**Test plan.**
+- *Unit:* extend `build-session.test.ts` — v1→v2 migrate fills `extensions: []` /
+ `mentorReflection: null`; Zod rejects malformed extensions; round-trip + cross-tab
+ event still fire (reuse the fake-DOM harness already in that file).
+- *Integration:* a jsdom-free node test that simulates Catalog write → BuildStudio read
+ through `localStorage` + `subscribeSession` (extend the existing pub/sub tests).
+
+**Rollback.** Single revert of the slice commit; `migrate` is forward-only and
+v1-compatible, so reverting to v1 readers ignores the new fields (they were additive &
+optional in storage). No data migration to undo.
+
+**Benchmark / threshold (proves it works).**
+- All four components demonstrably read+write the one object in an integration test
+ (Catalog selection observable in a BuildStudio-side read): **pass = 1 object, 4
+ participants**.
+- v1→v2 migrate property test: **100%** of randomly-mutated v1 blobs coerce to a valid
+ v2 (no throw, schema-valid).
+- `npm test` stays green at **≥185** tests; `astro check` 0 errors.
+
+---
+
+## SLICE B — Pure-client recipe serializer (session → Goose recipe)
+
+**Goal.** Deterministically turn a `BuildSession` into a Goose recipe object
+(`extensions` allowlisted from the Catalog, `parameters`, `activities`,
+`response.json_schema`, `instructions`, `prompt`) — superseding the minimal template at
+`src/components/BuildStudio.svelte:733-742`. Model-agnostic (no provider/model).
+
+**Gate (entry):** Slice A merged (needs `session.extensions` + typed schema).
+
+**Files to change.**
+- **New** `src/lib/goose-recipe.ts` — the pure serializer (extraction pattern of
+ `src/lib/catalog-filter.ts` / `src/lib/skill-doc.ts`).
+- **New** `src/lib/goose-recipe.test.ts`.
+- `src/schema/catalog.ts` — add a first-class **extension** representation (closes
+ `AUDIT.md` Gap #2): either `entry_type: 'extension'` in `ENTRY_TYPES` (`:28-40`) or a
+ `goose_extension` block, gated by `verification_status === 'verified'` in the existing
+ `superRefine` (`:140-207`).
+- **New** `src/pages/extensions.json.ts` — build-time static allowlist of vetted
+ extensions for the client serializer (same Content-Layer query pattern as
+ `src/pages/catalog.json.ts`).
+- `src/components/BuildStudio.svelte` — replace the inline `gooseRecipe` derived
+ (`:733-742`) with a call to `buildGooseRecipe(session, allowlist)`.
+- `enforcement/` — extend the layer-3 recipe contract (`enforcement/cli.ts` +
+ `enforcement/types.ts:92` `RecipeFinding`) to assert generated recipes reference only
+ allowlisted extensions; add fixtures under `enforcement/tests/`.
+
+**Contract / interface.**
+```ts
+// src/lib/goose-recipe.ts
+export interface GooseRecipe { // the stable recipe primitive we emit
+ version: string; // "1.0.0"
+ title: string;
+ description: string;
+ instructions: string;
+ prompt: string;
+ extensions: GooseExtensionRef[]; // ONLY from the allowlist
+ parameters: GooseParameter[]; // typed inputs the user fills in Goose
+ activities: string[]; // suggested next actions
+ response: { json_schema: JsonSchema }; // forces structured output (Slice D reads it)
+ // NOTE: no `settings.goose_provider` / model — model-agnostic by construction (G-Vendor)
+}
+export interface ExtensionAllowlist { // fetched from /extensions.json
+ byId: Record;
+}
+export function buildGooseRecipe(
+ session: BuildSession,
+ allow: ExtensionAllowlist,
+): GooseRecipe; // pure; drops any extension not in `allow`
+export const RESPONSE_JSON_SCHEMA: JsonSchema; // the shared schema Slice D reflects over
+```
+
+**Test plan.**
+- *Unit:* `goose-recipe.test.ts` — determinism (same session ⇒ deep-equal recipe);
+ **G-Trust property test**: for arbitrary `session.extensions`, every emitted
+ extension ∈ `allow` and any non-allowlisted ref is dropped (cannot appear);
+ no provider/model field ever present (G-Vendor); recipe validates against the Goose
+ recipe JSON-schema (vendored as a test fixture).
+- *Integration:* `enforcement` layer-3 run over a generated recipe fixture passes;
+ a recipe built from a representative session validates end-to-end.
+
+**Rollback.** Revert the slice; BuildStudio's `gooseRecipe` derived returns to the
+`:733-742` template (kept in git history). Schema addition is additive/optional, so
+existing catalog entries still validate.
+
+**Benchmark / threshold.**
+- **0** non-allowlisted extensions emitted across a 1,000-case property test.
+- **0** recipes carrying a provider/model field.
+- Recipe schema-validation: **100%** of generated recipes valid against the vendored
+ Goose recipe schema.
+- `enforcement` layer-3 passes on generated recipes; `npm test` green.
+
+---
+
+## SLICE C — Deeplink generator + "Open in Goose" UX
+
+**Goal.** Encode a recipe into `goose://recipe?config=` (UTF-8-safe), launch via
+an "Open in Goose" action with an **explain-before-launch** panel and a **copy-link**
+(and recipe-file) fallback when the deeplink exceeds the length budget.
+
+**Gate (entry):** Slice B merged (needs `GooseRecipe`).
+
+**Files to change.**
+- **New** `src/lib/goose-deeplink.ts` — pure encode/decode + length guard. The
+ UTF-8-safe base64 reuses the wrapper pattern in
+ `src/components/AccountWidget.astro:86-95` (encode) and its inverse for round-trip.
+- **New** `src/lib/goose-deeplink.test.ts`.
+- `src/components/BuildStudio.svelte` — the `handoff: 'goose'` branch (`:1357-1363`):
+ add "Open in Goose" (anchor to the deeplink), the explain panel, and the existing
+ copy/download fallbacks (`copy()` `:825`, `blobDownload()` `:828`). Keep the
+ `goose run --recipe …` file path as the >budget fallback.
+- (Optional) `src/components/CatalogExplorer.svelte` — a single-tool "Open in Goose"
+ using a minimal one-extension recipe (only if Slice A wired selection; else defer).
+
+**Contract / interface.**
+```ts
+// src/lib/goose-deeplink.ts
+export const DEEPLINK_MAX_BYTES = 8192; // budget; above this → fallback (tune in benchmark)
+export function encodeRecipeConfig(recipe: GooseRecipe): string; // UTF-8-safe base64
+export function decodeRecipeConfig(b64: string): GooseRecipe; // inverse (for tests)
+export function recipeDeeplink(recipe: GooseRecipe): {
+ url: string; // `goose://recipe?config=…`
+ bytes: number;
+ withinBudget: boolean; // false ⇒ UX shows copy-link/file fallback
+};
+export interface ExplainModel { // what the explain-before-launch panel renders
+ title: string;
+ extensions: Array<{ name: string; why: string }>; // rendered via textContent (G-NonDev)
+ willDoNothingUntilUserConsents: true; // copy: Goose "Trust & Execute" prompts at runtime
+}
+export function explainRecipe(recipe: GooseRecipe): ExplainModel;
+```
+
+**Test plan.**
+- *Unit:* `goose-deeplink.test.ts` — round-trip `decode(encode(r)) === r` incl.
+ **non-ASCII** (es/ar/emoji) titles/prompts (proves the UTF-8-safe wrapper); URL shape
+ `goose://recipe?config=`; `withinBudget` flips correctly at the boundary;
+ `explainRecipe` lists every extension (no omissions).
+- *Integration:* build a recipe (Slice B) → deeplink → decode → recipe equals input; a
+ jsdom/Playwright check that the explain panel renders every extension via `textContent`
+ (no HTML injection) and the fallback appears when over budget.
+
+**Rollback.** Revert; the `goose` handoff falls back to the existing recipe-file +
+`goose run` command (`:1360-1362`), which is unaffected.
+
+**Benchmark / threshold.**
+- Round-trip fidelity: **100%** over a fuzz corpus including multibyte text.
+- A representative full-stack recipe encodes to **< `DEEPLINK_MAX_BYTES`**; over-budget
+ recipes deterministically take the fallback path (asserted).
+- Explain panel enumerates **100%** of recipe extensions; **0** raw MCP config strings
+ in the DOM (asserted).
+
+---
+
+## SLICE D — Mentor persona SKILL + deterministic structured-reflection; retire the broker
+
+**Goal.** Ship the Yoda/Morpheus/J.A.R.V.I.S./Neo guidance as a Goose **persona SKILL**
+inside the recipe, and make the platform's ONLY model-output consumption a
+**deterministic** read of the recipe's `response.json_schema` JSON the user brings back
+— then **retire `/api/agent/kickoff`** so the platform makes zero inference calls
+(Path-A zero; `AUDIT.md §4`, Risk #1).
+
+**Gate (entry):** Slice B merged (needs `RESPONSE_JSON_SCHEMA` + recipe).
+
+**Files to change.**
+- **New** `src/lib/mentor-persona.ts` — builds the persona SKILL.md via the existing
+ `skillToMd` (`src/lib/skill-doc.ts`); included by the serializer (Slice B) as a recipe
+ skill/instruction. Pure, deterministic, no model.
+- `src/lib/mentor-engine.ts` — add `reflectFromResponse(json)` that deterministically
+ maps a `RESPONSE_JSON_SCHEMA`-conformant object → `SessionMentorReflection` (reuses
+ the existing `reflect()` constraint ordering `:155-186`). **No fetch, no LLM.**
+- `src/lib/mentor-engine.test.ts` — extend.
+- `src/components/BuildStudio.svelte` — replace the two broker calls
+ (`:904`, `:1012`) with: (a) generate recipe+deeplink (Slices B/C), (b) an
+ "import structured result" affordance that pastes/loads the Goose `response` JSON and
+ runs `reflectFromResponse` into `session.mentorReflection`.
+- `worker/index.ts` — **remove** `kickoffHandler` (`:182-207`), its route (`:463`), the
+ `PROVIDERS` table + router-allowlist (`:68-130`). (Keep `coerceUsageProfile`/pricing.)
+- `worker/tests/worker.test.ts` — drop the now-removed broker tests; keep the rest.
+- **New** `.github/workflows/path-a.yml` — ADDITIVE CI asserting zero model-host fetches
+ in `src/` + `worker/` (extends the signal set in
+ `enforcement/excluded-provider-signals.yaml`). Does NOT touch `verify.yml` (G-Gate).
+
+**Contract / interface.**
+```ts
+// src/lib/mentor-engine.ts (additive)
+export function reflectFromResponse(
+ response: unknown, // a RESPONSE_JSON_SCHEMA-shaped object (validated)
+): SessionMentorReflection; // deterministic; null-safe; never calls a model
+
+// src/lib/mentor-persona.ts
+export function mentorPersonaSkill(lang: 'en'|'es'|'ar'): DraftSkill; // → skillToMd → recipe
+```
+
+**Test plan.**
+- *Unit:* `reflectFromResponse` is deterministic and total (malformed input → safe empty
+ reflection, mirroring `coerceUsageProfile`'s defensive style); persona SKILL.md
+ validates (parse frontmatter with `yaml`, as in `src/lib/skill-doc.test.ts`).
+- *Integration:* recipe (Slice B) → its `response.json_schema` → a conformant sample →
+ `reflectFromResponse` → stable `SessionMentorReflection` written to the session.
+- *Gate test:* `path-a.yml` check fails if any model host appears in platform code.
+
+**Rollback.** The broker removal is its own commit; revert restores `/api/agent/kickoff`
+and the two client calls. The persona/reflection additions are independent and need not
+be reverted with it.
+
+**Benchmark / threshold.**
+- **G-PathA = 0**: zero model-host fetches in `src/` + `worker/` (new CI check passes).
+- `reflectFromResponse`: deterministic across a 500-case corpus; **0** throws on
+ malformed input.
+- No reachable `/api/agent/kickoff` route remains (worker route test asserts 404).
+
+---
+
+## SLICE E — Skills Creator authoring → session + Catalog sub-recipes
+
+**Goal.** Make the inline Skills Creator (Movement 3, `src/components/BuildStudio.svelte:962-981`)
+write authored skills into the session AND emit them as Goose **sub-recipes** the main
+recipe references, with an optional contribution path into the Catalog as vetted,
+reusable artifacts.
+
+**Gate (entry):** Slice B merged (sub-recipes are emitted by the serializer).
+
+**Files to change.**
+- `src/lib/skill-doc.ts` — add a sub-recipe projection of a `DraftSkill` (a skill →
+ a minimal `GooseRecipe` fragment / sub-recipe ref); pure.
+- `src/lib/skill-doc.test.ts` — extend.
+- `src/lib/goose-recipe.ts` (Slice B) — include `session.skills` as recipe
+ sub-recipes/activities.
+- `src/components/BuildStudio.svelte` — Skills Creator writes to `session.skills`
+ (already does `:297`) and now also surfaces "include as sub-recipe."
+- `src/schema/catalog.ts` — optional **skill** representation (parallel to the extension
+ work in Slice B) so a contributed skill can become a verified Catalog entry; gated by
+ `verification_status`.
+- `public/admin/config.yml` — add a skills collection for the contribution path (mirrors
+ the existing `recipes` collection at `:41-52`), so non-devs contribute via Sveltia CMS
+ → PR → enforcement.
+
+**Contract / interface.**
+```ts
+// src/lib/skill-doc.ts (additive)
+export function skillToSubRecipe(s: DraftSkill): GooseSubRecipe; // pure; no model
+export interface GooseSubRecipe { name: string; instructions: string; activities: string[] }
+```
+
+**Test plan.**
+- *Unit:* `skillToSubRecipe` deterministic; SKILL.md still valid YAML (existing
+ `skill-doc.test.ts` harness); a skill with hostile text stays safe (reuse the
+ `yamlDoubleQuoted` coverage already there).
+- *Integration:* author skill → `session.skills` → serializer emits it as a sub-recipe →
+ recipe still schema-valid (Slice B benchmark) and deeplink round-trips (Slice C).
+- *Contribution:* a CMS-authored skill file passes `enforcement` layer-3.
+
+**Rollback.** Revert; `session.skills` continues to flow into the starter zip as today
+(`src/components/BuildStudio.svelte:806-822`); no sub-recipe emission.
+
+**Benchmark / threshold.**
+- Authored skill round-trips session ⇄ SKILL.md ⇄ sub-recipe with **0** data loss.
+- Recipes including N authored sub-recipes stay schema-valid and **within the deeplink
+ budget** (else fallback) for N up to a documented cap.
+- `enforcement` layer-3 green on contributed skills.
+
+---
+
+## 3. Risks carried forward (from `AUDIT.md`) + new
+
+| # | Risk | Source | Handled in |
+|---|---|---|---|
+| R1 | Lone inference exception overlaps the pivot | AUDIT Risk #1 | **Slice D** (retire broker → Path-A zero) |
+| R2 | Extensions/skills not first-class; recipe too thin | AUDIT Risk #2 | **Slice B/E** (schema + serializer) |
+| R3 | Deeplink length limits truncate large recipes | new | **Slice C** budget + fallback |
+| R4 | Untrusted MCP tool descriptions / raw config leak | constraint #7 | **G-Trust + G-NonDev** (allowlist, textContent) |
+| R5 | Hardcoding a provider breaks model-agnosticism | constraint #6 | **Slice B** (no settings/provider; G-Vendor) |
+| R6 | Catalog translations absent (es/ar recipes) | AUDIT Risk #6 | recipe i18n via `lang` param (Slice C/D), tracked |
+| R7 | CSP still Report-Only; `goose://` scheme + new UX | AUDIT Risk #5 | verify CSP allows the scheme before enforce (note for CSP-promote work) |
+| R8 | "recipe" overload (policy recipe vs run recipe) | AUDIT Risk #7 | naming: `GooseRecipe`/sub-recipe vs `entry_type:'recipe'` policy recipes |
+
+---
+
+## 4. Definition of Done (whole pivot)
+
+- One typed build session; the four components read/write it (Slice A).
+- Pure client serializer → schema-valid, model-agnostic, allowlist-only Goose recipe
+ (Slice B).
+- `goose://recipe?config=` deeplink with explain-before-launch + fallback
+ (Slice C).
+- Mentor persona SKILL + deterministic structured-reflection; **`/api/agent/kickoff`
+ retired ⇒ platform inference calls = 0** (Slice D).
+- Skills Creator → session + sub-recipes + Catalog contribution path (Slice E).
+- All global gates (§1) hold: Path-A=0, vendor-clean, MCP-trust-boundary, additive CI,
+ determinism, suite green, build green.
+
+---
+
+## 5. Future enhancements (explicitly deferred — do NOT build now)
+
+- `goose serve` / ACP-over-HTTP / Goose TS SDK for a hosted, live agent loop
+ (experimental mid-2026). Would replace the bring-back-JSON step in Slice D with a live
+ session — design Slice D's `reflectFromResponse` so it can later accept a streamed
+ response without changing its deterministic contract.
+- Server-side recipe signing / shareable recipe links (needs the Worker; out of the
+ pure-client scope here).
+- Full catalog translation so es/ar recipes ship localized extension copy.
+
+---
+
+*STATUS (2026-07-11, E3): this plan was EXECUTED in full — Slices A–E all
+merged (recipe serializer, deeplink, mentor persona + deterministic reflection,
+sub-recipes) and the `/api/agent/kickoff` broker retired, making Path A = 0
+platform inference calls, CI-gated by `path-a.yml`. Archived as a historical
+record of the Goose pivot; §5's deferred items are tracked in BACKLOG.md.*
diff --git a/src/lib/goose-deeplink.ts b/src/lib/goose-deeplink.ts
index 6640622b..01f7b386 100644
--- a/src/lib/goose-deeplink.ts
+++ b/src/lib/goose-deeplink.ts
@@ -5,7 +5,7 @@
* explain-before-launch panel. No DOM, no network, deterministic.
*
* STABLE primitive only: recipe deeplinks. `goose serve` / ACP / the TS SDK are out of
- * scope (see PLAN.md §5).
+ * scope (see docs/archive/PLAN-goose-pivot.md §5).
*/
import type { GooseRecipe } from './goose-recipe.ts';