# PURE Boneyard — build spec & lane hand-off
_Owner: Design (Lane A, chat-Claude). Consumers: Claude Code (DB/RPC/cron), parallel Design lane._
_Stamp-first per CLAUDE.md. Live page: `acquisto.biz/boneyard.html` (admin-gated)._

## What's already LIVE (front-end, fail-soft today)
`boneyard.html` v2 is deployed and works **now** against a local fallback, and **auto-upgrades** the moment the table + RPCs below exist — no FE change needed.

- **Reads** `GET /rest/v1/pure_boneyard?select=*&order=parked_at.desc` (anon). If the table is absent/empty it unions a built-in seed so the known parked items always show. A live row **wins** over a same-`path` seed row.
- **Paste-a-URL → park in one step** (modal): normalizes a full URL or bare path, derives a title, lets the admin set the replacement target + category + reason, then calls `mcp_boneyard_park`. On RPC failure it queues locally (`localStorage`) and shows a "Queued → relayed to Code" state.
- **Per-item admin actions:** Restore (`mcp_boneyard_restore`), Edit (`mcp_boneyard_update`), Re-inject ribbon (`mcp_boneyard_inject`), Open target.
- **Run-a-sweep bar (admin):** buttons call `mcp_cron_run` with job ids `boneyard_orphan_scan`, `boneyard_ribbon_inject`, `boneyard_linkcheck`.
- **Gate:** admin-only (brand/tech/broker/staff/@acquisto), fail-open with no session for preview.
- Reachable from `/pure-home → Admin/Tech → Boneyard` and the Boneyard top bar (Home + Admin Console).

## CODE — please build (gated, service-role; anon read-only)

### 1. Table `public.pure_boneyard`
| col | type | notes |
|---|---|---|
| id | bigint identity PK | |
| path | text UNIQUE NOT NULL | normalized, leading slash, e.g. `/brand-admin.html` |
| title | text | |
| replacement | text default `/pure-home` | where the ribbon points |
| category | text check in (`overlay`,`legacy`,`experiment`,`deprecated`) | |
| reason | text | |
| status | text check in (`candidate`,`pending_ribbon`,`parked`,`restored`,`cold`) default `pending_ribbon` | |
| tags | text[] | |
| uses | int default 0 | restore/visit counter |
| parked_by | text | email/handle |
| parked_at | timestamptz default now() | |
| restored_at | timestamptz | |
**RLS:** `select` to anon (so the FE list works); all writes via the SECURITY-DEFINER RPCs below, which check the caller is a PURE admin. Seed the rows already parked (below).

### 2. RPCs (SECURITY DEFINER, admin-check inside, audit every call to `audit_log`)
- `mcp_boneyard_park(p_path,p_title,p_replacement,p_category,p_reason)` → upsert by path, `status='pending_ribbon'`, returns row. (Idempotent.)
- `mcp_boneyard_update(p_path,p_title,p_replacement,p_category,p_reason)` → edit in place.
- `mcp_boneyard_restore(p_path)` → `status='restored'`, set `restored_at`, **and** remove the ribbon script from the target page in the repo (reverse of inject).
- `mcp_boneyard_inject(p_path,p_replacement)` → enqueue/inject now (see cron).
- (optional) `mcp_boneyard_list()` — FE reads the table directly, so not required.

### 3. The "one step" completion — ribbon injection (needs Composio→GitHub, which chat-Claude FE cannot do)
Cron/job **`boneyard_ribbon_inject`** (nightly + on-demand via `mcp_cron_run`):
for each row `status='pending_ribbon'`, commit to `puremlstech/puremls @ Testin.MikeSupabase` an edit to `<path>` inserting before `</body>`:
```html
<!-- Parked in the Boneyard — superseded by {replacement} -->
<script src="/pure-boneyard.js" data-replacement="{replacement}"></script>
```
(skip if already present), then set `status='parked'`. This is what makes paste-a-URL truly one-step end-to-end.

### 4. Other crons (register so `mcp_cron_run(p_job)` accepts them)
- **`boneyard_orphan_scan`** (nightly) — **escape-prevention.** Crawl the `/pure-home` nav graph + sitemap; any top-level `*.html` not reachable from the merged nav → insert as `status='candidate'`. This is exactly how `brand-admin.html` "escaped"; the scan would have caught it.
- **`boneyard_linkcheck`** (weekly) — verify each parked `path` still returns 200 and its `replacement` resolves; flag broken in a `note`/status.
- **`boneyard_cold_review`** (monthly) — flag `parked` >90 days with `uses=0` as `status='cold'` for Mike's approval. **Never auto-delete.**

### 5. Seed these already-parked rows
`/icon-picker.html`→`/icons.html` (legacy) · `/brand-admin.html`→`/pure-brand.html` (legacy) · `/home.html`→`/pure-home` (legacy) · `/pure-merged.html`→`/pure-home` (legacy). Plus two feature-rows (not pages): `pure-auth.js · updateBadge()` identity chip (overlay) and `pure-home.html · .railnote` (experiment).

### 6. Memory keys (pure_memory)
- `merged.canonical_home = /pure-home`
- `merged.brand_workspace = /pure-brand`
- `boneyard.park_rule = "retire via mcp_boneyard_park / Park-a-URL — never silently delete or de-link"`
- `boneyard.ribbon = "/pure-boneyard.js with data-replacement; watermark BONEYARD"`
- `rule.orphan_escape = "every top-level page must be reachable from /pure-home for its owner's hat; boneyard_orphan_scan nominates strays"`
- `rule.role_coverage = "a manager workspace must be reachable by its OWNER's hat from the merged home (brand-admin failed: tech-only)"`

## DESIGN (parallel lane) — please know
- The Boneyard is the **canonical park surface.** Don't hardcode parked lists anywhere; read `pure_boneyard`.
- **Retire only via Park-a-URL / `mcp_boneyard_park`** — never just delete or de-link a page. Park → ribbon → keep.
- Canon: home = `/pure-home`, brand workspace = `/pure-brand`. Ribbon = `pure-boneyard.js` + `data-replacement`.
- Brand tokens only: navy `#172e55`, gold `#dccaaf`, green `#117f39`, Montserrat/Fraunces. No Inter, no off-brand navy `#11213a`.
