Build-in-public is a CLOUD feature. A self-hosted Motir is a team doing project management for itself — single-tenant, no directory of other people's projects, no public reading surface for strangers. This story makes that true in the product instead of true only in intent.
origin/main, 2026-08-29)motir-core ships app/(public)/explore/ (the square + topic/[slug]) and app/(public)/p/ (nine routes: the project, board, roadmap, tree, items, an item, changelog, the changelog feed, requests), over publicProjectsService (~14 read methods), projectSquareService and projectTagsService, plus app/api/public/ (10 anonymous routes). All of it is on by default in every build, self-hosted included. A self-hoster who deploys motir-core today gets a cross-tenant project directory they did not ask for and cannot turn off.
lib/billing/availability.ts carries isCloudBilling(), reading an explicit MOTIR_CLOUD env flag, decided in docs/decisions/billing-tiering.md §6: "Billing AND the §4 entitlement caps exist ONLY on Motir cloud; a self-hosted (GPL-3.0) build is uncapped and shows no checkout, no paywall, no caps." Its own comment states the discipline this story inherits: the flag is EXPLICIT and default false, deliberately NOT inferred from the presence of other config, "so a self-hoster who connects their OWN motir-ai is never force-billed."
The naming needs one decision this story makes rather than assumes: isCloudBilling() answers "is this a billing build?", and this story asks "is this a cloud build?". Read the ADR's §6 rule that two questions get two functions even when they read one variable, and follow it — a shared isCloud() beside it, or a second named predicate, but not a caller of isCloudBilling() from a non-billing surface.
This follows the shape already decided for the AI on 2026-06-16 and recorded in the chat front-door design: present-but-gated, NOT absent, NOT silently broken. A self-hosted build does not ship a broken route or a stack trace; it ships a deliberate answer. What that answer IS — a 404, or a short page saying this is a Motir Cloud feature — is this story's to decide and to state, because a 404 is a decision, not a default.
/explore and /p/* answer on is MOTIR-3876's and MOTIR-3877's. This story is about whether they answer AT ALL on a self-hosted build, and the two are independent: the gate is correct wherever the routes live./docs. Those pages describe the software and a self-hoster needs them for their own build./legal — that content leaves the repository entirely, which is its own story.motir-core single-tenant. Workspaces, RLS and the tenancy model are unchanged; this is about which SURFACES a non-cloud build serves, not about the schema.app/api/public/* by accident and it must not gate it by omission either — those 10 routes are the same feature. Decide them explicitly and say so.MOTIR_CLOUD unset. Request /explore, /explore/topic/<slug>, /p/<identifier> and each of its tabs — every one gives the decided not-available answer, and none gives a stack trace or a 500.app/api/public/* route on the same build — the decided answer, consistently with the pages.MOTIR_CLOUD=true and repeat — every surface renders exactly as it does today.done; the repository set is motir-core alone.MOTIR_CLOUD unset, /explore, /explore/topic/*, /p/* and the app/api/public/* routes all give the decided unavailable answer; with it set, all render as today. Both arms are tested — the self-host arm is the one that has never existed, so it is the one the suite must actually exercise.isCloudBilling(), reading MOTIR_CLOUD, and no non-billing surface calls isCloudBilling() — asserted by a test, in the shape tests/hosting/appUrlSeam.test.ts already uses for single-reader rules.app/(authed)/ changes behaviour in either arm.motir-core/lib/billing/availability.ts — isCloudBilling(), MOTIR_CLOUD, and the explicit-flag disciplinemotir-core/docs/decisions/billing-tiering.md §6 — the cloud-vs-self-host build gate and its two-questions-two-functions rulemotir-core/lib/ai/planningConfig.ts — isAiPlanningConfigured(), the OTHER flag this must not be confused withmotir-core/app/(public)/explore/ · app/(public)/p/ · app/api/public/ — the surfaces gatedmotir-core/lib/services/{publicProjectsService,projectSquareService,projectTagsService}.ts — the services behind them, which are NOT deletedreference → MOTIR-3876 and MOTIR-3877 (severity advisory): not consumed, and no blocked_by is owed. Both are named in this story's BOUNDARY, which exists to say they are somebody else's — the gate is correct wherever those routes are addressed, and those stories are correct whether or not the gate has landed. The two are genuinely parallel and wiring an edge would serialise work that shares no output. validate_work_item on this story reads valid: true, blockers: [], which is the intended shape rather than an oversight.