Hackorda Docs

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.md flow 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

ActorDB anchorSeesWrites
Adminusers.role_id = 1 (ROLES.ADMIN)EverythingAll admin surfaces, can impersonate any user
Cycle leadtesters.role = 'lead' for that cycleThe cycle they leadTriage, docs, members within their cycle
Testerusers.role_id = 4 (ROLES.QA, "Tester" in UI), and/or testers.role = 'tester'Cycles they're on, cycles open for self-joinFile issues, drop run packs, comment, edit drafts
Observertesters.role = 'observer'The cycle (read-only)Comments only
Guestusers.role_id = 3Public 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 journeyFlow files
A. Cycle goes liveF-04, F-05, F-06
B. Tester files a bugF-07, F-08
C. Admin triagesF-09
D. Verification gateF-10
E. Period batch payoutF-11
F. Tester earnings dashboardF-12
G. Linear exportF-15
H. Cycle close (with AI report)F-06, F-14
Cross-cuttingF-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_completed

Flow 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/reject

Flow 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_exported

Flow 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_linear

Flow 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-out

3. Feature registry

Status legend: โœ… shipped ยท ๐ŸŸก partial / behind flag ยท ๐Ÿ”ฒ planned ยท ๐Ÿšซ deferred.

3.1 Identity & access

FeatureStatusWhere it livesSuggested 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)โœ… #169src/components/admin/InlineRoleSelect.tsxuser.role_changed
Promote user to Tester (system role)โœ…/app/admin/all-users profileuser.role_changed
Self-demotion guardโœ…useRoleManagementโ€”
Admin impersonation ("View as user")โœ… #139src/app/api/admin/impersonate/*admin.impersonation_started/ended
Multi-tenant orgs๐Ÿšซ v1 single-tenantโ€”โ€”

3.2 Catalog (orgs, products, versions, cycles)

FeatureStatusWhereEvent
Organizations CRUDโœ…/app/admin/organizationsorg.created/updated/deleted
Products CRUD (incl. URL)โœ…/app/admin/productsproduct.created/updated/deleted
Product versionsโœ…inline on product pageproduct_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.tsxcycle.status_changed
Open-to-self-join toggleโœ… #148JoinPolicyPanel.tsxcycle.join_policy_changed
Tester self-joinโœ… #148/app/test-cycles/browsecycle.tester_joined
Per-cycle payout rate overridesโœ… #142PayoutRatesPanel.tsxcycle.rates_overridden
Per-cycle verification gate overrideโœ… #165VerificationGatePanel.tsxcycle.gate_changed

3.3 Cycle execution (tester flow)

FeatureStatusWhereEvent
Cycle docs (Notion-style multi-doc)โœ…CycleDocsPanel.tsxcycle_doc.created/updated
Cycle brief side panelโœ…CycleBriefSidePanel.tsxโ€”
Doc-level attachmentsโœ…CycleDocsPanelattachment.uploaded
Inline media in markdownโœ…MarkdownEditor.tsxattachment.uploaded
Test runs (start/complete)โœ…/app/test-cycles/[id]run.started, run.completed
Environment auto-captureโœ…run start handlerโ€”
Run pack attachmentsโœ…RunPackPanel.tsxattachment.uploaded
Issue draft autosaveโœ…useIssueDraft.tsissue.draft_saved
File issue (with attachments + AI intake)โœ…/issues/newissue.filed
Edit own issue (reporter)โœ…/issues/[id]/editissue.edited
Issue comments (markdown + media)โœ…issue detail pageissue_comment.created
Cross-cycle "All issues" tabโœ…/app/issuesโ€”

3.4 Triage & evaluation (admin/lead flow)

FeatureStatusWhereEvent
Triage queue (cross-cycle)โœ… #141/app/admin/triageโ€”
Single-card triage UXโœ… #164TriageQueue.tsxโ€”
Mobile triage (fullscreen overlay)โœ… #175TriageQueue.tsxโ€”
Inline severity reclassifyโœ… #154IssueTriageRow.tsxissue.reclassified
Click-to-zoom media (lightbox)โœ… #170MediaLightbox.tsxattachment.viewed
IssueSection visual structureโœ… #170IssueSection.tsxโ€”
Attachments inline in triageโœ… #172IssueAttachmentsPanel.tsxโ€”
Triage comments threadโœ… #171TriageCommentsPanel.tsxissue_comment.created
"Request info" decision (parking lot)โœ… #171triage decide endpointissue.info_requested
Auto re-queue on reporter replyโœ… #171comments POSTissue.info_provided
Approve / Reject decisionโœ…triage decide endpointissue.approved, issue.rejected
Issue prev/next nav (j/k keys)โœ… #173issue detailโ€”
Post-triage pipeline stripโœ… #173ApprovalPayoutCard.tsxโ€”
Mark Paid gated by verifiedโœ… #165, #173ApprovalPayoutCardโ€”
AI suggestions displayโœ…AiSuggestionsCard.tsxai.suggestions_viewed
AI retry button (admin/lead only)โœ…AiSuggestionsCardai.intake_retried

3.5 Payouts (admin flow)

FeatureStatusWhereEvent
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)โœ… #140BatchPayDialog.tsxpayout.tester_batch_paid
Period batch run (preferred path)โœ… #176RunBatchPanel.tsxpayout.batch_run
Live dry-run preview (period picker)โœ… #176RunBatchPanelโ€”
Verification-gate enforcementโœ… #165run-batch + batch-pay endpointsโ€”
Per-issue manual paymentโœ…issue detailpayout.issue_paid
Void/correct payment๐Ÿ”ฒ schema supports, no UIโ€”payout.voided

3.6 Tester earnings (tester flow)

FeatureStatusWhereEvent
Balance dashboard (5 buckets)โœ… #138, #176BalanceSummary.tsxbalance.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.csvbalance.csv_exported

3.7 Notifications

FeatureStatusWhereEvent
In-app bell + dropdownโœ… #160sidebarnotification.viewed, notification.opened
Mark all readโœ… #160/api/me/notifications/mark-all-readnotification.mark_all_read
11 event typesโœ… #160, #161, #171see 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โœ… #161POST .../testerscycle.tester_added
Email delivery๐Ÿ”ฒ deferredโ€”email.sent
Telegram delivery๐Ÿ”ฒ deferredโ€”telegram.sent

3.8 AI agents (Anthropic)

FeatureStatusWhereEvent
Intake agent (issue analysis)โœ…lib/ai/intake.tsai.intake_completed/failed
Run summary agentโœ…lib/ai/run-summary.tsai.run_summary_completed/failed
Cycle close report agentโœ…lib/ai/cycle-close.tsai.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/integrationsintegration.configured
Cost cap per cycle๐Ÿ”ฒ logged not enforcedโ€”โ€”
Duplicate detection (pgvector)๐Ÿšซ deferredโ€”โ€”

3.9 Integrations

FeatureStatusWhereEvent
Linear export (single + group)โœ… #166LinearExportDialog.tsxissue.exported_to_linear
Linear admin UI (DB-backed creds)โœ… #167/app/admin/integrationsintegration.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

FeatureStatusWhereEvent
Mobile-friendly tester pathsโœ… #155layout/cardsโ€”
Mobile-friendly triageโœ… #174, #175TriageQueue.tsxโ€”
Sidebar respects impersonationโœ… #147app-sidebar.tsxโ€”
Admin dashboard (at-a-glance)โœ… #146/app/adminadmin_dashboard.viewed
Cycle list health columnโœ… #146admin cycles listโ€”
Playwright e2e (Phase 1โ€“3)โœ… #156โ€“#159tests/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_failed

Domains 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 โ€” name
  • actor_id โ€” users.id
  • actor_role โ€” system role (admin / qa / student)
  • cycle_id โ€” when scoped to a cycle (most events)
  • org_id, product_id โ€” denormalised when present
  • issue_id โ€” when scoped to an issue
  • created_at โ€” server timestamp
  • metadata โ€” 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:

  1. Append-only events table (cheapest, one migration, queryable in Postgres). Drop a db.insert(events).values(...) next to every existing console.log at the eight sites listed below.
  2. PostHog / Plausible / Mixpanel via a src/lib/track.ts client wrapper.
  3. 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.)

FileCurrent logEvent
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_batches row, list issues paid, regenerate receipt, void if needed
  • โ˜ Telemetry / event sink โ€” wire the 8 log sites above to a real events table (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_status back)
  • โ˜ 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:

  1. Add a row to the right table in ยง3 (use the same shape).
  2. Suggest an event name in the rightmost column even if you don't wire it.
  3. If the feature defines a new end-to-end flow, add it to ยง2.
  4. If the feature exposes a new gap, add it to ยง5.

When you wire telemetry:

  1. Pick events from ยง4 (don't invent new names โ€” change this doc first).
  2. Update the rightmost column to โœ… once instrumented.

Drift check. If this doc and docs/test-cycles.md disagree, both are wrong. test-cycles.md is 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).

On this page