From 614f1416d426493d5c8978f9aa2fc1f80b49f31f Mon Sep 17 00:00:00 2001 From: Claude Code Date: Thu, 6 Aug 2026 14:17:11 +0900 Subject: [PATCH] AEG-X-004: align shadow run queued status contract --- ...0032_shadow_run_queued_status_contract.sql | 31 ++++++++++++ docs/CURRENT/AEG-X-004_DBUP_EVIDENCE.md | 14 ++++++ .../AEG-X-004_STATUS_CONTRACT_SLICE.md | 37 ++++++++++++++ .../CURRENT/CATALOGS/WBS_PROGRESS_TRACKER.csv | 2 +- .../DbUpMigrationTests.cs | 48 +++++++++++++++++++ 5 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 db/migrations/0032_shadow_run_queued_status_contract.sql create mode 100644 docs/CURRENT/AEG-X-004_STATUS_CONTRACT_SLICE.md diff --git a/db/migrations/0032_shadow_run_queued_status_contract.sql b/db/migrations/0032_shadow_run_queued_status_contract.sql new file mode 100644 index 00000000..83e1cfe2 --- /dev/null +++ b/db/migrations/0032_shadow_run_queued_status_contract.sql @@ -0,0 +1,31 @@ +-- AEG-X-004: align shadow_run status constraint with the existing Queued application state. +-- Prior migrations are immutable; this is an append-only correction migration. +DO $$ +DECLARE + shadow_run_oid oid := 'model_operations.shadow_run'::regclass; +BEGIN + IF shadow_run_oid IS NULL THEN + RAISE EXCEPTION 'model_operations.shadow_run must exist before 0032'; + END IF; + + IF EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conrelid = shadow_run_oid + AND conname = 'check_status' + ) THEN + ALTER TABLE model_operations.shadow_run DROP CONSTRAINT check_status; + END IF; + + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conrelid = shadow_run_oid + AND conname = 'check_status' + ) THEN + ALTER TABLE model_operations.shadow_run + ADD CONSTRAINT check_status CHECK ( + status IN ('Pending', 'Queued', 'DataBackfill', 'Replay', 'EvaluationComplete', 'Failed') + ); + END IF; +END $$; diff --git a/docs/CURRENT/AEG-X-004_DBUP_EVIDENCE.md b/docs/CURRENT/AEG-X-004_DBUP_EVIDENCE.md index d7656ad1..eacdf4a5 100644 --- a/docs/CURRENT/AEG-X-004_DBUP_EVIDENCE.md +++ b/docs/CURRENT/AEG-X-004_DBUP_EVIDENCE.md @@ -29,3 +29,17 @@ The evidence covers the repository's fresh/upgrade/re-run/recovery and checksum ## Completion boundary `AEG-X-004` is marked `COMPLETED` for the executed test-database rehearsal. Production deployment, DBA approval, and any production migration execution remain out of scope. + +## Status-contract correction evidence (2026-08-06) + +- Slice note: `docs/CURRENT/AEG-X-004_STATUS_CONTRACT_SLICE.md` +- Migration: `db/migrations/0032_shadow_run_queued_status_contract.sql` +- Regression: `DbUpMigrationTests.Migration0032_QueuedStatus_IsAccepted_AndRerunIsSafe` +- Command: `dotnet test tests/KArtSell.Integration.Tests/KArtSell.Integration.Tests.csproj -c Release --filter FullyQualifiedName~Migration0032_QueuedStatus --logger trx --verbosity minimal` +- Result: `1/1 passed`, TRX `tests/KArtSell.Integration.Tests/TestResults/kjh20_KIMJAEHYUN-OFFI_2026-08-06_14_15_10_net10.0.trx` +- Command: `dotnet test tests/KArtSell.Integration.Tests/KArtSell.Integration.Tests.csproj --no-build -c Release --filter FullyQualifiedName~DbUpMigrationTests --logger trx --verbosity minimal` +- Result: `12/12 passed`, TRX `tests/KArtSell.Integration.Tests/TestResults/kjh20_KIMJAEHYUN-OFFI_2026-08-06_14_15_28_net10.0.trx` +- Command: `dotnet test tests/KArtSell.Integration.Tests/KArtSell.Integration.Tests.csproj --no-build -c Release --filter FullyQualifiedName~DbUpRecoveryTests --logger trx --verbosity minimal` +- Result: `6/6 passed`, TRX `tests/KArtSell.Integration.Tests/TestResults/kjh20_KIMJAEHYUN-OFFI_2026-08-06_14_16_32_net10.0.trx` + +The correction accepts the existing application `Queued` state, rejects `UnknownStatus`, and preserves the inserted row across a direct re-run. This does not claim production migration, DBA approval, or Phase 1 execution/requeue. diff --git a/docs/CURRENT/AEG-X-004_STATUS_CONTRACT_SLICE.md b/docs/CURRENT/AEG-X-004_STATUS_CONTRACT_SLICE.md new file mode 100644 index 00000000..ff08295b --- /dev/null +++ b/docs/CURRENT/AEG-X-004_STATUS_CONTRACT_SLICE.md @@ -0,0 +1,37 @@ +# AEG-X-004 Status Contract Correction Slice + +## WBS / Scope + +- WBS ID: `AEG-X-004` +- Slice: `shadow_run.status` application/database contract correction +- Scope: Add an immutable follow-up migration so the existing `Queued` application state is accepted by the database. +- Explicitly out of scope: automatic requeue, model promotion, automatic order, KIS submission, production migration, and Phase 1 shadow execution. + +## Source + +- `src/KArtSell.Modules.ModelOperations/ShadowRun/Sql.cs` inserts `Queued`. +- `src/KArtSell.Host/Features/ShadowRun/Handler.cs` creates and reports `Queued`. +- `db/migrations/0022_model_operations_execution_schema.sql` rejects `Queued` through `check_status`. +- `docs/CURRENT/PHASE-1_SHADOW_RUN_STATUS_CORRECTION.md` records the observed HTTP 500 and PostgreSQL `23514` evidence. +- `docs/CURRENT/WBS_EXECUTION_PROCEDURES.md` requires one WBS slice, preserved execution evidence, and tracker update. + +## Assumption + +- `Queued` is an approved existing application lifecycle state because it is already emitted by the active endpoint and SQL path. +- A follow-up migration is required because prior migrations are immutable. + +## Unknown + +- Production database migration execution and DBA approval are not available in this slice. +- Phase 1 has not been requeued or started; this change only removes the known schema-contract failure. + +## Decision Required + +- Production rollout and explicit Phase 1 requeue approval remain required after this slice. + +## Acceptance Evidence + +- Migration applies on a fresh test database. +- Upgrade from the `0022` schema accepts `Queued` and rejects an unknown status. +- Re-running the follow-up migration is safe and preserves data. +- Actual test artifacts and tracker status are recorded after execution. diff --git a/docs/CURRENT/CATALOGS/WBS_PROGRESS_TRACKER.csv b/docs/CURRENT/CATALOGS/WBS_PROGRESS_TRACKER.csv index 549b5497..3344b2c7 100644 --- a/docs/CURRENT/CATALOGS/WBS_PROGRESS_TRACKER.csv +++ b/docs/CURRENT/CATALOGS/WBS_PROGRESS_TRACKER.csv @@ -2,7 +2,7 @@ WBS_ID,Sprint,Slice_ID,Task,Status,Completion_Date,Evidence_Link,Owner,Notes AEG-X-001,S0,Cross,Version Coverage Matrix 고도화,COMPLETED,2026-08-04,docs/contracts/platform/VERSION_COVERAGE_MATRIX.md,PM/Architect,"✅ Version matrix: v10/v12/v12.1 compatibility (Retained/Improved/Superseded 100%), Supersession registry, Breaking change assessment, Migration roadmap" AEG-X-002,S0,Cross,global.json 고도화,COMPLETED,2026-08-04,.gitea/workflows/ci.yml (dotnet/pnpm restore/build/test),DevOps,"✅ CI pipeline validates: dotnet restore/build/test (Release config), pnpm frozen install/build/e2e, PostgreSQL 17 health checks, Log output to .gitea/workflows/ci.yml" AEG-X-003,S0,Cross,Architecture tests 고도화,COMPLETED,2026-08-04,tests/KArtSell.ArchitectureTests/RepositoryRulesTests.cs (6 tests PASSING),Architect/QA,"✅ Architecture rules enforced: (1) No prohibited patterns, (2) Domain isolation from infrastructure, (3) SQL validation (no SELECT *, schema-qualified), (4) Endpoint authorization (Roles/Policies), (5) No placeholder files, (6) No duplicate aggregate IDs. All 6 tests PASS." -AEG-X-004,S0,Cross,DbUp 복구 rehearsal 고도화,COMPLETED,2026-08-06,"docs/CURRENT/AEG-X-004_DBUP_EVIDENCE.md; tests/KArtSell.Integration.Tests/DbUpMigrationTests.cs; tests/KArtSell.Integration.Tests/DbUpRecoveryTests.cs",DBA/BE,"✅ DbUpMigrationTests 11/11 and DbUpRecoveryTests 6/6 passed against approved test database; fresh/upgrade/re-run/failure-recovery evidence recorded. Full solution test remains separately unclaimed." +AEG-X-004,S0,Cross,DbUp 복구 rehearsal 고도화,COMPLETED,2026-08-06,"docs/CURRENT/AEG-X-004_DBUP_EVIDENCE.md; docs/CURRENT/AEG-X-004_STATUS_CONTRACT_SLICE.md; db/migrations/0032_shadow_run_queued_status_contract.sql; tests/KArtSell.Integration.Tests/DbUpMigrationTests.cs; tests/KArtSell.Integration.Tests/DbUpRecoveryTests.cs",DBA/BE,"✅ Queued status contract correction applied as append-only 0032; targeted 1/1, DbUpMigrationTests 12/12, DbUpRecoveryTests 6/6 passed against approved test database. Production migration/DBA approval and Phase 1 requeue remain unclaimed." AEG-X-005,S0,Cross,Security auth 고도화,COMPLETED,2026-08-04,"docs/decisions/ADR-SEC-001.md + tests/KArtSell.Integration.Tests/SecurityAuthenticationTests.cs (6 tests)",Security/BE,"✅ ADR-SEC-001 produced (OIDC/JWT/DevelopmentHeader tiers), SecurityAuthenticationTests.cs (6 tests): endpoint authorization, DevelopmentHeader mode check, secret logging prevention, secret hardcoding check, AI prompt PII, auth config validation. Acceptance_Evidence verified: '비개발 무인증 접근 0, secret/log/prompt 노출 0'" AEG-X-006,S0,Cross,Outbox publisher 고도화,COMPLETED,2026-08-04,"docs/CURRENT/ARTIFACTS/AEG-X-006_ACCEPTANCE_EVIDENCE.md + src/KArtSell.BuildingBlocks/Reliability/DapperOutboxWriter.cs + OutboxPollerJob.cs",BE/SRE,"✅ Outbox→Inbox async pipeline verified: DapperOutboxWriter (transactional), OutboxPollerJob (idempotent), DapperInboxStore (deduplication), 5 consumer implementations. Acceptance_Evidence: All criteria met. 177/177 tests PASS." AEG-X-007,S0,Cross,Serilog/OTel correlation 고도화,COMPLETED,2026-08-06,"tests/KArtSell.ArchitectureTests/PiiRedactionTests.cs (6 tests) + commit e7913db",SRE/Security,"✅ PII redaction policy VERIFIED: SSN/Email/CreditCard/ApiKey redaction (6 tests). Commit e7913db adds pattern-based sanitization validation. All tests PASS (249/253)." diff --git a/tests/KArtSell.Integration.Tests/DbUpMigrationTests.cs b/tests/KArtSell.Integration.Tests/DbUpMigrationTests.cs index bbc30d6e..cb7906f0 100644 --- a/tests/KArtSell.Integration.Tests/DbUpMigrationTests.cs +++ b/tests/KArtSell.Integration.Tests/DbUpMigrationTests.cs @@ -223,6 +223,46 @@ public sealed class DbUpMigrationTests : IAsyncLifetime () => invalidCmd.ExecuteNonQueryAsync()); } + [Fact] + public async Task Migration0032_QueuedStatus_IsAccepted_AndRerunIsSafe() + { + await ApplyMigration0008(); + await ApplyMigration0032(); + + await using var connection = await _dataSource.OpenConnectionAsync(); + var runId = Guid.NewGuid(); + var modelId = Guid.NewGuid(); + + await using var insertCmd = connection.CreateCommand(); + insertCmd.CommandText = """ + INSERT INTO model_operations.shadow_run (run_id, model_id, window_start, window_end, status) + VALUES (@runId, @modelId, @start, @end, 'Queued'); + """; + insertCmd.Parameters.AddWithValue("@runId", runId); + insertCmd.Parameters.AddWithValue("@modelId", modelId); + insertCmd.Parameters.AddWithValue("@start", new DateOnly(2024, 1, 2)); + insertCmd.Parameters.AddWithValue("@end", new DateOnly(2024, 8, 31)); + await insertCmd.ExecuteNonQueryAsync(); + + await ApplyMigration0032(); + + await using var selectCmd = connection.CreateCommand(); + selectCmd.CommandText = "SELECT status FROM model_operations.shadow_run WHERE run_id = @runId;"; + selectCmd.Parameters.AddWithValue("@runId", runId); + Assert.Equal("Queued", await selectCmd.ExecuteScalarAsync()); + + await using var invalidCmd = connection.CreateCommand(); + invalidCmd.CommandText = """ + INSERT INTO model_operations.shadow_run (run_id, model_id, window_start, window_end, status) + VALUES (@runId, @modelId, @start, @end, 'UnknownStatus'); + """; + invalidCmd.Parameters.AddWithValue("@runId", Guid.NewGuid()); + invalidCmd.Parameters.AddWithValue("@modelId", Guid.NewGuid()); + invalidCmd.Parameters.AddWithValue("@start", new DateOnly(2024, 1, 2)); + invalidCmd.Parameters.AddWithValue("@end", new DateOnly(2024, 8, 31)); + await Assert.ThrowsAsync(() => invalidCmd.ExecuteNonQueryAsync()); + } + /// /// Gate 3: Constraints - Window order enforced (start <= end) /// @@ -482,6 +522,14 @@ public sealed class DbUpMigrationTests : IAsyncLifetime await cmd.ExecuteNonQueryAsync(); } + private async Task ApplyMigration0032() + { + await using var connection = await _dataSource.OpenConnectionAsync(); + await using var cmd = connection.CreateCommand(); + cmd.CommandText = File.ReadAllText(Path.Combine(AppContext.BaseDirectory, "migrations", "0032_shadow_run_queued_status_contract.sql")); + await cmd.ExecuteNonQueryAsync(); + } + private async Task ApplyMigration0009() { await using var connection = await _dataSource.OpenConnectionAsync();