Feature Matrix
Single source of truth for "what does the QA platform actually do today?" and "what events will we capture once we wire telemetry?".
๐ Flow specs live in Flows. Each canonical journey in ยง2 below has a dedicated
F-NN-name.mdflow file with steps, acceptance criteria, manual QA checklist, automated test stubs, and code references. QA engineers should hand-verify and write Playwright specs against those files. This matrix is the index; the flows directory is the plan.
Companion docs:
- Flows โ file-per-flow QA specs (16 flows)
- Test Cycles โ architectural reference
Update this file whenever a feature lands. Update the corresponding
flow file in docs/flows/ whenever its behavior changes.
Scope. Test Cycles vertical only. The legacy quiz / events / leaderboard features are documented in
features.md,api-routes.md,database-schema.md.
1. Actors
| Actor | DB anchor | Sees | Writes |
|---|---|---|---|
| Admin | users.role_id = 1 (ROLES.ADMIN) | Everything | All admin surfaces, can impersonate any user |
| Cycle lead | testers.role = 'lead' for that cycle | The cycle they lead | Triage, docs, members within their cycle |
| Tester | users.role_id = 4 (ROLES.QA, "Tester" in UI), and/or testers.role = 'tester' | Cycles they're on, cycles open for self-join | File issues, drop run packs, comment, edit drafts |
| Observer | testers.role = 'observer' | The cycle (read-only) | Comments only |
| Guest | users.role_id = 3 | Public landing only | โ |
Two-axis access model: system role (global gate) ร per-cycle role
(per-cycle gate). ROLES.QA is purely a UI label โ the actual permission
gate is whether the user has a testers row on the cycle.
2. Canonical end-to-end flows
These are the reference scenarios to instrument. Every feature below is a step inside one of these flows. Each one has a dedicated flow doc in Flows โ open it for steps, acceptance criteria, manual checklist, and test stubs.
| Canonical journey | Flow files |
|---|---|
| A. Cycle goes live | F-04, F-05, F-06 |
| B. Tester files a bug | F-07, F-08 |
| C. Admin triages | F-09 |
| D. Verification gate | F-10 |
| E. Period batch payout | F-11 |
| F. Tester earnings dashboard | F-12 |
| G. Linear export | F-15 |
| H. Cycle close (with AI report) | F-06, F-14 |
| Cross-cutting | F-01, F-02, F-03, F-13, F-16 |
Flow A โ Cycle goes live
admin: create org โ create product (+ URL) โ create version
โ create test cycle (planned)
โ seed cycle docs (brief, runbook)
โ assign testers (or set "open to self-join")
โ flip status: planned โ active โ cycle.status_changed event
notification: every member gets "cycle is now active"Flow B โ Tester files a bug
tester: open active cycle โ click "Start Run" โ run.started
โ captures environment (UA, OS, browser, screen)
โ reproduces bug โ click "+ File Issue"
โ fills title/description/severity, drops screenshot/video
โ submit โ issue.filed
โ AI intake fires (fire-and-forget) โ ai.intake_completed
โ leads + admins get bell notification
tester: end run โ "Complete" โ run.completed
โ AI run summary fires (fire-and-forget) โ ai.run_summary_completedFlow C โ Admin triages
admin: open /app/admin/triage (cross-cycle queue)
โ sees one card at a time with screenshots inline
โ optionally reclassify severity (recomputes payout amount)
โ optionally add comment / "Request info" โ issue.info_requested
(reporter gets bell + comment thread)
(reporter replies โ auto re-enters queue) โ issue.info_provided
โ Approve โ payout_status='approved' โ issue.approved
โ Reject โ status='rejected' โ issue.rejected
โ Skip โ next card (no DB write)
notification: reporter gets bell on approve/rejectFlow D โ Verification gate (when active)
[gate active = cycle override OR product default = true]
admin: marks issue triaged โ dev fixes โ admin marks 'verified'
โ only then can it be marked paid โ issue.verified
[gate inactive] approval is enough; verification step skipped.Flow E โ Period batch payout (the preferred settlement path)
admin: open /app/admin/payouts โ "Period batch" panel
โ pick range (Today / Last 7 days / This month / Last month / custom)
โ live dry-run preview shows N issues, K testers, totals/currency
โ enter method (Kaspi/bank/cash/other), reference, notes
โ confirm โ POST /api/admin/payouts/run-batch (dryRun=false)
โ creates payout_batches row โ payout.batch_run
โ inserts N issue_payouts rows (with batch_id)
โ flips N issues to paid
โ fans out ISSUE_PAID notifications
notification: each reporter sees "Paid: $X via Kaspi ยท ref ABC"Flow F โ Tester earnings dashboard
tester: /app/test-cycles/balance
โ 5 columns: Awaiting triage ยท Pending verification ยท
Available ยท Total paid ยท Reports
โ by-cycle list + full issue history table
โ can export own earnings as CSV โ balance.csv_exportedFlow G โ Linear export (engineering handoff)
admin: open issue detail or triage โ "Export to Linear"
โ single OR group mode (multi-select)
โ preview Linear team + project + status
โ confirm โ creates Linear issue(s) via GraphQL
โ stores external_id / external_url on issues โ issue.exported_to_linearFlow H โ Cycle close (with AI report)
admin: cycle status โ 'closed'
โ AI cycle-report agent fires โ ai.cycle_report_completed
โ new cycle_documents row with kind='report'
โ cycle.status_changed notification fan-out3. Feature registry
Status legend: โ shipped ยท ๐ก partial / behind flag ยท ๐ฒ planned ยท ๐ซ deferred.
3.1 Identity & access
| Feature | Status | Where it lives | Suggested event |
|---|---|---|---|
| Email/password login | โ | src/app/api/account/* | auth.login |
| Google OAuth | โ | Clerk (Google OAuth via Clerk) | auth.login |
| Promote user to Admin (inline dropdown) | โ #169 | src/components/admin/InlineRoleSelect.tsx | user.role_changed |
| Promote user to Tester (system role) | โ | /app/admin/all-users profile | user.role_changed |
| Self-demotion guard | โ | useRoleManagement | โ |
| Admin impersonation ("View as user") | โ #139 | src/app/api/admin/impersonate/* | admin.impersonation_started/ended |
| Multi-tenant orgs | ๐ซ v1 single-tenant | โ | โ |
3.2 Catalog (orgs, products, versions, cycles)
| Feature | Status | Where | Event |
|---|---|---|---|
| Organizations CRUD | โ | /app/admin/organizations | org.created/updated/deleted |
| Products CRUD (incl. URL) | โ | /app/admin/products | product.created/updated/deleted |
| Product versions | โ | inline on product page | product_version.created |
Discipline taxonomy (web_app vs website) | โ | products page | โ |
| Test cycle CRUD | โ | /app/admin/test-cycles/* | cycle.created/updated/deleted |
| Cycle status state machine | โ | StatusControl.tsx | cycle.status_changed |
| Open-to-self-join toggle | โ #148 | JoinPolicyPanel.tsx | cycle.join_policy_changed |
| Tester self-join | โ #148 | /app/test-cycles/browse | cycle.tester_joined |
| Per-cycle payout rate overrides | โ #142 | PayoutRatesPanel.tsx | cycle.rates_overridden |
| Per-cycle verification gate override | โ #165 | VerificationGatePanel.tsx | cycle.gate_changed |
3.3 Cycle execution (tester flow)
| Feature | Status | Where | Event |
|---|---|---|---|
| Cycle docs (Notion-style multi-doc) | โ | CycleDocsPanel.tsx | cycle_doc.created/updated |
| Cycle brief side panel | โ | CycleBriefSidePanel.tsx | โ |
| Doc-level attachments | โ | CycleDocsPanel | attachment.uploaded |
| Inline media in markdown | โ | MarkdownEditor.tsx | attachment.uploaded |
| Test runs (start/complete) | โ | /app/test-cycles/[id] | run.started, run.completed |
| Environment auto-capture | โ | run start handler | โ |
| Run pack attachments | โ | RunPackPanel.tsx | attachment.uploaded |
| Issue draft autosave | โ | useIssueDraft.ts | issue.draft_saved |
| File issue (with attachments + AI intake) | โ | /issues/new | issue.filed |
| Edit own issue (reporter) | โ | /issues/[id]/edit | issue.edited |
| Issue comments (markdown + media) | โ | issue detail page | issue_comment.created |
| Cross-cycle "All issues" tab | โ | /app/issues | โ |
3.4 Triage & evaluation (admin/lead flow)
| Feature | Status | Where | Event |
|---|---|---|---|
| Triage queue (cross-cycle) | โ #141 | /app/admin/triage | โ |
| Single-card triage UX | โ #164 | TriageQueue.tsx | โ |
| Mobile triage (fullscreen overlay) | โ #175 | TriageQueue.tsx | โ |
| Inline severity reclassify | โ #154 | IssueTriageRow.tsx | issue.reclassified |
| Click-to-zoom media (lightbox) | โ #170 | MediaLightbox.tsx | attachment.viewed |
| IssueSection visual structure | โ #170 | IssueSection.tsx | โ |
| Attachments inline in triage | โ #172 | IssueAttachmentsPanel.tsx | โ |
| Triage comments thread | โ #171 | TriageCommentsPanel.tsx | issue_comment.created |
| "Request info" decision (parking lot) | โ #171 | triage decide endpoint | issue.info_requested |
| Auto re-queue on reporter reply | โ #171 | comments POST | issue.info_provided |
| Approve / Reject decision | โ | triage decide endpoint | issue.approved, issue.rejected |
| Issue prev/next nav (j/k keys) | โ #173 | issue detail | โ |
| Post-triage pipeline strip | โ #173 | ApprovalPayoutCard.tsx | โ |
Mark Paid gated by verified | โ #165, #173 | ApprovalPayoutCard | โ |
| AI suggestions display | โ | AiSuggestionsCard.tsx | ai.suggestions_viewed |
| AI retry button (admin/lead only) | โ | AiSuggestionsCard | ai.intake_retried |
3.5 Payouts (admin flow)
| Feature | Status | Where | Event |
|---|---|---|---|
| Admin payouts dashboard | โ #140 | /app/admin/payouts | โ |
| Aggregate header (4 stats) | โ | payouts/page.tsx | โ |
| Pay queue grouped by tester | โ | PayoutsByTesterTable.tsx | โ |
| Per-tester batch pay (one-off) | โ #140 | BatchPayDialog.tsx | payout.tester_batch_paid |
| Period batch run (preferred path) | โ #176 | RunBatchPanel.tsx | payout.batch_run |
| Live dry-run preview (period picker) | โ #176 | RunBatchPanel | โ |
| Verification-gate enforcement | โ #165 | run-batch + batch-pay endpoints | โ |
| Per-issue manual payment | โ | issue detail | payout.issue_paid |
| Void/correct payment | ๐ฒ schema supports, no UI | โ | payout.voided |
3.6 Tester earnings (tester flow)
| Feature | Status | Where | Event |
|---|---|---|---|
| Balance dashboard (5 buckets) | โ #138, #176 | BalanceSummary.tsx | balance.viewed |
| Pending verification vs Available split | โ #176 | /api/me/balance | โ |
| By-cycle earnings list | โ | BalanceSummary | โ |
| Full issue history table | โ | BalanceSummary | โ |
| CSV export of own earnings | โ #143 | /api/me/balance/export.csv | balance.csv_exported |
3.7 Notifications
| Feature | Status | Where | Event |
|---|---|---|---|
| In-app bell + dropdown | โ #160 | sidebar | notification.viewed, notification.opened |
| Mark all read | โ #160 | /api/me/notifications/mark-all-read | notification.mark_all_read |
| 11 event types | โ #160, #161, #171 | see schema | โ |
| Reporter notified on triage decision | โ | triage decide | (already covered above) |
| Reporter notified on payment | โ | run-batch / batch-pay / per-issue | โ |
| New tester gets cycle-assignment ping | โ #161 | POST .../testers | cycle.tester_added |
| Email delivery | ๐ฒ deferred | โ | email.sent |
| Telegram delivery | ๐ฒ deferred | โ | telegram.sent |
3.8 AI agents (Anthropic)
| Feature | Status | Where | Event |
|---|---|---|---|
| Intake agent (issue analysis) | โ | lib/ai/intake.ts | ai.intake_completed/failed |
| Run summary agent | โ | lib/ai/run-summary.ts | ai.run_summary_completed/failed |
| Cycle close report agent | โ | lib/ai/cycle-close.ts | ai.cycle_report_completed/failed |
| Vision via DO Spaces public URL | โ | intake.ts | โ |
Provenance log (ai_runs) | โ | DB table | โ |
| 8 categorised failure modes | โ | categoriseError() | ai.failure |
| Anthropic key in admin UI (DB-backed) | โ #168 | /app/admin/integrations | integration.configured |
| Cost cap per cycle | ๐ฒ logged not enforced | โ | โ |
| Duplicate detection (pgvector) | ๐ซ deferred | โ | โ |
3.9 Integrations
| Feature | Status | Where | Event |
|---|---|---|---|
| Linear export (single + group) | โ #166 | LinearExportDialog.tsx | issue.exported_to_linear |
| Linear admin UI (DB-backed creds) | โ #167 | /app/admin/integrations | integration.configured |
| Linear โ Hackorda webhook (status pull) | ๐ฒ deferred | โ | integration.status_synced |
| Jira / GitHub Issues | ๐ฒ schema supports, no UI | โ | โ |
| Multi-tenant Linear (per-org config) | ๐ฒ deferred | โ | โ |
3.10 Cross-cutting
| Feature | Status | Where | Event |
|---|---|---|---|
| Mobile-friendly tester paths | โ #155 | layout/cards | โ |
| Mobile-friendly triage | โ #174, #175 | TriageQueue.tsx | โ |
| Sidebar respects impersonation | โ #147 | app-sidebar.tsx | โ |
| Admin dashboard (at-a-glance) | โ #146 | /app/admin | admin_dashboard.viewed |
| Cycle list health column | โ #146 | admin cycles list | โ |
| Playwright e2e (Phase 1โ3) | โ #156โ#159 | tests/e2e/* | โ |
| Deploy serialization (concurrency group) | โ #152 | .github/workflows/* | โ |
4. Event taxonomy (proposed)
When we wire telemetry, we'll capture events using the domain.action
convention (lowercase, snake_case). The "Suggested event" column above is the
draft registry. Capture rules below.
4.1 Naming
<domain>.<action> e.g. issue.filed, payout.batch_run
<domain>.<action>_failed e.g. ai.intake_failedDomains we use today: auth, user, org, product, cycle, cycle_doc,
run, issue, issue_comment, attachment, payout, balance,
notification, ai, integration, admin_dashboard.
4.2 Required properties on every event
Whatever the event, we capture:
eventโ nameactor_idโusers.idactor_roleโ system role (admin / qa / student)cycle_idโ when scoped to a cycle (most events)org_id,product_idโ denormalised when presentissue_idโ when scoped to an issuecreated_atโ server timestampmetadataโ jsonb with action-specific payload (severity, currency, batch_id, โฆ)
4.3 Where to instrument
We don't have a telemetry sink yet. Three options when we wire it:
- Append-only
eventstable (cheapest, one migration, queryable in Postgres). Drop adb.insert(events).values(...)next to every existingconsole.logat the eight sites listed below. - PostHog / Plausible / Mixpanel via a
src/lib/track.tsclient wrapper. - Both โ DB for forensics, vendor for dashboards.
Recommended: option 1 first (no vendor, no key, no SLA), promote to option 3 when we want product analytics.
4.4 Eight existing log sites that should become events
(grep "console.log" src/app/api/admin src/app/api/test-cycles โ all of these
already log a structured one-liner; replace console.log with track(...)
plus the line.)
| File | Current log | Event |
|---|---|---|
admin/payouts/run-batch/route.ts | [run-batch] admin=โฆ batch=โฆ paid=โฆ | payout.batch_run |
admin/payouts/batch-pay/route.ts | [batch-pay] admin=โฆ paid=โฆ | payout.tester_batch_paid |
admin/triage/decide/route.ts | [triage-decide] admin=โฆ issue=โฆ decision=โฆ | issue.approved / issue.rejected / issue.info_requested |
admin/integrations/linear/route.ts | [linear-integration] saved by โฆ | integration.configured |
admin/integrations/anthropic/route.ts | [anthropic-integration] saved by โฆ | integration.configured |
admin/impersonate/route.ts (ร2) | [impersonate] start/end โฆ | admin.impersonation_started/ended |
test-cycles/[id]/join/route.ts | [cycle-join] tester=โฆ | cycle.tester_joined |
That's the path of least resistance โ those eight lines + a thin track()
helper give us 80% of the analytic value with one PR.
5. Gaps โ what's left to build
Bucketed by impact.
5.1 Operational (no code, your turn)
- โ Smoke-test the period batch flow end-to-end on production (file โ approve โ verify โ batch โ tester sees Paid + bell)
- โ Set Anthropic API key at
/app/admin/integrations(frees us from env vars) - โ Set Linear API key at
/app/admin/integrations - โ Phone-test the new mobile triage overlay (#175)
5.2 High-value code
- โ Past batches view โ drill into a
payout_batchesrow, list issues paid, regenerate receipt, void if needed - โ Telemetry / event sink โ wire the 8 log sites above to a real
eventstable (1 migration + 1 helper) - โ Cycle issues list polish โ payout-status badge column, attachment count, severity filter chips, group-by-status
- โ PWA manifest โ installable on phone (low effort, big win for tester ergonomics)
- โ Mobile audit on issue detail (same overlay treatment as triage if needed)
5.3 Bigger workstreams
- โ Telegram bot fan-out for notifications
- โ Multi-tenant Linear (per-org config)
- โ Linear โ Hackorda webhook (pull
external_statusback) - โ Cycle close report as downloadable PDF
- โ Email notifications
- โ Playwright Phase 4 (visual snapshot tests)
5.4 Deferred from original plan
- โ Encrypted credentials store (creds currently live in plain
organization_integrations.config) - โ pgvector embeddings for issue dedup
- โ Test cases / test plans (explicitly out-of-scope from v1; cycle docs cover the immediate need)
6. How to update this doc
When you ship a feature:
- Add a row to the right table in ยง3 (use the same shape).
- Suggest an event name in the rightmost column even if you don't wire it.
- If the feature defines a new end-to-end flow, add it to ยง2.
- If the feature exposes a new gap, add it to ยง5.
When you wire telemetry:
- Pick events from ยง4 (don't invent new names โ change this doc first).
- Update the rightmost column to โ once instrumented.
Drift check. If this doc and
docs/test-cycles.mddisagree, both are wrong.test-cycles.mdis the architectural reference; this doc is the product-shaped view. They should describe the same code, just at different angles.
Last updated: 2026-05-08 (after #176 โ period batch payouts).