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-3909Done

motir-core reads its legal documents from a CONFIGURED MANIFEST — the links, the re-consent gate over it, the subprocessor split, and the hosted provisioning

content/legal/*.md is moooon B.V.'s own contract text, and it is sitting in a GPL-3.0 repository that other people run. This story takes it out — and takes out only the text, because the same files are also what the re-consent gate reads.

⚠️ AMENDED 2026-09-01 — the DELETION half moved to a new sibling story, at the DEPLOY BOUNDARY

Submitted by motir run MOTIR-3909, which refused the parent-run at step 0. This story's own criteria implied two merges separated by a deployment, and a container is completed by ONE merge per repository (plan-rules/kind-container.md, COUNT THE MERGES A CONTAINER'S OWN CRITERIA IMPLY; run.md's third parent-run refusal; core.md gate 14(c)/(d)).

The deadlock: MOTIR-4012 confirmed the legal gate LIVE on app.motir.co — a reading of the DEPLOYED application this story's own merge produces — while MOTIR-4013 was blocked_by it and MOTIR-4014 / MOTIR-4015 behind that. A parent-run puts every child in one pull request, so no ordering of these children finished the story. Every signal read clean — ready: true, openBlockers: [], valid: true, no advisory — because all of them ask "has every work item this one waits on reached done?", and the thing being waited on was a deployment. The epic already knew this: MOTIR-3910's own body records it as what went wrong in the first version of this epic.

What LEFT, to the deletion story (which is blocked_by this story): the live-gate confirmation, the deletion of content/legal/ and app/(public)/legal/ with its 84-file sweep, and that story's own two mandatory test cards — the absence guard and the browser walk.

What STAYS here is the manifest half: the ADR amendment, the design, the manifest reader, the subprocessor evidence split, the three link surfaces, and both repositories' test gates over the CONFIGURED behaviour.

And one thing MOVED IN, which is the half a deploy-boundary split usually stops short of. MOTIR-4012 is re-scoped to steps 1–4 only — check the premise, compose the seven entries, SET the manifest, read it back from the platform — and stays in this story, because a secret needs no merge: it is inert until the reader deploys. That is what makes this story inert on arrival. Without it, this story merges, deploys, and app.motir.co reads an unset manifest — recording no acceptances and holding nobody, silently, for however long it takes somebody to run one command. That window is the exact failure this story's own explanation is about, and the split would have created it.

MOTIR-4014 and MOTIR-4015 stay here and lost the clauses that read on the deletion — the absence guard, the catalogue parity and the /legal 404 step — which are now asserted by the deletion story's own gates. Nothing was dropped and nothing is asserted twice.

⚠️ AMENDED 2026-08-30 — the RECEIVING half moved to MOTIR-3932

This story used to own both halves of the move. The two motir-marketing cards that build motir.co/legalMOTIR-4005 (design) and MOTIR-4009 (render) — were re-filed to MOTIR-3932, the story that renders the public reading surface on the brand host. Both compose that story's chrome asset and ship in that story's only repository. See the section on ordering below for why.

What stays here is the motir-core side, which is the whole point of the story: the configured manifest, the three link surfaces, the re-consent gate over the manifest , the deletion and its sweep (⚠️ the deletion left on 2026-09-01 — see the amendment above) — plus MOTIR-4011, the motir-marketing Vitest gate, which stays because its other blocker is a motir-core card.

What is true today — RE-MEASURED on origin/main e76d1afdd, 2026-08-30

motir-core ships seven documents — privacy.md, terms.md, dpa.md, subprocessors.md, acceptable-use.md, cookies.md, model-providers.md — rendered by app/(public)/legal/page.tsx and app/(public)/legal/[slug]/page.tsx. They name moooon B.V., its subprocessors and its transfer bases. Anybody who deploys motir-core today serves another company's privacy policy and terms of service from their own domain.

⚠️ THE SEAM THIS STORY WAS WRITTEN WITHOUT — the documents are an INPUT to the re-consent gate

lib/legal/documents.ts is the one module that reads content/legal/, and its non-test callers are not only the pages:

callerwhen it runs
lib/services/legalAcceptanceService.tsrecordAcceptanceevery sign-up, from the Better-Auth user.create.after hook (lib/auth/index.ts:328)
lib/services/legalAcceptanceService.tsresolveOutstandingevery signed-in page load, via resolveReconsentHold in the (authed), (onboarding) and (planning) layouts
app/(auth)/re-consent/page.tsx:63the deferred interstitial — takes slug / title / version

The front-matter version is what lib/legal/consent.ts reads to decide whether a change is MATERIAL, which is how content/legal/terms.md §14's promise — "we will not treat silence as agreement to a material change" — is actually kept.

So the naive removal fails silently. recordAcceptance carries an explicit "NO EMPTY-SET GUARD HERE, DELIBERATELY"; with no documents it writes zero rows, outstandingReconsent answers [], and a cloud deployment stops recording acceptances and stops holding anyone — no error, no red test.

The corrected shape: motir-core keeps the MECHANISM and loses the CONTENT. lib/legal/documents.ts becomes a reader of a configured legal-document manifest (slug, title, version, effective date, URL); consent.ts, legalAcceptanceService and reconsentGate are unchanged in shape. Unset ⇒ no documents, no links, no gate — which is the right answer for a self-hosted build. That is the same replacement pattern this story already stated for the links, applied one layer deeper.

⚠️ THREE READERS THIS STORY NAMED THAT ARE NOT READERS

the original body saidmeasured
app/sitemap.ts, and the legalDocumentSlugs helper it callsit calls no such thing. git grep -n legalDocumentSlugs origin/main returns two non-test hits, both inside app/(public)/legal/[slug]/page.tsx. The sitemap emits /, /explore, the topic pages and /p/*MOTIR-3881 already settled its contents
the OpenAPI / API-docs pages and lib/apiDocs/*git grep -i legal origin/main -- lib/apiDocs app/api matches only IllegalParentType / IllegalTransition. Zero legal readers
"the cookie surfaces"there is no cookie banner or consent surface. lib/legal/consent.ts records why: no cookie consent is sought at all, under the ePrivacy Art. 5(3) exemption

The real population is 84 filesgit grep -l -E "content/legal|lib/legal|'/legal|\"/legal|legalDocument|LegalAcceptance|reconsent" origin/main — and it sorts into: the machinery that STAYS, the pages that LEAVE, the three link surfaces, ~12 comment citations of a document section, the CI content/* arm, and a handful of illegal false positives.

⚠️ THE SUBPROCESSOR GUARD IS CROSS-REPO BY CONSTRUCTION

tests/legal/subprocessor-list-guard.test.ts holds subprocessors.md's rows against motir-core's own package.json dependencies and outbound hosts. The page moves; the evidence cannot. Deleting the guard is not available — its own header records the page going stale four times on 2026-08-26/27. The split, and the transport that carries the evidence across, is decided by this story's first card.

Why this one MOVES rather than being gated

The cloud gate is the right remedy for a capability that only makes sense hosted. This is content: no runtime flag makes moooon's contracts appropriate in an open-source repository, because the problem is not that they are reachable, it is that they are in the tree at all — copied by every fork, carried by every clone, and legally somebody else's. Gating it would leave the text exactly where it is.

What motir-marketing receives — and who builds it

The seven documents, byte-for-byte, and a renderer for them on the host that serves the brand. That work is MOTIR-3932's, in MOTIR-4005 and MOTIR-4009; this story consumes it as a precondition, not as a deliverable. That repository has no Markdown renderer, no front-matter parser and no i18n layer today, so the parser is PORTED from motir-core with its unit tests rather than rewritten. Consequence, stated rather than discovered: the page chrome around these documents loses its Chinese translationmotir-marketing is English-only. The documents themselves were never translated, so what is lost is a handful of labels, not contract text.

Boundary

  • It does not build motir.co/legal. MOTIR-3932 does, and this story is blocked_by it — so the documents are readable on the brand host before anything here removes them from motir-core.
  • It does not decide which host serves motir.co/legal. docs/decisions/public-surface-hosts.md §2 decided it; this story lands into that arrangement.
  • It does not rewrite the documents. They port across as they are; their content is moooon's legal work, not a planning deliverable.
  • It does not issue the 301. app.motir.co/{explore,docs,legal,p}/* is MOTIR-3884's, under MOTIR-3932 — and it fires before the deletion story deletes anything, so it does not redirect into a 404.
  • It does not touch the rest of app/(public). MOTIR-3951 removes the other 69 files and its own table names legal as this story's.
  • It DELETES NOTHING. content/legal/ and app/(public)/legal/ are still in the tree when this story merges — the loader simply stops reading them. Removing them is the deletion story's, which this story blocks.
  • It does not CONFIRM the hosted gate live. MOTIR-4012 SETS the manifest and reads it back from the platform, which needs no merge. Observing the deployed application actually holding a reader over it is the deletion story's first card, because that reading is of the deployment this story's own merge produces.
  • It does not touch /docs, which is MOTIR-3932's question.
  • It does not widen the re-consent gate. It stays MOTIR_CLOUD-gated: isMotirCloud() answers "is moooon B.V. the counterparty?", and a configured manifest does not answer that.

⚠️ Ordering — one STORY edge, and the cross-parent leaf edges that used to stand here

This story is blocked_by MOTIR-3932. That single edge carries every ordering constraint the leaves used to carry individually: motir.co serves /legal and the 301 fires, and only then does anything here read a manifest or delete a file.

What it replaced. As authored, this story held MOTIR-4009 — which had to ship before MOTIR-3884 — while MOTIR-4013 had to ship after it. That interleave made a story-level edge a cycle in either direction, so three cross-parent leaf edges were wired under plan-rules/fixtures/phase-skeleton.md's no-legal-lift limb, and the body argued at length that validate_work_item MOTIR-3909 returning valid: false was correct and not a defect.

That argument was wrong, and it is recorded here rather than deleted. The limb applies only when NONE of phase-skeleton.md's three remedies is available; only (a) LIFT was tested. (b) RE-FILE was available: MOTIR-4005 and MOTIR-4009 both ship in motir-marketing and both compose MOTIR-3880's chrome, so they belong under MOTIR-3932. Re-filed, every remaining cross-story edge runs one way and the lift is legal. The collateral the old argument missed: those leaf edges also made MOTIR-3932 itself valid: false, on MOTIR-3884 ← MOTIR-4009. It validates clean now.

This story still reports valid: false, on that one story edge — the ordinary state of any downstream story in a sequenced epic, and not a shape defect. Every LEAF edge beneath it is in-subtree.

Known cost of the lift: the readiness cascade holds MOTIR-4004, the decision card, until all of MOTIR-3932 is done — including MOTIR-3886 and MOTIR-3951, which it does not need. That is inherent to lifting rather than leaf-wiring and is accepted; if it becomes the critical path, the remedy is to carve MOTIR-4004 out to its own story, not to re-wire leaf edges.

Verification recipe

  1. On a build with no legal configuration, open sign-up — there is no legal notice on the card at all — and sign in: the rail has no Legal row. ⚠️ AMENDED 2026-09-01 (MOTIR-4004, docs/decisions/public-surface-hosts.md AMENDMENT 2 §D): the notice is ABSENT, not re-flowed — a sentence entirely about two documents becomes FALSE rather than merely weaker when they do not exist.
  2. On a configured build, sign-up shows both links, each pointing at the configured absolute URL on the brand host.
  3. On a cloud build whose manifest version has moved materially, a signed-in reader is held at /re-consent, each row links to that document's configured URL, and Agree and continue returns them to where they were going.
  4. fly secrets list -a <the motir-core app> shows the legal-document manifest set, pasted on MOTIR-4012before this story's pull request opens, so the reader never deploys into an unset manifest.
  5. motir-core's subprocessor seam FAILS when the evidence side and the page side diverge — demonstrated red, not asserted.

(app.motir.co/legal no longer serving a document, and git grep -n "moooon" coming back clean, are the deletion story's recipe — after this story has merged and deployed.)

(motir.co/legal rendering all seven documents is MOTIR-3932's recipe, and a precondition of every step above.)

Acceptance criteria

  • Every child is done. The repository set is motir-core + motir-marketing — the second because MOTIR-4011 asserts the page-side half of the subprocessor seam.
  • motir-core renders legal links from configuration, with a stated and tested default for the unconfigured case on all three surfaces.
  • The re-consent gate works over the manifest — a material version bump holds a signed-in reader, a patch bump does not — asserted through the whole seam against real Postgres.
  • A malformed manifest cannot hold the product: an entry whose version does not parse never reaches listLegalDocuments(), asserted with the ADR's chosen failure behaviour named.
  • The subprocessor disclosure still has a guard: the evidence side in motir-core, the page side in motir-marketing, and the seam FAILS when they diverge — shown red.
  • The hosted deployment carries the manifest, read back from the platform rather than from a file in the repository.
  • ⚠️ THIS STORY IS INERT ON ARRIVAL: its pull request does not open until MOTIR-4012 is done. The hosted manifest is SET before the reader that consumes it deploys, so there is no window in which app.motir.co reads an unset manifest, records no acceptances and holds nobody. This is a criterion and not a note about sequencing: without it the split at the deploy boundary trades a deadlock for a silent compliance gap of unbounded length.
  • content/legal/ and app/(public)/legal/ are UNCHANGED by this story, and its PR body says so — the loader stops reading them, and the deletion story removes them.

Precondition, not a criterion of this story: motir-marketing serving all seven documents byte-identically and motir.co/legal resolving are MOTIR-3932's acceptance criteria, discharged before this story starts.

Context refs

  • motir-core/content/legal/*.md — the seven documents
  • motir-core/app/(public)/legal/ — the renderer being removed
  • motir-core/lib/legal/{documents,consent,reconsentGate}.ts · lib/services/legalAcceptanceService.ts · lib/auth/index.ts — the machinery that STAYS
  • motir-core/tests/legal/subprocessor-list-guard.test.ts · tests/helpers/subprocessorRegistry.ts — the cross-repo guard
  • motir-core/.github/workflows/ci.yml · tests/ci-changed-paths-gate.test.ts — the content/* arm and the defect it exists for
  • motir-core/docs/decisions/public-surface-hosts.md · legal-document-set.md — the records this amends
  • MOTIR-3876 — the contract this story lands into; blocked_by
  • MOTIR-3932 — builds motir.co/legal and issues the 301; blocked_by, and the edge that carries this story's whole ordering
  • MOTIR-3951 — the rest of app/(public); its table names legal as this story's, and that ownership travelled to the deletion story with the deletion card
  • the deletion story — the post-deploy half: the live-gate confirmation, the deletion and its absence guards; this story blocks it