Skip to content

moooon

Motir

Vibe your whole project. Bring an idea — Motir's three AI layers plan it, track it, and ship it, end to end. You're looking at Motir, built in Motir.

  • Vibe Project
  • Open Source
  • AI Agent
  • AI Loop
1
requests
0
upvotes
145
planned
1,361
shipped

Motir · Work items

MOTIR-4034Done

Gate app/api/public/* behind isCloud() — every anonymous route returns 404 when MOTIR_CLOUD is unset

When MOTIR_CLOUD is unset, every anonymous route under app/api/public/ returns 404 instead of serving. This is the CAPABILITY gate: a self-hosted build offers no public read surface at all — not a hidden page, an absent capability.

The unavailable answer is 404 (a JSON { error: 'Not found' }), decided and recorded here: a 404 leaks no existence (unlike a 403, which admits "this exists but you may not see it"), and it is the convention for a feature an instance does not ship. It is the same shape on every gated route, via one shared guard, so the answer is consistent by construction.

Approach: a shared route-layer guard (a small helper the routes import, or a shared wrapper) that each app/api/public/* route calls first — returning NextResponse.json({ error: 'Not found' }, { status: 404 }) when !isCloud(). This is a route-layer concern only (routes are HTTP-only per the 4-layer convention); no service/repository change. The guard reads the isCloud() predicate, not isCloudBilling() and not a raw process.env read. When MOTIR_CLOUD=true the routes behave exactly as they do today. Ships its own per-route test: MOTIR_CLOUD unset → 404 on each of the 11 routes; set → the existing public-API contract tests still pass.

Acceptance criteria

  • With MOTIR_CLOUD unset, every route under app/api/public/ returns 404 (the one shared JSON shape); none returns 200, none 500s, none throws a stack trace.
  • With MOTIR_CLOUD=true, every route behaves byte-identically to today (the existing public-API contract tests still pass).
  • The guard reads isCloud() only (single-reader rule) — asserted by the single-reader test from the predicate card.
  • The 404 shape is shared by ONE helper, so every gated route answers identically; the card records WHY 404 (leaks no existence), not 403/redirect.
  • ≥90% coverage on the touched files.

Context refs

  • app/api/public/ — the 11 anonymous routes: categories, explore, p/[identifier]/{route,changelog,follow,items,roadmap,subscribe,tree}, projects/[projectId]/requests/{route,duplicates}
  • lib/billing/availability.tsisCloud()
  • lib/ai/planningConfig.ts — the flag pattern this must not be confused with