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

`saved_filter:manage` — authoring, owning, starring and subscribing to a project's saved queries

The saved-filter domain is the one member-facing key whose service already consults the access policy: savedFiltersService resolves projectAccessService.getSavedFilterCapabilities(project.id, ctx, tx) through a module-local helper and branches on the result. So this is a re-pointing, not a hole — with one genuine exception, /api/projects/[key]/saved-filters/[filterId]/subscription, which reaches no gate at all.

The shape, and the line it must not cross

  • The WRITE methods — create, update, delete, changeOwner, star, unstar, and the subscription write — assert projectAccessService.assertPermission(projectId, ctx, 'saved_filter:manage').
  • The READ methods — list, resolve, getDependents — stay at project:browse. Running a saved query is reading the project's work items, which every browser may already do.
  • The five getXCapabilities methods are NOT re-pointed here. assertPermission's card deliberately left them alone — "they are called from ~40 places; re-pointing them belongs with the surfaces that consume the set" — and that is the permission-gated UI story's work. This card adds the ASSERTION on the write path and leaves the capabilities read exactly as it is, so the two stories do not both edit the same method from opposite ends.
  • Ownership stays orthogonal. A filter has an owner, and changeOwner already has its own rule about who may reassign one; the permission answers may this actor manage saved filters in this project at all, and the ownership rule still applies on top. Read that method before adding the assert and keep both.

Acceptance criteria

  • Each of the seven write methods asserts saved_filter:manage before its write, in the service.
  • The three read methods assert project:browse, and a test proves a viewer can still list and run a saved filter while being refused authoring, starring and subscribing.
  • The subscription route's write reaches the gate — it is the one operation in this domain that reaches none today.
  • changeOwner retains its existing ownership rule in addition to the new assert, and a test covers the case where an actor holds saved_filter:manage but is not the filter's owner.
  • saved_filter:manage flips to enforcement: 'enforced' in lib/permissions/catalog.ts, with tests/permissions/catalog.test.ts's enforced list and length pin extended by exactly this key.
  • No getXCapabilities method is modified.
  • The inventory's five R16 rows record the key; the guard's pins are re-derived by running it on this branch.
  • pnpm test tests/permissions/ tests/services/ is green.

Context refs

  • lib/services/savedFiltersService.ts — the seven writes, the three reads, and the module-local capabilities helper.
  • lib/services/projectAccessService.tsassertPermission, and getSavedFilterCapabilities (read it; do not change it).
  • docs/decisions/permission-inventory.md — the five R16 rows.
  • docs/decisions/member-facing-permissions.md — the role assignment for this key.
  • The role-set seam — the prerequisite that makes the key resolve.
  • The permission-gated UI story — the owner of the capabilities-read half.