# Go-Live Runbook — candidate → relayctx.com

*The ordered, checkable path from this estate's candidate generation to the
live production site, and — separately, behind its own gate — the un-stealth
indexing flip. Read this before any conversation about "shipping the site".*

Companion to [`GUIDE.deploy.md`](GUIDE.deploy.md), which covers how **this
estate** deploys (Cloudflare Pages, `main` = production, no build step). This
file covers the one deploy this estate does **not** perform: the port of a
finished marketing-site generation onto the production host. The shape —
a go-live runbook with the human inputs as an explicit checklist — is adapted
from the Execution Space operating-model handoff (blueprint §5); every value
in it is Relay's own.

---

## TL;DR

- **The candidate generation is `marketing/web/v36-sharpened/`** — nine pages,
  clean URL `/v36`, the v3.6 Sharpened direction (marketing/CHANGELOG.md,
  2026-08-30 ×2).
- **Going live = porting the candidate to the production host**, which is
  relay-platform territory (`relay-platform/web/relayctx/`, serving
  relayctx.com). That repo is owned by a separate session (EC, 2026-08-26:
  *"you do NOT touch anything outside of creative"*). **This runbook describes
  the handoff; the port itself is never executed from this repo.**
- Four values change at the repo boundary and nowhere else: the analytics
  container, the bot-check sitekey, the social-card image, and the backend the
  forms post to. Each is a checklist row below, with an owner.
- **The un-stealth flip is a different event with a different gate.** Going
  live does not un-stealth anything: the production page today is itself
  `noindex, nofollow` (MEMORY.md § STEALTH), and a ported candidate stays on
  that side of the line until EC declares stealth over. §4 holds the flip.
- **Nothing in this document is itself a go-live or an un-stealth action.**
  It is a map. Every step below is executed by a person or a named session,
  on an explicit go-ahead, at its named gate.

---

## 1 · What "going live" means here

This estate is the workshop, not the venue. `creative.relayctx.com` is an
internal creative index — every page deliberately `noindex, nofollow`, every
response blanketed by `_headers` — and it stays that way regardless of what
launches. "Going live" means one thing:

> The candidate generation — today `marketing/web/v36-sharpened/` — is copied
> onto the production host and served at **relayctx.com**, replacing the
> current stealth page, with the four boundary values (§2) swapped from their
> estate stand-ins to their production values.

The production web root lives in **relay-platform** (`web/relayctx/`), whose
`main` auto-deploys to production immediately and which a separate session
owns. The precedent is already on record: MANIFEST.md marks the stealth v2
Sharpened work "production port to `relay-platform/web/relayctx/` pending".
The same handoff pattern applies to the full generation:

| This repo hands over | The platform session executes |
|---|---|
| The candidate pages (`v36-sharpened/` — nine pages + `site.css`) | Placement under `web/relayctx/`, server routing, clean URLs on the production host |
| The emitted token stylesheet (`brand/generated/relay-tokens-v36.css` — never the hand-edited source) | Serving it at the absolute path the pages link |
| Form wiring (`marketing/web/stealth/relay-access.js` conventions — see [`guidelines/GUIDE.forms.md`](guidelines/GUIDE.forms.md)) | The real `/api` backend, the production sitekey (§2), removing the concept-mirror simulation |
| Analytics wiring (`marketing/web/stealth/relay-analytics.js`, consent-gated) | Setting the production container ID (§2) |
| The checklist in §2, signed off | The deploy, and the §3 verification against the live host |

The handoff itself travels the way cross-repo work always does here: a
recommendation with paths named, not an action. **Recommend; do not port.**

---

## 2 · The path — candidate → production, in order

Steps 1–4 happen in this repo and are checkable here. Steps 5–8 are the
handoff — human inputs and platform-session work. Nothing in 5–8 can be
completed by editing this estate.

**In this repo first:**

- [ ] **1. All checkers green on the candidate.** The full pre-push battery
  (`CLAUDE.md`) exits 0 — including `check_forms.py` on the register page and
  `check_robots_posture.py`, which must still pass in **stealth** posture:
  going live does not touch the indexing line (§4).
- [ ] **2. Both-widths render pass on the candidate, re-asserted.** The
  generation shipped with a DOM pass — *"zero teal outside `.btn`/
  `.relay-lockup`, zero horizontal overflow at 1280/390, zero JS errors —
  asserted, not assumed"* (marketing/CHANGELOG.md, 2026-08-30) — re-run it on
  the tip of `main`, not on memory of it.
- [ ] **3. Content and claims sign-off.** Every factual claim, number, and
  name on the nine pages is checked against
  [`copy/CATALOG.claims.md`](copy/CATALOG.claims.md) — the claims ledger built
  alongside this runbook. Uncleaned phrasing is fixed in the candidate before
  the port, never after. Vocabulary is already CI-held (`check_vocab.py`);
  the ledger covers what the vocabulary checker cannot: facts.
- [ ] **4. EC sign-off on the candidate.** A walkthrough of all nine pages at
  both widths, on the live estate URL (`/v36`), with the verdict recorded
  verbatim in `marketing/CHANGELOG.md` next to what shipped for it. This is
  the go-ahead the handoff waits for.

**At the boundary — the human inputs.** These are the values that are
deliberately *wrong* in this repo and must be set on the production side:

- [ ] **5. Analytics container ID.** The candidate pages carry no analytics
  wiring; the port adds `relay-analytics.js` (consent-gated, pre-consent
  events queued) with `window.RELAY_GTM_ID` set to the canonical container —
  `GTM-PK7JRC76` per the script's own header. Verify the funnel events fire
  on the production host, including `register_result` with a real value
  (`success`/`error`), never `simulated` — the estate's static host simulates
  confirmation and says so in the console (marketing/CHANGELOG.md,
  2026-08-27/28).
- [ ] **6. Production bot-check sitekey.** The estate copy of the register
  **deliberately carries Cloudflare's always-pass TEST sitekey** — that is
  what draws the red "For testing only" band, and it is correct here. The
  record (marketing/CHANGELOG.md, 2026-08-27, verbatim): *"Swapping the key
  here would break it, not fix it. Turnstile sitekeys are bound to a
  hostname, and the production key is registered for relayctx.com only —
  dropped into a page on creative.relayctx.com the widget fails to render
  rather than working."* The swap to the production sitekey
  (`0x4AAAAAACsj2XqQHMrAi2_Q`) happens **only in the ported copy** on the
  production host, where its hostname binding is satisfied. Never "fix" the
  key in this repo.
- [ ] **7. Social-card image.** The candidate pages carry no `og:`/`twitter:`
  cards (cards are opt-in here, `check_social_meta.py`). The production pages
  get cards with an **absolute https `og:image`** hosted on the production
  domain — and under stealth, any card-carrying page must remain `noindex`
  (checker rule: *"a card can never quietly become an indexing surface"*).
  Card copy must match each page's own h1 and beat, same as the checker
  enforces here.
- [ ] **8. Forms post to the real backend.** The concept-mirror behaviours are
  stripped in the ported copy: no simulated confirmation, real server errors
  surfaced, Turnstile reset in the failure handler (a token is single-use).
  The full contract is [`guidelines/GUIDE.forms.md`](guidelines/GUIDE.forms.md);
  production is the canonical implementation and the estate mirror follows
  it, never the reverse.

---

## 3 · Verify live

After the platform session deploys, verification runs **against the
production host**, not the estate. Asserted, not assumed — the house habit:

- [ ] **Both widths render.** Every ported page at 1280px and 390px: zero
  horizontal overflow, zero console errors, the Sharpened rule holds (no
  brand teal outside `.btn`/`.relay-lockup`), reduced-motion shows a complete
  resting frame.
- [ ] **Forms end-to-end.** A real registration on relayctx.com: production
  Turnstile renders (no red test band), submit succeeds, the confirmation
  replaces the form with `role="status"`, and a deliberately failed attempt
  gets a reset widget and a retryable form. `register_submit` and
  `register_result` both land in the dataLayer with honest values.
- [ ] **Redirects and clean URLs.** Every nav and footer link on every ported
  page resolves on the production host; legacy production URLs land on their
  new homes; no redirect loops (the estate learned that one on 2026-08-04 —
  never point a clean URL at its own file).
- [ ] **Posture unchanged.** The live pages are still `noindex, nofollow` and
  the production host still serves no `robots.txt`/`sitemap.xml`/`llms.txt`.
  Going live and un-stealthing are different events; confirm the port did not
  blur them.
- [ ] **The estate records it.** `marketing/CHANGELOG.md` entry naming what
  ported, when, and the verification results; MANIFEST.md's "production port
  pending" note updated in the same PR.

---

## 4 · The un-stealth flip — gated, and not this document's to take

Everything in this section is **dormant by design**. The lock, quoted from
MEMORY.md § "STEALTH — indexability is the disclosure line" (relay `45XCKY`,
2026-07-26), which is the record of a 7-page indexable microsite that was
built and **fully reverted** (PR #159) as a disclosure risk:

> "The indexable-microsite concept does not ship anywhere until Relay
> formally leaves stealth."
>
> "The disclosure problem was indexability, not the copy or positioning.
> Ratified outcome-level positioning on a `noindex` page is fine; what is not
> fine is removing `noindex` or adding active search/LLM crawler indexing
> (`robots.txt` Allow, `sitemap.xml`, `llms.txt`, schema.org JSON-LD) while
> Relay is in stealth/NDA."

What is currently locked in place, mechanically: the `_headers` blanket
(`X-Robots-Tag: noindex, nofollow` on every response), per-page
`noindex, nofollow` metas, `check_robots_posture.py` with `POSTURE =
"stealth"` (which *fails* if `robots.txt`, `sitemap.xml`, or `llms.txt`
appears, or any page gains JSON-LD or `rel=canonical`), and
`check_social_meta.py`'s rule that card-carrying pages stay `noindex`.

**The gate for every row below is the same and is not interpretable: EC
declares stealth over — a formal, recorded decision (a relay-board decision
entry), not an inference from launch momentum.** The flip then lands as **one
PR**, in this order — the order matters because the posture checker enforces
it (flipping `POSTURE` while MEMORY.md still carries the lock exits 2: the
lock is lifted at its source first, never the constant first):

- [ ] **F1. Lift the lock at its source.** MEMORY.md § STEALTH is updated to
  record the un-stealth decision, dated, with the decision entry named. —
  *gated on: EC declares stealth over*
- [ ] **F2. Remove the `_headers` noindex blanket.** The `/*` `X-Robots-Tag`
  rule comes out (the file's own header comment names this runbook as the
  authority for when). — *gated on: EC declares stealth over*
- [ ] **F3. Strip per-page `noindex` metas from live pages.** Frozen records
  (`_archive/`, `-v33`/`-v34`) keep theirs; any live page deliberately staying
  out of the index keeps its meta with a `robots-ok` reason on the line.
  Delete `scripts/robots-baseline.txt` — its contents describe stealth debt,
  which is the correct state once public. — *gated on: EC declares stealth
  over*
- [ ] **F4. Flip `check_robots_posture.py` to `POSTURE = "public"`.** One-word
  diff; the checker's polarity inverts — a surviving unexcused `noindex` now
  fails, missing `robots.txt`/`sitemap.xml` now fails. — *gated on: EC
  declares stealth over*
- [ ] **F5. Add `robots.txt` with an explicit AI-crawler policy.** The
  recorded intent, from the Execution Space handoff blueprint (§7): *"we
  deliberately allow retrieval crawlers — buyers arrive via assistants now."*
  That stance — welcome retrieval/assistant crawlers rather than blocking
  them — is the starting position for the production site's policy; EC
  confirms or amends it at flip time, and the decision goes in the file's own
  comment header. — *gated on: EC declares stealth over*
- [ ] **F6. Add `sitemap.xml`** covering the live, indexable surface — not
  frozen records, not `robots-ok` pages. — *gated on: EC declares stealth
  over*
- [ ] **F7. Add `llms.txt`** at the production root, same allow-retrieval
  intent as F5. — *gated on: EC declares stealth over*
- [ ] **F8. Add JSON-LD** — Organization site-wide; Article/Breadcrumb where
  the content type warrants it. — *gated on: EC declares stealth over*
- [ ] **F9. Add canonical URLs** (`rel=canonical`) on the indexable pages, and
  retire `check_social_meta.py`'s cards-stay-noindex assertion in the same
  PR — under the public posture that rule inverts from protection to
  contradiction, and the flip PR fails CI if it is left standing. — *gated
  on: EC declares stealth over*
- [ ] **F10. Re-run the full checker battery on the flip PR's merge result**
  and record the flip in `marketing/CHANGELOG.md` and MEMORY.md. — *gated
  on: EC declares stealth over*

Two scopes, one open pick. F5–F9 describe the **production site** —
relayctx.com, relay-platform territory, executed by that session on the same
handoff terms as §1. For **this estate**, F2–F4 apply mechanically, but
whether `creative.relayctx.com` — an internal creative index — should itself
ever be indexed is a separate question: the public posture supports keeping
every estate page out of the index via `robots-ok` metas and a disallowing
`robots.txt` while still satisfying the checker. **That is EC's pick at flip
time; the default assumption is the estate stays unindexed even after
stealth ends.**

Also parked behind this gate: geo and landing-page variants of the
v36-sharpened template family — the family itself is documented in
`guidelines/GUIDE.component-contracts.md` (Landing-page family row); variants
ship only after the flip.

---

## 5 · What this document must never do

This runbook is deliberately inert. It names no step that executes on read,
ships no indexing artifact, and carries no live flip. If a future edit to
this file adds a `robots.txt`, strips a `noindex`, or flips a checker
constant "while we're here" — that edit is the incident, and
`check_robots_posture.py` exists to fail it. The path to live runs through
people: EC's sign-off in §2, the platform session's hands in §1 and §3, and
EC's formal declaration in §4. Offering the checklist is this repo's job;
walking it is not.

---

*Last updated: 2026-08-31 · relay-creative/GUIDE.golive.md*
