# Walking Skeleton — Bigscreen Website Page-Publishing Pipeline

**Phase:** 1 (Foundations, Registry & OAuth)
**Generated:** 2026-05-21

## Capability Proven End-to-End

> A developer can run the cloud monorepo locally, hit `POST /admin/site/pages/publish` with a `website:write` bearer token, watch a row appear in the `site_pages` Postgres table with a matching `site_pages_audit` row (actor=human|agent), AND hit `GET /site/pages.json` on `apps/api` (no token) to see the same slug appear in the public read snapshot — all against dev Postgres + LocalStack S3. Six external-system unknowns (FND-01..06) are closed in a written `VERIFICATION-MEMO.md`. A Shadow-DOM mount prototype against `bigscreen10` in this `website` repo proves visual fidelity + scroll behavior of the chosen render-layer isolation (D-04).

The Phase 1 "user" is a backend developer, NOT yet Max — Max becomes the user in Phase 2 when `bsweb` CLI ships. Phase 1 proves the backend contract Max's CLI will speak.

## Architectural Decisions

| Decision | Choice | Rationale |
|---|---|---|
| Backend hosting (where the work lives) | `cloud` monorepo (D-09 mirror approach) | Reuses existing `requireScopeAndPolicy`, OAuth provider (Arda), CORS umbrella on `apps/api`; no new microservice (RESEARCH §Anti-Patterns) |
| Write/read service split | Writes on `apps/admin_api` (IP-restricted), reads on `apps/api` (public LB + CORS) | RESEARCH §Pattern 3 / Anti-Pattern 2 — public reads MUST NOT mount on admin_api |
| Data model | Single mutable pointer (`site_pages`) + append-only audit log (`site_pages_audit`) | D-06; industry standard for edge-mutable-pointer-over-immutable-content deploy systems |
| Authorization | `requireScopeAndPolicy({ scopes: ['website:write'\|'website:read'], policies: [Admin] })` on every `/admin/site/*` route | AUTH-03 locked; D-09 reuse rule; do not invent new auth check |
| OAuth flow for `bsweb-cli` | To be locked by FND-02 closure (default PKCE-loopback) | RESEARCH §Open Questions 2 |
| Render-layer isolation | Shadow DOM web component scoped-inject (D-04) | D-03/D-04 locked; SPA `<Page>` wrapper cohesion + CSS isolation |
| Reserved-paths mechanism | TypeScript constant `reservedPaths.ts` in cloud repo (D-11 planner choice) | Version-controlled, PR-reviewable, startup-loaded; deferred DB-table mechanism unless marketing needs runtime additions |
| Rate-limit | `rate-limiter-flexible` keyed by OAuth JWT `sub`, 30 writes / 5 min | D-12 + RESEARCH Pitfall 5 (NEVER key by IP) |
| Migration runner | Match cloud monorepo's existing `apps/db_setup` convention (sequential file naming OR migrations table — verify before coding) | RESEARCH A1 + Pitfall 7; do not introduce a new migration library |
| Test framework | Match cloud monorepo's existing `tests/*` convention (verify before writing first test) | RESEARCH §Validation Architecture |
| Cross-repo traceability | `[cloud@<sha>]` annotation in every commit message in THIS repo per task milestone; no submodule | D-09/D-10 |
| Package gating | Every `npm install` in cloud monorepo behind a `checkpoint:human-verify` task (slopcheck unavailable) | RESEARCH §Package Legitimacy Audit |

## Stack Touched in Phase 1

- [x] Project scaffold — Cloud monorepo workspaces already exist; no new scaffold. This `website` repo: no scaffold change; `src/_prototype/` is the only new directory.
- [x] Routing — Six new admin routes on `apps/admin_api`: `POST /admin/site/pages/upload-url`, `POST /admin/site/pages/publish`, `POST /admin/site/pages/rollback`, `POST /admin/site/pages/unpublish`, `GET /admin/site/pages`, `GET /admin/site/pages/:slug/history`. One new public route on `apps/api`: `GET /site/pages.json`.
- [x] Database — `site_pages` (mutable pointer) + `site_pages_audit` (append-only). Real read in `GET /admin/site/pages` and `GET /site/pages.json`. Real write in `POST /admin/site/pages/publish` (transactional INSERT/UPSERT to `site_pages` plus INSERT to `site_pages_audit`).
- [x] UI — Throwaway Shadow-DOM mount prototype in this repo at `src/_prototype/ShadowMount.js`, fetching `bigscreen10`'s built `dist/` index.html and rendering it inside a `react-shadow` boundary inside the existing `<Page>` wrapper. Reachable only when `process.env.REACT_APP_ENABLE_PROTOTYPE === '1'`. Production `<DynamicPage>` does NOT ship in Phase 1.
- [x] Deployment — Cloud monorepo backend slice runs locally against dev Postgres + LocalStack S3 only. No CloudFront, no real S3, no production deploy. Documented in `VERIFICATION-MEMO.md` how the existing cloud-monorepo dev runner stands up these services (typically `docker-compose` per cloud convention — verify).

## Out of Scope (Deferred to Later Slices)

- **CLI (`bsweb`)** — Phase 2. Phase 1 ships the backend contract the CLI will call; no CLI binary exists yet.
- **CloudFront behaviors** — Phase 2 (CDN-01..09). Phase 1 only DOCUMENTS the planned `/static-pages/*` + `/api/site/pages.json` carve-out in `VERIFICATION-MEMO.md` (FND-01 closure); no actual CloudFront config is changed.
- **Real S3 bucket** — Phase 2 (CDN-01). Phase 1 uses LocalStack only.
- **SPA `<DynamicPage>` component** — Phase 3 (SPA-01..11). The Shadow DOM prototype in Phase 1 is throwaway proof, not the production mount.
- **`/10years` content adaptation** — Phase 3 (PAGE-03/04). Prototype consumes `bigscreen10/dist/` as-is; no adaptation.
- **`SiteEditor` Arda role** — Deferred to v2 (AUTH-V2-01). v1 uses existing `Admin` policy.
- **Per-actor cryptographic identity** — Deferred to v2 (D-07). Actor distinction is CLI-included-claim, `verified: false`.
- **DB-table reserved-paths mechanism** — Deferred unless marketing needs runtime additions (D-11). v1 ships `reservedPaths.ts` constant.
- **GitHub Actions workflow in static-pages repo** — Phase 3 (PAGE-08). Phase 1 makes no `static-pages` repo changes.
- **Production cutover, rollback smoke test, VPS retirement, Claude Skill** — Phase 4 (CUT-01..07, SKILL-01..05).

## Subsequent Slice Plan

Each later phase adds one vertical slice on top of this skeleton without altering its architectural decisions:

- **Phase 2:** Max (or a developer) can run `bsweb publish ./<dir>` end-to-end against the Phase 1 backend, with content reaching a real S3 bucket via CloudFront's new `/static-pages/*` behavior and the registry served from `/api/site/pages.json` via CloudFront's new behavior — both with precedence over the SPA catch-all.
- **Phase 3:** A user visiting `/10years` on the dev SPA sees the adapted `bigscreen10` content inside the site nav + footer, with the SPA dispatcher branch fetching `/api/site/pages.json` at runtime and the dynamic-routes branch sitting above `builderIoFilter`. A test mutation to `/10years` content deploys end-to-end via the new pipeline in dev.
- **Phase 4:** `https://www.bigscreenvr.com/10years` is live in production end-to-end; rollback smoke-tested; `bigscreen10` VPS retired; `.claude/skills/publish-page/SKILL.md` ships in `static-pages` repo.
