The short ask first, never the 21 questions. One record from the door to the deal. Every door,
every form, every branch and every event is a registry the office renders — the pages cannot
disagree with the build, and verify_intake refuses the ways they could.
Thirteen landing pages, one per ICP segment, at https://temerarii.xyz/go/<segment> (the map is
/go). Each is rendered from gtm.yaml icp.segments[]: the message, who it is for, the pain, the
trigger, the wedge — and the ask the stage rule chooses. The segment's own door and the
universal fallback (icp.default_door, the free consult) follow as secondary links. The office
lists them at /strategy/doors, from the same registries.
| kind | where it goes | segments today |
|---|---|---|
form | /inquire/<form>?segment=… | smb-local (workshop-seat) · municipal (capability-brief) · mission-nonprofit (mission-lane) · publishers, directories (inquiry) |
book | /book/<event>?segment=… | broken-attribution (audit-30) · neutral-builder, capital-allocators (walkthrough-30) · enterprise-dtc, agency-resellers (discovery-45) |
external | the product (SIE) | compliance-exposed · data-brokers · gtm-consolidators |
| default | /book/consult-60 | everyone else |
A form door may set detail: true to ask the second step right after the short ask. None does
today: the detail step is offered, never imposed.
forms.json stage_rule, applied by apps/web/lib/frontdoor/stage.ts and mirrored on the office page:
| stage | signal | first ask |
|---|---|---|
| warm | utm_campaign or ref in the query, or a contact on file behind a signed known= token | book — the segment's own event, else the event scheduling.json names for it, else the consult |
| product-led | the segment's door is external | the product (SIE trial) |
| cold | everything else | the short form — the segment's own form, else /inquire/inquiry step one |
Warm wins over product-led: a campaign click gets the call. /go/compliance-exposed shows the SIE
trial first; /go/compliance-exposed?utm_campaign=x shows the consult first. The known token is
an HMAC over the contact id + a timestamp (30 days), minted by `lib/frontdoor/token.ts
mintKnownToken` for outbound links — a raw email is never a stage signal and never in a URL.
forms.json is derived from the saved Typeform definition (inquiry-form.typeform.json, 21
questions, 8 logic blocks, 15 jump actions) by engine/stages/derive_intake_forms.py. The
definition stays the spec; edit the derivation, never the output.
| step | fields | when |
|---|---|---|
start | first name · email · company · the one need · consent (5) | always, first, alone |
detail | the other 17 Typeform questions | when the door sets detail: true, or when the person taps "add the detail" after the door is shown |
The three short forms (mission-lane · capability-brief · workshop-seat, 9 fields) are one step each.
Flow: POST /api/intake {step: "start"} validates only the start fields, writes the contact
(consent receipt at the root), the inquiry and a touch, routes by the need answer (or the
landing page's preset segment), and answers with the door, detail, and a prefill token —
an HMAC over the email on file + the inquiry id + a timestamp, 30 minutes. Step two calls
GET /api/intake/prefill?token= (returns only that inquiry's own asked answers; never another
person's; never the row's ip / user agent / receipt) and `POST /api/intake {step: "detail",
token}`; the answers merge into the same inquiry, the route is recomputed (an org type that
names a segment — municipal, agency, enterprise, SMB — overrides), the contact is updated by role,
a touch is recorded. dry: true validates and routes without writing. Every refusal the endpoint
had stays: honeypot, unknown form, required-when-shown, a choice the field lacks, a bad email,
consent missing, the per-address throttle.
Each of the 15 actions is either a show_if twin on its target field or an entry in
unmapped_logic with a reason; verify_intake re-enumerates the definition and fails on any
action with neither, on a recorded twin that is not actually on the field, and on an else-jump that
skips a field with no show_if (Typeform would never ask it; the office form would).
| rule | Typeform | twin / entry |
|---|---|---|
| org type is Municipality → procurement | is | show_if on procurement |
| need is Marketing / AI & Automation / Software / Creative / Media-PR → the five branch questions | 5 × is | show_if on each branch question |
| approve-budget is_not ticked → decision-maker | is_not (checkbox) | show_if {checked: false} on decision-maker |
| org type → need · need → budget · approve → keeping-you-up · each branch question → budget | 8 × always | unmapped_logic: else-jump fallthrough; each records the fields it skips, all of which are conditional |
7 twins + 8 unmapped = 15. derive_intake_forms.py prints the tally; the office page shows it.
When governance/intake/field_map.json exists (the contact-graph stream writes it), every
forms.json field id must name one destination — the gate fails otherwise and the office page shows
coverage per form. Absent, the gate prints a note and skips.
scripts/deploy.py deploy_lab writes the Lab's Build Output config.json with, after the
filesystem handle and before the SPA fallback, proxies for /go, /inquire, /book,
/api/intake, /api/book, /og-image.png and /sitemap-doors.xml to
https://temerarii-office-app.vercel.app. One implementation; the public host inherits it:
NEXT_PUBLIC_SITE_URL defaults to https://temerarii.xyz, so canonicals, share cards, the app
sitemap and robots.txt name the public host. The Lab's robots.txt (rendered by
render_static_blog.py) lists both sitemaps: its own and /sitemap-doors.xml. On the app the
doors' sitemap is /sitemap.xml (the door pages, the forms, the rail — nothing gated) and the proxy
maps the public name onto it (LAB_PROXY_ALIASES), because that path is already outside the
operator gate; the app also answers /sitemap-doors.xml itself once middleware.ts PUBLIC_PATHS
lists it.
NEXT_PUBLIC_GA4_ID (default G-PZKRJH7Y1F, the temerarii.xyz property, in .env.example;
inlined at build time — a change needs a rebuild). lib/frontdoor/analytics.tsx loads the tag
only on /go, /inquire, /book and fires:
| event | where | params |
|---|---|---|
door_view | /go/<segment> | segment · stage · reason · primary kind |
form_start | first answer on a form | form · step · segment |
form_submit | a step accepted by /api/intake | form · step · segment |
booking | a booking accepted by /api/book (observed on the response — the scheduler owns the request) | event type · segment |
No email, no name, no answer text rides on an event. The office never carries the tag.
temerarii.xyz/go/<segment> (200, canonical on xyz, tag present): Duda's "Get started" → https://temerarii.xyz/go. The Typeform stays the spec until then.
detail: true on any form door in gtm.yaml where the second step should be asked outright.INTAKE_TOKEN_SECRET and NEXT_PUBLIC_GA4_ID on the app's deployment; NEXT_PUBLIC_SITE_URLonly if the public host ever changes.
governance/intake/forms.json (steps · stage_rule · analytics · logic_twins · unmapped_logic) ·
engine/stages/derive_intake_forms.py · engine/gates/verify_intake.py ·
apps/web/lib/frontdoor/{data,intake,stage,token,analytics,store}.ts(x) ·
apps/web/app/api/intake/{route,prefill/route}.ts · apps/web/app/inquire/[form]/{page,StepForm}.tsx ·
apps/web/app/go/[segment]/{page,DoorView}.tsx · apps/web/app/sitemap-doors.xml/route.ts ·
scripts/deploy.py (LAB_PROXY_PREFIXES) · engine/stages/render_static_blog.py (robots) ·
apps/office/render_frontdoor.py → /strategy/doors.
The Chairman's audit (2026-08-25, through the public host): /go answered in 7.5 s, a door in
1.8 s, the form in 3.0 s, the rail in 4.4 s — every page a dynamic route reading three registries
from disk on every request, cold-started — and the copy on them was the internal GTM registry
(who · pain · trigger · message · wedge) shown verbatim. They read as made for the team, because
they were. This section is what replaced them.
Copy as data. governance/intake/landing.json holds, per segment, a block written for the
buyer the segment's titles name: hero (eyebrow · headline ≤ 12 words · sub ≤ 30) → stakes
(their pain and trigger, three lines, in their words) → mechanism (what we do, three steps,
from the playbook) → proof (≤ 3 receipts the reader can open) → offer (the ask per stage) →
objections (≤ 3) → close. A shared block carries the machine in one paragraph, the consent
promise and the no-payment line; _rules states the rails the copy follows and the word limits.
gtm.yaml keeps the internal fields for the office and the machine; none is shown to a buyer any
more. The CTA labels stay the doors' own (gtm.yaml door.label), so the office page at
/strategy/doors and the app cannot disagree on what a button says.
The funnel order on /go/<segment>: hero (eyebrow · the one h1 · sub · the primary ask by
stage · "see how it works") → laser rule → stakes → mechanism → proof → the offer (the one ask,
the other doors as quiet links) → objections (native <details>, no script) → close (h2 + the
same ask) → footer (consent promise · no payment · all doors · the office · the Lab). /go is
the hero plus the thirteen doors as cards grouped by pillar, each in the buyer's words. The form
and the rail wear the same shell (trim · header · grid · footer); StepForm and the Scheduler are
unchanged and sit on a white ground — a sanctioned brand ground — so their light styling holds.
The brand rules applied. Black ground (--tm-bg), white type (--tm-fg-1/2/3), red as
TRIM only: the eyebrow (red-400 for AA at 13 px), the hairline at the top of every page, the
laser-line dividers, the primary CTA (red-500 fill with the ember glow), the bracketed red
wireframe on proof cards, the UI accents. Never red body text on black. Tahoma Bold for every
heading, Tahoma Regular for body, JetBrains Mono for eyebrows and counters — all through the
generated tokens (styles/tokens.generated.css), no hex literal in a page. The effects are the
brand book's, in CSS only: the 44 px working grid showing through, the glow, the wireframe, the
laser line, and a slow dot drift under the hero ("signal field", ember-drift timing) that is a
static frame under prefers-reduced-motion. No 3D, no R3F, no web-font import: load time is the
first conversion factor. Mobile-first; the primary CTA sits above the fold at 390 × 844.
The static-render decision. /go, /go/[segment], /inquire/[form] and /book/[event]
are force-static with generateStaticParams (13 doors · 4 forms · 4 events), built once from
the registries; lib/frontdoor/data.ts reads each registry once per process (readRegistryOnce)
for the API routes that stay dynamic. The stage rule moved to the browser
(components/frontdoor/StageAsk + lib/frontdoor/stage-client.ts): the server renders the cold
ask, the client reads utm_campaign · ref · known after hydration and swaps in the warm ask
with the space reserved, then fires door_view once with the stage it resolved. The one honest
delta: a known token cannot be HMAC-verified in the browser, so one with the minted shape
inside its 30-day life counts as warm; a forged one only changes which of two open doors is the
button, and every API route still verifies for real. ?segment= on the form and the rail is
read the same way (useQuerySegment) and handed to StepForm / Scheduler exactly as before. The
booking page states the hosts' hours DERIVED from scheduling.json once confirmed: true; the
default short-form label counts the start step's questions from forms.json rather than saying
"five" (the step is seven fields now, six questions and the consent).
Before / after (before = the audit through the public host; after = next start locally,
warm, so the host adds its own network time but no render):
| route | before TTFB | after TTFB | after HTML | scripts (src) | h1 | sections |
|---|---|---|---|---|---|---|
/go | 7.5 s · ƒ dynamic | 9 ms · ○ static | 25.2 KB | 11 (6) | 1 | hero · doors · close |
/go/compliance-exposed | 1.8 s · ƒ | 10 ms · ● SSG | 27.9 KB | 13 (7) | 1 | hero · stakes · mechanism · proof · offer · objections · close |
/go/municipal?utm_campaign=x | — | 13 ms · ● (cache HIT) | 27.1 KB | 12 (7) | 1 | the seven |
/inquire/inquiry | 3.0 s · ƒ | 14 ms · ● SSG | 28.3 KB | 12 (7) | 1 | hero · form |
/book/consult-60 | 4.4 s · ƒ | 18 ms · ● SSG | 16.4 KB | 12 (7) | 1 | hero · booking |
The script count did not fall (11–14 before): six or seven are Next's chunks and the rest are the
inline RSC payload the app router ships on every page — that is the framework's floor, and the
payload is inline, not a request. Every page carries its canonical on temerarii.xyz and the GA4
id from the deployment env; POST /api/intake with dry: true still answers 200 with the door.
The gate. verify_intake now refuses a segment with no landing block, a missing section, a
field over its limit (headline ≤ 12 and sub ≤ 30 whatever the file says), a banned word from
brand.json in any of the file's strings, an offer whose door kind the stage rule would not
choose for that stage and segment (product must be null unless the door is external), and a
proof receipt that is not an office path existing under apps/office/public, an https URL on a
brand domain, or none; when a build is on disk it also refuses a built door page with other than
one <h1>.
Outside this page's reach. apps/office/render_frontdoor.py still labels the default short
form "Tell us in five questions" (the app counts; the office should read steps[0] the same
way). /inquire (the index) is a dynamic redirect. The Duda "Get started" link and the host's
DNS are the Chairman's switches, unchanged.