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
144
planned
1,365
shipped

Motir · Roadmap

MOTIR-3946Done

The public read surface gets its OWN versioned contract — same machinery as v1, its own version, document and drift guard (the spine + the three core reads)

Opened by Zhu Yue ·

Type: code · Executor: coding_agent · Repo: motir-core · ONE PR.

⚠️ THE DECISION, taken 2026-08-30 — and it is NOT "fold it into v1"

public-surface-hosts.md §3 left this card to decide where the public read surface is published. Three options were weighed against shipped code; the answer is its own versioned contract, generated by the same machinery as v1 — not folded into the v1 document, and not hand-written.

Folding into /api/v1 was investigated first and rejected on three measured grounds, each read from origin/main:

  1. /api/v1 is authenticated BY CONSTRUCTION. withV1Route takes a required options.permission: PermissionKey"a route that declares none fails MOTIR-1861's guard" — runs authenticateApiToken as step 1 with a 401 on failure, and hands its handler a non-optional userId, workspaceId and a ServiceContext bound to the token's workspace. Its rate limiter keys per token, deliberately. app/api/public/* is anonymous by design. Moving these routes under that prefix means either bypassing the wrapper — routes under /api/v1 that do not behave like v1, failing four guards that walk app/api/v1 — or widening a shipped auth contract so it has an anonymous mode. The second is a re-architecture of the API's security model to solve a documentation problem.
  2. The response shapes differ. Every v1 collection references ListEnvelope / RankedListEnvelope and v1's errorResponse; these routes return the DTO raw and { code } on 404. One document whose operations disagree about their envelope is a document that has to explain itself.
  3. The version would lie. V1_CONTRACT_VERSION is stamped on X-Motir-Api-Version by withV1Route. Routes that do not use the wrapper stamp nothing, so a client reading one info.version covering both surfaces gets a header on half the paths. A version number is a promise; one covering two surfaces with different stability is unreadable.

And a hand-written second document is the mirror this epic keeps arguing against — so the answer is neither. The public contract is GENERATED, from the same primitives (lib/api/v1/openapi/operation.ts's declaration type, the registry pattern, emit.ts's assembly), with its own registry and its own version. One source of truth per surface, one generator shared.

The honest cost, stated in the ADR amendment this card writes: two documents exist. That is the price of two genuinely different contracts, and it is smaller than either alternative — an anonymous hole in an authenticated contract, or a copy that rots.

What this card ships — the SPINE and three reads

It is deliberately not all eleven operations. The remaining eight and the totality guard are MOTIR-3990, because a card that also declares eleven operations with their schemas exceeds the estimation gate's one-hour run, and splitting at the spine is where the seam actually is.

  1. PUBLIC_CONTRACT_VERSION — a leaf module with no imports, for the same reason lib/api/v1/contractVersion.ts is one: the emitter and any future response header must read the same value without one depending on the other. Semver, with the same MAJOR/MINOR/PATCH meaning v1's records.
  2. The operations module + registry for the public surface, in the operations.ts shape the v1 resources use.
  3. Three READ operations, which prove the pipeline end to end on the paths motir-marketing needs first: GET /api/public/p/{identifier} (the subject, MOTIR-3945), GET /api/public/explore, GET /api/public/categories.
  4. The published document at its own path, emitted from the registry.
  5. The DEPRECATION POLICY, written down: what an additive change is, what a breaking one is, and what a consumer is guaranteed. This is the half that makes it a contract rather than a description.

⚠️ The guard belongs in THIS repository

A contract test living only in motir-marketing reports that motir-core broke it, after it has shipped. The guard worth having fails in motir-core's own CI, on the pull request that changes a response shape, naming the consumer. A consumer-side check is a smoke alarm in the wrong building.

Boundary

  • No route moves and no URL changes. The paths stay /api/public/* — see the decision above.
  • withV1Route is not touched. Widening it is the option this card rejected.
  • The remaining eight operations and the totality coverage guard are MOTIR-3990's.
  • It gates nothing — whether the surface exists on a self-hosted build is MOTIR-3908's.

Acceptance criteria

  • PUBLIC_CONTRACT_VERSION exists as an import-free leaf module, with its semver meaning documented as lib/api/v1/contractVersion.ts documents v1's.
  • The public document is EMITTED from a registry of operation declarations — no path, method or schema is written twice — and is served at its own published path.
  • The three read operations above appear in it with their real response schemas, and a test asserts each documented schema matches what the route actually returns.
  • The deprecation policy is recorded: the allowed additive changes, what constitutes a breaking one, and the guarantee a consumer gets. Prose, in the ADR or beside the version module, cited from both.
  • A drift guard runs in motir-core's CI and fails on a pull request that changes one of the three documented response shapes without changing the document.
  • docs/decisions/public-surface-hosts.md gains an amendment recording this decision, the three grounds for rejecting the fold, and the two-document cost.
  • Nothing a visitor sees changes; no route's behaviour changes.
  • ≥90% coverage on the files this PR touches.

Context refs

  • motir-core/lib/api/v1/route.tswithV1Route, the required permission, the 401 and the workspace-bound context: the evidence for ground 1
  • motir-core/lib/api/v1/contractVersion.ts — the leaf-module pattern and the semver meaning to mirror
  • motir-core/lib/api/v1/openapi/{operation,registry,emit,envelopes,errorResponse}.ts — the machinery reused, and the envelopes that are v1's alone
  • motir-core/tests/api/v1/openapi-{operations-coverage,registry,drift-guard,spec-route}.test.ts — the four guards that walk app/api/v1
  • motir-core/app/api/public/ — the surface being published
  • motir-core/docs/decisions/public-surface-hosts.md §3 — the question this card answers

Discussion

No comments yet.

Adding to this discussion signs you in on app.motir.co and brings you back to this request.

Add a comment