
`import access` keeps capability checks, allowed issue actions, audit
writes, and rate-limit decisions in one reusable component instead of
handler-local checks. The module pairs with `import api_context` (which
exposes the verified caller) and with route `auth` and `capability`
clauses (which declare the policy).

## Mental model

Capability checks live in three places:

| Layer | Responsibility |
|-------|----------------|
| Route metadata | Declares policy via `auth capability` clauses. |
| Runtime | Verifies session, exposes capability set on ctx. |
| Handler | Ad-hoc checks via `api_context` and `access`. |

The route adds extra `capability` clauses for cross-cutting requirements;
the runtime publishes the verified set as `RequestContext.capabilities`;
the handler uses `access` for richer authorizers (roles, allow-listed
actions, rate-limit keys, audit writes).

## Building services

```text
import access

let svc = access.memory_service()
let user = access.tracker_principal(
    user_id = "u1",
    session_id = "s1",
    api_token_id = "",
    profile = "moderator",
    trust_level = "trusted")
```

| Constructor | Purpose |
|---|---|
| `memory_service()` | Deterministic in-memory `AccessService` for tests. |
| `anonymous_user(ip)` | Anonymous principal keyed by IP. |
| `authenticated_user(user_id, session_id, ...)` | Authenticated principal with capability/role list. |
| `tracker_principal(user_id, session_id, ...)` | Issue-tracker principal from profile or token scope. |

Full signatures:

```text
authenticated_user(user_id, session_id, api_token_id?, roles?,
                   trust_level?, suspended?)
tracker_principal(user_id, session_id, api_token_id?, profile?,
                  trust_level?, suspended?, token_scopes?)
```

## Roles and capabilities

```text
import access

let cap_read = access.capability("issues.private.read")
let cap_write = access.issue_create()
```

`access.capability(name)` declares a required handler capability;
`access.role(name, capabilities)` declares a role with a comma-separated
capability list.

The module also ships built-in capability constructors for the issue
tracker domain:

| Helper | Capability |
|---|---|
| `issue_create()` | `issues.create` |
| `issue_private_read()` | `issues.private.read` |
| `issue_comment()` | `issues.comment` |
| `issue_edit_own()` | `issues.edit.own` |
| `issue_triage()` | `issues.triage` |
| `issue_lock()` | `issues.lock` |
| `issue_delete()` | `issues.delete` |
| `issue_moderate()` | `issues.moderate` |
| `user_suspend()` | `users.suspend` |
| `audit_read()` | `audit.read` |

`tracker_profile_capabilities(profile)` returns the comma-separated
capability set for a reusable access profile (e.g. `"member"`,
`"triager"`, `"moderator"`).

## Rate-limit keys

```text
import access

let rate_key = access.rate_limit_key(
    request_ip,
    actor_user_id,
    "/api/v1/issues",
    "create",
    trust_level)
```

Rate-limit keys cover at least IP, user, route, action, and trust level
so a single hostile client cannot exhaust a route by rotating one
dimension. See
[Public API security: audit and rate limits](/docs/public-api-security/#audit-and-rate-limits).

## Issue-tracker example

```text
import access
import api_context

def moderate_issue(request: ModerateIssueRequest, ctx: RequestContext, svc: AccessService) -> Str {
    api_context.required_capability(ctx, "issues.moderate")?
    let actor = api_context.authenticated_user(ctx)?
    let principal = access.tracker_principal(
        actor.value, ctx.session_id ?: "", "", "moderator", "trusted")
    let allowed = svc.allowed_actions(principal, request.issue_id)
    if not allowed.contains("moderate") {
        return api_response.capability_error("forbidden", "issues.moderate", ctx.request_id.value)
    }
    svc.record_audit_event(...)
    return api_response.ok_object("issue", Json.empty_object())
}
```

## Relation to route `auth capability`

`auth capability <name>` requires both `auth_verified` and the named
capability. `access` is what fills out the capability story past that
gate: derive capabilities from a role or profile, run extra cross-cutting
checks, and emit audit events with the same actor identity the
authorizer used.
