The coding-convention four — the ONE row in this story that would LOOSEN a gate, decided rather than swept along
Every other card in this story takes capability away. This one would GIVE it, and that is the whole reason it is not folded into a sibling.
Four operations — /api/ai/coding-convention/audit, /audit-coverage, /convention and /refresh — are gated assertCanManage today, i.e. admin-only: aiConventionService.getAudit, getConvention and reaudit each assert it (they are the three methods whose inline object-type parameter hid the gate from the guard until the extractor repair). The inventory maps all four to ai:plan, which the decision puts at member. Applying the mapping mechanically would widen admin-only operations to every member — including /refresh, which re-runs an audit and therefore spends credits.
The card implements whichever answer the decision recorded, and refuses to guess. The decision's own acceptance criteria require it to settle this explicitly, so this card reads it and does one of exactly two things:
- The mapping is wrong → the four rows are corrected to
ai:configure(the admin key MOTIR-2300 already wired for the AI settings write), the existingassertCanManagecalls becomeassertPermission(…, 'ai:configure'), and nobody's capability moves. - The widening is intended → the three asserts become
assertPermission(…, 'ai:plan'), the ungatedauditCoverageService.getCoveragegains one too, and the PR body states in one sentence which actors gained what.
Either way the fourth operation is not left as it is: /audit-coverage reaches its gate only through a sibling call today.
Acceptance criteria
- All four operations assert the SAME key, chosen by
docs/decisions/member-facing-permissions.md, and the PR body quotes the sentence it was chosen from. - If the outcome is
ai:configure, the inventory's four rows are corrected and the correction is annotated as a mapping fix rather than a policy change. - If the outcome is
ai:plan, a test asserts a projectmembernow passes where they were previously refused — a widening that no test proves is a widening nobody reviewed. auditCoverageService.getCoverageasserts the chosen key directly rather than relying on a sibling call.- No enforcement flag is flipped here:
ai:configureis alreadyenforced, andai:planbelongs to the close-out card. - 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/aiConventionService.ts—getAudit,getConvention,reaudit, and theirassertCanManagecalls.lib/services/auditCoverageService.ts—getCoverage, the one with no assert of its own.docs/decisions/member-facing-permissions.md— the sentence that decides this card.ai:configure— the shipped admin key for the AI domain, and the alternative destination for these rows.- The role-set seam — the prerequisite if the answer is
ai:plan.