Context. The base "Codebase Atlas Q&A" (appOrIGUAHo92K70t) is live but heavily underbuilt vs. the spec. The initial Omni build created the 7 tables and basic linking, but missed:
- ~70 spec fields across the 7 tables (the webhook integration cannot work without these specific field names + types)
- 11 of the 18 spec views (only default Grid views exist)
- All 10 Interface Designer pages
- All 14 Automations (the 12 internal A1–A12 plus the 2 webhook receivers — without these, the GitHub admission gate has no way to actually update the base)
This file is the focused follow-up handoff: give Omni this prompt + the original standalone build prompt (gist URL below) and it can finish the job. Work top-to-bottom; each section is independently shippable.
Gist URL of the full self-contained spec (Omni should fetch this first): https://gist.github.com/Kurry/256c286e60a846ff8c576e6ac6275e19
You have already built the base "Codebase Atlas Q&A" at appOrIGUAHo92K70t. The 7 tables exist with basic linking, but the field schemas don't match the spec, and views/interfaces/automations are mostly missing. Your job now is to reconcile the live base to the spec without losing data.
Read first:
- Fetch the full spec from: https://gist.github.com/Kurry/256c286e60a846ff8c576e6ac6275e19
- Read §C (Schema), §D (Views), §E (Interfaces), §F (Automations), §B (Webhook Receivers) end to end before touching anything in Airtable.
Operating principle: rename/add fields rather than rebuilding tables. The tables already have IDs that the linking relationships depend on. Dropping a table cascades destruction; instead, surgically rename mismatched fields and add missing ones.
Status: 67 of 72 missing spec fields added via scripts/airtable/reconcile_schema.py against the live base. 5 createdTime fields could not be added (Airtable API limitation: UNSUPPORTED_FIELD_TYPE_FOR_CREATE), but each table already has an equivalent existing createdTime field that the webhook receivers can use instead:
| Spec name | Use this existing field on the live base |
|---|---|
Findings.submitted_at |
Findings.Date Submitted |
Appeals.filed_at |
Appeals.Date Filed |
Arbitrations.triggered_at |
Arbitrations.Date Started |
Escrow Ledger.entered_escrow_at |
Escrow Ledger.Date Created |
Audit Log.recorded_at |
Audit Log.Event Timestamp |
You do NOT need to do schema reconciliation manually. Field count went from 82 → 148 across the 7 tables. The remaining UI-only tasks for you are:
-
Drop unused legacy fields (UI only — API can't delete fields):
Tasks.Task Type,Tasks.Bounty Amount,Tasks.Assigned Hunters/Triager/Solver,Tasks._api_test_field,Contributors.Payment History Notes,Findings.Is Appealed?,Findings.Supporting Evidence (Attachment). Hide rather than delete if uncertain. -
Rename existing legacy fields that overlap spec semantics (UI only — already-existing fields can be renamed in UI but the script avoided destructive renames):
Tasks.Task Name→ keep as primary, can rename toslugin UI if you prefer (or keep both —slugwas added as a new field)Contributors.Contributor Name→ rename to display labelFindings.Hunter (Contributor)→ rename tohunter; same forTriager (Contributor)→triagerAppeals.Linked Finding→finding;Appellant (Contributor)→appellant
-
Add rollup/formula fields that need linked-field IDs (the script skipped these because rollup setup needs cross-table field IDs that only exist after the link target exists). Build these in UI per §C of the original spec:
Tasks.total_deductions_to_author(rollup from Findings)Tasks.total_deductions_capped(formula)Tasks.remaining_author_at_risk(formula)Contributors.escrow_balance,escrow_locked,escrow_available,lifetime_payouts,lifetime_slashed,submissions_filed,submissions_accepted,accept_rate(mix of rollups + formulas + counts)Findings.tier_changed_from_claimed,bond_amount_locked,payout_to_hunter,deduction_from_author,clawback_amount,high_risk_queue,appeal_window_ends(formulas)Appeals.appeal_window_ends(formula)Arbitrations.bond_disposition_dispute,frivolous_dispute_quality_delta,overturn_bonus_paid(formulas)Escrow Ledger.release_eligible_at,status(formulas)
-
(Original Phase 1 content retained below for reference — but most of it is done.)
The webhook receivers (§B) POST payloads with specific field names. The live schema uses different names. Reconcile each table per below. Where the table already has a roughly-equivalent field, rename it; otherwise add a new field.
Rename:
Task Name→slug(keep as primary; rename only)Status→bounty_status(single select, options:Open,Closed— drop any other options)Bounty Window End→shipped_at_plus_7d(formula:DATEADD({shipped_at}, 7, 'days'))
Add these fields (none exist in the live schema; all are required by the admit webhook):
task_id— single line text (the T-prefixed Mercor ID, e.g.T002)author— already exists via implicitAssigned Hunters? NO — add a new linked fieldauthor→ Contributors (single record link)tier— single select, options:Easy,Medium,Hard,Referenceauthor_fee_usd— currency (USD)p_solve_ensemble— number (precision 2 decimal places)cross_family_passes— checkboxstage3_required— checkboxharbor_config_sha256— single line textshipped_at— date-time (UTC)admission_json_url— URLfinal_tier— single select, options:Easy,Medium,Hard,Reference(set by window_close webhook)final_author_fee_usd— currencystage3_outcome— single select, options:cleared,dropped_one_tier,defaulted_medium,defaulted_easy,rejected_cheat,not_applicableclosed_at— date-timetotal_deductions_to_author— rollup from linked Findings wheretriage_status = "Accepted", sum ofdeduction_from_authortotal_deductions_capped— formula:MIN({total_deductions_to_author}, 200)remaining_author_at_risk— formula:{author_fee_usd} - {total_deductions_capped}
Drop or hide (irrelevant per spec):
Task Type,Bounty Amount(redundant withtier+author_fee_usd),Assigned Hunters,Assigned Triager,Assigned Solver,Total Payout Amount,Linked Escrow Ledger Entries(move to lookup if needed),GitHub Issue URL(redundant withadmission_json_url)
Rename:
Contributor Name→display_nameRole(s)→roles_active(multi-select, options:Hunter,Author,Triager,Senior Triager,Frontier Verifier,Appeal Reviewer)GitHub Username→github_handle
Add:
contributor_id— autonumber (primary; demotedisplay_namefrom primary)quality_score— number (precision 3 decimal places, default 1.0)quality_score_history— long text (append-only event log; or linked to Audit Log entries via filtered view)escrow_balance— rollup from linked Escrow Ledger entries wherestatus IN ("Locked","Pending release"), sum ofamountescrow_locked— rollup from linked Findings wheretriage_status = "Pending"andhunter = self, sum ofbond_amount_lockedescrow_available— formula:{escrow_balance} - {escrow_locked}lifetime_payouts— rollup from accepted Findings, sum ofpayout_to_hunterlifetime_slashed— rollup from Findings, slashed amountsclawback_debt— currency, default 0ai_slop_warnings— number, default 0submissions_filed— count of linked Findings (any status)submissions_accepted— count wheretriage_status = "Accepted"accept_rate— formula:IF({submissions_filed} > 0, {submissions_accepted} / {submissions_filed}, BLANK())suspended_from— multi-select (role names; default empty)suspension_until— datetriager_quality_metric— number (default 1.0; only populated for Triager role)
Drop:
Payment History Notes(replaced by structured fields above)
Rename:
Task→target_task(keep linked field; rename only)Hunter (Contributor)→hunterTriager (Contributor)→triagerFinding Description(keep — this is the comment text)Severity Assessment→claimed_tier(single select:Catastrophic,Critical,Major,Minor,Duplicate,Co-finder)Triage Grade→triage_status(single select:Pending,Accepted,Rejected,Duplicate-of,Needs-more-evidence; defaultPending)Quality Score→quality_score_delta(number; the delta applied to the hunter on triage decision)
Add:
source_comment_url— URL (Airtable deep-link to the original comment)submitted_at— created timefailure_category— single select (options:Foil-pass,Contamination,Judge-prompt-leak,Likert-rescue,Calibration-instability,Tooling,Doc-fix,Ambiguity,Duplicate,Other)evidence_url— URL (gist / commit / trial output)reproduction_steps— long texttriaged_at— date-time (set by automation when status leaves Pending)actual_tier— single select (same options asclaimed_tier)tier_changed_from_claimed— formula:{claimed_tier} != {actual_tier}bond_amount_locked— formula:SWITCH({claimed_tier}, "Catastrophic", 50, "Critical", 25, "Major", 10, "Duplicate", 5, "Minor", 2, "Co-finder", 5)bond_disposition— single select (Pending,Returned,Slashed-50,Slashed-100; defaultPending)badness_tier— single select (N/A,Honest-mistake,Error,Flagrant,AI-slop,Malicious; defaultN/A)senior_triager_cosign— linked to Contributors (required whenbadness_tier IN ("Flagrant","AI-slop","Malicious"))payout_to_hunter— formula:IF({triage_status} = "Accepted", SWITCH({actual_tier}, "Catastrophic", 200, "Critical", 100, "Major", 50, "Duplicate", 50, "Minor", 25, "Co-finder", 25, 0), 0)deduction_from_author— formula:IF({triage_status} = "Accepted", SWITCH({actual_tier}, "Catastrophic", 200, "Critical", 100, "Major", 50, "Duplicate", 25, "Minor", 25, "Co-finder", 25, 0), 0)clawback_amount— formula:IF({badness_tier} = "AI-slop", {bond_amount_locked}, IF({badness_tier} = "Malicious", BLANK(), 0))auto_quality_signal— single select (clearly-ai,unclear,clearly-human)auto_quality_confidence— number 0–1templated_prose_score— number 0–1fabricated_evidence_likelihood— number 0–1high_risk_queue— formula:AND({auto_quality_signal} = "clearly-ai", {auto_quality_confidence} >= 0.9, {fabricated_evidence_likelihood} >= 0.7)bad_actor_signals— multi-selectgrace_applied— multi-selecttriager_notes— long textappeal_window_ends— formula:DATEADD({triaged_at}, 7, 'days')
Drop:
Is Appealed?(redundant — derive fromAppeal Recordlink being non-empty)Supporting Evidence (Attachment)(replace with theevidence_urlfield — Airtable attachments are not the auth flow we want)
Rename:
Appeal Status→outcome(single select:Pending,Upheld,Overturned,Partial)Linked Finding→findingAppellant (Contributor)→appellantAppeal Reason→appeal_basisArbitrator Involved→appeal_reviewer(this is the senior triager who reviews the appeal — NOT the LLM arbitrator)Resolution Notes→outcome_notesDate Filed→filed_at(created time)
Add:
decided_at— date-timeappeal_window_ends— formula:DATEADD({filed_at}, 7, 'days')
Drop:
Linked Arbitration(redundant — arbitrations link to Appeals, not the other way)
Rename:
Linked Appeal→ drop and re-link from Findings (arbitrations are filed by hunters against rejected findings, NOT appeals)Arbitrator (Contributor)→ drop (arbitrator is an LLM, not a contributor)Arbitration Status→ drop; replaced byoutput_decision+final_outcomeDecision/Ruling→output_rationale
Add (a lot — this is the LLM arbitrator integration):
arbitration_id— autonumber (primary; demote anything else)finding— linked to Findings (single record)disputer— linked to Contributors (must = finding's hunter; enforce via automation)triggered_at— created timehunter_dispute_text— long textdispute_bond— currency, default 20.00model_id— single line text (the pinned Claude model id)prompt_version_sha256— single line textinput_payload_sha256— single line textoutput_decision— single select (UPHOLD,OVERTURN,PARTIAL_OVERTURN,UNCERTAIN,Pending)output_revised_badness_tier— single select (Honest-mistake,Error,Flagrant,AI-slop,Malicious)arbitration_cost_usd— currencysenior_triager_panel— multi-link to Contributors (only set on UNCERTAIN escalation)final_outcome— single select (mirrorsoutput_decisionafter panel resolution)bond_disposition_dispute— formula:SWITCH({final_outcome}, "UPHOLD", "Slashed", "OVERTURN", "Returned", "PARTIAL_OVERTURN", "Returned", BLANK())frivolous_dispute_quality_delta— formula:IF({final_outcome} = "UPHOLD", -0.05, 0)overturn_bonus_paid— formula:IF({final_outcome} = "OVERTURN", 25, 0)
Rename:
Entry ID→ledger_id(autonumber primary)Linked Task→ drop (escrow is per-contributor, not per-task; remove the link)Contributor (Payee)→contributorTransaction Type→source(single select:Starter-grant,Payout-holdback,Bond-returned,Arbitrator-overturn-bonus)Amount→amountEscrow Status→ drop; replaced by formulastatusbelowSettlement Date→ drop; replaced byreleased_atbelowDate Created→entered_escrow_at
Add:
source_finding— linked to Findings (set whensource = Payout-holdbackorBond-returned)source_arbitration— linked to Arbitrations (set whensource = Arbitrator-overturn-bonus)release_eligible_at— formula:DATEADD({entered_escrow_at}, 90, 'days')(the 90-day holdback rule)released_at— dateslashed_at— dateslash_finding— linked to Findings (the finding that caused the slash)slash_amount— currencystatus— formula:IF({slashed_at}, "Slashed", IF({released_at}, "Released", IF({release_eligible_at} <= TODAY(), "Pending release", "Locked")))
Rename:
Event ID→audit_id(autonumber primary)Event Timestamp→recorded_at(created time)Event Type→entity_type(single select:Finding,Contributor,Arbitration,Escrow Ledger,Task,Appeal)Description→notes(free-form; should be auto-populated for state changes)Related Task→related_task(keep linked; rename)Related Contributor→actorRelated Finding→related_findingRelated Appeal→related_appealRelated Arbitration→related_arbitrationRelated Escrow Ledger Entry→related_escrow_ledger
Add:
entity_id— single line text (the affected row's primary key)field_changed— single line text (name of the field that changed)old_value— long text (stringified prior value)new_value— long text (stringified new value)change_source— single select (Triager-action,Automation,Senior-override,Admin-correction,Webhook-receiver)automation_name— single line text (e.g.,A-admit,A2-triage-decision)
Apply the per-role R/W permissions per §C of the spec. The roles are H Hunter, A Author, T Triager, ST Senior Triager, Adm Admin. The matrix is documented field-by-field in §C. Key points:
- Audit Log is Editor-locked: only Owner can modify existing rows; only Automations create new ones
- Hunter sees their own Findings + Contributor record + Escrow Ledger entries only (filter via Airtable's "Limited record permissions")
- Author sees their own Tasks + Findings against their tasks only
- Triager / Senior Triager see everything except restricted fields on Contributors
Phase 3 — Webhook receivers (highest functional priority — without these, the live base is decorative)
Build the two webhook-receiver Automations per §B of the spec. Single incoming webhook URL handles both event kinds; branch on payload.event. Both reply 200 OK on success (so GitHub doesn't retry) or 5xx on failure (so GitHub retries 3x with exponential backoff).
After building, get the webhook URL (Airtable will give you one when you create an "Incoming webhook" trigger) and report it back. The user will configure it as a GitHub Actions secret named AIRTABLE_ADMISSION_WEBHOOK_URL.
Build each Automation in §F in order. They all use the same formula catalog at the top of §F. Per-event quality-score deltas come from the tables in §F (hunter side + triager side).
Key Automations that must work before any real bounty flow can run:
- A1 (Finding created → AI-slop pre-screen + bond lock)
- A2 (Triage decision → branch on accept/reject; route to A3 cosign for severe rejections)
- A3 (Senior cosign → apply slash + clawback + suspension)
- A6 (Arbitration created → LLM arbitrator call + dispute bond lock)
- A7 (Arbitration decision applied → outcomes per UPHOLD/OVERTURN/PARTIAL)
- A9 (Daily escrow release sweep, scheduled)
- A10 (Daily quality-watch sweep, scheduled)
- A11 (Notifications)
- A12 (Audit log writer — runs as part of every other automation)
Build the 18 per-role views in §D. Each view has a documented filter / sort / group / visible-field list. Replace the default Grid views with these named views; the Grid views can stay as fallbacks.
Build the 10 Interface Designer pages in §E. ASCII mockups in §E are the layout reference. Permissions bind per page (a Hunter Interface user cannot see the Triager Workbench). Use the views from Phase 5 as the data sources for each page.
- Synthetic admit POST. Use
curlto POST a syntheticevent=admitpayload to the webhook URL. Verify aTasksrow appears withslug=test-task-1,tier=Easy,bounty_status=Open, all other fields populated. Verify Audit Log entry written. - Synthetic window_close POST. POST
event=window_closefor the sametask_slug. Verify the row flips toClosedwithfinal_tier,final_author_fee_usd,stage3_outcomepopulated. Verify Audit Log entry. - Idempotency. Re-POST the admit event with the same
(task_slug, admitted_at). Verify 200 OK + no duplicate row. - Real admission. Coordinate with the GitHub repo owner to merge a PR touching
tasks/hello-codebase-qa. Verify the workflow's webhook fires and the Tasks row appears.
After each Phase, reply with:
- ✅ Phase N: done. Field/view/automation count delta. Any spec drift you had to make (e.g., Airtable doesn't support a formula type you needed) documented inline.
- ⏳ Phase N: in progress. Current sub-step + ETA.
- ❌ Phase N: blocked. Specific error or Airtable limitation. Proposed workaround.
After Phase 7, return:
- The webhook URL (so the user can configure the GitHub secret)
- A list of any spec drift items
- A list of any Airtable-side gotchas worth documenting for next time
- This file:
docs/plans/airtable_followup_prompt.md - The original full prompt (gist URL above) is what Omni reads alongside this follow-up
- After Omni reports completion, run
gh api ... /v0/meta/bases/appOrIGUAHo92K70t/tablesto re-audit and check field-count deltas - If field-name drift remains (e.g., Omni used
Tierinstead oftier),scripts/post_admission_webhook.pywill need a mapping layer OR the field names need correction — the spec assumes snake_case lowercase field names because the JSON payloads use that convention