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

Story test gate (Vitest) — the coverage floor, the write→resolve seams, and the guards that stop a second role-writing path

The story-level gate, run after every code subtask has merged, because it measures their real combined result. It is not a list of cases decided in advance — each card ships its own units, and this card does the three things no individual card can.

1 · Coverage floor

Run coverage over this story's changed surface — lib/permissions/*, lib/services/projectRoleDefinitionService.ts, the touched part of projectAccessService, the two repositories, the route handlers, the editor and the roles components — and, wherever a file sits under the project's per-file gate, write the missing unit and branch tests to reach it. "Already covered by the per-subtask floor" is the expected normal outcome for most files, not a redundancy to subtract in advance; the job is the gap between them.

2 · The seams the units mock

Each of these drives one card's REAL output through the next card's REAL consumer. They are the tests that catch key drift, which every unit masks by construction:

  • Author → resolve. Create a role through the service, assign it through the members service, then resolve that actor with projectAccessService.getPermissions and assert the exact set. This is the story's central seam and the one thing no card owns end to end.
  • Store → read → screen. A stored ProjectRoleDefinition through getRoleCatalog into the widened RoleDTO and into RoleList / RoleDetail, asserting the rendered name, the Based on … · ±N chip and the counts — the DTO's keys read back through the component that consumes them, not compared to a fixture.
  • Editor → API → store. The editor's submitted payload through the real route into a stored row, and back out through the read.
  • Delete → reassign → resolve. Delete a role with a destination and assert every mover's resolved permissions changed to the destination's — not merely that a column moved.
  • Access level × custom role. The resolution's own parity table extended across all four access levels for a membership on a custom role, against real Postgres rather than the pure unit's inputs.

3 · The guards a coverage number cannot see

  • One write path. No second writer of role_definition_id exists outside projectMembershipRepository's three methods — a static guard over lib/, so a future card cannot add a parallel path and pass its own tests.
  • The catalog is the source of truth over stored data. A stored role holding a key no longer in ROLE_GATED_PERMISSIONS, or one that is not enforced, resolves without it — stale rows may never widen access.
  • Purity. lib/permissions/resolve.ts, catalog.ts and limits.ts import no Prisma and no React, so they load identically in a server component, a client bundle and a bare test.
  • Tenancy. Under a foreign workspace GUC, a project's role definitions are invisible to the read, uncountable by the catalog and unwritable by the service — asserted at the non-bypass app role, not only at the service.
  • Nothing regressed. tests/permissions/accessParity.test.ts, catalog.test.ts, noUngovernedOperation.test.ts and inventoryCoverage.test.ts are green unedited; a change forced on any of them is a behaviour change this story did not ask for, and is a finding rather than a fix.

Real Postgres throughout — no mocked database.

Scope boundary

In: the coverage top-up, the five seam tests, the structural guards, and any fixture they need. Out: re-deriving unit coverage a card already shipped; the browser flow, which the E2E owns; any production-code change — if a seam test fails because the product is wrong, that is a bug card, not an edit to the assertion.

Acceptance criteria

  • Every file this story changed meets the repo's per-file coverage gate, with the tests written here for whatever fell short; the run is reported in the PR.
  • All five seams above have a test that drives real code on both sides and reads the consumer's own output, never a hand-built fixture standing in for the producer.
  • Each structural guard exists and fails when deliberately broken — a temporary second write path, a stale permission key, a Prisma import into resolve.ts, each shown to turn the guard red before being reverted.
  • The four named existing suites pass with no edit; if any needed one, the PR says which and why rather than quietly changing it.
  • The suite runs against real Postgres and is deterministic across repeated runs and a warm connection pool.

Context refs

  • tests/permissions/accessParity.test.ts, catalog.test.ts, getPermissions.integration.test.ts, roleCatalogMappers.test.ts, rolesStoryGate.integration.test.ts, storyGate.integration.test.ts, noUngovernedOperation.test.ts, inventoryCoverage.test.ts — the shipped suites this extends and must not break.
  • The previous story's gate — the shape this follows.
  • motir-core/CLAUDE.md § coverage — the per-file gate this measures against.
  • The nine code cards this runs over: Checkbox · schema · resolution · service · API · read · delete · editor · assignment.