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

The unconfirmed gates (2/2) — the four public reads, the three ready-set routes and the two upload endpoints

The other half of the guard's CLAIMED_BUT_UNVERIFIED bucket. Same contract as its sibling — one read each, ending in a named hop or a gate — but these nine are harder, because two of the three groups are supposed to be reachable without a project membership and the question is what the right check is, not whether there is one.

OperationGate today (per the inventory)Claimed permission
/api/public/categories · /api/public/exploresession onlypublic_request:submit
/api/public/p/[identifier]/roadmap · /treesession onlypublic_request:submit
/api/ready · /api/ready/nextworkspace onlyproject:browse
/api/ready/nudgesession onlyproject:browse
/api/upload/avatar · /api/upload/issue-attachmentsession onlyattachment:create

What each group actually needs

  • The public reads. Two siblings in the same directory already do it properly — /api/public/p/[identifier]/items gates on assertCanBrowsePublic and /api/public/projects/[projectId]/requests/duplicates on assertCanSubmitToTriage. These four take a session and nothing else. The claimed key is wrong as well: reading a public project's roadmap or tree is browsing, not submitting, so the rows want project:browse resolved through the public-access path, and the mapping is corrected as part of the read.
  • The ready set. /api/ready and /api/ready/next answer what should I work on, which is a project-scoped read; nudge is session-only today. Gate on project:browse for the project each result belongs to — and check whether the LIST leaks across projects the actor cannot browse, which is the failure mode a per-request gate would not catch.
  • The uploads. A session-only upload endpoint accepts bytes from anyone signed in. attachment:create is the claimed key and it is project-scoped, so issue-attachment needs the project it is attaching to; avatar is genuinely user-scoped and its row is probably mis-mapped — if so, correct it to user-scoped rather than inventing a project for it.

Acceptance criteria

  • All nine are read, and each row records either the named hop that reaches its gate or the gate this card added.
  • The four public rows end gated through the same public-access predicate their two working siblings use, with their claimed permission corrected where the read shows it wrong.
  • /api/upload/issue-attachment asserts a project-scoped key resolved from the work item it attaches to; /api/upload/avatar is either gated or re-decided as user-scoped, and the PR body says which and why.
  • The ready-set routes cannot return an item from a project the caller may not browse, proved by a test with two projects and one membership — not by a single-project happy path.
  • Where a gate was real but invisible, the walk is extended to see it, with both halves of the control the file's earlier repairs use.
  • The guard's CLAIMED_BUT_UNVERIFIED pin is re-derived by running it on this branch and falls by exactly these nine, reaching zero once its sibling card has also landed.
  • pnpm test tests/permissions/ tests/services/ is green.

Context refs

  • app/api/public/p/[identifier]/items/route.ts — the sibling that gates correctly, and the predicate to copy.
  • app/api/public/p/[identifier]/roadmap/route.ts, /tree/route.ts, app/api/public/categories/route.ts, app/api/public/explore/route.ts — the four to read.
  • app/api/ready/route.ts, /next/route.ts, /nudge/route.ts and the service behind them.
  • app/api/upload/avatar/route.ts, app/api/upload/issue-attachment/route.ts.
  • lib/projects/access.tscanBrowsePublic / canSubmitToTriage and the level-gated grants.
  • docs/decisions/public-projects.md — the Story 6.12 decision that defines what an anonymous or non-member actor may read.
  • The unconfirmed gates (1/2) — the sibling card; the two together empty the bucket.