Hackorda Docs
Flows

F 10 Verification Gate

Status: 🟢 ready to test Owner: Adilet Last updated: 2026-05-08 Last verified on production: —

Goal

When the verification gate is active for a cycle, an approved issue cannot be paid until its issue status is explicitly set to verified. This separates "the bug is real and we'll pay" (approval) from "the fix landed and we re-tested" (verification). When the gate is inactive, approval is sufficient — verification is skipped.

Actors

  • Primary — Admin or Cycle lead (sets issue status to verified)
  • Secondary — Reporter (sees pipeline strip update + bell ping)

Preconditions

  • An approved issue exists (payout_status='approved' from F-09)
  • Either the cycle's payout_requires_verification = true, or the product's default is true and the cycle's flag is null (inheriting)

Trigger

Admin/lead opens the issue detail page. The pipeline strip (Triaged → Exported → Fix shipped → Verified → Paid) shows the current stage. Mark Paid is gated until verification.

Happy path — verify a fix

#ActorUI surfaceActionAPI callDB writesSide effectsSuggested event
1Admin/Leadissue detail ApprovalPayoutCardClick "Mark verified"PATCH /api/test-cycles/:id/issues/:issueId body { status: 'verified' }UPDATE issues SET status='verified', resolved_at = NOW()ISSUE_VERIFIED notification to reporterissue.verified
2Frontendpipeline advances; "Mark paid" enabled—————

Happy path — Mark Paid (per-issue, post-verification)

See F-11 for the manual per-issue payment path. This flow only documents the gate's effect on it.

#ActorUI surfaceActionAPI callDB writesSide effectsSuggested event
1Admin/Leadissue detailClick "Mark paid" (now enabled)POST /api/test-cycles/:id/issues/:issueId/paymentsINSERT INTO issue_payouts, UPDATE issues SET payout_status='paid'ISSUE_PAID notificationpayout.issue_paid

Gate inactive (alternative happy path)

When the cycle's effective gate = false:

#ActorUI surfaceActionAPI callDB writesSide effectsSuggested event
1Adminissue detailPipeline shows 3 stages (Triaged → Exported → Paid). "Mark paid" enabled directly on approved(same as above)(same)(same)(same)

Gate-resolution rule

effective gate = cycle.payout_requires_verification
             ?? product.payout_requires_verification
             ?? false

Cycle override beats product default. The product is the policy ("our internal app needs verification"); the cycle is the exception ("…except the maintenance sweep, just pay on approval").

Acceptance criteria

  • AC-1 — Given the gate is active and the issue is approved but not verified, when admin opens the issue detail, then "Mark paid" is disabled with a hint explaining "Awaiting verification".
  • AC-2 — Given the admin clicks "Mark verified", then issues.status flips to verified, resolved_at is set, and the reporter is notified.
  • AC-3 — Given the issue is verified, then "Mark paid" becomes enabled.
  • AC-4 — Given the period batch payout runs (F-11), when the gate is active and an approved issue is not verified, then the run-batch endpoint excludes it (returns it in unverifiedIssueIds).
  • AC-5 — Given the cycle override is null and the product default is true, then the effective gate is true.
  • AC-6 — Given the cycle override is false (explicit) and the product default is true, then the effective gate is false (cycle wins).
  • AC-7 — Given a pipeline strip is rendered, when the issue is approved, then the "Triaged" stage is filled, "Verified" is the next to-do (if gate active), and "Paid" is the last.
  • AC-8 — Given an admin reverts a verified issue to triaged or in_progress, then "Mark paid" disables again.

Test data / fixtures

  • Baseline seed (Hackorda Web has payout_requires_verification = true by default — verify on prod)
  • An approved issue (F-09) on a gate-active cycle

Negative paths

#ScenarioExpected behavior
N-1Mark paid on approved-but-not-verified issue (direct API call bypassing UI)API 400 "issue not verified" (gate enforcement in /payments POST)
N-2Mark paid via period batchIssue excluded; returned in unverifiedIssueIds
N-3Mark verified on a non-approved issueAPI allows but the UI flow makes this rare; verify behavior
N-4Toggle cycle gate off mid-cycleExisting approved-unpaid issues become payable immediately

Manual QA checklist

  • Open a cycle whose gate is active. Confirm VerificationGatePanel shows "Required by product" or "Required by this cycle"
  • Open an approved issue → pipeline shows "Approved" filled, "Verified" next, "Paid" last
  • "Mark paid" is disabled with hint "Awaiting verification"
  • Click "Mark verified" → status=verified, resolved_at set, reporter bell pings
  • "Mark paid" becomes enabled
  • Click "Mark paid" → issue_payouts row, issues.payout_status='paid', pipeline fully filled
  • On a different cycle, override gate to OFF (VerificationGatePanel)
  • Open an approved issue on that cycle → pipeline shows 3 stages, "Mark paid" enabled directly
  • Run-batch endpoint dry-run: confirm gate-active unverified issues appear in unverifiedIssueIds, gate-inactive issues are eligible

Automated test outline

test.describe("F-10 verification gate", () => {
  test("gate active → mark paid disabled until verified", async ({ page }) => { … });
  test("gate inactive → mark paid enabled directly", async ({ page }) => { … });
  test("cycle override beats product default", async ({ request }) => { … });
  test("run-batch excludes unverified", async ({ request }) => { … });
  test("revert verified disables mark paid again", async ({ page }) => { … });
});

Code references

  • UI: src/components/test-cycles/ApprovalPayoutCard.tsx (pipeline strip + gating logic), src/components/test-cycles/admin/VerificationGatePanel.tsx
  • API:
    • src/app/api/test-cycles/[id]/issues/[issueId]/route.ts (PATCH for status changes)
    • src/app/api/test-cycles/[id]/issues/[issueId]/payments/route.ts (per-issue payment)
    • src/app/api/admin/payouts/batch-pay/route.ts
    • src/app/api/admin/payouts/run-batch/route.ts
  • Lib: src/lib/issue-payout.ts → requiresVerification(...)
  • Schema: test_cycles.payout_requires_verification, products.payout_requires_verification, ISSUE_STATUSES.VERIFIED

Events emitted (proposed)

  • issue.verified — { issue_id, cycle_id, verified_by }
  • issue.unverified — { issue_id, reverted_to_status } (if revert path is exercised)
  • payout.gate_blocked — { issue_id, source: 'run_batch' \| 'batch_pay' \| 'manual' } (when an attempt is denied by the gate)

Open questions / known gaps

  • No "bulk verify" UI. Engineers verify one issue at a time.
  • No timeline / audit beyond resolved_at. If we want "who verified when" forensics, we need either a verified_by column or rely on events.
  • Pipeline strip currently only shows on issue detail; not on triage cards. Could add for triage too.

On this page