# Requirements: Bigscreen Website — Page Setup & Publishing Flow v1.0

**Defined:** 2026-05-21
**Core Value:** Max can publish and update a webpage to a live `bigscreenvr.com/<path>` URL via a CLI + Claude Code skill — without Jenkins, without Builder.io, without breaking what's already online.

## v1 Requirements

### Foundations & Verification (FND)

- [ ] **FND-01**: CloudFront distribution config (ID, behaviors, error-response rules) is documented; `/static-pages/*` + `/api/site/pages.json` carve-out plan locked
- [ ] **FND-02**: Arda OAuth flow choice confirmed (PKCE-loopback vs device-flow) based on Max's workflow; redirect URIs verified
- [ ] **FND-03**: `apps/admin_api` network reachability path for CLI traffic from public IPs is decided (SG open vs proxy vs VPN)
- [ ] **FND-04**: Render-layer decision (iframe vs scoped-inject) made after 1-day prototype against `bigscreen10`
- [ ] **FND-05**: Reserved-paths allowlist drafted and reviewed (covers `/`, `/account/*`, `/scan/*`, all existing Builder.io paths, all React-owned screens)
- [ ] **FND-06**: Registry SPOF mitigation strategy documented (baked-in fallback registry approach + SWR caching policy)

### Backend Registry (REG)

- [ ] **REG-01**: `site_pages` + `site_pages_audit` Postgres tables exist via migration in cloud monorepo's `apps/db_setup`
- [ ] **REG-02**: `apps/admin_api` exposes `POST /admin/site/pages/upload-url` returning presigned S3 PUT URLs (scoped to `website:write`)
- [ ] **REG-03**: `apps/admin_api` exposes `POST /admin/site/pages/publish` atomically flipping `currentSha` after asset reachability check
- [ ] **REG-04**: `apps/admin_api` exposes `GET /admin/site/pages` + `GET /admin/site/pages/:slug/history` (scoped to `website:read`)
- [ ] **REG-05**: `apps/admin_api` exposes `POST /admin/site/pages/rollback` flipping `currentSha` to a prior revision
- [ ] **REG-06**: Reserved-paths allowlist enforced server-side at every write; conflicting paths rejected with structured error
- [ ] **REG-07**: Every write produces an audit log entry distinguishing `actor=human` vs `actor=agent` via OAuth client identity
- [ ] **REG-08**: `apps/api` exposes public read snapshot at `/site/pages.json` behind bigscreenvr.com CORS umbrella

### OAuth Integration (AUTH)

- [ ] **AUTH-01**: New OAuth scopes `website:read` and `website:write` added to `auth/OAuthScopes.ts` in cloud monorepo
- [ ] **AUTH-02**: `bsweb-cli` OAuth client registered in Arda with confirmed redirect URI(s) per FND-02 outcome
- [ ] **AUTH-03**: All `/admin/site/*` endpoints gated by `requireScopeAndPolicy({ scopes: ["website:write"|"website:read"], policies: [Admin] })`
- [ ] **AUTH-04**: Per-actor write rate-limit enforced (prevents agent runaway from nuking many pages)

### CLI (CLI)

- [ ] **CLI-01**: `bsweb login` opens browser, completes Arda OAuth (PKCE-loopback or device-flow per FND-02), stores tokens in OS keychain via `@napi-rs/keyring`
- [ ] **CLI-02**: `bsweb logout` clears stored tokens
- [ ] **CLI-03**: `bsweb publish ./<dir>` validates manifest, content-hashes assets, requests presigned URLs, uploads to S3, calls publish endpoint
- [ ] **CLI-04**: `bsweb publish` is idempotent — re-publishing identical content+manifest is a no-op
- [ ] **CLI-05**: `bsweb publish` is dry-run by default when invoked by an LLM agent; live publish requires explicit `--apply` (or equivalent)
- [ ] **CLI-06**: `bsweb publish` prints diff before applying: list of files changed, URLs affected, byte deltas
- [ ] **CLI-07**: `bsweb validate ./<dir>` checks manifest schema, file references, asset paths without uploading
- [ ] **CLI-08**: `bsweb ls` lists published pages (slug, currentSha, lastModified, actor)
- [ ] **CLI-09**: `bsweb status <slug>` shows current published state + recent history
- [ ] **CLI-10**: `bsweb unpublish <slug>` requires typed-confirm token (`Type the exact path: /10years`) before removing from registry
- [ ] **CLI-11**: `bsweb rollback <slug> --to <sha>` flips `currentSha` to a prior revision
- [ ] **CLI-12**: Every CLI command supports `--json` producing stable, schema-versioned output
- [ ] **CLI-13**: All CLI errors are structured: `{code, message, suggestion, docs_url}`
- [ ] **CLI-14**: `bsweb schema --json` returns the manifest JSON schema for self-description
- [ ] **CLI-15**: Default-deny upload manifest — only files matching explicit `include:` list or detected manifest references are uploaded; `.env*`, `node_modules/`, `*.key`, `*.pem`, `.git/`, `*.tfstate` always excluded
- [ ] **CLI-16**: CLI declares `engines: { node: ">=20" }`; ships as installable Node binary (npm package or `pkg`-compiled)

### CloudFront + S3 (CDN)

- [ ] **CDN-01**: New `bigscreen-static-pages-prod` S3 bucket exists with OAC-only access
- [ ] **CDN-02**: S3 object keys follow `<slug>/<sha>/<path>` immutable pattern; content-hashed for atomic deploys
- [ ] **CDN-03**: S3 lifecycle policy expires unreferenced shas after 30 days
- [ ] **CDN-04**: CloudFront behavior for `/static-pages/*` routes to new S3 bucket via OAC, with CloudFront Function rewriting `<slug>` → `<currentSha>`
- [ ] **CDN-05**: CloudFront behavior for `/api/site/pages.json` routes to `apps/api` with 60s TTL + stale-while-revalidate
- [ ] **CDN-06**: New behaviors have higher precedence than the existing SPA catch-all
- [ ] **CDN-07**: Existing Custom Error Response (`403/404 → /index.html`) scoped to `Accept: text/html` OR excluded from `/static-pages/*` namespace
- [ ] **CDN-08**: Integration test verifies `curl https://<host>/static-pages/<slug>/index.html` returns correct Content-Type per file extension
- [ ] **CDN-09**: CloudFront invalidation on publish only targets `/api/site/pages.json*` (asset paths immutable, never need invalidation)

### SPA Dynamic Dispatcher (SPA)

- [ ] **SPA-01**: `react-router-dom` bumped to ≥6.28 in `package.json`; uses `createBrowserRouter` + `patchRoutesOnNavigation`
- [ ] **SPA-02**: New `src/components/DynamicPage/` directory with index + registry hook + mount component
- [ ] **SPA-03**: New dispatcher branch added in `src/App.js` ABOVE `builderIoFilter` — existing six-branch routing preserved untouched
- [ ] **SPA-04**: SPA fetches `/api/site/pages.json` on load with SWR caching + revalidate-on-focus
- [ ] **SPA-05**: SPA bundles a fallback registry at build time; live registry augments, not replaces — circuit-breaker after 2 consecutive fetch failures
- [ ] **SPA-06**: Matched dynamic pages render inside existing `<Page>` wrapper (Header + Footer)
- [ ] **SPA-07**: Render mode (iframe OR scoped-inject) implemented per FND-04 decision; CSS bleed prevented by structural isolation or publish-time CSS scoping
- [ ] **SPA-08**: Asset path resolution works for nested URLs (`<base href>` injection OR CLI path rewrite per FND-04)
- [ ] **SPA-09**: Resolution order documented + tested: exact-match registry → prefix-match registry → React-owned routes → `builderIoFilter` → 404
- [ ] **SPA-10**: All existing routes (`/`, `/about`, `/software`, `/privacypolicy`, `/enrollprivacy`, `/hardwareterms`, `/termsofservice`, `/scans/*`, `/account/*`, `/token2/*`, `/auth/*`, `/browser/*`) verified working via E2E smoke test after dispatcher change
- [ ] **SPA-11**: StrictMode double-mount handled cleanly (effect guards, cleanup on unmount, no duplicate event listeners)

### Content & Manifest (PAGE)

- [ ] **PAGE-01**: Manifest schema published, supports: `path`, `source`, `wrapper` (nav+footer | none), `title`, `meta`, `mountMode`, `namespace` (flat | subtree), optional `build`, optional `redirect_from`
- [ ] **PAGE-02**: Directory name *is* the slug (immutable per page directory)
- [ ] **PAGE-03**: `static-pages` repo created (separate repo) with at least one page (`10years/`)
- [ ] **PAGE-04**: `bigscreen10` content adapted into `static-pages/10years/` with manifest declaring wrap-in-nav/footer
- [ ] **PAGE-05**: `/10years` reachable on `www.bigscreenvr.com` wrapped in site nav + footer, via the new pipeline
- [ ] **PAGE-06**: At least one namespace sub-route (e.g., `/10years/intro` or similar) deployed to exercise subtree routing
- [ ] **PAGE-07**: A test mutation to `/10years` content deploys end-to-end via the new pipeline (no Jenkins, no Builder.io)
- [ ] **PAGE-08**: GitHub Actions workflow in `static-pages` repo runs optional `build` step + invokes `bsweb publish` on merge

### Claude Code Skill + Docs (SKILL)

- [ ] **SKILL-01**: `.claude/skills/publish-page/SKILL.md` exists in `static-pages` repo (Anthropic Agent Skills format)
- [ ] **SKILL-02**: SKILL.md enumerates forbidden operations with reasons (never publish to `/`, never `--force`, never edit `reserved-paths.json`)
- [ ] **SKILL-03**: SKILL.md instructs agent to always run `bsweb validate` + `bsweb publish --dry-run` before live publish
- [ ] **SKILL-04**: `CLAUDE.md` (or equivalent) in `static-pages` repo gives Max + his agent the day-to-day workflow
- [ ] **SKILL-05**: Reference docs (`manifest-schema.md`, `troubleshooting.md`) bundled with the skill

### Production Cutover (CUT)

- [ ] **CUT-01**: OAuth scopes + client promoted to production
- [ ] **CUT-02**: `apps/admin_api` + `apps/api` deployed with new endpoints in production
- [ ] **CUT-03**: CloudFront behaviors deployed to production distribution
- [ ] **CUT-04**: SPA changes shipped to production via Jenkins (the **last** Jenkins deploy required for the milestone)
- [ ] **CUT-05**: `/10years` live on `www.bigscreenvr.com` end-to-end via new pipeline; verified
- [ ] **CUT-06**: Rollback flow smoke-tested in production
- [ ] **CUT-07**: `bigscreen10` VPS + nginx + direct GHA deploy retired

## v2 Requirements

Deferred to future release. Tracked but not in current roadmap.

### Differentiators & Polish

- **CLI-V2-01**: `bsweb diff <slug>` shows local-vs-published changes
- **CLI-V2-02**: `bsweb logs <slug>` views operation log
- **PAGE-V2-01**: Draft URLs on prod CDN (preview-without-publish)
- **CDN-V2-01**: CLI does asset hash-stamping (further reduce invalidation needs)
- **AUTH-V2-01**: Dedicated `SiteEditor` Arda role (narrower than `Admin`)
- **CDN-V2-02**: Image optimization at publish time
- **PAGE-V2-02**: Scheduled publish / expiry
- **PAGE-V2-03**: Multi-environment (staging → prod) promotion
- **SPA-V2-01**: Builder.io routing subsumed into the registry (one source of truth for all routes)
- **SEO-V2-01**: Dynamic sitemap endpoint generated from registry + Builder.io + hardcoded routes
- **SEO-V2-02**: SPA route-change pageview emission (fixes pre-existing bug for new pages and all SPA routes)
- **SEO-V2-03**: react-helmet-async per-page `<title>` + `<meta>` + canonical management
- **SEO-V2-04**: Edge-prerender for known bot UAs (OG/Twitter card scrapers)
- **SEC-V2-01**: CSP report-only rollout; per-page `externalOrigins` allowlist
- **SEC-V2-02**: SRI required for external scripts/styles ≥ size threshold

## Out of Scope

Explicitly excluded. Documented to prevent scope creep.

| Feature | Reason |
|---------|--------|
| Web GUI / admin portal for Max | Max uses LLM agents + CLI; GUI cost not justified |
| Migrating existing Builder.io pages | Builder.io stays as-is; framework is *capable* of routing to Builder later (v2) |
| Replacing Jenkins for SPA code deploys | Jenkins remains gatekeeper for SPA application code; security-critical |
| Shopify storefront changes | Different backend; out of scope |
| Multi-tenant publishing (multiple marketers) | Single-user (Max) for v1; multi-actor logic deferred |
| Multi-user RBAC beyond Admin policy | `SiteEditor` role deferred to v2 |
| A/B testing infrastructure | Out of scope; defer entirely |
| Site search | Not a publishing-pipeline concern |
| Webhooks on publish events | Defer until external systems need to listen |
| i18n routing for static pages | SPA's existing i18n covers Builder.io only; not extended |
| Real-time collaborative editing | Not a CMS — git is the source of truth |
| Marketer-supplied build commands executed on Bigscreen infra | Builds run in GitHub Actions only; eliminates sandboxing problem |
| Custom CDN per page | One distribution, two new behaviors — no per-page CDN config |
| SPA-level error monitoring beyond existing | Pre-existing concern; not a milestone deliverable |

## Traceability

Populated by gsd-roadmapper during phase creation (2026-05-21).

| Requirement | Phase | Status |
|-------------|-------|--------|
| FND-01 | Phase 1 | Pending |
| FND-02 | Phase 1 | Pending |
| FND-03 | Phase 1 | Pending |
| FND-04 | Phase 1 | Pending |
| FND-05 | Phase 1 | Pending |
| FND-06 | Phase 1 | Pending |
| REG-01 | Phase 1 | Pending |
| REG-02 | Phase 1 | Pending |
| REG-03 | Phase 1 | Pending |
| REG-04 | Phase 1 | Pending |
| REG-05 | Phase 1 | Pending |
| REG-06 | Phase 1 | Pending |
| REG-07 | Phase 1 | Pending |
| REG-08 | Phase 1 | Pending |
| AUTH-01 | Phase 1 | Pending |
| AUTH-02 | Phase 1 | Pending |
| AUTH-03 | Phase 1 | Pending |
| AUTH-04 | Phase 1 | Pending |
| CLI-01 | Phase 2 | Pending |
| CLI-02 | Phase 2 | Pending |
| CLI-03 | Phase 2 | Pending |
| CLI-04 | Phase 2 | Pending |
| CLI-05 | Phase 2 | Pending |
| CLI-06 | Phase 2 | Pending |
| CLI-07 | Phase 2 | Pending |
| CLI-08 | Phase 2 | Pending |
| CLI-09 | Phase 2 | Pending |
| CLI-10 | Phase 2 | Pending |
| CLI-11 | Phase 2 | Pending |
| CLI-12 | Phase 2 | Pending |
| CLI-13 | Phase 2 | Pending |
| CLI-14 | Phase 2 | Pending |
| CLI-15 | Phase 2 | Pending |
| CLI-16 | Phase 2 | Pending |
| CDN-01 | Phase 2 | Pending |
| CDN-02 | Phase 2 | Pending |
| CDN-03 | Phase 2 | Pending |
| CDN-04 | Phase 2 | Pending |
| CDN-05 | Phase 2 | Pending |
| CDN-06 | Phase 2 | Pending |
| CDN-07 | Phase 2 | Pending |
| CDN-08 | Phase 2 | Pending |
| CDN-09 | Phase 2 | Pending |
| SPA-01 | Phase 3 | Pending |
| SPA-02 | Phase 3 | Pending |
| SPA-03 | Phase 3 | Pending |
| SPA-04 | Phase 3 | Pending |
| SPA-05 | Phase 3 | Pending |
| SPA-06 | Phase 3 | Pending |
| SPA-07 | Phase 3 | Pending |
| SPA-08 | Phase 3 | Pending |
| SPA-09 | Phase 3 | Pending |
| SPA-10 | Phase 3 | Pending |
| SPA-11 | Phase 3 | Pending |
| PAGE-01 | Phase 3 | Pending |
| PAGE-02 | Phase 3 | Pending |
| PAGE-03 | Phase 3 | Pending |
| PAGE-04 | Phase 3 | Pending |
| PAGE-05 | Phase 3 | Pending |
| PAGE-06 | Phase 3 | Pending |
| PAGE-07 | Phase 3 | Pending |
| PAGE-08 | Phase 3 | Pending |
| CUT-01 | Phase 4 | Pending |
| CUT-02 | Phase 4 | Pending |
| CUT-03 | Phase 4 | Pending |
| CUT-04 | Phase 4 | Pending |
| CUT-05 | Phase 4 | Pending |
| CUT-06 | Phase 4 | Pending |
| CUT-07 | Phase 4 | Pending |
| SKILL-01 | Phase 4 | Pending |
| SKILL-02 | Phase 4 | Pending |
| SKILL-03 | Phase 4 | Pending |
| SKILL-04 | Phase 4 | Pending |
| SKILL-05 | Phase 4 | Pending |

**Coverage:**
- v1 requirements: 74 total (FND:6 + REG:8 + AUTH:4 + CLI:16 + CDN:9 + SPA:11 + PAGE:8 + SKILL:5 + CUT:7)
- Mapped to phases: 74 ✓
- Unmapped: 0 ✓

---
*Requirements defined: 2026-05-21*
*Last updated: 2026-05-21 after roadmap creation (4 coarse phases)*
