Decision (ADR) — one origin per hostname: which host answers for each public surface, and what becomes of motir-marketing
Type: decision · Executor: coding_agent · Write docs/decisions/public-surface-hosts.md in motir-core, and AMEND docs/decisions/marketing-site-hosting.md to point at it. No application behaviour ships in this subtask.
The QUESTION
marketing-site-hosting.md (Accepted 2026-08-27) answered "where does the marketing SITE run?" and answered it correctly. It never asked "which host answers for each PUBLIC SURFACE?", because when it was written the only public surface anyone was thinking about was a landing page. Three of them already existed on the other host.
So: which hostname serves /, /explore, /docs, /legal and /p/*, and by what mechanism?
The options, and the one to record
- A — one origin per hostname;
motir.cois served bymotir-core, and the landing moves into it. RECOMMENDED. - B — an edge router (Cloudflare, or Next
rewritesinmotir-marketing) path-splitsmotir.coacross two Fly origins. - C — keep the split; make
motir.coa better hub and accept that content lives onapp.motir.co. - D — move
/explore·/docs·/legalINTOmotir-marketing.
Rung-2 evidence (the shipped code — grep origin/main, not a local checkout)
motir-marketing/app/holds ONE route.app/sitemap.tsreturns one URL and says "⚠️ ONE ENTRY, AND THAT IS THE HONEST ANSWER."motir-marketing/lib/destinations.ts—EXPLORE,DOCS,LEGAL_PRIVACY,LEGAL_TERMS,LEGAL_INDEXare all built fromAPP_ORIGIN.motir-marketing/app/_components/RootJsonLd.tsx— theWebSiteSearchAction targets the other origin, under its own ⚠️.motir-core/app/(public)/—/explore,/docs,/legalmake nogetSession()call;/p/*does, in twelve files.motir-core/lib/baseUrl.ts— ONE variable,MOTIR_BASE_URL, currently answers both "where is the app?" and "what origin do public canonicals use?". This decision splits those two questions.motir-core/tests/navigation/proxy-matcher.test.ts— the evidence against option B: "the matcher was a COMMENT asking future authors to remember, and thirteen of sixteen segments were never added."- D is refuted by measurement, not preference: there is no
app/api/public/p/[identifier]/route.ts, six of the public API routes read the session,/api/public/*is outside the v1 contract, and the surface is ~25 components plus Server Actions over RLS-bound reads.
Rung-1 evidence (the mirrors — observe, do not recall)
Vercel and Notion serve marketing plus first-party content from one origin each (vercel.com/docs, vercel.com/templates; notion.com/help, notion.com/blog). Stripe splits reference documentation to docs.stripe.com — a hostname per origin, not a path-split across origins. No mirror in this set stitches one hostname across two origins with an edge router, which is what makes B the deviation rather than the default.
What the record must decide, explicitly
- Which host answers each of the five surfaces, in a table.
- Whether the session cookie's scope changes. It does not — and the record says why, citing GitHub's "Yummy cookies across domains" and the Public Suffix List.
- What becomes of
motir-marketing: the repository, the Fly app, its CI, and whether the landing is ported or packaged (@motir/design-systemand@motir/brandare the precedent for packaging — MOTIR-3724). - Which owner writes
robots.txtandsitemap.xmlfor each host after the change. - The COSTS of A, stated rather than glossed — marketing copy rides motir-core's CI, migrations and
fly-deployconcurrency group (whichmarketing-site-hosting.md§2's option D raised and this record must answer, not ignore); theen/zhcatalogue-parity gate the landing inherits; and the external registrations that move withMOTIR_BASE_URL. - The reversal condition, concretely.
Acceptance criteria
motir-core/docs/decisions/public-surface-hosts.mdexists, in the directory's Status → Context → Decision → Consequences shape with a numbered-Q section and a per-Q rejected-alternatives table, matchingmarketing-site-hosting.md's own convention.- It carries a per-surface host table for
/,/explore,/explore/topic/*,/docs,/legal,/p/*— and marks/p/*as DEFERRED to its own story, citing that story by key, so no reader takes silence for a decision. - Every rung-2 claim above appears with the file it was read from; every rung-1 claim names the product and what was observed.
- Options B, C and D each have a rejected-alternatives row giving the reason, and B's row states the routing-manifest cost in the terms
proxy-matcher.test.tssets. - §5's costs are written as costs, not as a case for the decision.
marketing-site-hosting.mdgains a dated AMENDMENT pointing at this record and naming which of its own answers now stand (Q3 CI, Q4 subprocessor) and which are superseded (Q1's origin for the apex, Q2's A/AAAA target).- The record does not decide
/p/*'s namespace — that is the next story's ADR, and this one says so.
Context refs
motir-core/docs/decisions/marketing-site-hosting.md— the record amended, especially §2's option D and its stated objectionmotir-core/docs/decisions/application-hosting.md— the Fly shape both records build onmotir-core/docs/decisions/work-item-type-taxonomy.md— the decision-record conventionmotir-core/lib/baseUrl.ts·lib/publicProjects/urls.ts·tests/navigation/proxy-matcher.test.tsmotir-marketing/lib/destinations.ts·lib/appOrigin.ts·lib/siteOrigin.ts·app/sitemap.ts·app/robots.ts·app/_components/RootJsonLd.tsx- MOTIR-2854 — the ADR this one follows; MOTIR-3724 — the packaging precedent
- GitHub, Yummy cookies across domains; the Public Suffix List
Comments (0)
No comments yet — be the first to weigh in.