From df7d41df7d7ca0834e1ebfbf4d836c98530ed9d0 Mon Sep 17 00:00:00 2001 From: kjh2064 Date: Sat, 8 Aug 2026 12:39:18 +0900 Subject: [PATCH] docs: add DBA grant script and Phase 1 VersionSet approval checklist MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - scripts/dba/grant-migration-test-db-ownership.sql: for a DBA to run, fixes the kartsell_migration_test ownership regression blocking DbUpMigrationTests/DbUpRecoveryTests (12 tests) locally. - docs/CURRENT/PHASE-1_APPROVAL_CHECKLIST.md + scripts/phase1/template-approve-versionset.sql: documents/templates the human maker-checker approval steps needed to freeze a VersionSet before Phase 1 shadow run can be re-queued. Does not perform any approval — every placeholder must be filled by a real, named maker and a different named checker. No automation should insert rows into dataset_manifest / model_version_registry / evidence_snapshot / release_evidence_bundle. Co-Authored-By: Claude Sonnet 5 --- docs/CURRENT/PHASE-1_APPROVAL_CHECKLIST.md | 71 +++++++++++++++++++ .../dba/grant-migration-test-db-ownership.sql | 30 ++++++++ .../phase1/template-approve-versionset.sql | 71 +++++++++++++++++++ 3 files changed, 172 insertions(+) create mode 100644 docs/CURRENT/PHASE-1_APPROVAL_CHECKLIST.md create mode 100644 scripts/dba/grant-migration-test-db-ownership.sql create mode 100644 scripts/phase1/template-approve-versionset.sql diff --git a/docs/CURRENT/PHASE-1_APPROVAL_CHECKLIST.md b/docs/CURRENT/PHASE-1_APPROVAL_CHECKLIST.md new file mode 100644 index 00000000..3c492321 --- /dev/null +++ b/docs/CURRENT/PHASE-1_APPROVAL_CHECKLIST.md @@ -0,0 +1,71 @@ +# 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. diff --git a/scripts/dba/grant-migration-test-db-ownership.sql b/scripts/dba/grant-migration-test-db-ownership.sql new file mode 100644 index 00000000..7bb75c28 --- /dev/null +++ b/scripts/dba/grant-migration-test-db-ownership.sql @@ -0,0 +1,30 @@ +-- DEBT: AEG-X-004 regression (2026-08-07) — see docs/CURRENT/CATALOGS/WBS_PROGRESS_TRACKER.csv row AEG-X-004 +-- and CURRENT_ROADMAP.md "알려진 문서 정합성 문제" #2. +-- +-- Symptom: 12 DbUpMigrationTests (Migration0008/0009/0010/0032) fail locally with +-- Postgres 42501 "must be owner of database kartsell_migration_test" — the `kartsell` +-- DB user can no longer DROP+CREATE that database, which the fresh/upgrade/re-run +-- rehearsal requires. +-- +-- Run this as a Postgres superuser (or the role that currently owns kartsell_migration_test) +-- against the target server, NOT as the `kartsell` application user (it cannot grant itself +-- ownership of a database it does not already own). +-- +-- Usage: +-- psql -h -p 5432 -U -d postgres -f scripts/dba/grant-migration-test-db-ownership.sql + +ALTER DATABASE kartsell_migration_test OWNER TO kartsell; + +-- If the database does not exist yet on this server, create it first, then re-run the ALTER +-- above (CREATE DATABASE already makes the creating role the owner, so the ALTER becomes a +-- no-op in that case): +-- CREATE DATABASE kartsell_migration_test OWNER kartsell; + +-- Verify: +-- SELECT datname, pg_catalog.pg_get_userbyid(datdba) AS owner +-- FROM pg_database WHERE datname = 'kartsell_migration_test'; +-- Expected: owner = kartsell + +-- After this grant, re-run the isolated rehearsal to confirm: +-- dotnet test --filter "FullyQualifiedName~DbUpMigrationTests" -c Release +-- dotnet test --filter "FullyQualifiedName~DbUpRecoveryTests" -c Release diff --git a/scripts/phase1/template-approve-versionset.sql b/scripts/phase1/template-approve-versionset.sql new file mode 100644 index 00000000..0d7c4082 --- /dev/null +++ b/scripts/phase1/template-approve-versionset.sql @@ -0,0 +1,71 @@ +-- Phase 1 Shadow Run — VersionSet approval TEMPLATE. +-- See docs/CURRENT/PHASE-1_APPROVAL_CHECKLIST.md before touching this file. +-- +-- THIS IS NOT A SCRIPT TO RUN AS-IS. Every :placeholder below must be filled in by a real +-- person who actually reviewed the referenced evidence. Do not fill these in programmatically, +-- from a template default, or because "the checklist items are all checked" — the checker must +-- be a different person from the maker, and both must be named humans who can be held +-- accountable for the decision. Running this file IS the approval action. +-- +-- Usage: fill in every :placeholder, remove this comment block, then run interactively: +-- psql "$KARTSELL_POSTGRES" -f scripts/phase1/template-approve-versionset.sql +-- Do not run non-interactively / in CI / from a job. + +begin; + +-- 1) Dataset manifest: propose, then (as a DIFFERENT session/person) approve + freeze. +insert into evaluation.dataset_manifest + (dataset_id, scope_key, content_hash, source_catalog_version, lineage_hash, status, frozen_at) +values + (:'dataset_id', :'scope_key', :'content_hash', :'source_catalog_version', :'lineage_hash', + 'PROPOSED', now()); + +-- -- Run as a SEPARATE step by the checker, after real review, not in the same transaction: +-- update evaluation.dataset_manifest +-- set status = 'APPROVED', approved_by = :'checker_name', approved_at = now() +-- where dataset_id = :'dataset_id'; +-- -- table is append-only after that point; freezing requires a new row, not an UPDATE to FROZEN +-- -- (see db/migrations/0034_dataset_manifest_freeze_contract.sql trigger) — insert a follow-up +-- -- row with status = 'FROZEN' and the same content_hash lineage instead. + +-- 2) Model version registry. +insert into governance.model_version_registry + (model_version, scope_key, config_version, code_sha, contract_version, lifecycle_state, + effective_at, model_card_hash) +values + (:'model_version', :'scope_key', :'config_version', :'code_sha', :'contract_version', + 'CANDIDATE', now(), :'model_card_hash'); + +-- -- Checker step, separately, after real review: +-- update governance.model_version_registry +-- set lifecycle_state = 'APPROVED', approved_by = :'checker_name', approved_at = now() +-- where model_version = :'model_version' and scope_key = :'scope_key' and effective_at = :'effective_at'; + +-- 3) Evidence snapshot (references the approved dataset + model version above). +insert into signal_engine.evidence_snapshot + (evidence_id, as_of, published_at_cutoff, dataset_id, data_hash, model_version, + config_version, payload, content_hash) +values + (:'evidence_id', now(), :'published_at_cutoff', :'dataset_id', :'data_hash', + :'model_version', :'config_version', :'payload_json'::jsonb, :'content_hash'); + +-- 4) Release evidence bundle — maker creates, a DIFFERENT checker approves. +insert into governance.release_evidence_bundle + (bundle_id, release_version, source_manifest_hash, build_artifact_hash, test_artifact_hash, + migration_artifact_hash, security_artifact_hash, rollback_artifact_hash, decision_log_hash, + status, maker_id, created_at, content_hash) +values + (gen_random_uuid(), :'release_version', :'source_manifest_hash', :'build_artifact_hash', + :'test_artifact_hash', :'migration_artifact_hash', :'security_artifact_hash', + :'rollback_artifact_hash', :'decision_log_hash', 'REVIEW_REQUIRED', :'maker_name', now(), + :'bundle_content_hash'); + +-- -- Checker step, separately, after real review (checker_id must differ from maker_id — enforced +-- -- by CHECK constraint): +-- update governance.release_evidence_bundle +-- set status = 'APPROVED', checker_id = :'checker_name', decided_at = now() +-- where bundle_id = :'bundle_id'; + +-- Do not COMMIT this transaction until you are the maker completing steps above as one unit. +-- The checker updates are separate transactions run later by a different person. +commit;