MotirBuilding in public
MOTIR · moooon
onMotir
You’re viewing a public project. Anyone can view it — no account needed. Sign in to submit, upvote, or comment on requests.View-only — you can’t edit work items
Back to roadmap
To Do

Build-in-public is a CLOUD feature — MOTIR_CLOUD gates /explore and /p/*, and a self-hosted Motir is single-tenant

Storyopened by Zhu YueMOTIR-3908

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.

What is true today (read from 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.

The mechanism already exists — this story uses it, it does not invent one

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.

The gate is PRESENT-but-off, not absent

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.

Boundary — where this story ENDS

  • It does not move any URL. Which hostname /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.
  • It does not touch /docs. Those pages describe the software and a self-hoster needs them for their own build.
  • It does not touch /legal — that content leaves the repository entirely, which is its own story.
  • It does not make 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.
  • It does not gate 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.

Verification recipe

  1. Build with 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.
  2. Request each app/api/public/* route on the same build — the decided answer, consistently with the pages.
  3. Set MOTIR_CLOUD=true and repeat — every surface renders exactly as it does today.
  4. Confirm the authenticated application is untouched in both builds: sign in, open a project, a board and an item.

Acceptance criteria

  • Every child is done; the repository set is motir-core alone.
  • With 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.
  • The predicate is a named function beside 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.
  • The unavailable answer is the same on every gated surface, and the card records WHAT it is and why.
  • Nothing in app/(authed)/ changes behaviour in either arm.
  • ≥90% coverage on every file the story's PRs touch.

Context refs

  • motir-core/lib/billing/availability.tsisCloudBilling(), MOTIR_CLOUD, and the explicit-flag discipline
  • motir-core/docs/decisions/billing-tiering.md §6 — the cloud-vs-self-host build gate and its two-questions-two-functions rule
  • motir-core/lib/ai/planningConfig.tsisAiPlanningConfigured(), the OTHER flag this must not be confused with
  • motir-core/app/(public)/explore/ · app/(public)/p/ · app/api/public/ — the surfaces gated
  • motir-core/lib/services/{publicProjectsService,projectSquareService,projectTagsService}.ts — the services behind them, which are NOT deleted

Advisory dispositions

  • referenceMOTIR-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.

Comments (0)

No comments yet — be the first to weigh in.

Sign in to comment

Reading is open to everyone; posting needs a Motir account.

Sign in