Permissions
Complete reference for who can do what in Hackorda — covering human users,
API keys, and AI agents.
See Agent Platform for the agent-native
strategy. See Authentication for the Clerk
implementation. See system-overview.md §4 for
the high-level summary.
| Actor | How they authenticate | Identity source |
|---|
| Human (browser) | Clerk session cookie | users.clerk_id |
| External agent | API key hk_live_... | api_keys table |
| Internal runner | System token (env var) | Internal service identity |
Applies to humans only. Agents use the scope system (§4) instead.
| Role | ID | Passes any requireAdmin? | Passes requireSuperAdmin? | Notes |
|---|
SUPER_ADMIN | 5 | ✅ | ✅ | Money + role-change gating |
ADMIN | 1 | ✅ | ❌ | Everything else |
QA | 4 | ❌ | ❌ | "Tester" in UI — not the cycle-access gate |
STUDENT | 2 | ❌ | ❌ | Legacy LMS role |
GUEST | 3 | ❌ | ❌ | Public landing only |
Key rule: ROLES.QA does not grant cycle access. Having a testers row
for that specific cycle grants access.
Scoped to one cycle. Applies to humans; agents use cycleIds restriction
on their API key.
| Role | Filed issues | View all issues | Triage/approve | Edit cycle | Manage members |
|---|
lead | ✅ | ✅ | ✅ | ✅ | ✅ |
tester | ✅ | Own only | ❌ | ❌ | ❌ |
observer | ❌ | ✅ (read) | ❌ | ❌ | ❌ |
Admin users bypass this table — checkTestCycleAccess() returns isAdmin: true
and skips the testers row check.
| Scope | Resource | Action | Human equivalent |
|---|
cycles:read | Cycles, docs, members, test plan | GET | Any member of the cycle |
cycles:write | Create/update cycles | POST/PATCH | Admin |
issues:read | Issues, comments, attachments, AI suggestions | GET | Tester (own) or admin (all) |
issues:write | File issues, add comments, upload attachments | POST | Tester |
issues:triage | Approve/reject/reclassify, change payout amount | POST | Admin |
runs:write | Start + complete test runs | POST/PATCH | Tester |
payouts:read | Payout status, balance, batch history | GET | Admin or own-tester |
payouts:write | Run batch, mark paid, void | POST | Super-admin |
analytics:read | Usage events, cycle reports | GET | Admin |
ai:write | Trigger/re-run AI analysis on issues | POST | Admin |
admin:read | Orgs, products, users (read) | GET | Admin |
| Use case | Minimum scopes |
|---|
| CI bug filing | cycles:read, issues:write, runs:write |
| Triage automation | issues:read, issues:triage, ai:write |
| Read-only reporting | cycles:read, issues:read, payouts:read, analytics:read |
| Linear sync agent | issues:read, issues:write |
| Full admin automation | All scopes (use sparingly; prefer narrow keys) |
Beyond scopes, a key can be further restricted:
cycleIds: limit to specific cycles (empty = all cycles in the org)
expiresAt: auto-expire the key on a date
rateLimit: calls per minute (default: 60/min)
| Action | Admin | Lead | Tester | Observer | Agent (cycles:read) | Agent (cycles:write) |
|---|
| List cycles | ✅ | ✅ | ✅ (joined) | ✅ (joined) | ✅ | ✅ |
| Get cycle detail | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Create cycle | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ |
| Update cycle status | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ |
| Manage members | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ |
| Read cycle docs | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Write cycle docs | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ |
| Action | Admin | Lead | Tester | Observer | Agent (issues:read) | Agent (issues:write) | Agent (issues:triage) |
|---|
| List all issues | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ | ✅ |
| List own issues | ✅ | ✅ | ✅ | ❌ | ✅ | ✅ | ✅ |
| Get issue detail | ✅ | ✅ | Own only | ✅ | ✅ | ✅ | ✅ |
| File issue | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ |
| Edit own issue | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ | ❌ |
| Add comment | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ | ❌ |
| Triage (approve/reject) | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ |
| Change severity | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ |
| Action | Super-admin | Admin | Lead | Tester | Agent (payouts:read) | Agent (payouts:write) |
|---|
| View payout status | ✅ | ✅ | ✅ | Own only | ✅ | ✅ |
| View own balance | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Run batch payout | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ |
| Mark issue paid | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ |
| Void payment | ✅ | ❌ | ❌ | ❌ | ❌ | ✅ |
| Layer | File | What it enforces |
|---|
| Clerk middleware | middleware.ts | Protects all /app/* routes; redirects unauth to sign-in |
| Global admin gate | src/lib/auth/roles.ts → requireAdmin() | Admin-only API routes |
| Super-admin gate | src/lib/auth/roles.ts → requireSuperAdmin() | Money + role-change actions |
| Cycle access | src/lib/auth/test-cycle-auth.ts → checkTestCycleAccess() | Per-cycle data |
| API middleware | src/lib/auth/api-middleware.ts | Route-level wrappers |
| Issue permissions | src/lib/issues/permissions.ts | Issue-level read/write |
| (planned) API key middleware | src/lib/auth/api-key-auth.ts | Agent API key validation + scope check |
When Phase 2 (agent runner) ships, the internal runner needs to file issues
on behalf of an org — without a human's credentials. The model:
- The runner holds an internal system token (env var, rotated periodically).
- Issues filed by the runner carry
reporter_type: 'agent' + runner_run_id
for provenance.
- Attribution is to a synthetic "Agent tester" user per org, created on first
run.
- Payouts don't apply to agent-filed issues (or go to the org, not a tester).