# Phase 1 Shadow Run — VersionSet approval checklist **Purpose:** `PHASE-1-SHADOW-RUN` (WBS tracker row, `docs/CURRENT/PHASE-1_SHADOW_RUN_STATUS_CORRECTION.md`) is blocked because `evaluation.dataset_manifest`, `governance.model_version_registry`, `signal_engine.evidence_snapshot`, and `governance.release_evidence_bundle` contain no approved/frozen rows. Nothing can be RunId/JobId-enqueued until a human approves a VersionSet. **This document is a checklist, not an approval.** It exists so the approval step is fast and auditable once a real human decides to do it. It does not grant approval, and no automation in this repository should insert rows into these tables on a schedule or "because the checklist passed" — every `approved_by`/`checker_id` value below must be a real person who reviewed the evidence, per CLAUDE.md's maker-checker principle and AGENTS.md's guardrail against fabricated governance records. ## Why this can't be scripted away Each table below enforces maker-checker at the database level (see the CHECK constraints in `db/migrations/0016_continuous_model_operations.sql`, `0017_execution_assurance.sql`, `0034_dataset_manifest_freeze_contract.sql`): - `dataset_manifest`: `FROZEN` requires `approved_by`, `approved_at`, `frozen_at` all set, and the table is append-only (trigger blocks UPDATE/DELETE — corrections are new rows). - `model_version_registry`: `lifecycle_state = 'APPROVED'` requires `approved_by` + `approved_at`. - `release_evidence_bundle`: `status IN ('APPROVED','REJECTED')` requires `checker_id IS NOT NULL`, `checker_id <> maker_id` (the checker cannot be the maker), and `decided_at`. There is no code path that satisfies these constraints without a named maker and a *different* named checker actually deciding. That is intentional — do not add one. ## Checklist (walk in order) 1. **Dataset manifest exists and is content-addressed.** - [ ] A `dataset_manifest` row exists for the target `scope_key` with `content_hash` computed from the actual dataset (not a placeholder). - [ ] `lineage_hash` traces back to real source ingestion (KRX/OpenDart/KIS via `src/KArtSell.Modules.ModelOperations/Infrastructure/`), not synthetic/test data. - [ ] A maker sets `status = 'PROPOSED'`. - [ ] A **different** person (checker) reviews the dataset and, if acceptable, updates status to `APPROVED` and later `FROZEN` (setting `approved_by`, `approved_at`, `frozen_at`). 2. **Model version registered.** - [ ] `model_version_registry` row exists for the model/scope with `code_sha` matching the exact commit that will run, `model_card_hash` matching a real ModelCard document. - [ ] `lifecycle_state` progressed through `RESEARCH → CHALLENGER → SHADOW → CANDIDATE` with real review at each step (not skipped). - [ ] Checker sets `lifecycle_state = 'APPROVED'` with `approved_by`/`approved_at`. 3. **Evidence snapshot frozen.** - [ ] `signal_engine.evidence_snapshot` row references the approved `dataset_id` and `model_version` above, with `content_hash` computed from the actual frozen payload. 4. **Release evidence bundle.** - [ ] `release_evidence_bundle` row aggregates build/test/migration/security/rollback artifact hashes for the exact code that will run. - [ ] Maker (`maker_id`) creates it in `DRAFT`/`REVIEW_REQUIRED`. - [ ] A different checker (`checker_id <> maker_id`) reviews and sets `status = 'APPROVED'`, `decided_at`. 5. **Only after all four are real and APPROVED/FROZEN:** - [ ] Reconcile `shadow_run.check_status` constraint (already done — see `db/migrations/0032_shadow_run_queued_status_contract.sql`, `AEG-X-004`). - [ ] Call `POST /api/shadow-runs` referencing the approved VersionSet. - [ ] Record the returned RunId/JobId in `docs/CURRENT/PHASE-1_SHADOW_RUN_STATUS_CORRECTION.md` and update `WBS_PROGRESS_TRACKER.csv`'s `PHASE-1-SHADOW-RUN` row to `RUNNING` — only with that real RunId/JobId cited as evidence. ## SQL template `scripts/phase1/template-approve-versionset.sql` has the parameterized statements for steps 1-4, with every value that must be a real human decision left as an explicit placeholder. It is a template to hand-fill and run interactively (e.g. via `psql`), not a script to execute as-is.