Redpoint Engine — status
Snapshot as of 2026-07-17. Headless multi-tenant middleware for human-edited
social-video jobs. Tenant #1: Wakecut (yachting); tenant #2: expocut (pure-API
seat, live). See CLAUDE.md, docs/ARCHITECTURE.md, docs/BUILD-PLAN.md.
Editor-program wave (2026-07-17) — closeout batch #65–#79, live DB at 0039
All of #65–#79 merged to main (a1cbd64); migrations 0034–0039 pasted to
the live DB and probe-verified. This wave completed Phase 10, delivered the
Phase 11 commission dry-run + arming-prep (arming still awaits an operator
money ruling), and shipped a self-contained editor-program (client voice
notes, editor ratings, the specify-your-editor paid upsell, and a config-gated
supervision engine).
Merged this batch (PR → what shipped):
- #65
feat(brief)— optional client voice note at intake, played in the editor workspace (migration 0034,files.kind='voice_note'). - #66
feat(webhooks)— Phase 10 slice 10a-2: review/delivery handle in webhook payloads (data.reviewon draft.ready + →CLIENT_REVIEW;data.deliveryon job.delivered + →DELIVERED).docs/PARTNER-API.md§04 updated this wave to document the new handles. - #67
docs(api)— the tier-3 partner API reference (docs/PARTNER-API.md) landed. - #68
feat(payouts)— Phase 11 Option A partner-tenant commission, dry-run-first (migration 0035tenant_connect/partner_commissions;src/lib/partner-commission.ts; two-gate arming). - #69
docs(commission)— Option A + B topology design of record (docs/COMMISSION-TOPOLOGY.md). - #70
docs(commission)— Option B white-label build plan + decisions (docs/OPTION-B-BUILD-PLAN.md, decisions D1–D10). - #71
feat(payouts)— Phase 11 commission arming-prep (migration 0036): EURneeds_fx_reviewholds, a P&L ledger leg for partner commissions, and fee-unset surfacing. Closes the code-side arming blockers; arming itself awaits the operator split/VAT ruling. - #72
feat(notify)— editor notification when a client appends a voice note (voice-brief change reaches the editor). - #73
docs(editors)— review-system + specify-editor build plan & decisions (docs/EDITOR-REVIEW-AND-SELECTION-PLAN.md). - #74
feat(editors)— 1A: client editor-rating store +rate_jobSECURITY DEFINER RPC + aggregate (migration 0037). - #75
feat(dispatch)— specify-your-editor paid upsell + refund-on-miss (migration 0038): paidpackage_extrasre-request on the continuity-hold machinery, cron-swept CAS outcome (jobs.specify_editor_status), Stripe refund on a miss. LIVE on wakecut at £10, 48h hold; EUR deliberately hidden until priced. - #76
feat(editors)— 1C: pure composite-score + config-driven supervision policy engine (src/lib/supervision.ts, no I/O, two-critic). - #77
feat(editors)— 1B: client "rate your edit" capture surface (star rating + optional comment, pseudonym-only, skippable). - #78
feat(editors)— 1D: supervision persistence + dispatch hooks + audit + confirm queue (migration 0039editor_supervision/supervisory_actions; per-rating refresh + daily cron backstop; per-tenant advisory-lock + CAS + circuit breaker inrecord_supervision; two-critic). - #79
feat(editors)— DX: surface the specify-editor upsell after a high (5★) rating (the review→upsell tie-in, using the client's own rating only).
Live vs dormant vs in-PR:
- LIVE: client voice notes (+ editor notify + refund-window retention); client editor ratings (capture UI + store); the specify-your-editor upsell (£10 / 48h on wakecut, refund-on-miss); the 5★→upsell tie-in.
- DORMANT (config-gated, ships OFF): the editor supervision engine —
dispatch_config.supervision.enableddefaults OFF andauto_actionsdefaults OFF (human-confirm). Persistence, dispatch hooks, per-rating refresh, the daily cron backstop, and the audit trail are all wired; with the layer disabled (every tenant today) the board and assign paths behave byte-identically to before. Operator enables + tunes the config when ready. - IN A PR, NOT MERGED: packet 1E — the ops supervisory surface (roster + scorecard + manual warn/throttle/suspend/reinstate + confirm queue + per-editor audit log). In flight; not on main yet. Until it merges, supervision has no operator UI (config-only).
Live DB level with main at 0039 — migrations 0034–0039 pasted and probe-verified this wave. Next free number is 0040.
Phases 1–9 COMPLETE (2026-07-16) — closeout batch #56–#62
All nine build phases are done and live. Batch #56–#62 merged to main
(414439c), migrations through 0033 applied to the live DB, and the
Phase 9 ffmpeg transcode worker is deployed on Fly for the first time.
Merged this batch:
- #56
feat(erasure)— Phase 9 Slice C, GDPR Art.17 right-to-erasure (migration 0031). - #57
fix(transcode)— route the tenanttheme.logo_urlfetch through the SSRF guard. - #58
feat(webhooks)— emitjob.cancelledon →CANCELLED (lifecycle parity for headless tenants) + fix webhook dispatch for email-less tenants. - #59
docs(roadmap)—docs/ROADMAP-PHASE-10-PLUS.md(Phase 10+, seat-business ordered). - #60
feat(api)— Phase 10 Slice 10a-1: headless review-player endpointGET /api/v1/jobs/[jobId]/previews+ draft↔preview link (migration 0033). - #61
feat(api)— Phase 10 Slice 10b: per-orderreturn_urlallowlist for domainless pure-API tenants. - #62
feat(marketing)—/pro-editorsprofessional-editor recruitment page +editor_applicationscapture (migration 0032).
Live DB level with main at 0033 — probe-verified 2026-07-16: migrations
0027–0033 all applied (objects answer 200); the expocut (tenant #2,
pure-API) row is active; anon is 401 on editor_applications and
erasure_requests (0005 default-deny holds for the new tables).
Fly — transcode node LIVE (first deploy): redpoint-transcode (ams,
performance-1x / 2GB, 60GB encrypted scratch volume, 1 machine, checks 1/1,
/healthz 200). Secrets set incl. APP_ORIGIN=https://wakecut.com and a
rotated CRON_SECRET (one shared value across Vercel + .env.local + the
transcode worker). redpoint-ingest redeployed with the #57 SSRF guard
(1 machine, checks 1/1). Config ingest-service/fly.transcode.toml; the app is
a sibling of redpoint-ingest — keep both at 1 machine (fly deploy
defaults to 2).
Phase 10 — COMPLETE (2026-07-17)
Roadmap: docs/ROADMAP-PHASE-10-PLUS.md (seat-business ordered). All slices
merged: 10a-1 (#60), 10b (#61), and 10a-2 (#66) — the review/delivery
handle now rides the webhook payloads (data.review / data.delivery, through
the same API_JOB_FIELDS allowlist; documented in docs/PARTNER-API.md §04) —
plus the client voice-brief (files.kind='voice_note', migration 0034, #65).
Phase 11 landed its dry-run + arming-prep (Option A commission, #68/#71,
migrations 0035–0036); arming awaits the operator money ruling (below).
Operator-owed (current, 2026-07-17):
- Enable + tune the supervision config when ready —
dispatch_config.supervisionships OFF (enabled:false,auto_actions:false); set thresholds/weights/ladder and flip it on per tenant after validating on real ratings (dormant until then). - Set the EUR price for the specify-your-editor upsell (GBP is live at £10 on wakecut; EUR is deliberately hidden until priced).
- Arm Phase 11 commission: the code-side blockers are CLOSED (#71 — ledger leg,
EUR
needs_fx_reviewholds, fee-unset surfacing). Arming now needs only the operator/accountant ruling:platform_fee_bpssplit + VAT topology, then flip the two arming gates. - Option-B (white-label) decisions D1–D10 (
docs/OPTION-B-BUILD-PLAN.md§1) — gate any merchant-topology build; unanswered. - Merge packet 1E (ops supervisory surface) once its PR lands — supervision has no operator UI until then.
- Mint the expocut
rpk_API key (formally closes the Phase 8 gate). - Seed wakecut's
pro_editor_landingdeck + real{X}%payout /{N}reply-timeline for/pro-editors. - Real
survey_url+discord_urlon/editors(still#survey/#discord). - Register the expocut partner return-origins
(
billing_config.allowed_return_origins) for #61's per-orderreturn_urlallowlist. - Rotate the 2 chat-transited Resend keys (carried from the mobile-apps section).
- Stripe live activation (still test mode).
- Product ruling that gates Phase 12: pushback-email content/recipients + surge-dispatch rules.
Mobile apps (2026-07-11) — installable client + editor iOS apps, LIVE
Both customer-visible workflows ship as installable home-screen web apps (PWA packaging — deliberately no App Store/Capacitor: no Xcode on the build machine, and Apple guideline 3.1.1 makes an in-app Stripe checkout a rejection risk). Merged via the full two-critic pipeline, one PR per invariant:
- #27
test(e2e): session cache per user across workers/runs — killed the systematic GoTrue 429 that was failing every full e2e run (also unblocks the open #25 rerun). Residuals:payouts-rls.spec.tsmints outside the cache; ~3 concurrent CI runs could still 429 (reduced, not eliminated). - #28
feat(auth): additive email OTP-code sign-in on /login (installed standalone apps have storage isolated from Safari — magic links alone strand them). Single shared redirect policy (src/lib/auth-redirect.ts, backslash open-redirect hardened + unit-pinned). Live OTP is 8 digits on this project. - #29
feat(pwa): client app (start/, identity 100% DB-driven from the tenant row — live-verified serving "Wakecut" + theme colors) + editor app (start/workspace, engine-dark), engine-neutral icons, iOS standalone meta/safe-area, per-app install hint. Gate: sentinel-binding manifest spec + icon-200 checks (tests/e2e/pwa-manifests.spec.ts).
Operator-owed to finish: (1) DONE
2026-07-14 and verified live end-to-end: custom SMTP via Resend
({{ .Token }} email templatesmtp.resend.com:465, sender hello@wakecut.com, domain DKIM/SPF verified,
Cloudflare auto-configured) + code block in both templates (Magic link or OTP,
Confirm signup) — operator signed in with the emailed 8-digit code. This also
closes Phase 7's Resend prerequisite (account + verified domain + working
SMTP). Note: two Resend API keys transited chat/screenshots during setup —
rotate once more and swap into the Supabase SMTP field. Still owed:
(2) install both apps from an iPhone (Share → Add to Home Screen on / and on
/workspace) and visually QA standalone mode; (3) optional per-tenant app
icons via tenants.theme URL keys. Ops note: vercel-secrets.sh now actively
unsets the retired NEXT_PUBLIC_BASE_PATH (69b379f) — a re-run would
previously have 404'd the live site.
Incident log
- 2026-07-14 — sentinel leak on live /editors (resolved same day). A local
e2e run of the editor-landing sentinel spec died between its tenant-row write
and afterAll restore, leaving SENTINEL copy live (one project serves dev AND
prod). Operator spotted it pre-UAT; restored surgically from migration 0007's
canonical deck via service-role PATCH (only
marketing_config.editor_landingwas contaminated; name/theme/contact clean; verified live). Structural fix LANDED 2026-07-14 (PR #37, merged + deployed + live-verified): dedicated e2e tenant (e2e-tenant, fixed id…e2, active, unroutablee2e-tenant.invalid; migration 0019 + seed + TS mirror, applied to the live DB incl. its pool package…e3);rest()guard throws on any non-GET totenantsnot pinned to the e2e tenant (matcher AND wiring unit-pinned, falsification-proven); middleware honours?tenant=/x-tenant-slugonly in dev or underE2E_ALLOW_TENANT_OVERRIDE=1(CI) and strips inbound spoofs — prod ignores both (probe-verified post-deploy with the row ACTIVE); a wakecut-row drift tripwire (snapshot in global-setup, byte-compare in global-teardown) alarms on any contamination; all 8 tenant-mutating specs converted (editor-landing, home-landing, analytics-consent, pwa-manifests, pool-pushback, continuity, extras + #35's pool-pushback-intervention), each confirmed EXECUTED on CI. During the build a transient exposure existed (proof-run planted the active row while main's ungated middleware was still deployed; wakecut.com?tenant= briefly served harness pages) — conductor deactivated the row same day; re-activated only after the gated middleware deployed and the prod probe held. Local tenant-mutating e2e is safe again. Known residuals (accepted): e2e jobs/packages created under the live wakecut tenant by finance/orders/createJob() specs (row-scoped, not tenant-scoped, isolation — finance conversion spawned as a follow-up task); e2e tenant row is anon-SELECT-visible and shows as a chip in prod /ops filters (dummy data).
Phase status
| Phase | Scope | State |
|---|---|---|
| 1 | Foundation: tenant-aware schema, Supabase SSR auth, tenant resolution, RLS | ✅ verified (13/13 RLS + tenant checks) |
| 2 | Upload pipeline: Uppy + presigned multipart → R2, resumable, signed download | ✅ verified (R2 round-trip 6/6, download gate 4/4, CORS confirmed). Browser multi-GB pause/resume run is the only manual item left |
| 2b | Remote ingest (pull from Dropbox/Drive/transfer links → R2) | 🚧 in progress — see below |
| 3 | Catalogue → Stripe Checkout (manual capture) → verified webhook → NEW job; billing_config dispatch; rush; VAT | ✅ verified (13/13 e2e + billing units 8/8) |
| 4 | Ops console + transitionJob() |
✅ built + reviewed; gate loop automated as 6 UI e2e tests (ops-loop.spec.ts) — operator sign-off run still owed |
| 5 | Editor workspace | ✅ basic workspace built (/workspace, dark surface) + job board (/board, PR #21): swipe/claim pool jobs, hybrid dispatch (packages.dispatch_mode + jobs.dispatch_override), auto deadline from package turnaround. Migration 0006_job_board.sql applied (live DB is one project serving dev AND prod, now at 0033) |
| 6 | Client review, approval, capture, Connect payout, delivery | ✅ COMPLETE — built + merged 2026-07-09 late (#23 home / #24 money core / #26 Connect+payout surfaces; #25 review page merged after its CI rate-limit rerun). Migration 0008 APPLIED live. Each PR passed two-critic adversarial review with falsification runs; review caught+fixed: free-finals leak, APPROVED-uncaptured divergence (revert-on-capture-failure), double-pay on retry (classified idempotency), untested CAS lock (+10-min expiry), onboarding double-account race. Operator gate still owed: full NEW→DELIVERED happy path in Stripe test mode incl. a real Connect transfer (editor must onboard via /workspace first). Named residuals: re-authorization flow for expired auths (manual capture ~7-day window — matters if turnaround+review exceeds it), void_failed/capture_failed alerting (Phase 7), e2e session-mint caching (auth 429 pressure), paid-revision billing mechanism |
| 7 | Resend notifications + SLA machinery | ✅ COMPLETE — notifications (0020/0021) + Web Push third channel (#51) + SLA machinery |
| 8 | Headless tenant API + signed webhooks + theming + 2nd tenant | ✅ COMPLETE — tenant API keys (0024), /api/v1 orders (0025), signed webhooks (0026), 2nd tenant expocut (0027, active). Formal gate closes when the operator mints the expocut rpk_ key |
| 9 | ffmpeg previews, retention cron, right-to-erasure, quality counters | ✅ COMPLETE — transcode worker (0029) deployed on Fly (redpoint-transcode), retention cron (0030), right-to-erasure (0031, #56), quality counters (#52) |
Phase 2b (remote ingest) — remaining work
Done: migrations 0003_ingest.sql (ingests table, files.ingest_id) and
0004_ingest_source_url.sql (share-token hardening: source_url column-revoked
from clients+editors, redacted source_display, admin-only accessor fn);
src/lib/ingest.ts (provider detect, classify, redact, SSRF pre-check); ingest
API (/api/ingests create+list, /api/ingests/[id]/cancel); Companion sources
wired into the uploader (gated by a Companion URL); IngestPanel UI (paste link
- polling status chips + cancel) on the upload page; footage-readiness helper
(
src/lib/readiness.ts, surfaced on NEW jobs;transitionJob()enforces at Phase 4); standaloneingest-service/(Companion server + resolver worker: DNS-validating SSRF guard, wetransfer/swisstransfer/icloud_album/url resolvers, R2 multipart streaming, ×3 retries → failed / queued_manual, size cap) with Dockerfile + fly.toml + README; ingest env vars in both.env.examples. Verified locally: typecheck + build green, Companion boots (healthz 200), SSRF guard rejects localhost/metadata-IP/ipv6/.internal/dns-to-private probes.
Reviewed (branch phase-2b-ingest): two-critic adversarial review, findings
verified against HEAD and landed. cd ingest-service && npm test runs 67 gate
checks (SSRF probe classes incl. mapped-IPv6 + dns-to-private + redirect-hop
re-validation; content-type matrix; size cap; resolver link parsing). Fixes
landed: mapped-IPv6 hex-form bypass, SSRF never-retry classification, capped
metadata JSON reads, explicit multipart abort, clean-slate multi-file retry,
prod fail-fast on missing COMPANION_UPLOAD_URLS, pruning session store.
Known residuals (recorded, not blocking): no automated tests for the Supabase-coupled paths (worker state transitions, cancel race, RLS/redaction, IngestPanel React behaviour) — covered by the post-deploy acceptance gate rather than a local test framework; Tier-2 resolver fixtures are shape-based (unofficial endpoints verified only at the parsing layer until e2e).
Deployed 2026-07-03: Fly app redpoint-ingest (ams, 1 machine — the worker
is single-queue by design; fly deploy defaults to 2, scale back down), secrets
via ingest-service/scripts/fly-secrets.sh, NEXT_PUBLIC_COMPANION_URL +
NEXT_PUBLIC_INGEST_PROVIDERS set in the web app. Note: the app sits in the
personal Fly org, not rpv — move/billing TBD.
§6 gate — automatable items PASSED on the deployed node (2026-07-03): SSRF
probe classes (API precheck + worker DNS-stage, "never fetched" confirmed in
logs, no retry); garbage WeTransfer link → queued_manual; redaction (client
denied source_url 42501, sees source_display; admin gets the full URL via
ingest_source_url(); non-participant sees zero rows); generic URL ingest e2e
(991017-byte mp4 → done, files row with ingest_id, canonical
{tenant}/{job}/original/… key = retention parity, signed download served
real bytes from EU R2).
The gate also caught a live defect, fixed same day (suite 67 → 71 checks): a
dead URL held the single-worker queue 15 min (undici's 300s defaults × 3
attempts). The dispatcher now bounds header wait (30s) and body idle (60s —
deliberately not a total cap), env-overridable via INGEST_HEADERS_TIMEOUT_MS
/ INGEST_BODY_IDLE_TIMEOUT_MS; safeFetch uses undici's own fetch so the
SSRF dispatcher can't be silently dropped by Node-version skew
(test/stall-gate.mjs, falsification-proven). Deployed to the node
2026-07-04; a live probe against a fast-failing origin confirmed the deployed
retry/disposal path (dead URL disposed in 20s).
Tier-2 live e2e PASSED (2026-07-04): real SwissTransfer link (3,145,760 bytes)
and real WeTransfer we.tl short link (15,375,308 bytes) both resolved on the
deployed node → done, files rows with ingest_id, canonical job-prefix
keys. Dropbox OAuth app created (redpoint-ingest, key c0w73ovtywde0wo,
read scopes + redirect URI configured; secret push pending — operator).
Remaining (operator) to close the gate:
- Push Dropbox app secret:
fly secrets set COMPANION_DROPBOX_KEY=… SECRET=…(operator's Dropbox currently over quota — picker test deferred), then Dropbox picker multi-GB ingest. - Google OAuth client (operator-created) +
NEXT_PUBLIC_GOOGLE_PICKER_CLIENT_ID; Google Photos picker ingest. Tier-2 resolvers remain unofficial-API best-effort — flag providers off if their endpoints drift.
Testing (2026-07-07)
Two layers, all merged to main via the Opus build→adversarial-review pipeline:
- CI: GitHub Actions (
.github/workflows/ci.yml) runs unit + e2e on every push/PR against a dedicated CI Supabase project (redpoint-engine-ci, EU). The DB-touching e2e job serializes globally (e2e-ci-shared-dbconcurrency) so cross-ref runs can't collide on the shared DB. - Unit gates:
npm test(16 checks: state machine + brief-view);cd ingest-service && npm test(71 checks, SSRF/resolvers/stall bounds). - E2E (Playwright, real dev Supabase + real R2):
npm run test:e2e— 10 spec files: foundation smoke/RLS/transition-matrix, orders + entitlement race, uploads incl. hash-verified downloads, ops-console UI loop = the automated Phase 4 gate, ingest panel incl. one real worker import, editor workspace, ops download-all, and the job board (claim/dismiss/dispatch — 8 checks incl. a two-editor claim race). CI mapsE2E_BASE_URL_BOARDto the CI server; migrations apply by glob (both fixed after PR #21 went red). Remaining packets indocs/E2E-TEST-PLAN.md: 5/6 (phase-gated), 7 (CI — needs a dedicated Supabase project, operator decision pending).
Design (2026-07-08) — IMPLEMENTED
Spec pack: docs/DESIGN-BRIEF.md (system) + docs/design/PAGE-SPECS.md
(per-page) + docs/design/mockups/ (Claude Design HTML). Now BUILT into the app
via agent packets:
- Tailwind v4 + design-token layer; tenant colours/font injected at RUNTIME
from
tenants.theme(src/lib/theme.tson the layout) — tenant #2 = zero code (CLAUDE.md rule 5). Fonts DM Sans + JetBrains Mono + Barlow Condensed (ops) self-hosted vianext/font(GDPR-safe). - Component library
src/components/ui/(status chip w/ client label map insrc/lib/job-status.ts, ingest chip, SLA badge, file row, form-field kit, brief renderer, events timeline, empty-state, button, tenant-header). - Client light surfaces restyled (#15): /login /order /order/success /jobs /jobs/[id] /jobs/[id]/upload — mobile-first, tenant-accented.
- Engine-dark surfaces restyled (#17): /ops /ops/jobs/[id] /workspace via a
[data-surface="ops"]token scope; neutral (not tenant-themed) per BRIEF §5. - Every restyle kept the e2e suite green; one client-facing assertion retargeted to the shared label map (client sees "Order received", not raw "NEW").
- Not yet styled:
/home page (out of the client inventory). Client review page/jobs/[id]/reviewis spec'd (normative) but unbuilt — lands with Phase 6. - Marketing pages (updated 2026-07-09):
/editorsBUILT + merged (PR #22) via the full orchestrator pipeline (Opus build agent in a worktree → two-critic review → review-verifier → verified fixes on the same PR → CI green with the new spec confirmed run → operator-approved squash merge). Direction changed 2026-07-09: the page targets hobbyist phone editors (survey + Discord CTAs, no application form) — design of recorddocs/design/mockups/wakecut-editor-landing.dc.html, specdocs/design/EDITOR-LANDING-SPEC.md. This supersedes HANDOFF-MARKETING-PAGES.md §Page 2 for this page only; the professional-editor version is deliberately reserved for a separate future marketing/recruitment stream (operator decision — never a same-page variant). Implementation: all copy/links intenants.marketing_config.editor_landing(migration0007_marketing_config.sql+ seed; rule 5 — zero vertical strings insrc/), typed parser + URL-scheme sanitizer insrc/lib/marketing.ts, UTM-passthrough client island, sentinel-binding e2e gatetests/e2e/editor-landing.spec.ts(probes for 0007 and skips loudly until applied; confirmed running on CI). 0007 live-verified APPLIED 2026-07-09 (REST probe answered 200) — the page is live. Still owed: realsurvey_url+discord_url(seeded#survey/#discord); optionalhero_image_urlasset.robots.tsdisallows all crawlers engine-wide — fine for link-driven recruitment traffic (Discord/TikTok/Reddit), revisit if the page should rank./home still open: handoffdocs/design/HANDOFF-MARKETING-PAGES.md§Page 1 + mockup "Wakecut Client Landing.dc.html" in the operator's Claude Design project (export it, or fetch via the logged-in-browser route used for the editor mockup). Supporting research:docs/design/MARKETING-PAGES-BRIEF.md,UX-RESEARCH-{CLIENT,EDITOR}-FLOW.md.
Production (unlisted) — wakecut.com live, verified 2026-07-12
The engine serves at https://wakecut.com (still unlisted: robots
disallow-all, nothing links to it). Chain: GoDaddy NS → Cloudflare zone
wakecut.com → A @ → 76.76.21.21, CNAME www → cname.vercel-dns.com
(both DNS-only, NOT proxied) → Vercel project rpv/redpoint-engine
(auto-deploys main, no basePath). www.wakecut.com and the legacy
wakecut.redpoint-vision.com are 308 redirects to the apex (both
live-verified 2026-07-12). The main redpoint-vision.com PHP site stays
untouched. Note the Cloudflare account split: the wakecut.com zone is in a
separate account from the redpoint-vision account holding R2/Workers (see
Operations-Runbook).
- Vercel env via
scripts/vercel-secrets.sh(DEV_DEFAULT_TENANT_SLUGempty in prod;NEXT_PUBLIC_APP_ORIGIN=https://wakecut.comnow PINNED in the script — it was hand-set in the dashboard before). - Stripe (test mode) prod webhook
we_1TqwhVE0QX9Ke47uvIUe8WlV→ URL updated in place tohttps://wakecut.com/api/webhooks/stripe(in-place keeps the signing secret; stays in Vercel env only). - Supabase auth:
site_url = https://wakecut.com; allowlist = wakecut.com + localhost. Residual (operator): the legacyhttps://wakecut.redpoint-vision.com/**allowlist entry still needs removing — needs the Management API (sbp_token) or dashboard → Auth → URL config; no token was available to the closing session. - Residual (operator): revoke the four cutover-era tokens — the Vercel CLI
token used for the 308 flip, any Supabase
sbp_personal token minted for migrations, the Fly token, and the R2/Cloudflare API token — now that the cutover is closed. (Consolidated here 2026-07-14 from the domain session's handover so it can't get lost.) - R2 CORS: legacy subdomain origin dropped 2026-07-12 (via wrangler,
--jurisdiction eu); origins now localhost + redpoint-vision.com + wakecut.com (www not needed — it redirects). - Ingest node
APP_ORIGIN=https://wakecut.com(Companion CORS points at prod — local-dev picker testing needs it flipped back temporarily). - Auth: login form builds callbacks through
withBase; all server-side redirects (auth callback, Stripe success/cancel) go throughpublicUrl()pinned toNEXT_PUBLIC_APP_ORIGIN. - Phase D verification gate PASSED 2026-07-12: apex 200 + robots
disallow-all; www and legacy subdomain 308 → apex (curl-verified); login
round-trip verified via admin
generate_link→/auth/v1/verify303 →https://wakecut.com/#access_token=…(valid wakecut-tenant session);tenants.domainsprobed over REST — containswakecut.com+www.wakecut.com(legacy kept in the array, harmless behind the edge redirect). Stripe test event / upload smoke / Companion picker were verified by the orchestrating session (steps 2–6 of the runbook). - Operator sign-off walkthrough of the live site (login → order → upload) has NOT been done yet (owed since the subdomain era).
Backlog (cross-phase, recorded 2026-07-12)
- Google Analytics — BUILT 2026-07-12 (this branch; unit 89/89, tsc +
build green; e2e
analytics-consent.spec.tsruns on CI, probe-and-skip locally). Design honours the recorded constraints: measurement ID read fromtenants.marketing_config.analytics.ga4_measurement_id(src/lib/analytics.ts— malformed/absent = feature fully dark, so tenant #2 default-off), GDPR hard gate (AnalyticsConsentbanner; gtag.js not fetched, no cookie until explicit Accept; Decline persisted), engine-dark/ops+/workspaceexcluded by path;/editors+ all client surfaces included. Invariant test bans quotedG-…literals insrc/(PR #22 hex-literal precedent). Operator to enable on live (seed left dark deliberately):update tenants set marketing_config = jsonb_set(coalesce(marketing_config,'{}'::jsonb), '{analytics}', '{"ga4_measurement_id":"G-PQ3CGVFER0"}') where slug = 'wakecut';
Infra / operations
- Supabase project
jhjobfyuzqammhbqzyme— one project serves dev AND prod (scripts/vercel-secrets.shpushes prod env from.env.local; the earlier "dev vs prod apply" wording was drift). Migrations run via the Management API (/v1/projects/{ref}/database/query) or SQL-editor paste. Applied state, live-verified 2026-07-17 by REST probe (noschema_migrationstable — always probe objects): 0001–0039 all applied — the live DB is level with main at 0039; the editor-program migrations 0034–0039 (voice-note kind, partner commission, arming-prep, editor ratings, specify-editor upsell, editor supervision) are pasted and probe-verified. Next free number is 0040. Still owed: premium packages'dispatch_mode='managed'per the hybrid model. 0005 strips the default blanketanongrants from all public tables (defense-in-depth behind RLS; anon keeps onlytenantsSELECT for pre-auth theming) and alters postgres default privileges so future tables don't get the anon grant — tables needing anon access must grant it explicitly. - Cloudflare R2 bucket
redpoint-media, EU jurisdiction (.eu.S3 endpoint). CORS set (localhost origin, ETag exposed). Token is bucket-scoped. - Stripe test mode; manual capture per-payment; webhook
checkout.session.*. - Analytics (2026-07-14, two-critic reviewed on PR #33): GA4 is
consent-hard-gated and PRIVACY-SANITIZED — the vanilla auto page_view is
disabled (
send_page_view:false); every page_view is emitted manually with UUIDs collapsed to:idand query strings stripped (job ids + Stripe?session_idnever reach Google; e2e-pinned). Consent withdrawal = the "Cookies" control (bottom-right, Art 7(3)). Operator (once, in the GA4 property): disable Enhanced Measurement "Page changes based on browser history events" — it would emit raw-URL page_views past our sanitizer. - Email (2026-07-14):
hello@wakecut.com+editors@wakecut.comlive via Apple iCloud+ custom domain (operator-managed). DNS verified: MX →mx01/mx02.mail.icloud.com, SPFinclude:icloud.com ~all, DKIMsig1CNAME present. Addresses recorded intenants.marketing_config.contact(general/editors) — surfaces should read from there (rule 5), nothing reads it yet. Phase 7 note: Resend sending coexists with iCloud receiving (Resend adds its own DKIM + return-path subdomain; MX untouched); proposed senderhello@wakecut.com, reply-to same;editors@= recruitment stream reply-to. - Secrets live only in
.env.local(gitignored) — not in this repo.
Local dev
npm install
npm run dev # needs .env.local (Supabase + R2 + Stripe test keys)
npm run typecheck
npm run build
Test users (dev), shared password in .env.local context: rp.client@…,
rp.client2@… (non-participant), rp.editor@… (assigned). The admin account
is the operator's real address: man_on_rock@redpoint-vision.com
(renamed from rp.admin@… 2026-07-06; same password).