| A2-1 |
life-app |
packages/server/src/data-lab/exports/eligibility-computer.ts + routes/data-lab/index.ts + approval writers |
Wire computeEligibility() into every training_approvals write (pair-preferences, turns-edit, product-tagging, promote-reaction, selections) and add a /data-lab/eligibility/recompute route or worker pass. This is the keystone fix — without it nothing exports. |
| A1-2 |
life-app |
packages/server/src/data-lab/exports/canonical-loaders.ts |
Resolve system_prompts.body in the SFT/DPO/prompt-eval loaders and inject it as messages[0]={role:'system'} (OpenAI/spec) or a top-level system field (Anthropic). |
| A1-4 |
review-ui |
supabase/migrations/ (new) export_format enum |
ALTER TYPE export_format ADD VALUE IF NOT EXISTS 'profiler_labels_jsonl' (outside a txn block, matching the reaction-promotion migration pattern); apply via supabase db push. |
| A4-2 |
life-app |
packages/server/src/data-lab/exports/reaction-export-product.ts |
Coerce predicted_confidence = p.predicted_confidence == null ? null : Number(p.predicted_confidence) (mirror reaction-prediction.ts:1264); guard transformReactionRow on Number.isFinite instead of typeof. |
| B1-1 |
life-app |
packages/server/src/data-lab/services/ai-provider.ts + anthropic-provider.ts + openrouter-provider.ts + generation-service.ts |
Extend ChatGenerationOutput with stopReason/providerRequestId/costUsd, populate both providers (stop_reason/finish_reason + response.id; OpenRouter cost via /generation), thread into the generation_attempts insert. Columns already exist — additive. |
| B2-2 |
life-app |
packages/server/src/data-lab/exports/canonical-loaders.ts |
Add created_at to loadBranches select and pass branch.created_at (or trajectory_label.created_at) to applyFilterSpec; delete the incorrect 'branches lack a created_at' comment. |
| A2-2 |
life-app |
packages/server/src/routes/data-lab/ (new approvals route) + index.ts |
Add a training-approvals POST endpoint (per unit_type/target_id/bucket, append-only supersedes/is_current) that writes training_approvals + training_approval_audit and calls computeEligibility; expose it in the UI. |
| A2-3 |
life-app |
packages/server/src/data-lab/ (all training_approvals writers) |
Insert a training_approval_audit row (from_state,to_state,reviewer_id,reason) in the same operation as every training_approvals write. |
| A2-5 |
life-app |
packages/server/src/data-lab/exports/eligibility-computer.ts |
Make demote+insert atomic via a single Postgres RPC/transaction (or delete+insert with ON CONFLICT on the partial-current index — heed the project's PostgREST partial-index ON CONFLICT gotcha); stop overloading computation_version as a retry counter. |
| A3-2 |
life-app |
packages/server/src/data-lab/exports/canonical-loaders.ts (or judge fanout in review-ui) |
Add per-unit-type zod schemas (score range, required keys) validated before surfacing judge grades; drop/flag grades that fail validation instead of exporting raw. |
| B3-1 |
life-app |
packages/server/src/data-lab/exports/sft-adapter.ts + dpo-adapter.ts + canonical-loaders.ts |
Select and surface model, provider, producer_type, assist_model, assist_provider, was_steered in SFT/DPO loaders + adapter metadata (StandardRecord metadata has an index signature — additive). |
| B4-2 |
life-app |
packages/server/src/data-lab/exports/dpo-adapter.ts |
Add `if (row.chosen.classifier_modified || row.rejected.classifier_modified) return false` to dpoRowEligible, mirroring sftRowEligible (defense-in-depth). |
| A5-1 |
life-app |
packages/server/src/data-lab/exports/__tests__/ (new judge-panel-leak.test.ts) |
Add a judge-panel leak guard: assert no canonical loader's fromCalls contains judge_panel_runs/judge_panel_grades, plus a static-source grep that canonical-loaders.ts contains no 'judge_panel' substring. |
| B3-2 |
life-app |
packages/server/src/data-lab/services/steering-promote-service.ts |
In promote(), if message.promoted_candidate_id is already set, return the existing candidate (idempotent) instead of inserting a duplicate. |
| B2-3 |
life-app |
packages/server/src/data-lab/exports/canonical-loaders.ts |
Use branches.system_prompt_id (add to loadBranches select) as the authoritative trajectory system_prompt_id; drop the generation_attempts cross-walk. |
| B4-3 |
life-app |
packages/server/src/data-lab/exports/sft-adapter.ts + dpo-adapter.ts |
Add edit_distance, reviewer_id, was_steered (and optionally a raw_model_response hash) to SFT/DPO export metadata (additive, no schema change). |
| A1-7 |
life-app |
packages/server/src/data-lab/exports/canonical-loaders.ts + adapters |
Add .order('id') in loaders and a stable per-record sort before serialize so identical filter_spec yields byte-identical exports. |
| A1-8 |
life-app |
packages/server/src/data-lab/exports/__tests__/sft-adapter.test.ts (+ new loader test) |
Add a loader-level test with user/assistant-only model_seen_messages_jsonb asserting the loader injects the resolved system prompt; stop pre-seeding system into the adapter fixture as if the loader produced it. |
| A4-3 |
life-app |
packages/server/src/data-lab/exports/types.ts |
Extend TrainingUnitType to include 'reaction_prediction'|'reaction_label' and derive promote-reaction's tuple from it (mirror the DB enum). |
| A3-4 |
review-ui |
supabase/migrations/20260503204523_data_lab_aux.sql (comment) + judge-writers docstring |
Correct the parsed_jsonb shape documentation to the actual per-unit-type shapes (pairwise: preferred/score_a/score_b/...; candidate/trajectory: score/turn_scores/...); note the shape is unit-type-dependent. |
| A1-9 |
life-app |
packages/server/src/data-lab/worker/export-worker.ts |
For a legitimately-empty bucket, record an explicit {filename,row_count:0} entry (or emit a header-only file) so empty splits are observable in the files list. |