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

`ai:view_plan` — reading a generated plan, and the approve / decline / proposal-edit that turn it into work items

The last member-facing key, and the smallest domain: four routes plus the server actions behind the plan-review surface.

OperationService methodToday
GET /api/plans/[id]planReviewService.getPlanReviewreaches a gate indirectly — planReviewService holds no assert of its own
POST /api/plans/[id]/approveplansService.approvePlanassertCanEdit
POST /api/plans/[id]/declineplansService.declinePlanassertCanEdit
PATCH /api/plans/[id]/items/[itemId]plansService.updateProposalassertCanEdit
app/(authed)/plans/_actions.tsthe plan-review server actionsmapped to ai:plan by the inventory; they act on a PLAN, so they belong to this key

Approving materializes work items — that is what makes ai:view_plan a write key rather than a read one, and why docs/decisions/member-facing-permissions.md puts it at member rather than at browse. The name is the misleading part of the domain and is worth a comment where the gate lands: ai:view_plan governs reading a generated plan AND acting on it, because the two are the same surface and a reviewer who may not act has nothing to review for.

The plan's project comes from the PLAN row, not from the actor's active project. Resolve it there.

Acceptance criteria

  • All four routes' service methods assert assertPermission(projectId, ctx, 'ai:view_plan'), with the project id resolved from the plan.
  • The server actions in app/(authed)/plans/_actions.ts assert the same key; their inventory row is corrected from ai:plan, with the correction annotated.
  • A test proves approvePlan refuses a viewer before any work item is created — assert on the absence of the rows, not only on the thrown error.
  • A test proves a plan belonging to another project cannot be approved by an actor who holds the key in their own.
  • ai:view_plan does NOT flip to enforcement: 'enforced' here if any mapped operation is still ungated after this PR; check by running the guard, and if every one of them is covered, flip it and extend tests/permissions/catalog.test.ts by exactly this key. Say which happened in the PR body.
  • The inventory rows record the gate; the guard's pinned counts are re-derived by running it on this branch.
  • pnpm test tests/permissions/ tests/services/ is green.

Context refs

  • lib/services/plansService.tsapprovePlan, declinePlan, updateProposal and their assertCanEdit calls.
  • lib/services/planReviewService.tsgetPlanReview, with no assertion of its own.
  • app/(authed)/plans/_actions.ts — the server-action file the inventory maps to ai:plan.
  • docs/decisions/member-facing-permissions.md — the role assignment and the read-versus-act reasoning.
  • The role-set seam — the prerequisite.