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

Built-in roles as permission SETS + `hasPermission` — re-express every predicate in `lib/projects/access.ts`, proven behaviour-neutral by a truth table

Put the indirection in. Define the three built-in roles as SETS over the catalog, add the resolution that turns { accessLevel, workspaceRole, projectRole } into an actor's effective permission set, and rewrite each of the eleven predicates in lib/projects/access.ts as a one-line lookup — with the answers unchanged for every input, proven by an exhaustive truth table rather than asserted.

The shape

  • lib/permissions/builtinRoles.tsBUILTIN_ROLE_PERMISSIONS: Record<ProjectRole, ReadonlySet<PermissionKey>> for admin / member / viewer, typed against the catalog so a key that does not exist fails to compile. ProjectRole keeps its current home in lib/projects/roles.ts; this card does not move or rename it.
  • resolvePermissions(inputs): ReadonlySet<PermissionKey> in lib/permissions/resolve.ts — the whole policy, expressed once, with both shipped rails INSIDE it rather than around it:
    • a workspace manager (isWorkspaceManager) resolves to the full catalog;
    • a null workspaceRole resolves to the empty set — except on a public project, which grants project:browse plus the three public_request:* keys to any actor including an anonymous one, exactly the shipped Story 6.12 grant;
    • otherwise the actor's project role supplies the base set, and the ACCESS LEVEL subtracts from it: on limited and private a non-member holds view-and-comment but not work_item:edit, matching the level table canEdit and canComment implement today.
  • hasPermission(inputs, key) — the membership test the predicates call.
  • The eleven predicates keep their names, signatures and exports and become hasPermission(i, '<key>'). canCreateAttachments and canDeleteAllAttachments, which today are re-exported aliases of canComment / canModerateComments, become real lookups on their OWN keys — the aliasing exists only because the two shared a table, and the catalog is what lets them diverge later without a call-site change.
  • The header comment in lib/projects/access.ts is rewritten, not left describing a decision table the file no longer contains. The same for the sentence in lib/mcp/scopes.ts that refers to "the 6.4 role model" — one clause, so it names the catalog.

Acceptance criteria

  • resolvePermissions and hasPermission exist, are pure (no Prisma import, no IO), and are typed over PermissionKey.
  • All eleven exported predicates in lib/projects/access.tscanBrowse, canEdit, canComment, canModerateComments, canCreateAttachments, canDeleteAllAttachments, canManageWatchers, canManageProject, canSubmitToTriage, canUpvotePublicRequest, canCommentPublicRequest — keep their exported names and signatures and each resolve through hasPermission.
  • The parity truth table: a Vitest table drives every combination of accessLevel (4) × workspaceRole (owner / admin / member / null) × projectRole (admin / member / viewer / null) — 64 rows — through all eleven predicates, and asserts each result against the expected value transcribed from the pre-change policy. The expectations are written as literal booleans in the test, not computed from the new code.
  • The anonymous public actor case is covered explicitly: workspaceRole and projectRole both null on a public project grants exactly project:browse and the three public_request:* keys, and nothing else.
  • No assertCan* call site anywhere in lib or app is edited by this card — git diff --stat touches only lib/permissions/*, lib/projects/access.ts, the scope-module comment, and tests.
  • Per-file coverage on every new and changed module meets the ≥90% floor.

Context refs

  • lib/projects/access.ts — the eleven predicates and the decision tables being re-expressed; its header records the access-level semantics the resolution must reproduce.
  • lib/projects/roles.tsPROJECT_ASSIGNABLE_ROLES, ProjectRole, isWorkspaceManager, PROJECT_ACCESS_LEVELS.
  • lib/mcp/scopes.ts — the one comment clause naming the role model, corrected here.
  • docs/decisions/public-projects.md — the Story 6.12 grant the anonymous-actor branch must preserve.
  • The catalog card — the keys this card resolves over.