Skip to content

Instantly share code, notes, and snippets.

@Kurry
Created May 28, 2026 01:58
Show Gist options
  • Select an option

  • Save Kurry/256c286e60a846ff8c576e6ac6275e19 to your computer and use it in GitHub Desktop.

Select an option

Save Kurry/256c286e60a846ff8c576e6ac6275e19 to your computer and use it in GitHub Desktop.
Codebase Atlas Airtable base — self-contained build prompt for Omni

Codebase Atlas Airtable Base — Self-Contained Build Prompt

For use with Omni or any browser-driving agent. Everything you need is inline below — no external repo fetches required. The originating files (private repo) are listed at the end for reference, but you do NOT need to fetch them.

This single file contains, in order:

  1. §A — Task overview + build sequence (what to build, in what order)
  2. §B — Webhook receivers spec (the integration with the GitHub admission gate)
  3. §C — Schema spec (7 tables, every field, every formula)
  4. §D — Views spec (per-role views)
  5. §E — Interfaces spec (Interface Designer pages, with ASCII mockups)
  6. §F — Automations spec (A1–A12, the full event-driven logic)
  7. §G — Acceptance criteria + handoff

If you're an AI agent: read all of §A–§G before starting any action in Airtable. Then execute in the order above. Reply with a build-progress checklist after each phase.


§A — Task Overview + Build Sequence

You are building the Codebase Atlas bounty + compensation Airtable base. This base is the operational home for everything that happens after a task is admitted via the GitHub admission gate: hunters filing findings, triagers grading them, solvers attempting Hard / Reference tasks, payouts settling. The GitHub admission gate POSTs webhooks to this base when tasks admit and when their 7-day bounty windows close; you wire those webhooks into the right tables and Automations.

A single Airtable base named "Codebase Atlas Bounty (v1)" containing:

  1. 7 core tables built exactly per §C below
  2. 18 views per §D
  3. 10 Interface pages per §E
  4. 12 Automations (A1–A12) per §F
  5. Two webhook-receiver Automations per §B (not in §F — these are new for the GitHub admission gate integration)

Build order:

  1. Tables (§C) — leaf nodes everything else links to. Build Tasks and Contributors first; then Escrow Ledger; then Findings, Appeals, Arbitrations; then Audit Log last.
  2. Permissions matrix (§C inline) — apply R/W per role before adding any test data.
  3. Views (§D) — bind each view to the right table with the documented filter/sort/group.
  4. Interfaces (§E) — build the 10 Interface Designer pages; bind to views; gate permissions per page.
  5. Automations (§F) — wire A1–A12; test each on synthetic data before going to next.
  6. Webhook receivers (§B) — set up the incoming webhook URL, branch on payload.event, route to A-admit and A-window-close logic.
  7. Acceptance test (§G) — end-to-end smoke test with synthetic + real-admission payloads.

§B — Webhook Receivers (Admission Gate Integration)

Trigger: Incoming webhook from a GitHub Actions secret (AIRTABLE_ADMISSION_WEBHOOK_URL). One webhook URL handles both event kinds; the receiver branches on payload.event.

A-admit (event=admit)

Payload:

{
  "event": "admit",
  "task_slug": "rustfs-iam-proxy-security",
  "task_id": "T002",
  "author_github_handle": "kurry",
  "tier": "Hard",
  "author_fee_usd": 750,
  "p_solve_ensemble": 0.12,
  "cross_family_passes": true,
  "stage3_required": true,
  "harbor_config_sha256": "9f3a1b2c...",
  "admitted_at": "2026-06-15T12:34:56Z",
  "admission_json_url": "https://github.com/.../admission.json"
}

Actions:

  1. Idempotency check. Reject (200 OK + no-op) if a Tasks row already exists with slug = payload.task_slug AND admitted_at = payload.admitted_at.
  2. Resolve author. Find or create a Contributors row matching payload.author_github_handle. If new, initialize quality_score = 1.0, roles_active = ["Author"].
  3. Create Tasks row with: slug, task_id, author (link to Contributor), tier, author_fee_usd, p_solve_ensemble, cross_family_passes, stage3_required, harbor_config_sha256, shipped_at = admitted_at, bounty_status = "Open", admission_json_url.
  4. Book the author fee. Append to the Mercor contributor-payment intake queue for payload.author_fee_usd to author_github_handle.
  5. Write Audit Log entry: event_type = "task_admitted", all payload fields recorded.
  6. Reply 200 OK. On failure, reply 5xx so GitHub's webhook retry logic fires.

A-window-close (event=window_close)

Payload:

{
  "event": "window_close",
  "task_slug": "rustfs-iam-proxy-security",
  "task_id": "T002",
  "shipped_at": "2026-06-15T12:34:56Z",
  "closed_at": "2026-06-22T00:00:00Z",
  "stage3_outcome": "cleared",
  "final_tier": "Hard",
  "final_author_fee_usd": 750
}

stage3_outcome{cleared, dropped_one_tier, defaulted_medium, defaulted_easy, rejected_cheat}.

Actions:

  1. Idempotency check. Reject if Tasks.bounty_status is already "Closed".
  2. Find Tasks row by slug = payload.task_slug.
  3. Update: bounty_status = "Closed", closed_at, final_tier, final_author_fee_usd, stage3_outcome.
  4. If final_tier ≠ tier (Stage 3 fell back), reconcile the author-fee booking — clawback the difference. Clawback infrastructure already exists in §F.
  5. For accepted Findings against this task, settle deductions per §F. Per-task deduction is uncapped; residuals become clawback debts.
  6. For Hard / Reference with stage3_outcome != cleared, mark the task as eligible for Hard re-admission — author has 7 days to file a re-admission request, which fires a new GitHub PR + admission run.
  7. Write Audit Log entry: event_type = "task_window_closed".
  8. Reply 200 OK.

Webhook security

  • Verify request origin via X-Hub-Signature-256 HMAC. Configure shared secret env var on GitHub side (AIRTABLE_WEBHOOK_HMAC_SECRET); verify HMAC-SHA256 of request body matches header. Reject 401 if not.
  • HTTPS only.
  • Airtable Automations native rate-limiting must be enabled.

§C — Schema

This file is the build spec. Anyone setting up the v1 base creates these seven tables with exactly these fields. Field-by-field. Formulas given verbatim where applicable.

Supersedes the schema fragments in files 14 and 16.

Table inventory

# Table Purpose Approx rows in steady state
1 Tasks One row per shipped task; canonical bounty target 100–500
2 Contributors One row per real-identity contributor; escrow + reputation 50–200
3 Findings One row per bounty submission (promoted from Airtable comment) 200–2000
4 Appeals One row per author-filed appeal against a triage decision 10–50
5 Arbitrations One row per hunter-filed dispute against a rejection (LLM arbitrator output) 20–100
6 Escrow Ledger Per-dollar release schedule for contributor escrow balances 1000–10000
7 Audit Log Immutable record of every state change on Findings / Contributors / Arbitrations 10000+

The two new ones since file 16 are Escrow Ledger (already referenced) and Audit Log (new — addresses the dispute-reconstruction gap).

Conventions

  • Primary keys are Airtable Autonumber unless stated; Tasks.slug is the exception (single-line text, unique).
  • Linked records are bidirectional by default; the reverse name in the linked table is given in parentheses.
  • Formulas use Airtable's formula language verbatim, monospace.
  • Required means Airtable's "required" flag is set OR an Automation rejects rows without it.
  • Default value is set via Airtable's Default Value field option unless stated.
  • Permission column (R/W per role) is intentional. Captures the permissions matrix inline so the schema and the access-control plan don't drift. Roles: H Hunter, A Author, T Triager, ST Senior Triager, Adm Admin. R = read, W = write, = no access.

Table 1: Tasks

One row per shipped task. Created manually or by sync from tasks/<slug>/ on the repo.

Field Type Required Formula / link H A T ST Adm
slug Single line (Primary) Yes unique; matches tasks/<slug>/ R R(own) R R RW
author Linked → Contributors Yes (reverse: tasks_authored) R R(own) R R RW
track Single select Yes options: standard, frontier; default standard R R(own) R R RW
shipped_at Date Yes bounty window opens R R(own) R R RW
bounty_status Single select Yes options: Open, Closed; default Open R R(own) R R RW
findings Linked → Findings (reverse: target_task) R R(own) R R R
total_deductions_to_author Rollup SUM({deduction_from_author}) from linked Findings where triage_status = Accepted, capped via formula: MIN(SUM({deduction_from_author}), 200) R R(own) R R R
remaining_author_at_risk Formula 250 - {total_deductions_to_author} R R(own) R R R
notes Long text No admin notes R R RW

Hunters see all Tasks (need to choose what to hunt). Authors only see their own task rows. Triagers and Admin see everything.

Table 2: Contributors

One row per real-identity contributor. The system of record for reputation, escrow, and clawback debt.

Field Type Required Formula / link H A T ST Adm
contributor_id Autonumber (Primary) R(self) R(self) R R RW
email Email Yes Mercor contributor record key R(self) R(self) R RW
display_name Single line Yes shown in UI R R R R RW
roles_active Multi-select Yes options: Hunter, Author, Triager, Senior Triager, Frontier Verifier, Appeal Reviewer R(self) R(self) R R RW
quality_score Number Yes default 1.0, range 0.0–2.0 R(self) R(self) R R RW
quality_score_history Linked → Audit Log filtered to entity_type=Contributor rows for this contributor R(self) R(self) R R R
escrow_balance Rollup SUM({amount}) from linked Escrow Ledger rows where released_at IS NULL AND slashed_at IS NULL R(self) R(self) R R R
escrow_locked Rollup sum of bond_amount_locked from Findings where triage_status = Pending and hunter = this R(self) R(self) R R R
escrow_available Formula {escrow_balance} - {escrow_locked} R(self) R(self) R R R
lifetime_payouts Rollup SUM({payout_to_hunter}) from accepted Findings R(self) R(self) R R R
lifetime_slashed Rollup SUM({bond_amount_locked} * IF({bond_disposition} = "Slashed-100", 1, IF({bond_disposition} = "Slashed-50", 0.5, 0))) R(self) R(self) R R R
clawback_debt Number default 0; written by Automation on AI-slop/Malicious decisions R(self) R(self) R R RW
ai_slop_warnings Number default 0 R(self) R(self) R R RW
submissions_filed Count count of linked Findings (any status) R(self) R(self) R R R
submissions_accepted Count count where triage_status = Accepted R(self) R(self) R R R
accept_rate Formula IF({submissions_filed} > 0, {submissions_accepted} / {submissions_filed}, BLANK()) R(self) R(self) R R R
suspended_from Multi-select role names; default empty R(self) R(self) R R RW
suspension_until Date auto-lift date R(self) R(self) R R RW
triager_quality_metric Number only populated for Triager role; default 1.0. Updated by arbitrator outcomes (see Automations) R(self) R RW
created_at Created time R(self) R(self) R R R

Self-only visibility: for fields marked R(self), an Airtable Interface filter restricts the view to the contributor's own row. Hunters and Authors cannot see other contributors' escrow / reputation / debt.

Table 3: Findings

The core operational table. One row per bounty submission.

Submission flow: hunter leaves a comment on a Tasks row → triager promotes the comment into a Findings row by clicking "Promote to Finding" on an Interface page (a Run-a-Script action under the hood that pre-populates target_task, hunter, source_comment_url, submitted_at). The triager then completes the structured fields and makes a decision.

Field Type Required Formula / link H A T ST Adm
finding_id Autonumber (Primary) R(own) R(own task) R R R
target_task Linked → Tasks Yes (reverse: findings) R(own) R(own task) R R RW
hunter Linked → Contributors Yes (reverse: findings_as_hunter) R(own) R(own task) R R RW
source_comment_url URL Yes Airtable deep link to original comment R(own) R(own task) R R RW
submitted_at Created time auto R(own) R(own task) R R R
claimed_tier Single select Yes Catastrophic, Critical, Major, Minor, Duplicate, Co-finder R(own) R(own task) R R RW
failure_category Single select Yes Foil-pass, Contamination, Judge-prompt-leak, Likert-rescue, Calibration-instability, Tooling, Doc-fix, Ambiguity, Duplicate, Other R(own) R(own task) R R RW
evidence_url URL Yes gist / commit / trial output R(own) R(own task) R R RW
reproduction_steps Long text Yes (Catastrophic/Critical/Major) R(own) R(own task) R R RW
triage_status Single select Yes Pending, Accepted, Rejected, Duplicate-of, Needs-more-evidence; default Pending R(own) R(own task) R R RW
triager Linked → Contributors Auto on status change (reverse: findings_triaged) R(own) R(own task) R R RW
triaged_at Date Auto on status change written by Automation when status leaves Pending R(own) R(own task) R R R
actual_tier Single select Yes if Accepted same options as claimed_tier R(own) R(own task) R R RW
tier_changed_from_claimed Formula {claimed_tier} != {actual_tier} R(own) R(own task) R R R
bond_amount_locked Formula SWITCH({claimed_tier}, "Catastrophic", 50, "Critical", 25, "Major", 10, "Duplicate", 5, "Minor", 2, "Co-finder", 5) R(own) R(own task) R R R
bond_disposition Single select Pending, Returned, Slashed-50, Slashed-100; default Pending R(own) R(own task) R R RW
badness_tier Single select Yes if Rejected N/A, Honest-mistake, Error, Flagrant, AI-slop, Malicious; default N/A R(own) R(own task) R R RW
senior_triager_cosign Linked → Contributors Yes if badness_tier ∈ {Flagrant, AI-slop, Malicious} R(own) R(own task) R R RW
payout_to_hunter Formula IF({triage_status} = "Accepted", SWITCH({actual_tier}, "Catastrophic", 200, "Critical", 100, "Major", 50, "Duplicate", 50, "Minor", 0, "Co-finder", 25), 0) R(own) R(own task) R R R
deduction_from_author Formula IF({triage_status} = "Accepted", SWITCH({actual_tier}, "Catastrophic", 200, "Critical", 100, "Major", 50, "Duplicate", 25, "Minor", 0, "Co-finder", 25), 0) (Duplicate: 25 per author, two authors per Duplicate finding) R(own) R(own task) R R R
clawback_amount Formula IF({badness_tier} = "AI-slop", {bond_amount_locked}, IF({badness_tier} = "Malicious", BLANK(), 0)) — Malicious clawback is computed by Automation against 90-day rolling earnings R(own) R(own task) R R R
auto_quality_signal Single select clearly-ai, unclear, clearly-human; written by AI-slop pre-screen Automation R R RW
auto_quality_confidence Number 0.0–1.0 R R RW
templated_prose_score Number 0.0–1.0 R R RW
fabricated_evidence_likelihood Number 0.0–1.0 R R RW
high_risk_queue Formula AND({auto_quality_signal} = "clearly-ai", {auto_quality_confidence} >= 0.9, {fabricated_evidence_likelihood} >= 0.7) R R R
arbitration Linked → Arbitrations (reverse: finding) R(own) R(own task) R R R
appeal Linked → Appeals (reverse: finding) R(own) R(own task) R R R
bad_actor_signals Multi-select options from file 12 R R RW
grace_applied Multi-select options from file 12 R(own) R(own task) R R RW
triager_notes Long text reasoning R(own) R(own task) R R RW
appeal_window_ends Formula DATEADD({triaged_at}, 7, 'days') R(own) R(own task) R R R

Author bad-faith hook: the deduction_from_author rollup feeds back to Tasks.total_deductions_to_author, capped at $200/task per file 01. A pattern of multiple shipped tasks where the author hit the $200 cap quickly is a signal for senior-triager attention. Surfaced via a Senior Triager view filtering Tasks by total_deductions_to_author >= 150.

Triager bad-faith hook: triager link + arbitrator-outcome Automation decrements the triager's own triager_quality_metric on OVERTURN (−1.0) and PARTIAL_OVERTURN (−0.5). A triager's metric falling below 0.5 freezes their triage assignments pending senior review. Implemented in Automations (file 20).

Table 4: Appeals

Filed by authors against Accepted decisions (not by hunters — hunter disputes go through Arbitrations).

Field Type Required Formula / link H A T ST Adm
appeal_id Autonumber (Primary) R(own) R R RW
finding Linked → Findings Yes (reverse: appeal) R(own) R R RW
appellant Linked → Contributors Yes must be finding.target_task.author R(own) R R RW
filed_at Created time auto R(own) R R R
appeal_basis Long text Yes new evidence / wrong tier / misclassification R(own) R R RW
appeal_reviewer Linked → Contributors Yes Senior Triager; must differ from original finding.triager R(own) R R RW
outcome Single select Pending, Upheld, Overturned, Partial; default Pending R(own) R R RW
outcome_notes Long text reviewer's rationale R(own) R R RW
decided_at Date auto on outcome change R(own) R R R

Table 5: Arbitrations

LLM arbitrator output for hunter-filed disputes against rejections.

Field Type Required Formula / link H A T ST Adm
arbitration_id Autonumber (Primary) R(own) R R RW
finding Linked → Findings Yes (reverse: arbitration) R(own) R R RW
disputer Linked → Contributors Yes must be finding.hunter R(own) R R RW
triggered_at Created time auto R(own) R R R
hunter_dispute_text Long text Yes R(own) R R RW
dispute_bond Number default 20; locked from disputer's escrow on creation R(own) R R R
model_id Single line written by arbitrator Automation; e.g., claude-opus-4-7 R(own) R R R
prompt_version_sha256 Single line hash of rubrics/bounty-arbitration.toml at call time R(own) R R R
input_payload_sha256 Single line hash of (finding payload, triager reason, dispute text, task snapshot) R(own) R R R
output_decision Single select UPHOLD, OVERTURN, PARTIAL_OVERTURN, UNCERTAIN, Pending R(own) R R R
output_rationale Long text arbitrator's reasoning R(own) R R R
output_revised_badness_tier Single select set on PARTIAL_OVERTURN R(own) R R R
arbitration_cost_usd Number ~$0.50 typical R R RW
senior_triager_panel Multi link → Contributors set on UNCERTAIN R(own) R R RW
final_outcome Single select mirrors output_decision or senior panel verdict R(own) R R RW
bond_disposition_dispute Formula SWITCH({final_outcome}, "UPHOLD", "Slashed", "OVERTURN", "Returned", "PARTIAL_OVERTURN", "Returned", BLANK()) R(own) R R R
frivolous_dispute_quality_delta Formula IF({final_outcome} = "UPHOLD", -0.05, 0) R(own) R R R
overturn_bonus_paid Formula IF({final_outcome} = "OVERTURN", 25, 0) R(own) R R R

Table 6: Escrow Ledger

Per-dollar entries. Each entry tracks one inflow to a contributor's escrow and its release / slash status.

Why per-dollar (or per-batch) and not per-contributor: the 90-day release rule applies per-entry (when each dollar entered), not per- contributor. A single per-contributor escrow_balance would lose the release timing.

Field Type Required Formula / link H A T ST Adm
ledger_id Autonumber (Primary) R(own contrib) R(own contrib) R R RW
contributor Linked → Contributors Yes (reverse: escrow_entries) R(own contrib) R(own contrib) R R RW
amount Number Yes USD R(own contrib) R(own contrib) R R RW
source Single select Yes Starter-grant, Payout-holdback, Bond-returned, Arbitrator-overturn-bonus R(own contrib) R(own contrib) R R RW
source_finding Linked → Findings set when source = Payout-holdback or Bond-returned R(own contrib) R(own contrib) R R RW
source_arbitration Linked → Arbitrations set when source = Arbitrator-overturn-bonus R(own contrib) R(own contrib) R R RW
entered_escrow_at Created time auto R(own contrib) R(own contrib) R R R
release_eligible_at Formula DATEADD({entered_escrow_at}, 90, 'days') R(own contrib) R(own contrib) R R R
released_at Date written by release Automation R(own contrib) R(own contrib) R R RW
slashed_at Date written by slash Automation R(own contrib) R(own contrib) R R RW
slash_finding Linked → Findings the finding that caused the slash R(own contrib) R(own contrib) R R RW
slash_amount Number actual dollars slashed (may be partial if last entry covers a remainder) R(own contrib) R(own contrib) R R RW
status Formula IF({slashed_at}, "Slashed", IF({released_at}, "Released", IF({release_eligible_at} <= TODAY(), "Pending release", "Locked"))) R(own contrib) R(own contrib) R R R

Table 7: Audit Log

Immutable record of every state change on the four operational tables (Findings, Contributors, Arbitrations, Escrow Ledger). Written exclusively by Automations; never edited by humans.

Purpose: dispute reconstruction (a contributor claims their escrow was wrongly slashed → audit log shows exactly which Automation fired when, who triggered the source event), and triager-quality auditing (a senior triager investigating drift can see every classification a specific triager made and how the arbitrator ruled).

Field Type Required Formula / link H A T ST Adm
audit_id Autonumber (Primary) R R
recorded_at Created time auto R R
entity_type Single select Yes Finding, Contributor, Arbitration, Escrow Ledger R R
entity_id Single line Yes the affected row's primary key R R
field_changed Single line Yes name of the field R R
old_value Long text stringified prior value R R
new_value Long text stringified new value R R
change_source Single select Yes Triager-action, Automation, Senior-override, Admin-correction R R
actor Linked → Contributors for Triager-action and Senior-override; null for Automation R R
automation_name Single line for Automation source R R
related_finding Linked → Findings for any change traceable to a finding R R
related_arbitration Linked → Arbitrations for arbitrator-triggered changes R R
notes Long text for Admin-correction only, explain why R RW

Hunters and authors cannot see this table. Only Senior Triagers and Admin. Disputes that require an audit reference are routed through Senior Triager review.

Audit log entries cannot be edited or deleted (Airtable Base permission: this table is "Editor-locked"; only Owner can modify existing rows; only Automations can create new ones). This is enforced at the base-level permission setting, not in a formula.

Permissions summary

Translating the per-column R/W into Airtable's actual permission model:

Role Base permission level Per-table overrides Per-row filter
Hunter Read-only on base Findings: editor on rows where hunter = self (for comment-promotion preview only — but the triager owns the promote); Arbitrations: editor on rows where disputer = self for the initial dispute text only hunter = currentUser() filter on Findings views; contributor_id = currentUser() filter on Contributors view; matching filter on Escrow Ledger
Author Read-only on base Appeals: editor on rows where appellant = self author = currentUser() filter on Tasks; downstream filters on Findings (target_task.author = currentUser())
Triager Editor on Findings, Arbitrations Read-only on Contributors (no W on quality_score directly — Automations write it); read-only on Escrow Ledger None
Senior Triager Editor everywhere except Audit Log (which is Editor-locked); can sign-off on Flagrant/AI-slop/Malicious; can override Triager decisions None
Admin Owner / Creator Everything writable except Audit Log row deletion (which is impossible) None

The author/hunter row filters are implemented as Interface filters plus base-level "Limited record permissions" rules. Airtable's permission model supports both. Hunters and authors operate primarily through Interface pages (file 19), not direct base access — Interface permissions are the primary control, with base permissions as the backstop.

Author bad-faith — schema treatment

The system catches author bad-faith via three derived signals visible to Senior Triagers:

  1. Tasks.total_deductions_to_author rollup. A task hitting the $200 cap in < 7 days of shipping is a signal. Senior Triager view: Tasks filtered by (TODAY() - shipped_at) <= 7 AND total_deductions_to_author >= 150.
  2. Per-author rollup across tasks. Add a Contributors rollup: tasks_hit_deduction_cap = count of linked tasks_authored rows where total_deductions_to_author >= 200. Two such tasks in 30 days triggers manual senior-triager review.
  3. Collusion indicator. A Contributors formula field dominant_hunter_pct = "what % of accepted bounty findings against this author's tasks come from a single hunter contributor". > 40% triggers manual review (the M3 lifetime cap from 02-anti-collusion.md, applied manually via the surfaced view).

These are signal-surfacing fields, not auto-enforcement. The Senior Triager makes the call to suspend / clawback based on what the signal shows + investigation.

Triager bad-faith — schema treatment

The Contributors.triager_quality_metric field is the primary deterrent. Automations update it:

  • On every Arbitration final_outcome = OVERTURN against a triager's rejection: −1.0 to the triager's metric
  • On PARTIAL_OVERTURN: −0.5
  • On every Appeal outcome = Overturned: −0.5

If triager_quality_metric < 0.5, an Automation sets the triager's suspended_from = [Triager] and suspension_until = TODAY() + 30. Senior Triager review required to restore.

Visibility: a Triager sees their own metric (R(self)) so they have feedback. Other Triagers and Senior Triagers see all metrics for calibration.

Build order

When standing up the base:

  1. Create Tables 1, 2 (Tasks, Contributors) first — they're the leaf nodes everything else links to.
  2. Create Table 6 (Escrow Ledger) — needed for Contributor rollups.
  3. Create Table 3 (Findings) — links to 1, 2, 6.
  4. Create Tables 4, 5 (Appeals, Arbitrations) — link to 3.
  5. Create Table 7 (Audit Log) — links to everything; Automations populate it.
  6. Configure base permissions per the matrix above.
  7. Create Automations per file 20.
  8. Create Views and Interfaces per files 18 and 19.

What's NOT in this schema (intentional)

  • Onboarding (KYC, contributor agreement signing, starter-escrow grant trigger). Mercor admin process, not in the Airtable.
  • Task admission (how a shipped tasks/<slug>/ becomes a Tasks row). Manual or external sync, not in the Airtable.
  • Triager rotation / assignment (who picks up which Finding from the queue). Self-assign in v1; no schema needed.
  • Tax / 1099 (out of scope per user direction).
  • Public stats dashboard (deferred to v2 per file 14).

Acceptance criteria

  • All 7 tables build cleanly in a fresh Airtable base in one afternoon by following this spec.
  • Field formulas all parse and evaluate correctly on test rows.
  • Permissions matrix matches the R/W columns when tested with each role's actual account.
  • Audit Log cannot be edited by any non-Owner role.
  • Escrow Ledger rollups produce correct escrow_balance, escrow_locked, and escrow_available for a contributor with a mix of released, locked, and slashed entries.
  • Triager and senior-triager views immediately surface author bad-faith signal rows and triager-quality drift.

§D — Views

One view per workflow surface, per role. Names use the format <Role>: <View name> so they sort naturally in the Airtable view sidebar.

Field column lists use the table-spec field names from 17-airtable-schema.md. Filter conditions use Airtable filter-builder syntax conceptually.

Hunter views

Hunter: Open Tasks

  • Source: Tasks
  • Filter: bounty_status = "Open"
  • Sort: shipped_at desc
  • Group: track (standard / frontier separated)
  • Fields shown: slug, author.display_name, track, shipped_at, remaining_author_at_risk
  • Purpose: browse what's available to hunt

Hunter: My Findings

  • Source: Findings
  • Filter: hunter = LOGGED_IN_USER()
  • Sort: submitted_at desc
  • Group: triage_status
  • Fields shown: finding_id, target_task.slug, claimed_tier, actual_tier, triage_status, badness_tier, bond_amount_locked, bond_disposition, payout_to_hunter, triaged_at, appeal_window_ends
  • Purpose: track own submissions through triage

Hunter: My Escrow

  • Source: Escrow Ledger
  • Filter: contributor = LOGGED_IN_USER()
  • Sort: entered_escrow_at desc
  • Group: status (Locked / Pending release / Released / Slashed)
  • Fields shown: ledger_id, amount, source, source_finding.finding_id, entered_escrow_at, release_eligible_at, status
  • Purpose: see escrow inflows / outflows + upcoming releases. Read-only.

Hunter: My Contributor Record

  • Source: Contributors
  • Filter: contributor_id = LOGGED_IN_USER()
  • Layout: single-record view (only one row will match)
  • Fields shown: display_name, quality_score, escrow_balance, escrow_locked, escrow_available, lifetime_payouts, lifetime_slashed, clawback_debt, ai_slop_warnings, submissions_filed, submissions_accepted, accept_rate, suspended_from, suspension_until
  • Purpose: self-status at a glance

Hunter: My Disputes

  • Source: Arbitrations
  • Filter: disputer = LOGGED_IN_USER()
  • Sort: triggered_at desc
  • Group: final_outcome (Pending / UPHOLD / OVERTURN / PARTIAL_OVERTURN / UNCERTAIN)
  • Fields shown: arbitration_id, finding.finding_id, triggered_at, dispute_bond, output_decision, final_outcome, bond_disposition_dispute, overturn_bonus_paid
  • Purpose: track dispute outcomes

Author views

Author: My Tasks

  • Source: Tasks
  • Filter: author = LOGGED_IN_USER()
  • Sort: shipped_at desc
  • Fields shown: slug, track, bounty_status, shipped_at, total_deductions_to_author, remaining_author_at_risk
  • Purpose: monitor own tasks' bounty exposure

Author: Incoming Findings (Pending)

  • Source: Findings
  • Filter: target_task.author = LOGGED_IN_USER() AND triage_status = "Pending"
  • Sort: submitted_at desc
  • Fields shown: finding_id, target_task.slug, claimed_tier, failure_category, evidence_url, submitted_at
  • Visibility caveat: author sees the structured Finding fields but not the triager, triager_notes, bad_actor_signals, or auto_quality_signal (those are triager-internal during Pending). Permissions in 17 §Permissions enforce this.
  • Purpose: awareness of what's being decided against own task. No action available until triage_status changes.

Author: Decided Findings

  • Source: Findings
  • Filter: target_task.author = LOGGED_IN_USER() AND triage_status != "Pending"
  • Sort: triaged_at desc
  • Group: triage_status
  • Fields shown: finding_id, target_task.slug, claimed_tier, actual_tier, triage_status, badness_tier, deduction_from_author, triaged_at, triager_notes, appeal_window_ends
  • Purpose: see accepted findings against own tasks; basis for filing an Appeal

Author: My Appeals

  • Source: Appeals
  • Filter: appellant = LOGGED_IN_USER()
  • Sort: filed_at desc
  • Group: outcome
  • Fields shown: appeal_id, finding.finding_id, finding.target_task.slug, filed_at, appeal_basis, appeal_reviewer.display_name, outcome, outcome_notes, decided_at
  • Purpose: track appeal status

Author: My Contributor Record

  • Source: Contributors
  • Filter: contributor_id = LOGGED_IN_USER()
  • Layout: single-record view
  • Fields shown: same as Hunter equivalent, scoped to author-relevant fields
  • Purpose: self-status

Triager views (the primary working surface)

Triager: Pending Queue

  • Source: Findings
  • Filter: triage_status = "Pending" AND high_risk_queue = FALSE
  • Sort: submitted_at asc (oldest first — fairness)
  • Fields shown: finding_id, target_task.slug, hunter.display_name, hunter.quality_score, claimed_tier, failure_category, evidence_url, reproduction_steps, auto_quality_signal, auto_quality_confidence, submitted_at, bond_amount_locked
  • Purpose: the main triage queue. AI-slop high-risk ones are siphoned off into the separate view below so the normal queue stays clean.

Triager: High-Risk Queue

  • Source: Findings
  • Filter: triage_status = "Pending" AND high_risk_queue = TRUE
  • Sort: submitted_at asc
  • Fields shown: same as Pending Queue plus templated_prose_score, fabricated_evidence_likelihood, hunter.ai_slop_warnings, hunter.lifetime_slashed
  • Purpose: AI-slop pre-screen positives that need a senior triager's attention. By Airtable Interface permissions this view is read-only for regular Triagers and editable only by Senior Triagers — the high-risk queue requires Senior co-sign anyway, so routing directly is cleaner than the regular Triager triaging and then asking for co-sign.

Triager: My Recent Decisions

  • Source: Findings
  • Filter: triager = LOGGED_IN_USER() AND triaged_at >= TODAY() - 30
  • Sort: triaged_at desc
  • Group: triage_status then badness_tier
  • Fields shown: finding_id, target_task.slug, hunter.display_name, claimed_tier, actual_tier, triage_status, badness_tier, triaged_at, appeal (link badge → if appealed, see outcome), arbitration (link badge → if disputed, see outcome)
  • Purpose: triager's own work; the appeal/arbitration link badges show which decisions got pushed back, useful for self-calibration

Triager: My Triager Quality

  • Source: Contributors
  • Filter: contributor_id = LOGGED_IN_USER() AND "Triager" IN roles_active
  • Layout: single-record + Audit Log linked view filtered to entity_type = Contributor AND entity_id = self AND field_changed = "triager_quality_metric"
  • Fields shown: triager_quality_metric, plus the linked Audit Log entries showing every metric delta with the related Finding/Arbitration
  • Purpose: triager sees their own quality trajectory and what caused each delta

Triager: Pending Cosign Requests

  • Source: Findings
  • Filter: badness_tier IN ("Flagrant", "AI-slop", "Malicious") AND senior_triager_cosign IS NULL
  • Sort: triaged_at asc
  • Fields shown: finding_id, target_task.slug, triager.display_name, badness_tier, triager_notes, bond_amount_locked, hunter.display_name, hunter.quality_score
  • Visibility: read-only to regular Triagers (informational — they can see what's queued), editable to Senior Triagers (who do the co-sign)
  • Purpose: Senior Triager workload queue

Senior Triager views (oversight surfaces)

Senior: Cosign Queue

  • Same as Triager: Pending Cosign Requests above, but writable for Senior to set senior_triager_cosign = self

Senior: Appeal Queue

  • Source: Appeals
  • Filter: outcome = "Pending"
  • Sort: filed_at asc
  • Fields shown: appeal_id, finding.finding_id, finding.target_task.slug, finding.actual_tier, appellant.display_name, appellant.quality_score, filed_at, appeal_basis, finding.triager.display_name, finding.triager_notes
  • Auto-assignment hint: an Automation flags appeals where appeal_reviewer = finding.triager as invalid (reviewer must differ); Senior Triager picks the reviewer from this view.

Senior: UNCERTAIN Arbitrations

  • Source: Arbitrations
  • Filter: output_decision = "UNCERTAIN" AND final_outcome = "Pending"
  • Sort: triggered_at asc
  • Fields shown: arbitration_id, finding.finding_id, finding.target_task.slug, disputer.display_name, hunter_dispute_text, output_rationale, senior_triager_panel
  • Purpose: arbitrator escalations; Senior panel decides

Senior: Triager Quality Watch

  • Source: Contributors
  • Filter: "Triager" IN roles_active
  • Sort: triager_quality_metric asc
  • Group: none (flat sorted list — worst-at-top draws attention)
  • Fields shown: display_name, triager_quality_metric, submissions_accepted (count of findings they triaged that were Accepted), suspended_from, suspension_until
  • Purpose: the canonical "are any triagers drifting" view. Anyone below 0.7 is on watch; below 0.5 is auto-suspended.

Senior: Author Bad-Faith Signal

  • Source: Tasks
  • Filter: total_deductions_to_author >= 150 AND (TODAY() - shipped_at) <= 14
  • Sort: total_deductions_to_author desc
  • Fields shown: slug, author.display_name, shipped_at, total_deductions_to_author, remaining_author_at_risk, findings (count badge)
  • Purpose: tasks that hit deduction cap fast → investigate

Senior: Author Collusion Watch

  • Source: Contributors
  • Filter: "Author" IN roles_active AND dominant_hunter_pct >= 0.4
  • Sort: dominant_hunter_pct desc
  • Fields shown: display_name, dominant_hunter_pct, submissions_accepted, linked tasks_authored
  • Purpose: the M3 lifetime-cap manual check. Above 40% triggers investigation.

Senior: Weekly Decision Scan

  • Source: Findings
  • Filter: triaged_at >= TODAY() - 7
  • Sort: triaged_at desc
  • Group: triager.display_name
  • Fields shown: finding_id, target_task.slug, hunter.display_name, claimed_tier, actual_tier, triage_status, badness_tier, appeal.outcome, arbitration.final_outcome
  • Purpose: the M7-equivalent weekly 100% senior scan. Grouped by triager so calibration drift across triagers is visible.

Senior: Frontier Cosign Requests

  • Source: Findings
  • Filter: target_task.track = "frontier" AND badness_tier IN ("Flagrant", "AI-slop", "Malicious") AND senior_triager_cosign IS NULL
  • Same shape as Cosign Queue but frontier-specific (frontier findings get extra scrutiny because the verifier hash is at stake)

Admin views (everything plus financial)

Admin: Clawback Debts

  • Source: Contributors
  • Filter: clawback_debt > 0
  • Sort: clawback_debt desc
  • Fields shown: display_name, email, clawback_debt, lifetime_payouts, suspended_from, suspension_until
  • Purpose: dollar amounts to deduct from next Mercor payments. Exported to Mercor's contributor-payment system on a cadence.

Admin: Suspended Contributors

  • Source: Contributors
  • Filter: suspended_from IS NOT EMPTY
  • Sort: suspension_until asc
  • Fields shown: display_name, email, suspended_from, suspension_until, quality_score, clawback_debt
  • Purpose: active-suspension list; near-expiry rows show what's about to come back online

Admin: Audit Log

  • Source: Audit Log
  • Filter: none
  • Sort: recorded_at desc
  • Group: entity_type then change_source
  • Fields shown: all
  • Purpose: dispute reconstruction. Filter on-demand by related_finding or actor when investigating a specific case.

Admin: Escrow Ledger Full

  • Source: Escrow Ledger
  • Filter: none
  • Sort: entered_escrow_at desc
  • Group: status
  • Fields shown: all
  • Purpose: financial overview of all in-flight escrow

Admin: Arbitration Cost Tracking

  • Source: Arbitrations
  • Filter: none
  • Sort: triggered_at desc
  • Summary bar: SUM({arbitration_cost_usd}) by month
  • Fields shown: arbitration_id, triggered_at, model_id, arbitration_cost_usd, final_outcome, bond_disposition_dispute
  • Purpose: monitor LLM arbitrator spend against the $25/quarter ceiling from file 16

Views NOT to create

Listed deliberately so future contributors don't add them:

  • Public stats view. Deferred to v2 (M9 dashboard).
  • Cross-role pseudonym tracking. No pseudonyms in v1.
  • Throughput-allocation queue. No automated assignment in v1.
  • Per-author hunter cooldown enforcement view. Manual M2 in v1 — Senior Triager checks during Collusion Watch.

Permissions binding

Each view is bound to one or more Airtable Interface pages (file 19). Direct base access for non-Admin roles should be disabled — Hunters / Authors / Triagers / Senior Triagers work exclusively through Interface pages, which inherit the view filters and apply additional UI gating. Direct base access bypasses the per-row filters and shows raw data.

Acceptance criteria

  • Each view in this file exists in the Airtable base with the documented filter, sort, group, and field selection.
  • A test Hunter account sees only their own Findings + Contributor record + Escrow Ledger entries via the Hunter views; they cannot see other Hunters' data.
  • A test Author account sees only Findings against their own Tasks and cannot see triager-internal fields on Pending Findings.
  • The Senior Triager Weekly Decision Scan view, when filtered to a single triager, immediately surfaces all that triager's decisions in the week + the appeal/arbitration outcome on each.
  • The Author Bad-Faith Signal view triggers a test row when a contrived task accumulates $150+ in deductions within 14 days.
  • The Triager Quality Watch view sorts the worst triager to the top and includes the auto-suspension threshold (0.5) as a visible reference (via a Summary field or conditional color).

§E — Interfaces

The actual UI for each role. Non-Admin roles work exclusively through Interfaces — direct base access is disabled.

Each page below has: who sees it, what views it surfaces (from 18-airtable-views.md), and an ASCII mockup.

Interface elements used: Header, Number cards, Record list (filtered to a view), Record detail, Form, Button (running an Automation), Filter bar.

Hunter pages

Hunter Home

┌──────────────────────────────────────────────────────────────────────────┐
│ Codebase Atlas Bounty                              [Alice Chen ▼] [Help] │
├──────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│   Welcome back, Alice                                                    │
│                                                                          │
│   ┌─Quality─────────┐ ┌─Escrow──────────┐ ┌─Lifetime─────────────────┐  │
│   │                 │ │                 │ │                          │  │
│   │      1.45       │ │   $87 / $200    │ │  $1,240 paid             │  │
│   │   (rank: ★★★)   │ │  $25 locked     │ │  18 of 23 accepted (78%) │  │
│   │                 │ │  $62 available  │ │  0 AI-slop warnings      │  │
│   │                 │ │                 │ │                          │  │
│   └─────────────────┘ └─────────────────┘ └──────────────────────────┘  │
│                                                                          │
│   ┌─My recent findings──────────────────────────────────────────────┐   │
│   │ #234  rustfs-iam-proxy    Critical→Critical  ✓ Accepted   $100 │   │
│   │ #229  spacy-training      Major→Major        ✓ Accepted    $50 │   │
│   │ #225  harbor-oauth        Critical           ⏳ Pending         │   │
│   │ #221  rustfs-default      Critical→Major     ⚠ Tier-inflate  $50 │ │
│   │                                          [See all my findings →]  │   │
│   └─────────────────────────────────────────────────────────────────┘   │
│                                                                          │
│   ┌─Escrow releases coming up──────────────────────────────────────┐    │
│   │ $20  unlocks 2026-06-15 (from finding #229)                    │    │
│   │ $20  unlocks 2026-07-02 (from finding #234)                    │    │
│   │ $5   unlocks 2026-08-14 (from finding #225, conditional)       │    │
│   └────────────────────────────────────────────────────────────────┘    │
│                                                                          │
│   ┌──────────────────────────────────────────────────────────────────┐  │
│   │  [Browse open tasks →]      [My disputes]      [My escrow log]   │  │
│   └──────────────────────────────────────────────────────────────────┘  │
│                                                                          │
└──────────────────────────────────────────────────────────────────────────┘

Surfaces: Hunter: My Contributor Record, Hunter: My Findings (top 5), Hunter: My Escrow (Pending release subset).

Hunter: Browse Tasks

┌──────────────────────────────────────────────────────────────────────────┐
│ Browse Open Tasks                                       [← Home]         │
├──────────────────────────────────────────────────────────────────────────┤
│ Filter: ◉ All  ○ Standard  ○ Frontier         Sort: [Newest first ▼]   │
├──────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  ╭─ rustfs-default-creds-startup-abort ────────────── standard ─╮       │
│  │  by Bob Lee · shipped 2026-05-24 (3d ago)                    │       │
│  │  Author at risk: $200 of $200 remaining                      │       │
│  │  Findings filed: 0                                            │       │
│  │  [Open task →]                                                │       │
│  ╰───────────────────────────────────────────────────────────────╯       │
│                                                                          │
│  ╭─ spacy-training-state-flow ────────────────────── frontier ─╮        │
│  │  by Carol Wu · shipped 2026-05-22 (5d ago)                   │       │
│  │  Author at risk: $150 of $200 remaining                      │       │
│  │  Findings filed: 1 (1 accepted Major)                        │       │
│  │  [Open task →]                                                │       │
│  ╰───────────────────────────────────────────────────────────────╯       │
│                                                                          │
│  ╭─ harbor-claude-oauth-auth ─────────────────────── standard ─╮        │
│  │  by Dan Park · shipped 2026-05-20 (7d ago)                   │       │
│  │  Author at risk: $200 of $200 remaining                      │       │
│  │  Findings filed: 2 (0 accepted, 2 pending)                   │       │
│  │  [Open task →]                                                │       │
│  ╰───────────────────────────────────────────────────────────────╯       │
│                                                                          │
│                            [Load more]                                   │
└──────────────────────────────────────────────────────────────────────────┘

Surfaces: Hunter: Open Tasks view.

Hunter: Task Detail

┌──────────────────────────────────────────────────────────────────────────┐
│ rustfs-default-creds-startup-abort                  [← Browse]           │
├──────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  Track: standard · Author: Bob Lee · Shipped: 2026-05-24                │
│  Bounty status: Open · Author at risk: $200 of $200                     │
│                                                                          │
│  Task materials                                                          │
│   ▸ README                  ▸ instruction.md       ▸ solution/answer.txt│
│   ▸ Reward rubrics (4)      ▸ Run an oracle locally                     │
│                                                                          │
│  Existing findings (check before submitting — duplicates pay 50%)        │
│  ┌────────────────────────────────────────────────────────────────────┐ │
│  │ (none yet)                                                         │ │
│  └────────────────────────────────────────────────────────────────────┘ │
│                                                                          │
│  ┌─ How to submit ──────────────────────────────────────────────────┐   │
│  │ 1. Click [Submit Finding] below                                  │   │
│  │ 2. Leave a comment with: what's wrong, repro steps, evidence URL │   │
│  │ 3. Choose your claimed tier — your bond is locked from escrow:   │   │
│  │      Catastrophic $50 · Critical $25 · Major $10 · Minor $2      │   │
│  │ 4. A triager reviews within 72hr typical                         │   │
│  │                                                                   │   │
│  │  Your available escrow: $62  →  you can submit at any tier       │   │
│  └──────────────────────────────────────────────────────────────────┘   │
│                                                                          │
│                       [📝 Submit Finding]                                │
│                                                                          │
└──────────────────────────────────────────────────────────────────────────┘

The Submit Finding button opens a structured comment compose dialog on the Task record. The comment becomes the source the triager promotes to a Finding row.

Hunter: My Finding Detail

┌──────────────────────────────────────────────────────────────────────────┐
│ Finding #221  rustfs-default-creds-startup-abort         [← My Findings] │
├──────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  Status: ⚠ Accepted at lower tier (tier-inflation)                       │
│  Submitted: 2026-05-21      Triaged: 2026-05-23      Triager: Erin S.   │
│                                                                          │
│  Claimed: Critical      Actual: Major                                    │
│  Failure category: Foil-pass                                             │
│  Bond: $25 returned to escrow                                            │
│  Payout: $50 (Major)                                                     │
│  Quality score impact: −0.05 (tier inflation)                            │
│                                                                          │
│  Triager notes:                                                          │
│   ┌────────────────────────────────────────────────────────────────┐    │
│   │ The finding is real — the foil scores 0.42 instead of <0.4 —  │    │
│   │ but the impact is contained to one task, not systemic, so this │    │
│   │ is a Major, not a Critical. Critical requires either           │    │
│   │ cross-task impact or contamination evidence; neither was shown.│    │
│   └────────────────────────────────────────────────────────────────┘    │
│                                                                          │
│  ╭─ Dispute window open until 2026-05-30 ──────────────────────╮        │
│  │  Think the triager got the tier wrong?                       │        │
│  │  File a dispute. Costs $20 from your escrow (locked during   │        │
│  │  arbitration). Returned if the arbitrator overturns;         │        │
│  │  slashed if the arbitrator upholds.                          │        │
│  │                              [🗳 File Dispute]                │        │
│  ╰──────────────────────────────────────────────────────────────╯       │
│                                                                          │
└──────────────────────────────────────────────────────────────────────────┘

For rejected findings the panel changes:

│  Status: ✗ Rejected (Flagrant error)                                     │
│  Bond: $12.50 slashed, $12.50 returned                                   │
│  Quality score impact: −0.10                                             │

For AI-slop:

│  Status: ✗ Rejected (AI-slop)                                            │
│  Bond: $25 slashed                                                       │
│  Clawback: $25 owed from past 30 days of earnings                        │
│  Quality score impact: −0.20                                             │

Author pages

Author Home

┌──────────────────────────────────────────────────────────────────────────┐
│ Codebase Atlas — Author View                       [Bob Lee ▼] [Help]    │
├──────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│   ┌─Quality─────────┐ ┌─Tasks shipped───┐ ┌─Lifetime deductions────┐    │
│   │     1.10        │ │       8         │ │  $325 across 8 tasks    │    │
│   │   ●●●○○         │ │   (1 frontier)  │ │  Avg $41/task           │    │
│   └─────────────────┘ └─────────────────┘ └─────────────────────────┘    │
│                                                                          │
│   ┌─My tasks ────────────────────────────────────────────────────────┐  │
│   │ ⓘ rustfs-default-creds         Open  $0 deducted   $250 at risk │   │
│   │ ⓘ harbor-oauth                 Open  $50 deducted  $200 at risk │   │
│   │ ⓘ spacy-training (frontier)    Open  $150 deducted $100 at risk │   │
│   │ ⓘ rustfs-iam-proxy             Open  $200 deducted $50 at risk  │   │
│   │ ▸ ... (4 more closed)                                            │   │
│   └──────────────────────────────────────────────────────────────────┘   │
│                                                                          │
│   ┌─Findings pending against my tasks (informational) ───────────────┐  │
│   │ #229  rustfs-iam-proxy   Critical claimed   filed 2d ago        │   │
│   │ #231  harbor-oauth       Major claimed      filed 1d ago        │   │
│   │ #233  rustfs-default     Minor claimed      filed 5h ago        │   │
│   └──────────────────────────────────────────────────────────────────┘   │
│                                                                          │
│   ┌─Recently decided ────────────────────────────────────────────────┐  │
│   │ #227 spacy-training  ✓ Accepted Major   −$50  [Appeal until 6/1]│   │
│   │ #225 harbor-oauth    ✓ Accepted Critical −$100 [Appeal until 5/30] │ │
│   │ #220 rustfs-default  ✗ Rejected (no action needed)              │   │
│   └──────────────────────────────────────────────────────────────────┘   │
│                                                                          │
└──────────────────────────────────────────────────────────────────────────┘

Surfaces: Author: My Tasks, Author: Incoming Findings (Pending), Author: Decided Findings.

The pending findings card is informational only — author cannot see the triager or triager notes until the triager decides. This preserves triage independence.

Author: File Appeal

┌──────────────────────────────────────────────────────────────────────────┐
│ File Appeal · Finding #227                              [← Author Home]  │
├──────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  Appeal against:                                                         │
│    Task:        spacy-training-state-flow                                │
│    Hunter:      Alice Chen (quality 1.45)                                │
│    Triager:     Erin Santos                                              │
│    Decision:    Accepted at Major   ($50 deducted from you)              │
│    Triaged:     2026-05-25 (2d ago)                                     │
│                                                                          │
│  Triager's reasoning:                                                    │
│   "The trajectory drift between training states is observable when      │
│    spaCy's pipeline serializes interim weights..."                       │
│                                              [Read full reasoning →]     │
│                                                                          │
│  Your appeal basis (3-10 sentences, specific):                          │
│   ┌────────────────────────────────────────────────────────────────┐    │
│   │                                                                │    │
│   │                                                                │    │
│   │                                                                │    │
│   │                                                                │    │
│   └────────────────────────────────────────────────────────────────┘    │
│                                                                          │
│  Valid bases: new evidence not previously surfaced; demonstrably         │
│  wrong tier classification; misapplication of a documented criterion.    │
│  Not valid: dislike of the hunter; subjective disagreement.              │
│                                                                          │
│  ⓘ Your appeal goes to an independent Senior Triager (not Erin).        │
│  ⓘ If Overturned, the finding is reversed and your $50 returned.        │
│  ⓘ Decisions typically within 7 days.                                   │
│                                                                          │
│                      [Cancel]      [Submit Appeal]                       │
│                                                                          │
└──────────────────────────────────────────────────────────────────────────┘

Triager pages

Triager Workbench

The primary working surface. Three-pane layout:

┌──────────────────────────────────────────────────────────────────────────┐
│ Triager Workbench                          [Erin Santos · Q-metric 0.92] │
├─────────────────┬────────────────────────────────────────────────────────┤
│ QUEUES          │ Finding #235 · harbor-oauth-auth                       │
│                 │                                                         │
│ Pending (12)    │ Hunter: Alice Chen  (Q 1.45 · 18/23 accepted)         │
│ ● #235 5h ago   │ Submitted: 2026-05-27 09:14                            │
│   #234 7h ago   │ Bond locked: $25 (Critical)                            │
│   #233 8h ago   │                                                         │
│   #232 11h ago  │ Claimed: Critical · Failure: Judge-prompt-leak         │
│   ...           │                                                         │
│                 │ ┌─Comment thread (source) ─────────────────────────┐  │
│ High-risk (2)   │ │ Alice: The grading prompt at rubrics/oauth-flow  │  │
│   #218 2d ago   │ │ leaks the answer when the agent quotes the env   │  │
│   #210 4d ago   │ │ var verbatim. See repro at gist.github.com/...   │  │
│                 │ └──────────────────────────────────────────────────┘  │
│ Cosigns (3)     │                                                         │
│   #209 senior   │ Pre-screen signals                                     │
│   #207 senior   │  auto_quality_signal: clearly-human  (0.91)            │
│   #205 senior   │  templated_prose_score: 0.12                           │
│                 │  fabricated_evidence_likelihood: 0.08                  │
│                 │                                                         │
│ My week         │ Hunter history                                         │
│  18 decided     │  Recent 30d: 4 Accepted, 0 Rejected                   │
│  0 overturned   │  Lifetime slashed: $0                                  │
│  Q-metric 0.92  │  AI-slop warnings: 0                                   │
│                 │                                                         │
│                 │ ─────────────── Decision ───────────────               │
│                 │  ◉ Accept    ○ Reject    ○ Needs more evidence        │
│                 │  Actual tier: [Critical    ▼]                          │
│                 │  Payout: $100 to Alice    Deduction: $100 from Dan    │
│                 │  Bond disposition: Returned to escrow                  │
│                 │                                                         │
│                 │  Triager notes: ┌──────────────────────────────────┐  │
│                 │                 │ Reproduced cleanly. The leak is  │  │
│                 │                 │ deterministic with the prompt as │  │
│                 │                 │ written.                          │  │
│                 │                 └──────────────────────────────────┘  │
│                 │                                                         │
│                 │           [Save Draft]    [Submit Decision]             │
└─────────────────┴────────────────────────────────────────────────────────┘

Rejection mode swaps the lower panel:

│                 │  ○ Accept    ◉ Reject    ○ Needs more evidence        │
│                 │                                                         │
│                 │  Badness tier: ◉ Honest-mistake ○ Error ○ Flagrant    │
│                 │                ○ AI-slop ○ Malicious                   │
│                 │                                                         │
│                 │  ⓘ Flagrant/AI-slop/Malicious require Senior cosign   │
│                 │     before applying. Submitting will route to the      │
│                 │     Senior Cosign queue.                                │
│                 │                                                         │
│                 │  Bond disposition: Returned to escrow (Honest-mistake) │
│                 │  Quality delta: −0.025                                 │
│                 │                                                         │
│                 │  Triager notes: [required for rejections]              │

Triager: Promote Comment

┌──────────────────────────────────────────────────────────────────────────┐
│ Promote Comment to Finding                            [← Workbench]      │
├──────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  Pending comments (unpromoted)                                           │
│  ╭──────────────────────────────────────────────────────────────────╮   │
│  │ Task: harbor-oauth-auth                          Alice · 5h ago  │   │
│  │ "The grading prompt at rubrics/oauth-flow leaks..."              │   │
│  │ [Promote →]                                                      │   │
│  ├──────────────────────────────────────────────────────────────────┤   │
│  │ Task: rustfs-default-creds                       Frank · 1d ago  │   │
│  │ "I think this fails to handle the case where..."                 │   │
│  │ [Promote →]                                                      │   │
│  ├──────────────────────────────────────────────────────────────────┤   │
│  │ Task: spacy-training                             Greta · 2d ago  │   │
│  │ "Bug: the answer doesn't account for X"                          │   │
│  │ [Promote →]                                                      │   │
│  ╰──────────────────────────────────────────────────────────────────╯   │
│                                                                          │
│  Promoting creates a Findings row pre-filled with:                      │
│   · target_task, hunter, source_comment_url, submitted_at               │
│  You then fill in: claimed_tier, failure_category, evidence_url,        │
│  reproduction_steps (from the comment content) before sending to        │
│  the triage queue.                                                       │
│                                                                          │
│  Auto-extract:                                                           │
│   ☑ Pull URLs from comment as evidence_url candidates                   │
│   ☑ Detect claimed tier from comment keywords ("Critical", etc.)        │
│   ☑ Run AI-slop pre-screen on promotion                                 │
│                                                                          │
└──────────────────────────────────────────────────────────────────────────┘

Senior Triager pages

Senior Triager Workbench

Single landing page with three queues stacked:

┌──────────────────────────────────────────────────────────────────────────┐
│ Senior Triager Workbench               [Hannah Park · ST role active]    │
├──────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  ┌─Cosign queue (3) ────────────────────────────────────────────────┐   │
│  │ #209 · spacy-training        Triager: Erin     Badness: AI-slop  │   │
│  │      Hunter Q: 0.85   Bond at stake: $25   [Review →]            │   │
│  │ #207 · rustfs-default        Triager: Erin     Badness: Flagrant │   │
│  │      Hunter Q: 1.10   Bond at stake: $25   [Review →]            │   │
│  │ #205 · harbor-oauth          Triager: Frank    Badness: Malicious│   │
│  │      Hunter Q: 0.60   Bond + clawback     [Review →]             │   │
│  └──────────────────────────────────────────────────────────────────┘   │
│                                                                          │
│  ┌─Appeal queue (2) ────────────────────────────────────────────────┐   │
│  │ #189-appeal · Bob (author)   v Finding #227     filed 1d ago     │   │
│  │      [Review →]                                                   │   │
│  │ #185-appeal · Carol (author) v Finding #220     filed 3d ago     │   │
│  │      [Review →]                                                   │   │
│  └──────────────────────────────────────────────────────────────────┘   │
│                                                                          │
│  ┌─UNCERTAIN arbitrations (1) ──────────────────────────────────────┐   │
│  │ Arb #45 · Finding #210     Arbitrator: UNCERTAIN                 │   │
│  │      Disputer: Alice    Panel size: 0/3 assigned   [Assemble →]  │   │
│  └──────────────────────────────────────────────────────────────────┘   │
│                                                                          │
│  ┌─Triager quality watch ───────────────────────────────────────────┐   │
│  │ ⚠ Frank Lopez       Q-metric 0.62   ← approaching 0.5 threshold │   │
│  │   Erin Santos       Q-metric 0.92                                │   │
│  │   Ivan Wei          Q-metric 1.18                                │   │
│  │                                                  [Full watch →]  │   │
│  └──────────────────────────────────────────────────────────────────┘   │
│                                                                          │
│  ┌─Author bad-faith signal ─────────────────────────────────────────┐   │
│  │ ⚠ Task `rustfs-iam-proxy` by Bob — $200 deducted in 4 days       │   │
│  │   Bob's lifetime cap-hits: 2 in 30 days  [Investigate →]         │   │
│  └──────────────────────────────────────────────────────────────────┘   │
│                                                                          │
│                  [Weekly Decision Scan (this week: 87)]                  │
│                                                                          │
└──────────────────────────────────────────────────────────────────────────┘

Senior: Cosign Detail

┌──────────────────────────────────────────────────────────────────────────┐
│ Cosign Request · Finding #209                       [← Workbench]        │
├──────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  Triager Erin classifies this as: AI-slop                                │
│  Consequence if confirmed:                                               │
│     · Hunter Alice: $25 bond slashed                                     │
│     · Clawback: $25 from past 30 days ($150 earned) → $25 owed         │
│     · Quality score: 1.45 → 1.25                                        │
│     · AI-slop warnings: 0 → 1                                           │
│                                                                          │
│  Full finding context:                                                   │
│   ┌────────────────────────────────────────────────────────────────┐    │
│   │ [Finding #209 detail — same layout as Triager Workbench right] │    │
│   └────────────────────────────────────────────────────────────────┘    │
│                                                                          │
│  Triager's reasoning:                                                    │
│   "Reproduction steps reference `pipeline.serialize()` which does       │
│    not exist in spaCy 3.x — see grep output. Comment prose matches      │
│    template patterns from finding #195 (same hunter, also rejected)."   │
│                                                                          │
│  Pre-screen signals:                                                     │
│     auto_quality_signal: clearly-ai (0.93)                              │
│     fabricated_evidence_likelihood: 0.82                                 │
│                                                                          │
│  Senior decision:                                                        │
│   ◉ Confirm AI-slop      ○ Downgrade to Flagrant     ○ Downgrade to Error│
│   ○ Reject classification (Triager misjudged — escalate to investigation)│
│                                                                          │
│   Senior notes: [required]                                              │
│   ┌────────────────────────────────────────────────────────────────┐    │
│   │                                                                │    │
│   └────────────────────────────────────────────────────────────────┘    │
│                                                                          │
│                   [Cancel]      [Apply Decision]                         │
│                                                                          │
└──────────────────────────────────────────────────────────────────────────┘

Admin pages

Admin Dashboard

┌──────────────────────────────────────────────────────────────────────────┐
│ Admin Dashboard                                       [Mercor Admin ▼]   │
├──────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  This month                                                              │
│   ┌─Findings──┐ ┌─Payouts─────┐ ┌─Slashes───┐ ┌─Arbitrator spend─────┐ │
│   │   142     │ │  $4,250     │ │  $187     │ │  $12.50 / $25 budget │ │
│   │ +18% MoM  │ │ to 23 ctrs  │ │ 11 events │ │  20 arbitrations     │ │
│   └───────────┘ └─────────────┘ └───────────┘ └──────────────────────┘ │
│                                                                          │
│  ┌─Clawback debts (active) ─────────────────────────────────────────┐   │
│  │ Dave Smith       $87   AI-slop on #198 + #203                    │   │
│  │ Greta Lin        $25   AI-slop on #189                           │   │
│  │ Mike Torres      $310  Malicious on #178 (collusion w/ #176 auth)│   │
│  │                                          Total owed: $422        │   │
│  │ [Export to Mercor contributor payment system]                    │   │
│  └──────────────────────────────────────────────────────────────────┘   │
│                                                                          │
│  ┌─Suspended contributors ──────────────────────────────────────────┐   │
│  │ Dave Smith       suspended from [Hunter]      until 2026-09-12   │   │
│  │ Mike Torres      suspended from [all roles]   until 2027-05-15   │   │
│  └──────────────────────────────────────────────────────────────────┘   │
│                                                                          │
│  ┌─Operational signals ─────────────────────────────────────────────┐   │
│  │  Pending findings queue depth:   12 (target: <30)               │   │
│  │  Avg time-to-triage (7d rolling): 38 hours (target: <72h)       │   │
│  │  Senior cosign backlog:           3 (target: <10)               │   │
│  │  Appeals open:                    2 (target: <5)                │   │
│  │  Arbitrations open:               1 (target: <5)                │   │
│  └──────────────────────────────────────────────────────────────────┘   │
│                                                                          │
│  [Audit Log Search →]   [Full Escrow Ledger →]   [Weekly Scan →]       │
│                                                                          │
└──────────────────────────────────────────────────────────────────────────┘

Admin: Audit Log Search

┌──────────────────────────────────────────────────────────────────────────┐
│ Audit Log Search                                       [← Dashboard]     │
├──────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  Filter by:                                                              │
│   Entity: [Finding   ▼]   Entity ID: [#209          ]                    │
│   Date range: [2026-05-20] to [2026-05-27]                              │
│   Actor: [any contributor ▼]   Source: [any ▼]                          │
│                            [Search]                                      │
│                                                                          │
│  Results: 14 entries                                                     │
│  ┌────────┬─────────────────────┬──────────────────┬────────────────┐   │
│  │ Time   │ Field changed       │ Old → New        │ Source / Actor │   │
│  ├────────┼─────────────────────┼──────────────────┼────────────────┤   │
│  │ 5/27   │ created             │ —                │ Auto / promote │   │
│  │ 5/27   │ auto_quality_signal │ — → clearly-ai   │ Auto / prescreen│  │
│  │ 5/27   │ high_risk_queue     │ — → TRUE         │ Auto / formula │   │
│  │ 5/27   │ triage_status       │ Pending → Rejct  │ Triager / Erin │   │
│  │ 5/27   │ badness_tier        │ N/A → AI-slop    │ Triager / Erin │   │
│  │ 5/27   │ senior_triager_...  │ — → Hannah Park  │ Senior / Hannah│   │
│  │ 5/27   │ bond_disposition    │ Pending → Slshd-100│ Auto / bond  │   │
│  │ 5/27   │ clawback_amount     │ — → $25          │ Auto / clawbck │   │
│  │ ...    │ (6 more)            │                  │                │   │
│  └────────┴─────────────────────┴──────────────────┴────────────────┘   │
│                                                                          │
│                          [Export CSV]                                    │
│                                                                          │
└──────────────────────────────────────────────────────────────────────────┘

Interface permissions binding

Interface page Roles with access
Hunter Home Hunter
Hunter: Browse Tasks Hunter
Hunter: Task Detail Hunter
Hunter: My Finding Detail Hunter (own findings only)
Author Home Author
Author: File Appeal Author (own decided findings only)
Triager Workbench Triager, Senior Triager
Triager: Promote Comment Triager, Senior Triager
Senior Triager Workbench Senior Triager
Senior: Cosign Detail Senior Triager
Admin Dashboard Admin
Admin: Audit Log Search Admin, Senior Triager (read-only)

A contributor holding multiple roles (e.g., both Hunter and Triager) sees both Home pages in their navigation, switchable from the top-right account menu.

What's NOT a separate Interface page

  • Onboarding flow — handled outside Airtable (per file 17 §What's NOT)
  • Public stats dashboard — deferred to v2
  • Frontier verifier attestation page — uses the same Triager Workbench pattern; documented in frontier_track_admission/02-human-verification-protocol.md
  • Self-audit script results — runs in the author's local environment; not surfaced in Airtable

Acceptance criteria

  • A test Hunter account loads Hunter Home and sees only their own data; cannot navigate to Author / Triager / Admin pages.
  • The Triager Workbench three-pane layout works for actual triage — triager can read a comment, make a decision, and submit without switching pages.
  • The decision form gates: rejecting with badness_tier ∈ {Flagrant, AI-slop, Malicious} routes the finding to the Senior Cosign queue and does not apply slashes/clawback until cosigned.
  • The Audit Log Search returns all 14 events for a fixture finding in the example.
  • A Hunter with insufficient escrow cannot submit at the tier they want — the Task Detail page disables the higher tiers and shows "insufficient escrow" on the disabled options.
  • An Author cannot see triager_notes or auto_quality_signal on Pending findings (verified by inspecting the Author: Incoming Findings card on Author Home).

§F — Automations

This file is the build spec for every Automation and formula in the v1 Airtable base. Field references use 17-airtable-schema.md names.

Formula catalog

Consolidated here for one-place lookup. Field-by-field these also appear in file 17; the verbatim formula text is below.

Findings table formulas

## bond_amount_locked
SWITCH(
  {claimed_tier},
  "Catastrophic", 50,
  "Critical",     25,
  "Major",        10,
  "Duplicate",    5,
  "Minor",        2,
  "Co-finder",    5,
  0
)

## payout_to_hunter
IF(
  {triage_status} = "Accepted",
  SWITCH(
    {actual_tier},
    "Catastrophic", 200,
    "Critical",     100,
    "Major",        50,
    "Duplicate",    50,
    "Minor",        0,
    "Co-finder",    25,
    0
  ),
  0
)

## deduction_from_author
## (Duplicate: 25 per author; the *other* author is debited via the
## second Finding linked to the same root issue)
IF(
  {triage_status} = "Accepted",
  SWITCH(
    {actual_tier},
    "Catastrophic", 200,
    "Critical",     100,
    "Major",        50,
    "Duplicate",    25,
    "Minor",        0,
    "Co-finder",    25,
    0
  ),
  0
)

## tier_changed_from_claimed
{claimed_tier} != {actual_tier}

## clawback_amount
## Malicious is left BLANK and computed by Automation against
## 90-day earnings rollup; AI-slop is exactly the slashed bond.
IF(
  {badness_tier} = "AI-slop",  {bond_amount_locked},
  IF(
    {badness_tier} = "Malicious", BLANK(),
    0
  )
)

## high_risk_queue
AND(
  {auto_quality_signal} = "clearly-ai",
  {auto_quality_confidence} >= 0.9,
  {fabricated_evidence_likelihood} >= 0.7
)

## appeal_window_ends
DATEADD({triaged_at}, 7, 'days')

Tasks table formulas

## total_deductions_to_author
## (rollup with conditional; field is a Rollup field
##  pointing to linked Findings filtered to Accepted)
SUM(values)  # base
## Then in the rollup conditions: WHERE {triage_status} = "Accepted"
## Apply cap separately via:

## total_deductions_capped (NEW formula field)
MIN({total_deductions_to_author}, 200)

## remaining_author_at_risk
250 - {total_deductions_capped}

Contributors table formulas

## escrow_balance (Rollup from Escrow Ledger)
## Source: Escrow Ledger rows where status IN ("Locked", "Pending release")
## Aggregation: SUM({amount})

## escrow_locked (Rollup from Findings)
## Source: Findings where triage_status = "Pending" AND hunter = self
## Aggregation: SUM({bond_amount_locked})
## Plus disputer bond from Arbitrations where final_outcome IS NULL:
##   add a second rollup `dispute_bonds_locked` from Arbitrations
##   summing {dispute_bond} where {final_outcome} = "Pending"
## escrow_locked is then a formula: {finding_bonds_locked} + {dispute_bonds_locked}

## escrow_available
{escrow_balance} - {escrow_locked}

## accept_rate
IF({submissions_filed} > 0, {submissions_accepted} / {submissions_filed}, BLANK())

## dominant_hunter_pct (Author-side collusion signal)
## Computed via a separate scheduled Automation (file below); not pure formula.
## Falls back to a cached number field.

## tasks_hit_deduction_cap (Author-side)
## Rollup from linked tasks_authored:
##   COUNTIF(values, {total_deductions_capped} = 200)

Arbitrations table formulas

## bond_disposition_dispute
SWITCH(
  {final_outcome},
  "UPHOLD",            "Slashed",
  "OVERTURN",          "Returned",
  "PARTIAL_OVERTURN",  "Returned",
  "UNCERTAIN",         BLANK(),     # determined post-panel
  BLANK()
)

## frivolous_dispute_quality_delta
IF({final_outcome} = "UPHOLD", -0.05, 0)

## overturn_bonus_paid
IF({final_outcome} = "OVERTURN", 25, 0)

Escrow Ledger table formulas

## release_eligible_at
DATEADD({entered_escrow_at}, 90, 'days')

## status
IF({slashed_at}, "Slashed",
  IF({released_at}, "Released",
    IF({release_eligible_at} <= TODAY(), "Pending release",
      "Locked"
    )
  )
)

Quality-score delta table

The exact deltas applied by Automations. Hunter side:

Trigger Delta
Finding accepted at Catastrophic +0.10
Finding accepted at Critical +0.05
Finding accepted at Major +0.025
Finding accepted at Duplicate +0.025
Finding accepted at Minor +0.01
Finding accepted at Co-finder +0.0125 (half of Major)
Tier inflation (accepted at lower than claimed) −0.05 (in addition to the positive accrual for the accepted tier)
Rejected — Honest mistake −0.025
Rejected — Error −0.05
Rejected — Flagrant −0.10
Rejected — AI-slop −0.20
Rejected — Malicious −0.50
Appeal Overturned against your finding (you're the hunter, finding gets reversed) −0.075
Arbitration UPHOLD (your dispute was frivolous) −0.05
Arbitrator overturn bonus +0.025 (positive: a valid dispute was vindicated)

Triager side (triager_quality_metric):

Trigger Delta
Arbitrator OVERTURN against your rejection −1.0
Arbitrator PARTIAL_OVERTURN against your rejection −0.5
Appeal Overturned against your acceptance −0.5
Appeal Partial against your acceptance −0.25
Senior Triager rejects your badness_tier classification entirely on cosign review −0.5
30 consecutive accepted/upheld decisions +0.1 (slow recovery)

Automations inventory

A1 — Finding created (post-promotion)

  • Trigger: Record created in Findings
  • Conditions: none
  • Actions:
    1. Run script: lock bond. Sum hunter's current escrow_balance via Escrow Ledger rollup. If escrow_available < bond_amount_locked → delete the row + post error comment to source comment URL: "Insufficient escrow for claimed tier — submit at lower tier or wait for escrow release." Halt.
    2. Run script (Haiku call): AI-slop pre-screen
      • Input: comment text + reproduction_steps + evidence_url contents
      • Prompt: rubrics/bounty-ai-slop-prescreen.toml
      • Model: claude-haiku-4-5
      • Write: auto_quality_signal, auto_quality_confidence, templated_prose_score, fabricated_evidence_likelihood
      • Cost: ~$0.005/call
    3. Audit Log entry: change_source = "Automation", automation_name = "A1-finding-created", field_changed = "created"

A2 — Triage decision submitted (status leaves Pending)

  • Trigger: Findings.triage_status changed from "Pending" to anything else
  • Conditions: none
  • Actions:
    1. Set triaged_at = NOW(), triager = currentUser()
    2. Conditional branch: if triage_status = "Accepted":
      • Apply hunter quality_score delta per actual_tier (from table above)
      • If tier_changed_from_claimed = TRUE: additional −0.05 delta
      • Set bond_disposition = "Returned". Create Escrow Ledger row: contributor=hunter, amount=bond_amount_locked, source="Bond-returned", source_finding=this.
      • Create Escrow Ledger row: contributor=hunter, amount=ROUND(payout_to_hunter * 0.20), source="Payout-holdback", source_finding=this. (20% holdback into escrow.)
      • Trigger external payout (Mercor payment system webhook): pay hunter payout_to_hunter * 0.80 immediately.
      • Trigger author deduction: add deduction_from_author to author's clawback_debt OR offset against their next scheduled payment (logic external to Airtable).
    3. Conditional branch: if triage_status = "Rejected":
      • Require badness_tier != "N/A" (validation: if N/A, halt + flag triager)
      • If badness_tier IN ("Flagrant", "AI-slop", "Malicious"): require senior_triager_cosign != NULL. If null, halt without applying slash/clawback; route to Senior Cosign Queue (file 19). The slash/clawback applies under A3 below when cosign lands.
      • If badness_tier IN ("Honest-mistake", "Error"): apply delta from quality-score table; set bond_disposition = "Returned". Create Escrow Ledger row: bond returned (same as Accepted branch).
    4. Conditional branch: if triage_status IN ("Duplicate-of", "Needs-more-evidence"):
      • Bond returned (no slash); no quality delta. Treats as informational.
    5. Audit Log entry for every field change.

A3 — Senior triager cosign applied

  • Trigger: Findings.senior_triager_cosign changed from NULL to a contributor link
  • Conditions: badness_tier IN ("Flagrant", "AI-slop", "Malicious")
  • Actions:
    1. Apply hunter quality_score delta per badness_tier
    2. Slash:
      • Flagrant: slash 50% of bond_amount_locked
      • AI-slop or Malicious: slash 100%
      • Slash means: walk Escrow Ledger rows for hunter ordered by entered_escrow_at desc (newest first — least time-vested); for each, set slashed_at = NOW(), slash_finding = this, slash_amount = MIN(remaining_to_slash, row.amount) until remaining_to_slash = 0.
    3. Clawback debt:
      • AI-slop: compute past-30d earnings (rollup over hunter's accepted findings where triaged_at >= TODAY()-30); set hunter.clawback_debt += MIN(bond_amount_locked, past_30d_earnings)
      • Malicious: compute past-90d earnings; set hunter.clawback_debt += past_90d_earnings (uncapped). Set hunter.suspended_from = ["Hunter", "Author", "Triager", "Senior Triager", "Frontier Verifier", "Appeal Reviewer"], hunter.suspension_until = TODAY() + 365
    4. AI-slop warnings increment: for AI-slop only, hunter.ai_slop_warnings += 1
    5. Audit Log: detailed entries for every change

A4 — Appeal created

  • Trigger: Record created in Appeals
  • Conditions: none
  • Actions:
    1. Validate: appellant = finding.target_task.author (halt + delete if mismatched)
    2. Validate: finding.triage_status = "Accepted" and TODAY() <= finding.appeal_window_ends (halt if outside window)
    3. Validate: appeal_reviewer != finding.triager (if equal, halt + prompt for different reviewer)
    4. Audit Log: record creation

A5 — Appeal outcome decided

  • Trigger: Appeals.outcome changed from "Pending" to anything else
  • Actions:
    1. Set decided_at = NOW()
    2. Branch: if outcome = "Overturned":
      • Reverse the finding: set finding.triage_status = "Rejected", finding.badness_tier = "Error" (default for reversed acceptances), finding.actual_tier = BLANK().
      • Reverse hunter accrual: subtract the original accept delta from hunter.quality_score; add −0.075 (Appeal overturned penalty).
      • Slash hunter escrow by the original payout amount (clawback the payout) — same walking-ledger logic as A3.
      • Refund author: subtract original deduction_from_author from author.clawback_debt (or credit next payment).
      • Triager quality delta: triager.triager_quality_metric −= 0.5
    3. Branch: if outcome = "Partial":
      • Revise finding.actual_tier downward (Senior Triager fills in the revised tier in outcome_notes; Automation parses or it's set manually on the Finding record).
      • Recompute payout / deduction at new tier.
      • Net out the difference: pay back the diff from hunter, credit author the diff.
      • Triager quality delta: −0.25
    4. Branch: if outcome = "Upheld": no changes; the decision stands.
    5. Audit Log: all changes

A6 — Arbitration created (hunter dispute)

  • Trigger: Record created in Arbitrations
  • Actions:
    1. Validate: disputer = finding.hunter, finding.triage_status = "Rejected", TODAY() <= finding.appeal_window_ends (yes, same window — file 16)
    2. Validate: disputer.escrow_available >= 20 (halt + post error if insufficient)
    3. Lock dispute_bond: set dispute_bond = 20 (already defaulted)
    4. Run script: arbitrator call
      • Build payload: finding payload, triager's reason, hunter's dispute text, task snapshot
      • Hash with SHA-256 → input_payload_sha256
      • Call Claude API: model claude-opus-4-7, prompt from rubrics/bounty-arbitration.toml, prompt_version_sha256 set from prompt file
      • Parse output → output_decision, output_rationale, output_revised_badness_tier
      • Record arbitration_cost_usd from API response
      • Run within 24 hours of trigger (Airtable Automations limit: schedule via cron job if synchronous call would exceed Automation timeout)
    5. Audit Log entry

A7 — Arbitration decision applied

  • Trigger: Arbitrations.output_decision changed from NULL to a value
  • Conditions: none
  • Actions:
    1. Branch: output_decision = "UPHOLD":
      • Set final_outcome = "UPHOLD"
      • Slash dispute_bond from disputer escrow (same walking-ledger logic; $20)
      • Apply disputer.quality_score −= 0.05 (frivolous_dispute_quality_delta)
    2. Branch: output_decision = "OVERTURN":
      • Set final_outcome = "OVERTURN"
      • Return dispute_bond (no Escrow Ledger change — bond was held, not slashed)
      • Reverse finding: set triage_status = "Accepted", badness_tier = "N/A", actual_tier = claimed_tier. Trigger A2 follow-on paths (payout, holdback escrow, etc.) — Airtable nested-automation pattern
      • Pay disputer overturn bonus $25 from slashed-bond pool (Mercor ops): create Escrow Ledger row, source="Arbitrator-overturn-bonus", amount=25
      • Disputer quality_score += 0.025
      • Triager metric: finding.triager.triager_quality_metric −= 1.0
    3. Branch: output_decision = "PARTIAL_OVERTURN":
      • Set final_outcome = "PARTIAL_OVERTURN"
      • Return dispute_bond
      • Revise finding.badness_tier to output_revised_badness_tier
      • Re-apply A3 with the new (lower) badness_tier
      • Triager metric: −0.5
    4. Branch: output_decision = "UNCERTAIN":
      • Set final_outcome = "Pending" (will be set by A8 when senior panel decides)
      • Notify Senior Triagers via Airtable Slack/email integration
    5. Audit Log

A8 — Senior panel resolves UNCERTAIN arbitration

  • Trigger: Arbitrations.final_outcome changed from "Pending" (after output_decision = "UNCERTAIN")
  • Actions: same as A7 branches based on final_outcome; senior panel's decision overrides arbitrator's UNCERTAIN

A9 — Daily escrow release sweep

  • Trigger: Scheduled (every day at 00:00 UTC)

  • Actions: for each Escrow Ledger row where:

    • release_eligible_at <= TODAY()
    • released_at IS NULL
    • slashed_at IS NULL
    • AND contributor has no Findings with triage_status = "Pending" (block release while a decision is in flight)
    • AND contributor has no Arbitrations with final_outcome = "Pending"

    Set released_at = TODAY(). Trigger Mercor payment webhook for {amount}. Audit Log entry.

A10 — Daily quality-watch sweep

  • Trigger: Scheduled (daily, 01:00 UTC)
  • Actions:
    1. For each Contributor with "Triager" in roles_active:
      • If triager_quality_metric < 0.5 AND "Triager" NOT IN suspended_from: set suspended_from += "Triager", suspension_until = TODAY() + 30. Notify Senior Triagers.
    2. For each Contributor with "Hunter" in roles_active:
      • If quality_score < 0.5 AND "Hunter" NOT IN suspended_from: set suspended_from += "Hunter", suspension_until = TODAY() + 180. Notify hunter.
      • If quality_score >= 0.5 AND "Hunter" IN suspended_from AND suspension_until <= TODAY(): remove "Hunter" from suspended_from. Notify.
    3. For each Contributor with "Author" in roles_active:
      • Compute dominant_hunter_pct: of accepted Findings where target_task.author = this contributor, the % from a single hunter (across all hunters that have submitted ≥1 accepted finding against this author). Write to cached field.
      • If dominant_hunter_pct >= 0.40: surface on Senior: Author Collusion Watch.
    4. Audit Log entry for each suspension action.

A11 — Notification dispatch

Triggered by the relevant state changes. Each notification sends an email (Airtable Email Notification action) and optionally a Slack DM (if Slack workspace integration is configured).

Event Notify Message
Findings.triage_status → "Accepted" hunter "Your finding #X was accepted at . $ paid (less 20% escrow holdback)."
Findings.triage_status → "Rejected" hunter "Your finding #X was rejected (<badness_tier>). . You have 7 days to dispute."
Findings.triage_status → "Accepted" author "A finding against your task was accepted at . $ deducted. You have 7 days to appeal."
Findings.senior_triager_cosign NULL → set hunter "Your finding #X has been classified as <badness_tier> by senior review. <slash/clawback summary>."
Findings.high_risk_queue → TRUE hunter "Your finding #X was routed to enhanced review (AI-content signals). Triage may take longer."
Appeals.outcome → "Overturned" hunter, author "Appeal on finding #X was overturned.
Details."
Arbitrations.final_outcome → set disputer "Arbitration decision on your dispute: .
Details."
Escrow Ledger row.released_at set contributor "$ from escrow released to your next payment."
Contributors.suspended_from changed contributor "Your access to role is suspended until . Reason: ."

A12 — Audit Log writer (meta-automation)

This is implemented as part of every other Automation rather than a standalone one — every action in A1–A11 ends with an Audit Log write. The standalone presence here is a reminder that the Audit Log is non-optional.

For changes that happen outside Automations (e.g., a Triager manually edits a field), Airtable's per-field "On record updated" trigger fires an Audit Log write. The trigger condition is a field-by-field list of "watched" fields per table (the operational fields, not formula fields).

Notification permissions

The notifications in A11 respect the per-row visibility filters from 18 §Permissions binding:

  • Hunters get notifications about their own findings only
  • Authors get notifications about findings against their own tasks only
  • Senior Triagers get notifications about cosign requests, UNCERTAIN arbitrations, and quality-watch alerts
  • Admin gets a daily digest of all financial events

A notification never contains data the recipient couldn't see by logging in to the Airtable Interface.

External integrations

The Automations call out to external systems at three points. These are not Airtable-native; they need glue scripts:

  1. Mercor payment webhook (A2, A5, A7, A9) — when payouts / clawback / overturn bonuses fire, push a row to the Mercor contributor payment system's existing intake.
  2. Claude API (A1 pre-screen, A6 arbitrator) — call from an Airtable Run Script action. Cost-tracked in arbitration_cost_usd field (arbitrator) or in a separate Admin monthly rollup (pre-screen).
  3. Slack notifications (A11, optional) — Airtable Slack integration if the Mercor workspace has it configured.

Estimated total external call volume in steady state:

  • 200 pre-screens/month × $0.005 = $1
  • 30 arbitrator calls/month × $0.50 = $15
  • ~250 webhook payment events/month = free
  • ~500 notification dispatches/month = free

Total monthly ops cost from Automations: ~$16. Well within the $50 budget from file 16.

Test fixtures

Each Automation should have a paired fixture in docs/plans/bounty_economics_v1/automation_fixtures/ (this directory to be created when the automations are implemented). Each fixture is a JSON payload representing the trigger event and the expected post-state.

Minimum coverage:

  • A1: insufficient-escrow rejection; successful pre-screen
  • A2: Accept happy path; Reject Honest-mistake; Reject Flagrant (routes to A3)
  • A3: Flagrant cosign applies 50% slash; AI-slop cosign applies 100% slash + clawback; Malicious applies suspension
  • A4: invalid appellant rejected; valid appeal accepted
  • A5: Overturned reverses payouts; Partial revises tier
  • A6: insufficient escrow blocks dispute; valid dispute records hash
  • A7: UPHOLD slashes bond; OVERTURN reverses + pays bonus; PARTIAL revises badness
  • A9: release skipped when pending findings exist; release proceeds when clear
  • A10: triager auto-suspension at < 0.5; hunter auto-restore at ≥ 0.5 after suspension expires

Acceptance criteria

  • Every Automation in A1–A11 implemented in the Airtable base.
  • Every formula in the catalog parses and evaluates correctly on test rows.
  • The 9 minimum-coverage fixtures pass end-to-end.
  • Total monthly external-call cost stays under $50 in normal volume.
  • Audit Log captures every state change documented in the per- Automation actions; no Automation skips its Audit Log write.
  • Insufficient escrow on a hunter submission produces a clear error comment on the source Task row within 60 seconds of the comment being promoted.
  • A pre-screen-flagged AI-slop submission is auto-routed to the High-Risk Queue before any Triager sees it in the regular Pending Queue.
  • A triager with triager_quality_metric driven below 0.5 by arbitrator overturns is auto-suspended within 24 hours of crossing the threshold.

§G — Acceptance Criteria + Handoff

When you're done, all of the following must work end to end:

  1. Schema validates — every field, type, formula, rollup, link, and permission matches §C exactly.
  2. Views render — each of the 18 views from §D exists with the right filter/sort/group/columns.
  3. Interfaces work — the 10 pages from §E exist; per-role permissions gate correctly.
  4. Automations fire — A1–A12 plus the two new webhook receivers (A-admit + A-window-close) all trigger on synthetic events.
  5. Webhook end-to-end — synthetic event=admit POSTed to the webhook URL creates a Tasks row + books author fee. Subsequent event=window_close flips status to Closed and reconciles. Idempotency works on duplicate POSTs.
  6. Smoke test on a real admission — coordinate with the repo owner to run admission against tasks/hello-codebase-qa; confirm the webhook arrives, Tasks row appears, bounty window opens.

Out of scope

  • The GitHub-side POSTing code (scripts/post_admission_webhook.py + .github/workflows/window_close.yml) — already shipped in the source repo. You just receive what it sends.
  • Mercor finance integration internals — append to the existing intake; don't build it.
  • Mercor staff onboarding flow — Airtable Auth handles it.

Reply contract

After each major phase, reply with: ✅ done | ⏳ in progress | ❌ blocker + Airtable error. If a step is impossible (Airtable limitation), document and propose a workaround inline; don't skip silently.


Source provenance (reference only — files are in a private repo)

Originating files in github.com/Kurry/codebase-atlas-samples (private):

  • docs/plans/bounty_economics_v1/17-airtable-schema.md → §C
  • docs/plans/bounty_economics_v1/18-airtable-views.md → §D
  • docs/plans/bounty_economics_v1/19-airtable-interfaces.md → §E
  • docs/plans/bounty_economics_v1/20-airtable-automations.md → §F
  • docs/plans/admission_gate.md → sender side of the webhooks
  • docs/plans/airtable_build_prompt.md → repo-internal version of this handoff (with cross-refs instead of inlined content)

This standalone file is the agent-readable artifact; regenerate from sources whenever a spec changes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment