---
phase: rules
kind: level
level: access-rules
---

# Level guide — access rules (rules that become permissions)

> Emit rules of type **`access`** — transverse rules that define *who can do what
> at the app or module boundary* and which `/ba-create-rbac` later materialises as
> concrete permissions. This complements `identify.md` Path E. It is consulted to
> distinguish access control from plain business logic.

## When to use `Type: access`
Use `access` when the rule answers **"who is allowed to do X on which scope?"** at
a structural level:

| Phrasing | Type |
|----------|------|
| "Only the record owner can update the quote" | `access`, scope `own` |
| "Only members of the sales team can view the pipeline" | `access`, scope `team` |
| "All authenticated users may read the public catalogue" | `access`, scope `all` |
| "Only managers may approve leave > 10 days" | `access`, scope `custom` |
| "A rep can only export leads from her region" | `access`, scope `custom` |

Do NOT use `access` for: "all mutations produce an audit trail" (`cross-cutting`),
"creator cannot approve own record" (`workflow` — segregation of duties), "stock
≥ 0" (`constraint`), "status draft→submitted" (`state-transition`), "email unique"
(`validation`).

**Discrimination test** — *"Would changing this rule require editing the RBAC
matrix?"* Yes → `access`. No → another type.

## The permission spec (carried in the rule)
An `access` rule names, in its body, the permission(s) it produces — a
forward-looking spec for the RBAC generator. Write it as a `## Permissions` block
inside the rule:

```markdown
### BR-007 — Un client ne modifie que ses propres commandes
- **Type** : access
- **Sévérité** : err
- **Portée** : SALES / ORDERS
- **Condition** : QUAND un utilisateur modifie une commande ALORS user.id doit
  égaler order.ownerId.
- **Expression** : `actor.id === target.ownerId`
- **Code d'erreur** : `ORDER_NOT_OWNER`
- **Cas valides** : le propriétaire X modifie sa commande O → accepté.
- **Cas invalides** : Y (non-propriétaire) modifie O → 403 `ORDER_NOT_OWNER`.
- **Cas d'usage liés** : —
- **Permissions** :
  - acteur `BA-001-AC-003` (Client) · action `update` · cible `resource`
    SALES / ORDERS / orders / Order · portée `own`
```

### Fields of a permission line

<!-- permission-actions:v1 — drift-tested against lib/permission-actions.ts (edit ALL carriers or the suite fails) -->
| Field | Values |
|-------|--------|
| acteur | an actor code from the app's `acteur.md` (pick from the catalog — do not invent) |
| action | `access` / `read` / `create` / `update` / `delete` / `export` / `import` / `approve` / `reject` / `assign` / `execute` / `lookup`. One action per line — emit several lines for several actions. |
| cible (targetLevel) | `application` / `module` / `section` / `resource` — at or below the rule's own scope (a module rule never materialises an app-level permission) + the scope path |
| portée (conditionScope) | `all` / `own` / `team` / `custom`. Use `custom` only when `own`/`team` cannot express it; then add the condition text ("actor.region == target.region"). |
<!-- /permission-actions:v1 -->

A rule implying several actions (owners read + update + delete their orders) lists
**several permission lines** under one rule — keep one rule per concept.

## How access rules flow to RBAC
You **capture** the permission spec here (the `## Permissions` block); you do
**not** build the RBAC matrix — that is `/ba-create-rbac`'s job. After Writing the
rules, the handoff to `/ba-create-rbac` carries these specs so the matrix is
pre-filled. The rule records the *reason* (ownership, team, compliance); the RBAC
matrix records the *grant*.

## Example — proposing access rules for a module
Reading the `ORDERS` module + its `acteur.md`, propose in prose then ask via
AskUserQuestion (multi-select):

```
4 règles d'accès candidates pour ORDERS :
- BR-001 — Un client ne voit que ses commandes (Client, read, own)
- BR-002 — Un client ne modifie que ses commandes non expédiées (Client, update, own+custom)
- BR-003 — Les gestionnaires voient toutes les commandes (Back-office, read, all)
- BR-004 — Seul un manager annule une commande > 1000€ (Manager, approve, custom)
```

After the user confirms, Write the module's `règles-métier.md` with each rule's
`## Permissions` block filled.

## Edge cases

| Situation | Action |
|-----------|--------|
| No actors yet (`acteur.md` empty/placeholder) | STOP. Access rules need actors — defer to `/ba-create-actors`. |
| Rule spans several actor roles | One rule per actor — do not multiplex the actor field. |
| `custom` scope but no clear condition text | Downgrade to `workflow` — `custom` is not a dumping ground. |
| Access rule at resource level | Allowed, but check it is not really a field-level `validation`. |
| User describes a permission, not a rule ("admins can do everything") | Skip the access rule, point at `/ba-create-rbac`. |

## Quality checks before writing
For each `access` rule: the `## Permissions` block is present and non-empty; each
actor exists in `acteur.md`; the target level is at or below the rule's scope; the
scope path is complete; `portée` is `all`/`own`/`team`/`custom` (custom carries a
condition text); the rule still has ≥1 valid + ≥1 invalid example; the rule sits
at application or module scope (challenge it if pushed lower).
