Skip to content

Commit 6dc4535

Browse files
atharvasclaude
andcommitted
Resolve issue #23: expose benchmark_codes and standardize task identity
Closes #23. The FormulaCode website's `npm run sync` script needs benchmark source code, a queryable task identifier, the level-aggregation statistic, and (optionally) `benchmark_type` exposed by `api.formulacode.org`. This change lands all four, and along the way collapses the two competing `task_id` string formats (`owner__repo-N` in publish, `owner_repo_N` in harbor_adapter) into a single integer alias of `issue_number`, with `(owner, repo, issue_number)` as the canonical row identity everywhere. New Supabase tables / columns - `benchmark_codes(owner, repo, benchmark_without_params, source, ...)` — public-read; populated by stage 9. - `candidate_containers.task_id` — generated column = issue_number. - `benchmark_information.benchmark_type` — generated column derived from the ASV `time_*`/`timeraw_*`/`mem_*`/`peakmem_*`/`track_*` convention. New pipeline stage 9 `scrape_benchmark_source` - Reuses `prepare_repo_checkout` / AST traversal under each repo's `benchmark_dir` (resolved from `asv.conf.json` when present) to extract per-function source + co-located `setup` / `setup_cache`. - Idempotent upsert keyed on (owner, repo, benchmark_without_params). - Tunables: `DATASMITH_BENCH_SCRAPE_MAX_FILE_BYTES`, `DATASMITH_BENCH_SCRAPE_DIRS`. task_id collapse - `FormulaCodeRecord.task_id` is now `int` = `issue_number` across `github.models`, `publish.records`, `publish.huggingface`, and `harbor_adapter.{records,adapter,utils,template/*}`. - Harbor templates pass `OWNER`/`REPO`/`ISSUE_NUMBER` to `upload.py` / `parser.py`; storage layout moves from `snapshots/{task_id}/...` to `snapshots/{owner}/{repo}/{issue_number}/...`; Supabase queries on the `tasks` table switch to a composite filter. - Harbor task directory layout uses a derived `task_dir_name` (`owner__repo__issue_number`) so Harbor's flat-dataset discovery and trial-name patching keep working. Companion scripts (apply once against Harbor's Supabase, not datasmith's) - `scripts/harbor_tasks_migration.sql` — backfill `owner/repo/issue_number` from legacy task_id strings, swap the PK to the triple, retain `task_id` as a deprecated mirror. - `scripts/migrate_snapshot_keys.py` — rename existing `snapshots/{old_task_id}/oracle.tar.gz` objects under the new prefix. Defaults to `--dry-run`; pass `--apply` to commit. Docs - CLAUDE.md gains the new table, stage 9, tunables, and a "Level aggregation" subsection documenting the geomean rollup (`harbor_adapter/template/parser.py:158-204`) — the answer the issue explicitly asks for. Verification - `make check` clean (ruff + mypy + deptry). - `pytest tests/scrape tests/publish tests/github` — 108 passing, including a new AST-extraction test against an inline ASV fixture. - Migrations 00016/00017/00019/00020 applied locally; schema introspection confirms generated columns produce expected values (35,152 benchmark_information rows resolved to `time`). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 89efaad commit 6dc4535

32 files changed

Lines changed: 1314 additions & 82 deletions

CLAUDE.md

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ fc-data --stage 7 --harbor-limit 10 # Smoke t
6262
6. **synthesize_images** — Agent-based Docker build context synthesis (uses env_payload/python_version from stage 4)
6363
7. **harbor_healthcheck** — Run every synthesized container through Harbor's oracle agent, record per-benchmark speedups to `harbor_runs`. Supports local Docker and Daytona via `--harbor-environment`; the row records which one in `harbor_runs.environment`. Local runs are useful for iteration; only Daytona runs gate stage 8.
6464
8. **publish** — Build, verify, and publish Docker images to DockerHub. Only publishes PRs with at least one successful **Daytona** `harbor_runs` row whose `max_speedup >= 1.05`.
65+
9. **scrape_benchmark_source** — For each `(owner, repo)` in `candidate_containers`, check out the repo at its container SHA, AST-parse every ASV-style benchmark function under the repo's `benchmark_dir`, and upsert one row per `(owner, repo, benchmark_without_params)` into `benchmark_codes` for the FormulaCode website's data sync.
6566

6667
### Dataset verification (`dataset/`)
6768

@@ -112,7 +113,8 @@ import time (`src/datasmith/__init__.py` → `dotenv.load_dotenv`), so reading
112113
- **Existing uses** (non-exhaustive, grep `DATASMITH_` for the full list):
113114
`DATASMITH_RL_DEFAULT_PAUSE_S`, `DATASMITH_RL_PAUSE_JITTER_S`,
114115
`DATASMITH_RL_MAX_RETRIES`, `DATASMITH_NEIGHBOR_WINDOW_DAYS`,
115-
`DATASMITH_NEIGHBOR_CAP`.
116+
`DATASMITH_NEIGHBOR_CAP`, `DATASMITH_BENCH_SCRAPE_MAX_FILE_BYTES`,
117+
`DATASMITH_BENCH_SCRAPE_DIRS`.
116118

117119
## Supabase
118120

@@ -138,7 +140,7 @@ Local vLLM servers (8123, 8124) are exposed via a LiteLLM proxy on `https://mode
138140

139141
### Public read-only access (RLS)
140142

141-
Four tables are readable by the `anon` role: `repositories`, `pull_requests`, `candidate_containers`, `harbor_runs`. Migration `00012_public_read_rls.sql` enables RLS with a `public_read` SELECT policy on those tables; migration `00015_revoke_anon_select.sql` revokes Supabase's default broad `anon` `SELECT` grant and re-grants it only on the four, so every other table returns `permission denied`. The service-role key bypasses both layers, so pipeline processes are unaffected. Public anon access is served on `https://api.formulacode.org` (no Cloudflare Access gate); pipeline operators continue to use `https://db.formulacode.org` with CF Access + service-role key.
143+
Six tables are readable by the `anon` role: `repositories`, `pull_requests`, `candidate_containers`, `harbor_runs`, `benchmark_information`, `benchmark_codes`. Migration `00012_public_read_rls.sql` enables RLS with a `public_read` SELECT policy on the original four; migration `00016_benchmark_information.sql` adds the same policy to `benchmark_information`; migration `00017_benchmark_codes.sql` adds it to `benchmark_codes`. Migration `00015_revoke_anon_select.sql` revokes Supabase's default broad `anon` `SELECT` grant and re-grants it only on the public set, so every other table returns `permission denied`. The service-role key bypasses both layers, so pipeline processes are unaffected. Public anon access is served on `https://api.formulacode.org` (no Cloudflare Access gate); pipeline operators continue to use `https://db.formulacode.org` with CF Access + service-role key.
142144

143145
### Key tables
144146

@@ -148,6 +150,8 @@ Four tables are readable by the `anon` role: `repositories`, `pull_requests`, `c
148150
| `packages` | Resolved `env_payload` (pinned deps) and `python_version` per commit | Stage 4 |
149151
| `candidate_containers` | Successful agent-generated `build_pkg_sh` / `build_run_sh` per SHA | Stage 6 (on success) |
150152
| `harbor_runs` | One row per Harbor oracle trial for a synthesized container: `max_speedup`, `geomean_speedup`, `n_benchmarks`, `wallclock_sec`, `reward_payload`, `status`. One-to-many FK on `candidate_containers(owner, repo, sha)`. | Stage 7 |
153+
| `benchmark_information` | Per-benchmark speedup measurements from terminal-bench eval runs: one row per (run, owner/repo/issue, benchmark, agent, model). `speedup` is `(agent/nop)/(oracle/nop)` so 1.0 = parity with the human expert. `benchmark_type` (`time`/`mem`/`peakmem`/`track`) is a generated column derived from the ASV naming convention. Loaded out-of-band via `scripts/load_benchmark_information.py`. | (manual) |
154+
| `benchmark_codes` | One row per `(owner, repo, benchmark_without_params)` carrying the Python source of each ASV benchmark function plus its setup. Joined to `benchmark_information` on `(owner, repo, benchmark_name)` by the FormulaCode website. | Stage 9 |
151155
| `error_logs` | Per-attempt synthesis results: agent output, failure stage/return code, error messages | Stage 6 (`Synthesizer._log_attempt`) |
152156
| `runner_progress` | Live progress counters (total/completed/failed) per pipeline run | `BaseRunner` (all stages) |
153157
| `runner_failures` | One row per item failure with error message + traceback | `BaseRunner._log_failure` |
@@ -170,6 +174,21 @@ conn.cursor().execute(open('supabase/migrations/00007_error_logs.sql').read())
170174

171175
Use `datasmith.utils.db.fetch_all(table, select=..., filters=..., gte_filters=..., ...)` for paginated reads, or `get_client()` for direct Supabase client access.
172176

177+
### Level aggregation
178+
179+
Per-benchmark speedups are rolled up into four levels via **geometric mean** in `src/datasmith/harbor_adapter/template/parser.py` (`geometric_mean()` + `aggregate_by_hierarchy()`):
180+
181+
- **level1** — identity, one entry per benchmark (`module.Class.method`)
182+
- **level2** — grouped by `module.Class` (drop the last dotted segment), geomean within each group
183+
- **level3** — grouped by `module` (top dotted segment), geomean within each group
184+
- **level4** — a single overall geomean across every benchmark
185+
186+
`benchmark_information.benchmark_name` is already param-stripped (e.g. `benchmarks.ConstructorsSuite.time_point`), so consumers can replicate the rollup locally without re-parsing. The FormulaCode website CSV's `1-Params` row is equivalent to datasmith's `level1`; the four upper website rows map onto `level2`/`level3`/`level4` modulo column naming.
187+
188+
### Task identity
189+
190+
The canonical identifier for one PR / one task is the tuple `(owner, repo, issue_number)`. Tables that need a single-column join key expose a `task_id` field whose value equals `issue_number`; never construct it as a derived string. `candidate_containers.task_id` is a STORED generated column (see migration `00019_candidate_containers_task_id.sql`).
191+
173192
## Environment setup
174193

175194
Requires a `tokens.env` file in the repo root with `GH_TOKEN`, `CACHE_LOCATION`, `SUPABASE_URL`, `SUPABASE_KEY`, and optionally `DSPY_*` vars for LLM backends and `DOCKERHUB_*` vars for publishing. See README.md for the full template.

scripts/harbor_tasks_migration.sql

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
-- ============================================================================
2+
-- Harbor `tasks` table migration: composite (owner, repo, issue_number) PK
3+
-- ============================================================================
4+
--
5+
-- Target: Harbor's Supabase project (see HARBOR_SUPABASE_URL in tokens.env),
6+
-- NOT datasmith's local Supabase. Apply via:
7+
--
8+
-- psql "$HARBOR_DATABASE_URL" -f scripts/harbor_tasks_migration.sql
9+
--
10+
-- This script realigns Harbor's tasks table with the canonical FormulaCode
11+
-- identity tuple `(owner, repo, issue_number)`. The legacy `task_id` column
12+
-- was constructed inconsistently across the codebase (`owner__repo-N` in
13+
-- the publish path, `owner_repo_N` in harbor_adapter), so we extract its
14+
-- components and key the table on the tuple instead. The `task_id` column
15+
-- is retained for one release as an integer mirror of `issue_number`.
16+
--
17+
-- Companion change: scripts/migrate_snapshot_keys.py walks Supabase Storage
18+
-- and renames `snapshots/{old_task_id}/oracle.tar.gz` →
19+
-- `snapshots/{owner}/{repo}/{issue_number}/oracle.tar.gz` once this script
20+
-- has run successfully (so the new tasks rows already carry the triple).
21+
--
22+
-- Re-runnable: every statement is idempotent.
23+
24+
BEGIN;
25+
26+
-- 1. Add the three target columns (nullable initially so the backfill can
27+
-- run before we add NOT NULL constraints).
28+
ALTER TABLE tasks ADD COLUMN IF NOT EXISTS owner TEXT;
29+
ALTER TABLE tasks ADD COLUMN IF NOT EXISTS repo TEXT;
30+
ALTER TABLE tasks ADD COLUMN IF NOT EXISTS issue_number INT;
31+
32+
-- 2. Backfill from the legacy task_id string.
33+
-- Two patterns observed in the wild:
34+
-- Format A: owner__repo-N (publish/HuggingFace path)
35+
-- Format B: owner_repo_N (harbor_adapter path)
36+
-- Format A uses a double-underscore between owner and repo and a dash
37+
-- before the PR number; Format B uses single underscores throughout.
38+
--
39+
-- We try Format A first (more specific), then fall back to Format B.
40+
-- Rows that match neither stay NULL and will be flagged below.
41+
UPDATE tasks
42+
SET owner = split_part(task_id, '__', 1),
43+
repo = split_part(split_part(task_id, '__', 2), '-', 1),
44+
issue_number = NULLIF(
45+
regexp_replace(
46+
split_part(task_id, '__', 2),
47+
'^[^-]*-(\d+)$',
48+
'\1'
49+
),
50+
split_part(task_id, '__', 2)
51+
)::INT
52+
WHERE owner IS NULL
53+
AND task_id ~ '^[^_]+__[^_-]+-\d+$';
54+
55+
-- Fallback: Format B (owner_repo_N) — but only for rows still unbackfilled.
56+
-- This is brittle because owner and repo can both contain underscores, so
57+
-- we assume the last underscore-separated segment is the integer issue.
58+
UPDATE tasks
59+
SET owner = regexp_replace(task_id, '^(.+)_([^_]+)_(\d+)$', '\1'),
60+
repo = regexp_replace(task_id, '^(.+)_([^_]+)_(\d+)$', '\2'),
61+
issue_number = regexp_replace(task_id, '^(.+)_([^_]+)_(\d+)$', '\3')::INT
62+
WHERE owner IS NULL
63+
AND task_id ~ '^.+_[^_]+_\d+$';
64+
65+
-- Surface anything that didn't match either pattern.
66+
DO $$
67+
DECLARE n_orphaned INT;
68+
BEGIN
69+
SELECT count(*) INTO n_orphaned FROM tasks WHERE owner IS NULL;
70+
IF n_orphaned > 0 THEN
71+
RAISE WARNING 'tasks: % rows have task_id values that do not parse — leaving them with NULL owner/repo/issue_number; review manually before enforcing NOT NULL', n_orphaned;
72+
END IF;
73+
END $$;
74+
75+
-- 3. Once the backfill is verified clean, enforce NOT NULL + swap the PK.
76+
-- Skip this block if any orphans remain — operator handles them first.
77+
DO $$
78+
DECLARE n_orphaned INT;
79+
BEGIN
80+
SELECT count(*) INTO n_orphaned FROM tasks WHERE owner IS NULL OR repo IS NULL OR issue_number IS NULL;
81+
IF n_orphaned = 0 THEN
82+
ALTER TABLE tasks ALTER COLUMN owner SET NOT NULL;
83+
ALTER TABLE tasks ALTER COLUMN repo SET NOT NULL;
84+
ALTER TABLE tasks ALTER COLUMN issue_number SET NOT NULL;
85+
86+
-- Replace the PK only if it isn't already on the triple.
87+
IF NOT EXISTS (
88+
SELECT 1
89+
FROM pg_constraint
90+
WHERE conrelid = 'tasks'::regclass
91+
AND contype = 'p'
92+
AND pg_get_constraintdef(oid) ILIKE '%(owner, repo, issue_number)%'
93+
) THEN
94+
ALTER TABLE tasks DROP CONSTRAINT IF EXISTS tasks_pkey;
95+
ALTER TABLE tasks ADD CONSTRAINT tasks_pkey PRIMARY KEY (owner, repo, issue_number);
96+
END IF;
97+
ELSE
98+
RAISE WARNING 'tasks: skipping PK swap — % rows still have NULL owner/repo/issue_number', n_orphaned;
99+
END IF;
100+
END $$;
101+
102+
-- 4. Reduce the legacy task_id column to an integer mirror of issue_number.
103+
-- Keep the column for one release so any code we missed still reads
104+
-- something sensible. The column is no longer a unique key.
105+
ALTER TABLE tasks ALTER COLUMN task_id DROP NOT NULL;
106+
UPDATE tasks SET task_id = issue_number::TEXT WHERE issue_number IS NOT NULL;
107+
COMMENT ON COLUMN tasks.task_id IS
108+
'deprecated: equal to issue_number::text — use (owner, repo, issue_number) instead';
109+
110+
COMMIT;

0 commit comments

Comments
 (0)