`ai:configure` — the project's AI-planning cadence and planner model (the settings write only, not the planner itself)
The smallest of the domain cards and the cleanest re-pointing in the story: projectAiSettingsService already asserts assertCanManage on its writes, and /api/projects/[key]/ai-settings is already assertCanBrowse on GET and assertCanManage on PATCH. All that changes is which key the write asks for — ai:configure instead of the umbrella.
Its whole value is the boundary it draws. ai:configure is "who may change the auto-plan cadence, the AI sprint-planning switch, the planner model and the drafted-explanation setting" — a settings decision with a spend consequence. It is NOT "who may run the planner", which is ai:plan and is MOTIR-2291's: those are ~26 routes, currently governed by nothing, and moving them changes what an ordinary member can do. Keeping the two apart is why the catalog has two keys.
The UI is out of scope. app/(authed)/settings/project/ai-planning/page.tsx and _components/AiPlanningSettingsEditor.tsx both read canManage to render the panel read-only for a non-admin. That is an affordance decision and belongs to the permission-gated UI story — leave both alone, and leave the nav entry browse-gated, so a member keeps seeing the configuration read-only exactly as today.
Acceptance criteria
- Every write in
lib/services/projectAiSettingsService.tsassertsai:configurethroughprojectAccessService.assertPermission; the read keeps its browse gate. git grep -n "assertCanManage\|canManage" -- lib/services/projectAiSettingsService.ts "app/(authed)/settings/project/ai-planning"first, and the PR body states, per hit, whether it moved and why. The two client-sidecanManagereads must appear in that list as deliberately NOT moved.ai:configureflips toenforcement: 'enforced'inlib/permissions/catalog.ts.- The
/api/projects/[key]/ai-settingsrow in the inventory'saisection has its gate-after corrected and movesnew→existing. No other row in that section is touched — everyai:planandai:view_planrow is MOTIR-2291's, and a diff here that moves one fails this card. - Tests: an admin saves the AI-planning settings; a project member is refused 403; a member can still READ them (the panel renders read-only); a non-browser gets 404.
- The guard's pending pin is re-derived by running the suite on the branch (this card is expected to move it by zero — the route is already gated — and the PR says so rather than leaving the reader to wonder).
pnpm testgreen;pnpm lintand the prettier check pass repo-wide.
Context refs
lib/services/projectAiSettingsService.ts— the threeassertCanManagecalls.app/api/projects/[key]/ai-settings/route.ts— GET browse, PATCH manage.app/(authed)/settings/project/ai-planning/page.tsx·_components/AiPlanningSettingsEditor.tsx— the two client-sidecanManagereads this card leaves alone.lib/settings/projectSettingsNav.ts— theai-planningentry, browse-gated on purpose; read only.docs/decisions/permission-inventory.md— theaisection; reason R17 (this card) versus R5 and R11 (not this card).- The seam this calls · the story that takes
ai:plan.