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,362
shipped

Motir · Roadmap

MOTIR-4004Done

Decide what motir-core reads once content/legal/ leaves — the legal-document manifest, the unconfigured default, and where the subprocessor guard's evidence lives

Opened by Zhu Yue ·

Type: decision · Executor: coding_agent · Repo: motir-core · ONE PR. The deliverable is an AMENDMENT to docs/decisions/public-surface-hosts.md, cross-referenced from docs/decisions/legal-document-set.md.

content/legal/*.md is not only rendered copy. It is the input to the re-consent gate, and public-surface-hosts.md §7 records MOTIR-3909 as UNCHANGED without noticing that. This card is what makes the story buildable.

What was measured, on motir-core origin/main e76d1afdd (2026-08-30)

readingcommandresult
callers of the loader outside its own modulegit grep -n "listLegalDocuments|getLegalDocument" origin/main -- ':!lib/legal/documents.ts'app/(public)/legal/{page,[slug]/page}.tsx, app/(auth)/re-consent/page.tsx, lib/services/legalAcceptanceService.ts, plus 6 test files
content/ top levelgit ls-tree origin/main content/ --name-onlycontent/legal and nothing else
the whole referrer populationgit grep -l -E "content/legal|lib/legal|'/legal|\"/legal|legalDocument|LegalAcceptance|reconsent" origin/main84 files

legalAcceptanceService.recordAcceptance runs from the Better-Auth user.create.after hook (lib/auth/index.ts:328) on every sign-up; resolveOutstanding runs from resolveReconsentHold, called by the (authed), (onboarding) and (planning) layouts on every signed-in page load. Both call listLegalDocuments(), which is a readdirSync of content/legal/.

So the naive removal is silent. recordAcceptance has an explicit "NO EMPTY-SET GUARD HERE, DELIBERATELY" comment; with no documents it writes zero rows and outstandingReconsent answers []. A cloud deployment would stop recording acceptances and stop holding anyone, with no error and no red test.

Q1 — what does motir-core read instead?

RECOMMENDED: a CONFIGURED MANIFEST. lib/legal/documents.ts keeps its exported surface — listLegalDocuments(), getLegalDocument(slug), legalDocumentSlugs(), LegalDocument — and changes its SOURCE from the filesystem to configuration. Each entry carries slug, title, version, url (absolute, on whatever host the operator publishes), effectiveDate and changeSummary; the body field goes, because the body is what leaves. Unset ⇒ [].

Everything downstream is then unchanged in shape: lib/legal/consent.ts stays pure and keeps RECONSENT_DOCUMENT_SLUGS; legalAcceptanceService keeps its read-at-call-time contract; reconsentGate is untouched.

Rejected, each with its reason:

alternativewhy not
keep the seven documents in motir-core, gated on MOTIR_CLOUDpublic-surface-hosts.md §2 already rejected it: this is CONTENT, and no runtime flag makes another company's privacy policy appropriate in a GPL tree
motir-core FETCHES the manifest from motir.co at runtimeputs a network hop inside sign-up and inside every signed-in page load, and makes the brand host a hard runtime dependency of the application host. The failure mode is the one resolveReconsentHold already logs around — and it would become routine
publish the documents as an npm artifact motir-core installsre-introduces moooon B.V.'s contract text into a package every self-hoster installs. The problem is not where it renders; it is whose it is

The ADR must also record the shape of the configuration — one env value or a config module — and the VALIDATION, because a malformed manifest is the one input that can make the gate wrong rather than absent: parseSemanticVersion returns null for a version it cannot read and isMaterialChange then answers true, so a typo in the manifest holds every signed-in reader at /re-consent. Decide whether that failure is loud (refuse to boot) or silent (log and treat as unset), and say which.

Q2 — what does an UNCONFIGURED build do?

RECOMMENDED: render no link, and hold nobody. With no manifest, app/(auth)/sign-up's legal sentence re-flows without its two links, the rail's Legal row is absent, and the re-consent rows have no read the document target. A link to a document the operator has not published is a 404 inside a legal notice, which is worse than no link.

And the gate stays MOTIR_CLOUD-gated — DECIDED here, not deferred. isMotirCloud()'s own comment says it answers "is moooon B.V. the counterparty to these documents?", and a configured manifest does not answer that question. An operator publishing their own terms does not thereby acquire our re-consent semantics. Widening it is a product decision this record explicitly does not make and does not owe a follow-up card, because nothing in this story depends on it.

Q3 — where does the subprocessor guard's evidence live?

tests/legal/subprocessor-list-guard.test.ts holds content/legal/subprocessors.md's rows against motir-core's own package.json dependencies and outbound hosts (tests/helpers/subprocessorRegistry.ts's VENDOR_SIGNATURES). The page moves; the evidence cannot. Deleting the guard is not available — its own header records that the page went stale four times on 2026-08-26/27.

RECOMMENDED: split it at the repository line, and make the seam fail on divergence. motir-core keeps the measurement — the signatures, run against its own tree — and emits the result as a committed, guarded egress manifest (a fact about the open-source software's outbound dependencies, which genuinely belongs in motir-core). motir-marketing's /legal build asserts every vendor row on its subprocessors.md has a matching entry in that manifest.

The ADR decides WHICH TRANSPORT carries the manifest across, and it is the same question MOTIR-3932 raises for /docs — a published artifact that does not rot, versus a copy that does. State the coupling: if this record and MOTIR-3932 pick differently, one of them is wrong. The requirement this record imposes is not the transport but the property: the seam must FAIL when the two sides diverge, and a copy with no drift check does not satisfy it.

FIRST STEP — the rung-1 check, which this card owes and must not assert from memory

Before writing the recommendation, check how the mirror open-core products actually do this: read GitLab's, Sentry's, Mattermost's and Plane's open repositories for whether the vendor's own Terms / Privacy / subprocessor text ships in the tree, and what a self-hosted install links to instead. Cite what you read, with URLs, in the ADR's own evidence section. A remembered claim is not a check (plan-rules/core.md, rung 1) — and if the mirrors turn out to keep their legal text in-tree, that is an argument this record has to answer rather than omit.

Boundary

  • It writes no application code. Every mechanism it decides is built by a named sibling.
  • It does not decide the /legal page's layoutthe motir.co design card does.
  • It does not decide the 301MOTIR-3884 already owns app.motir.co/{explore,docs,legal,p}/*.
  • It does not re-open the move itself. public-surface-hosts.md §2 decided that /legal leaves; this amends how, not whether.

Acceptance criteria

  • docs/decisions/public-surface-hosts.md carries a dated AMENDMENT answering Q1, Q2 and Q3, each with its rejected alternatives and the reason, in the record's existing table style.
  • The amendment quotes the measurement above — the loader's non-test callers and the two runtime entry points — as the reason §7's "MOTIR-3909 UNCHANGED" is superseded, and edits that §7 row to say so.
  • The manifest's field set and its validation behaviour are written down, including what a build does with a manifest whose version does not parse, and why.
  • The unconfigured behaviour is stated per surface — sign-up, the rail row, the re-consent rows, the gate — with the reason a link is omitted rather than pointed at a default.
  • The MOTIR_CLOUD gating of re-consent is stated as retained, with its reason, so a later reader does not read the manifest as having widened it.
  • Q3 names the transport for the egress manifest, states the coupling to MOTIR-3932's /docs mechanism, and requires the seam to fail on divergence.
  • The rung-1 reading of at least three open-core mirrors is cited with URLs in the record's evidence section.
  • docs/decisions/legal-document-set.md gains a one-line pointer to the amendment, since it owns the document set this changes the home of.

Context refs

  • motir-core/docs/decisions/public-surface-hosts.md — the record being amended; §2's host table, §5's self-host table and §7's card list
  • motir-core/docs/decisions/legal-document-set.md — the record that owns the seven-document set
  • motir-core/lib/legal/documents.ts — the loader whose source changes
  • motir-core/lib/legal/consent.tsRECONSENT_DOCUMENT_SLUGS and the materiality rule, both unaffected
  • motir-core/lib/legal/reconsentGate.tsisMotirCloud() and its recorded reason
  • motir-core/lib/services/legalAcceptanceService.ts · motir-core/lib/auth/index.ts — the two runtime entry points
  • motir-core/tests/helpers/subprocessorRegistry.ts · motir-core/tests/legal/subprocessor-list-guard.test.ts — Q3's subject
  • MOTIR-3932 — the story raising the same cross-repo transport question for /docs

Advisory dispositions

  • likely-missing-edgeMOTIR-3932: NOT REAL, and no edge is owed. Q3 names that story to record a coupling — if the two records pick different transports for the same kind of cross-repo artifact, one of them is wrong — not to consume anything it produces. This card's own requirement is a PROPERTY (the seam fails on divergence), dischargeable whichever transport either record picks and whichever lands first. An edge would also be cross-parent and would make one ADR wait on an entire sibling story.
  • The remaining reference advisories name cards this one's Boundary excludes. A boundary exists to say a deliverable is somebody else's, and naming the owner is what makes the exclusion checkable — so the reference is deliberate and no edge is owed.

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