Skip to content

Instantly share code, notes, and snippets.

@Kurry
Last active May 28, 2026 02:32
Show Gist options
  • Select an option

  • Save Kurry/21e6c952ee00e3e1298f3dd10d002955 to your computer and use it in GitHub Desktop.

Select an option

Save Kurry/21e6c952ee00e3e1298f3dd10d002955 to your computer and use it in GitHub Desktop.
Codebase Atlas Airtable — follow-up reconciliation prompt for Omni

Airtable Follow-Up Prompt — Reconcile Live Base to Spec

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


The Follow-Up Prompt (copy from here down)

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:

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.


✅ Phase 1 — Schema reconciliation (DONE via API)

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:

  1. 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.

  2. 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 to slug in UI if you prefer (or keep both — slug was added as a new field)
    • Contributors.Contributor Name → rename to display label
    • Findings.Hunter (Contributor) → rename to hunter; same for Triager (Contributor)triager
    • Appeals.Linked Findingfinding; Appellant (Contributor)appellant
  3. 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)
  4. (Original Phase 1 content retained below for reference — but most of it is done.)


Phase 1 (reference / done) — Schema reconciliation

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.

Table: Tasks (live primary Task Name → must become slug)

Rename:

  • Task Nameslug (keep as primary; rename only)
  • Statusbounty_status (single select, options: Open, Closed — drop any other options)
  • Bounty Window Endshipped_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 implicit Assigned Hunters? NO — add a new linked field author → Contributors (single record link)
  • tier — single select, options: Easy, Medium, Hard, Reference
  • author_fee_usd — currency (USD)
  • p_solve_ensemble — number (precision 2 decimal places)
  • cross_family_passes — checkbox
  • stage3_required — checkbox
  • harbor_config_sha256 — single line text
  • shipped_at — date-time (UTC)
  • admission_json_url — URL
  • final_tier — single select, options: Easy, Medium, Hard, Reference (set by window_close webhook)
  • final_author_fee_usd — currency
  • stage3_outcome — single select, options: cleared, dropped_one_tier, defaulted_medium, defaulted_easy, rejected_cheat, not_applicable
  • closed_at — date-time
  • total_deductions_to_author — rollup from linked Findings where triage_status = "Accepted", sum of deduction_from_author
  • total_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 with tier + 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 with admission_json_url)

Table: Contributors (live primary Contributor Name)

Rename:

  • Contributor Namedisplay_name
  • Role(s)roles_active (multi-select, options: Hunter, Author, Triager, Senior Triager, Frontier Verifier, Appeal Reviewer)
  • GitHub Usernamegithub_handle

Add:

  • contributor_id — autonumber (primary; demote display_name from 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 where status IN ("Locked","Pending release"), sum of amount
  • escrow_locked — rollup from linked Findings where triage_status = "Pending" and hunter = self, sum of bond_amount_locked
  • escrow_available — formula: {escrow_balance} - {escrow_locked}
  • lifetime_payouts — rollup from accepted Findings, sum of payout_to_hunter
  • lifetime_slashed — rollup from Findings, slashed amounts
  • clawback_debt — currency, default 0
  • ai_slop_warnings — number, default 0
  • submissions_filed — count of linked Findings (any status)
  • submissions_accepted — count where triage_status = "Accepted"
  • accept_rate — formula: IF({submissions_filed} > 0, {submissions_accepted} / {submissions_filed}, BLANK())
  • suspended_from — multi-select (role names; default empty)
  • suspension_until — date
  • triager_quality_metric — number (default 1.0; only populated for Triager role)

Drop:

  • Payment History Notes (replaced by structured fields above)

Table: Findings (live primary Finding ID)

Rename:

  • Tasktarget_task (keep linked field; rename only)
  • Hunter (Contributor)hunter
  • Triager (Contributor)triager
  • Finding Description (keep — this is the comment text)
  • Severity Assessmentclaimed_tier (single select: Catastrophic, Critical, Major, Minor, Duplicate, Co-finder)
  • Triage Gradetriage_status (single select: Pending, Accepted, Rejected, Duplicate-of, Needs-more-evidence; default Pending)
  • Quality Scorequality_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 time
  • failure_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 text
  • triaged_at — date-time (set by automation when status leaves Pending)
  • actual_tier — single select (same options as claimed_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; default Pending)
  • badness_tier — single select (N/A, Honest-mistake, Error, Flagrant, AI-slop, Malicious; default N/A)
  • senior_triager_cosign — linked to Contributors (required when badness_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–1
  • templated_prose_score — number 0–1
  • fabricated_evidence_likelihood — number 0–1
  • high_risk_queue — formula: AND({auto_quality_signal} = "clearly-ai", {auto_quality_confidence} >= 0.9, {fabricated_evidence_likelihood} >= 0.7)
  • bad_actor_signals — multi-select
  • grace_applied — multi-select
  • triager_notes — long text
  • appeal_window_ends — formula: DATEADD({triaged_at}, 7, 'days')

Drop:

  • Is Appealed? (redundant — derive from Appeal Record link being non-empty)
  • Supporting Evidence (Attachment) (replace with the evidence_url field — Airtable attachments are not the auth flow we want)

Table: Appeals (live mostly aligned)

Rename:

  • Appeal Statusoutcome (single select: Pending, Upheld, Overturned, Partial)
  • Linked Findingfinding
  • Appellant (Contributor)appellant
  • Appeal Reasonappeal_basis
  • Arbitrator Involvedappeal_reviewer (this is the senior triager who reviews the appeal — NOT the LLM arbitrator)
  • Resolution Notesoutcome_notes
  • Date Filedfiled_at (created time)

Add:

  • decided_at — date-time
  • appeal_window_ends — formula: DATEADD({filed_at}, 7, 'days')

Drop:

  • Linked Arbitration (redundant — arbitrations link to Appeals, not the other way)

Table: Arbitrations (live very underspecced)

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 by output_decision + final_outcome
  • Decision/Rulingoutput_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 time
  • hunter_dispute_text — long text
  • dispute_bond — currency, default 20.00
  • model_id — single line text (the pinned Claude model id)
  • prompt_version_sha256 — single line text
  • input_payload_sha256 — single line text
  • output_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 — currency
  • senior_triager_panel — multi-link to Contributors (only set on UNCERTAIN escalation)
  • final_outcome — single select (mirrors output_decision after 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)

Table: Escrow Ledger (live very underspecced)

Rename:

  • Entry IDledger_id (autonumber primary)
  • Linked Task → drop (escrow is per-contributor, not per-task; remove the link)
  • Contributor (Payee)contributor
  • Transaction Typesource (single select: Starter-grant, Payout-holdback, Bond-returned, Arbitrator-overturn-bonus)
  • Amountamount
  • Escrow Status → drop; replaced by formula status below
  • Settlement Date → drop; replaced by released_at below
  • Date Createdentered_escrow_at

Add:

  • source_finding — linked to Findings (set when source = Payout-holdback or Bond-returned)
  • source_arbitration — linked to Arbitrations (set when source = Arbitrator-overturn-bonus)
  • release_eligible_at — formula: DATEADD({entered_escrow_at}, 90, 'days') (the 90-day holdback rule)
  • released_at — date
  • slashed_at — date
  • slash_finding — linked to Findings (the finding that caused the slash)
  • slash_amount — currency
  • status — formula:
    IF({slashed_at}, "Slashed",
      IF({released_at}, "Released",
        IF({release_eligible_at} <= TODAY(), "Pending release", "Locked")))
    

Table: Audit Log (live underspecced)

Rename:

  • Event IDaudit_id (autonumber primary)
  • Event Timestamprecorded_at (created time)
  • Event Typeentity_type (single select: Finding, Contributor, Arbitration, Escrow Ledger, Task, Appeal)
  • Descriptionnotes (free-form; should be auto-populated for state changes)
  • Related Taskrelated_task (keep linked; rename)
  • Related Contributoractor
  • Related Findingrelated_finding
  • Related Appealrelated_appeal
  • Related Arbitrationrelated_arbitration
  • Related Escrow Ledger Entryrelated_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)

Phase 2 — Permissions matrix (after Phase 1)

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.


Phase 4 — Automations A1–A12 (per §F)

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)

Phase 5 — Views (per §D)

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.


Phase 6 — Interface Designer pages (per §E)

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.


Phase 7 — End-to-end smoke test

  1. Synthetic admit POST. Use curl to POST a synthetic event=admit payload to the webhook URL. Verify a Tasks row appears with slug=test-task-1, tier=Easy, bounty_status=Open, all other fields populated. Verify Audit Log entry written.
  2. Synthetic window_close POST. POST event=window_close for the same task_slug. Verify the row flips to Closed with final_tier, final_author_fee_usd, stage3_outcome populated. Verify Audit Log entry.
  3. Idempotency. Re-POST the admit event with the same (task_slug, admitted_at). Verify 200 OK + no duplicate row.
  4. 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.

Reply contract

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

End of follow-up prompt


For the prompt author (not for Omni)

  • 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/tables to re-audit and check field-count deltas
  • If field-name drift remains (e.g., Omni used Tier instead of tier), scripts/post_admission_webhook.py will 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment