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