Compare commits

...

28 Commits

Author SHA1 Message Date
kjh2064 5db772f404 docs(architecture): add VS-01/VS-02 slice specs, register VS-02 tech debt
- VS-01-SLICE_SPEC.md: Identity/MFA/RBAC/maker-checker contract (draft, ready for security review)
  * Roles: GUEST/USER/OPERATOR/ADMIN/SUPER_ADMIN
  * MFA tiers: NO_MFA → TOTP_REQUIRED → HARDWARE_KEY
  * Maker-checker approval workflow for role elevation
  * Schema: users/roles/user_roles/role_approval_requests/mfa_devices/permissions (PIT-tracked)
  * Prerequisite AEG-X-001/AEG-VS-00-02 already COMPLETED → can proceed immediately

- VS-02-SLICE_SPEC.md: Financial security master (listing/delisting/product structure) [DRAFT]
  * Correct domain: Financial PIT data, NOT access-control rules
  * Existing VS-02 code (RBAC rule sync) is mislabeled → registered as tech debt
  * Schema stub: securities/trading_restrictions (PIT-tracked)
  * CRITICAL: Source Unknown — awaiting data governance approval of:
    (1) KRX data source endpoint (not in source-catalog.md)
    (2) Import SLA (daily? intraday?)
    (3) Audit/corrections policy
  * Status: DRAFT (cannot proceed without unknowns resolved)

- TECH_DEBT_REGISTER.md: Added DEBT-016 (VS-02 mislabeled)
  * Impact: Medium (design confusion), Effort: Low (doc) → Medium (code removal)
  * Endpoints disabled (DISABLED comment), schema never migrated, never deployed
  * Dead code marked for removal decision (separate PR recommended)

- WBS_PROGRESS_TRACKER.csv: Updated status
  * AEG-VS-01-01: PLANNED → IN_PROGRESS (SLICE_SPEC ready)
  * AEG-VS-02-01: PLANNED → DRAFT (unknowns documented, data gov approval needed)

AGENTS.md: Maturity (contract before code), Necessity (fix only real issues — domain confusion + unknowns)

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-08-07 14:05:17 +09:00
kjh2064 67274cbdb6 Merge pull request '배포: 날짜·일련번호 기반 프런트엔드 버전 계약 적용' (#15) from fix/deploy-build-frontend-artifact into main
deploy / deploy (push) Successful in 2m15s
deploy / notify (push) Successful in 1s
Reviewed-on: #15
2026-08-06 16:28:38 +09:00
kjh2064 9e4346efa9 DEPLOY: generate date sequence semantic frontend version 2026-08-06 16:23:47 +09:00
kjh2064 07b6fc6bb3 Merge pull request 'DEPLOY: rebuild frontend before publishing host artifact' (#14) from fix/deploy-build-frontend-artifact into main
ci / static (push) Successful in 10s
ci / frontend (push) Has been cancelled
ci / publish (push) Has been cancelled
ci / backend (push) Has been cancelled
Build & Test with Secrets / frontend (push) Has been cancelled
Build & Test with Secrets / security-scan (push) Has been cancelled
Build & Test with Secrets / notification (push) Has been cancelled
Build & Test with Secrets / build (push) Has been cancelled
deploy / deploy (push) Successful in 1m53s
deploy / notify (push) Successful in 0s
Reviewed-on: #14
2026-08-06 16:13:37 +09:00
kjh2064 366978ce0f DEPLOY: rebuild frontend before publishing host artifact
ci / static (push) Has been cancelled
ci / publish (push) Has been cancelled
ci / backend (push) Has been cancelled
ci / frontend (push) Has been cancelled
Build & Test with Secrets / build (pull_request) Has been cancelled
Build & Test with Secrets / frontend (pull_request) Has been cancelled
Build & Test with Secrets / security-scan (pull_request) Has been cancelled
Build & Test with Secrets / notification (pull_request) Has been cancelled
ci / static (pull_request) Successful in 7s
ci / publish (pull_request) Has been cancelled
ci / frontend (pull_request) Has been cancelled
ci / backend (pull_request) Has been cancelled
2026-08-06 16:13:01 +09:00
kjh2064 c0b49959d4 Merge pull request 'DEPLOY: one-time sudo delegation for kartsell restart' (#13) from fix/deploy-kartsell-nopasswd into main
ci / static (push) Has been cancelled
ci / backend (push) Has been cancelled
ci / frontend (push) Has been cancelled
ci / publish (push) Has been cancelled
Build & Test with Secrets / build (push) Has been cancelled
Build & Test with Secrets / frontend (push) Has been cancelled
Build & Test with Secrets / security-scan (push) Has been cancelled
Build & Test with Secrets / notification (push) Has been cancelled
deploy / deploy (push) Successful in 2m35s
deploy / notify (push) Successful in 1s
Reviewed-on: #13
2026-08-06 16:05:28 +09:00
kjh2064 1c685e2285 DEPLOY: delegate kartsell restart without interactive sudo
ci / static (push) Successful in 11s
ci / publish (push) Has been cancelled
ci / backend (push) Has been cancelled
ci / frontend (push) Has been cancelled
ci / static (pull_request) Successful in 9s
Build & Test with Secrets / build (pull_request) Failing after 2s
Build & Test with Secrets / security-scan (pull_request) Has been cancelled
Build & Test with Secrets / notification (pull_request) Has been cancelled
Build & Test with Secrets / frontend (pull_request) Has been cancelled
ci / backend (pull_request) Has been cancelled
ci / frontend (pull_request) Has been cancelled
ci / publish (pull_request) Has been cancelled
2026-08-06 15:59:16 +09:00
kjh2064 aee4a4d624 Merge pull request 'Deploy: fail closed on migration and restart errors' (#12) from fix/deploy-fail-closed into main
Build & Test with Secrets / build (push) Has been cancelled
Build & Test with Secrets / frontend (push) Has been cancelled
Build & Test with Secrets / security-scan (push) Has been cancelled
Build & Test with Secrets / notification (push) Has been cancelled
ci / static (push) Successful in 13s
ci / publish (push) Has been cancelled
ci / frontend (push) Has been cancelled
ci / backend (push) Has been cancelled
deploy / deploy (push) Failing after 1m41s
deploy / notify (push) Successful in 0s
Reviewed-on: #12
2026-08-06 15:32:26 +09:00
kjh2064 36479307e9 Deploy: fail closed when migration or restart fails
ci / static (push) Successful in 14s
ci / static (pull_request) Successful in 12s
ci / frontend (pull_request) Has been cancelled
ci / publish (pull_request) Has been cancelled
ci / backend (pull_request) Has been cancelled
Build & Test with Secrets / build (pull_request) Has been cancelled
Build & Test with Secrets / frontend (pull_request) Has been cancelled
Build & Test with Secrets / security-scan (pull_request) Has been cancelled
Build & Test with Secrets / notification (pull_request) Has been cancelled
ci / publish (push) Has been cancelled
ci / frontend (push) Has been cancelled
ci / backend (push) Has been cancelled
2026-08-06 15:30:08 +09:00
kjh2064 74b50465fe Merge pull request 'AEG-X-004: deploy DbUp migrations with release artifact' (#11) from fix/deploy-db-migrator-migrations into main
ci / static (push) Successful in 11s
ci / backend (push) Successful in 4m1s
Build & Test with Secrets / build (push) Failing after 2s
deploy / deploy (push) Successful in 3m54s
ci / frontend (push) Successful in 5m15s
Build & Test with Secrets / security-scan (push) Failing after 9s
deploy / notify (push) Successful in 2s
ci / publish (push) Failing after 1m53s
Build & Test with Secrets / frontend (push) Successful in 4m17s
Build & Test with Secrets / notification (push) Failing after 1s
2026-08-06 15:21:21 +09:00
kjh2064 dc087969c5 CI: honor PostgreSQL service connection in integration tests
ci / static (pull_request) Successful in 15s
ci / static (push) Successful in 13s
ci / backend (push) Successful in 3m49s
ci / frontend (push) Successful in 5m5s
Build & Test with Secrets / build (pull_request) Failing after 1s
ci / backend (pull_request) Successful in 3m55s
Build & Test with Secrets / security-scan (pull_request) Failing after 9s
ci / publish (push) Has been skipped
ci / frontend (pull_request) Successful in 5m6s
Build & Test with Secrets / frontend (pull_request) Successful in 5m2s
ci / publish (pull_request) Has been skipped
Build & Test with Secrets / notification (pull_request) Failing after 1s
2026-08-06 15:13:20 +09:00
kjh2064 f4c195a56d CI: align v16 validator with available evidence artifacts
ci / static (push) Successful in 9s
ci / static (pull_request) Successful in 10s
ci / backend (push) Failing after 3m19s
ci / backend (pull_request) Failing after 3m32s
Build & Test with Secrets / build (pull_request) Failing after 1s
ci / frontend (push) Has been cancelled
ci / publish (push) Has been cancelled
ci / publish (pull_request) Has been cancelled
ci / frontend (pull_request) Has been cancelled
Build & Test with Secrets / security-scan (pull_request) Failing after 7s
Build & Test with Secrets / frontend (pull_request) Successful in 4m55s
Build & Test with Secrets / notification (pull_request) Failing after 1s
2026-08-06 15:08:47 +09:00
kjh2064 41b96022db CI: connect backend tests to PostgreSQL service hostname
ci / static (push) Failing after 10s
ci / static (pull_request) Failing after 8s
ci / backend (push) Failing after 3m22s
ci / backend (pull_request) Failing after 3m8s
Build & Test with Secrets / build (pull_request) Failing after 2s
ci / frontend (pull_request) Failing after 18s
Build & Test with Secrets / security-scan (pull_request) Failing after 8s
ci / publish (pull_request) Has been skipped
ci / frontend (push) Successful in 4m8s
ci / publish (push) Has been skipped
Build & Test with Secrets / frontend (pull_request) Successful in 2m8s
Build & Test with Secrets / notification (pull_request) Failing after 1s
2026-08-06 15:02:52 +09:00
kjh2064 b9e4fb0146 CI: isolate PostgreSQL service port on Gitea runner
ci / static (push) Failing after 12s
ci / static (pull_request) Failing after 11s
ci / backend (pull_request) Failing after 1s
ci / backend (push) Failing after 2m52s
Build & Test with Secrets / build (pull_request) Failing after 2s
ci / frontend (pull_request) Failing after 1m40s
Build & Test with Secrets / security-scan (pull_request) Failing after 8s
ci / publish (pull_request) Has been skipped
ci / frontend (push) Successful in 4m33s
ci / publish (push) Has been skipped
Build & Test with Secrets / frontend (pull_request) Successful in 2m56s
Build & Test with Secrets / notification (pull_request) Failing after 1s
2026-08-06 14:49:34 +09:00
kjh2064 30f4858a34 AEG-X-004: deploy DbUp migrations with release artifact
ci / backend (push) Failing after 1s
ci / static (push) Failing after 8s
ci / backend (pull_request) Failing after 2s
ci / static (pull_request) Failing after 11s
Build & Test with Secrets / build (pull_request) Failing after 1s
ci / publish (push) Has been cancelled
ci / frontend (push) Has been cancelled
ci / frontend (pull_request) Has been cancelled
ci / publish (pull_request) Has been cancelled
Build & Test with Secrets / security-scan (pull_request) Failing after 8s
Build & Test with Secrets / frontend (pull_request) Successful in 4m42s
Build & Test with Secrets / notification (pull_request) Failing after 1s
2026-08-06 14:46:01 +09:00
kjh2064 1de41b5055 PHASE-1-SHADOW-RUN: record production schema preflight
ci / backend (push) Failing after 1s
ci / static (push) Failing after 11s
Build & Test with Secrets / build (push) Failing after 1s
Build & Test with Secrets / security-scan (push) Has been cancelled
Build & Test with Secrets / notification (push) Has been cancelled
Build & Test with Secrets / frontend (push) Has been cancelled
ci / publish (push) Has been cancelled
ci / frontend (push) Has been cancelled
deploy / deploy (push) Successful in 1m27s
deploy / notify (push) Successful in 1s
2026-08-06 14:39:49 +09:00
kjh2064 07f2eb803c PHASE-1-SHADOW-RUN: preserve read-only preflight evidence
ci / backend (push) Failing after 1s
ci / static (push) Failing after 10s
Build & Test with Secrets / build (push) Failing after 1s
deploy / deploy (push) Successful in 2m44s
Build & Test with Secrets / security-scan (push) Failing after 7s
deploy / notify (push) Successful in 2s
ci / frontend (push) Successful in 3m46s
ci / publish (push) Has been skipped
Build & Test with Secrets / frontend (push) Successful in 3m21s
Build & Test with Secrets / notification (push) Failing after 1s
2026-08-06 14:33:09 +09:00
kjh2064 258eb7ef2f PHASE-1-SHADOW-RUN: define concrete execution evidence plan
ci / static (push) Failing after 8s
ci / backend (push) Failing after 1s
ci / publish (push) Has been cancelled
ci / frontend (push) Has been cancelled
Build & Test with Secrets / build (push) Failing after 1s
deploy / deploy (push) Successful in 2m46s
Build & Test with Secrets / security-scan (push) Failing after 7s
Build & Test with Secrets / frontend (push) Successful in 3m49s
deploy / notify (push) Successful in 1s
Build & Test with Secrets / notification (push) Failing after 1s
2026-08-06 14:31:43 +09:00
kjh2064 fbff7cfbda PHASE-1-SHADOW-RUN: assign readiness owner and deadline
ci / backend (push) Failing after 1s
ci / static (push) Failing after 11s
ci / publish (push) Has been cancelled
ci / frontend (push) Has been cancelled
Build & Test with Secrets / build (push) Failing after 1s
Build & Test with Secrets / security-scan (push) Failing after 6s
deploy / deploy (push) Successful in 2m59s
Build & Test with Secrets / frontend (push) Successful in 3m57s
deploy / notify (push) Successful in 1s
Build & Test with Secrets / notification (push) Failing after 1s
2026-08-06 14:28:00 +09:00
kjh2064 1904b4fcbf PHASE-1-SHADOW-RUN: block unsafe legacy execution path
ci / backend (push) Failing after 1s
ci / static (push) Failing after 10s
ci / publish (push) Has been cancelled
ci / frontend (push) Has been cancelled
Build & Test with Secrets / build (push) Failing after 1s
deploy / deploy (push) Successful in 2m44s
Build & Test with Secrets / security-scan (push) Failing after 7s
Build & Test with Secrets / frontend (push) Successful in 4m11s
deploy / notify (push) Successful in 2s
Build & Test with Secrets / notification (push) Failing after 2s
2026-08-06 14:25:37 +09:00
kjh2064 f09a65e909 PHASE-1-SHADOW-RUN: prepare requeue readiness gates
ci / backend (push) Failing after 1s
ci / static (push) Failing after 9s
ci / publish (push) Has been cancelled
ci / frontend (push) Has been cancelled
Build & Test with Secrets / build (push) Failing after 1s
deploy / deploy (push) Successful in 2m42s
Build & Test with Secrets / security-scan (push) Failing after 7s
deploy / notify (push) Successful in 2s
Build & Test with Secrets / frontend (push) Successful in 4m7s
Build & Test with Secrets / notification (push) Failing after 1s
2026-08-06 14:23:38 +09:00
kjh2064 6fc79c8ead AEG-X-004: record phase one approval gate
ci / static (push) Failing after 11s
ci / backend (push) Failing after 1s
Build & Test with Secrets / build (push) Failing after 1s
deploy / deploy (push) Successful in 2m44s
ci / publish (push) Has been cancelled
ci / frontend (push) Has been cancelled
Build & Test with Secrets / security-scan (push) Failing after 7s
deploy / notify (push) Successful in 1s
Build & Test with Secrets / frontend (push) Successful in 4m9s
Build & Test with Secrets / notification (push) Failing after 1s
2026-08-06 14:18:00 +09:00
kjh2064 614f1416d4 AEG-X-004: align shadow run queued status contract
ci / static (push) Failing after 8s
ci / backend (push) Failing after 1s
ci / publish (push) Has been cancelled
ci / frontend (push) Has been cancelled
Build & Test with Secrets / build (push) Failing after 1s
Build & Test with Secrets / security-scan (push) Failing after 7s
deploy / deploy (push) Successful in 2m48s
Build & Test with Secrets / frontend (push) Successful in 4m7s
deploy / notify (push) Successful in 1s
Build & Test with Secrets / notification (push) Failing after 1s
2026-08-06 14:17:11 +09:00
kjh2064 6126289baf Merge pull request 'docs: correct shadow run status from evidence' (#10) from agent/correct-phase1-shadow-status into main
ci / static (push) Failing after 11s
ci / backend (push) Failing after 1s
Build & Test with Secrets / build (push) Failing after 1s
ci / publish (push) Has been cancelled
ci / frontend (push) Has been cancelled
Build & Test with Secrets / security-scan (push) Failing after 6s
deploy / deploy (push) Successful in 2m57s
Build & Test with Secrets / frontend (push) Successful in 4m7s
deploy / notify (push) Successful in 2s
Build & Test with Secrets / notification (push) Failing after 1s
2026-08-06 14:11:40 +09:00
kjh2064 c3242e3c67 docs: correct shadow run status from evidence
ci / backend (push) Failing after 0s
ci / static (push) Failing after 5s
ci / backend (pull_request) Failing after 1s
ci / static (pull_request) Failing after 9s
Build & Test with Secrets / build (pull_request) Failing after 1s
ci / frontend (push) Successful in 3m42s
Build & Test with Secrets / security-scan (pull_request) Failing after 8s
Build & Test with Secrets / frontend (pull_request) Successful in 3m46s
ci / frontend (pull_request) Successful in 3m56s
ci / publish (push) Has been skipped
Build & Test with Secrets / notification (pull_request) Failing after 1s
ci / publish (pull_request) Has been skipped
2026-08-06 14:11:22 +09:00
kjh2064 b0c6718ce9 Merge pull request 'docs: close DbUp rehearsal evidence (AEG-X-004)' (#9) from agent/wbs-aeg-x-004-evidence into main
ci / backend (push) Has been cancelled
ci / frontend (push) Has been cancelled
ci / publish (push) Has been cancelled
ci / static (push) Has been cancelled
deploy / deploy (push) Successful in 59s
Build & Test with Secrets / build (push) Has been cancelled
Build & Test with Secrets / frontend (push) Has been cancelled
Build & Test with Secrets / security-scan (push) Has been cancelled
Build & Test with Secrets / notification (push) Has been cancelled
deploy / notify (push) Successful in 1s
2026-08-06 14:09:09 +09:00
kjh2064 060205eea1 docs: close DbUp rehearsal evidence (AEG-X-004)
ci / backend (push) Has been cancelled
ci / frontend (push) Has been cancelled
ci / publish (push) Has been cancelled
ci / static (push) Has been cancelled
Build & Test with Secrets / build (pull_request) Has been cancelled
Build & Test with Secrets / frontend (pull_request) Has been cancelled
Build & Test with Secrets / security-scan (pull_request) Has been cancelled
Build & Test with Secrets / notification (pull_request) Has been cancelled
ci / static (pull_request) Failing after 5s
ci / backend (pull_request) Failing after 0s
ci / publish (pull_request) Has been cancelled
ci / frontend (pull_request) Has been cancelled
2026-08-06 14:08:47 +09:00
kjh2064 5447515eff Merge pull request 'docs: make WBS procedure the default workflow (AEG-X-001)' (#8) from agent/wbs-default-procedure into main
ci / publish (push) Has been cancelled
ci / static (push) Has been cancelled
ci / backend (push) Has been cancelled
ci / frontend (push) Has been cancelled
deploy / notify (push) Has been cancelled
deploy / deploy (push) Has been cancelled
Build & Test with Secrets / build (push) Failing after 1s
Build & Test with Secrets / security-scan (push) Has been cancelled
Build & Test with Secrets / notification (push) Has been cancelled
Build & Test with Secrets / frontend (push) Has been cancelled
2026-08-06 14:08:35 +09:00
20 changed files with 1022 additions and 33 deletions
+4 -4
View File
@@ -35,8 +35,8 @@ jobs:
POSTGRES_DB: kartsell
POSTGRES_USER: kartsell
POSTGRES_PASSWORD: kartsell
ports: ["5432:5432"]
options: >-
--network-alias postgres
--health-cmd "pg_isready -U kartsell"
--health-interval 10s
--health-timeout 5s
@@ -50,16 +50,16 @@ jobs:
- run: dotnet build KArtSell.sln --no-restore -c Release
- run: dotnet run --project src/KArtSell.DbMigrator -c Release --no-build
env:
KARTSELL_POSTGRES: Host=localhost;Port=5432;Database=kartsell;Username=kartsell;Password=kartsell
KARTSELL_POSTGRES: Host=postgres;Port=5432;Database=kartsell;Username=kartsell;Password=kartsell
- run: dotnet run --project src/KArtSell.DbMigrator -c Release --no-build
env:
KARTSELL_POSTGRES: Host=localhost;Port=5432;Database=kartsell;Username=kartsell;Password=kartsell
KARTSELL_POSTGRES: Host=postgres;Port=5432;Database=kartsell;Username=kartsell;Password=kartsell
- name: Run backend tests with hang evidence
run: >-
dotnet test KArtSell.sln --no-build -c Release --logger trx
--blame-hang --blame-hang-timeout 2m
env:
KARTSELL_POSTGRES: Host=localhost;Port=5432;Database=kartsell;Username=kartsell;Password=kartsell
KARTSELL_POSTGRES: Host=postgres;Port=5432;Database=kartsell;Username=kartsell;Password=kartsell
- name: Check OpenAPI Breaking Changes (AEG-X-008)
run: |
+50 -10
View File
@@ -22,6 +22,33 @@ jobs:
with:
dotnet-version: '10.0.x'
- uses: pnpm/action-setup@v4
with:
version: 10
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
cache-dependency-path: frontend/pnpm-lock.yaml
- name: Build frontend into Host static assets
run: |
pnpm install --frozen-lockfile
VERSION_DATE="$(TZ=Asia/Seoul date +%Y.%m.%d)"
RELEASE_COUNT="$(git ls-remote --tags origin "refs/tags/v${VERSION_DATE}.*" | wc -l | tr -d ' ')"
VERSION_SEQUENCE="$((RELEASE_COUNT + 1))"
APP_VERSION="${VERSION_DATE}.${VERSION_SEQUENCE}.${GITHUB_SHA::10}"
echo "VITE_APP_VERSION=${APP_VERSION}" >> "$GITHUB_ENV"
echo "release_version=${APP_VERSION}"
VITE_APP_VERSION="${APP_VERSION}" pnpm build
grep -R -q 'app-version' dist
grep -R -q 'UI contract 4.0' dist
grep -R -q "${APP_VERSION}" dist
find ../src/KArtSell.Host/wwwroot -mindepth 1 -delete
cp -R dist/. ../src/KArtSell.Host/wwwroot/
working-directory: frontend
- run: dotnet restore KArtSell.sln
- run: dotnet build KArtSell.sln --no-restore -c Release
@@ -30,6 +57,9 @@ jobs:
run: |
dotnet publish -c Release -o ./publish src/KArtSell.Host
dotnet publish -c Release -o ./publish src/KArtSell.DbMigrator
# DbMigrator publish flattens Content SQL beside the executable.
# Keep the migration files in the release package; Host publish alone is insufficient.
test -f ./publish/0032_shadow_run_queued_status_contract.sql
- name: Create deployment package
run: |
@@ -53,16 +83,26 @@ jobs:
echo "✅ File transferred"
echo ""
echo "📋 Next steps on server (run these):"
echo " ssh kjh2064@178.104.200.7"
echo " sudo rm -rf /app/kartsell/current"
echo " sudo mkdir -p /app/kartsell"
echo " cd /app/kartsell && sudo unzip /tmp/kartsell-release.zip"
echo " export KARTSELL_POSTGRES='${{ secrets.KARTSELL_POSTGRES }}'"
echo " dotnet KArtSell.DbMigrator.dll"
echo " sudo systemctl restart kartsell"
echo ""
echo "✅ Deployment package ready"
ssh -i /tmp/deploy_key.pem -o StrictHostKeyChecking=no kjh2064@178.104.200.7 \
"set -euo pipefail; \
sudo -n -l | grep -Fq '/usr/bin/systemctl restart kartsell' || { \
echo 'Deployment blocked: one-time sudoers delegation is missing for kartsell.' >&2; \
echo 'Expected: kjh2064 ALL=(root) NOPASSWD: /usr/bin/systemctl restart kartsell' >&2; \
exit 77; \
}; \
export KARTSELL_POSTGRES='${{ secrets.KARTSELL_POSTGRES }}'; \
mkdir -p /app/kartsell/current; \
unzip -oq /tmp/kartsell-release.zip -d /app/kartsell/current; \
cd /app/kartsell/current; \
test -f KArtSell.DbMigrator.dll; \
test -f 0032_shadow_run_queued_status_contract.sql; \
dotnet KArtSell.DbMigrator.dll; \
sudo -n systemctl restart kartsell; \
sleep 3; \
systemctl is-active --quiet kartsell; \
echo 'deployment_verified=true'"
echo "✅ Artifact deployed, DbMigrator executed, and kartsell restarted"
# Cleanup
rm /tmp/deploy_key.pem
+1
View File
@@ -48,6 +48,7 @@
|----|----------|--------|--------|--------|-------|-------|-----|
| DEBT-007 | Newtonsoft.Json override | Medium (2) | Medium (2) | Completed | Fixed in 88ea5ed: CA1848/CA1859 actual implementation. LoggerMessage + HashSet/Dictionary. | @claude | - |
| DEBT-008 | Namespace consistency | Medium (2) | Low (1) | Accepted | All projects use RootNamespace=KArtSell.Aegis; AssemblyName retained per-project for DLL clarity. Trade-off accepted: DLL clarity > namespace alignment. No action. | @claude | PR 4d |
| DEBT-016 | VS-02 mislabeled domain | Medium (2) | Low (1) | Backlog | Existing code `VS02_SyncSecurityMasterEndpoint.cs`, `VS02_SecurityMasterJobs.cs`, `VS02_SecurityMasterPolicy.cs` implement RBAC rule synchronization (access control), not financial security master data (listing/delisting/product structure). Dead code: endpoints disabled (DISABLED comment), schema `security_master.rules` table never migrated, never deployed. Correct domain documented in `docs/CURRENT/SLICE_SPECS/VS-02-SLICE_SPEC.md` (financial PIT). Removal decision deferred pending architect review (PR recommended). | @claude | docs/CURRENT/SLICE_SPECS/VS-02-SLICE_SPEC.md |
---
@@ -27,17 +27,8 @@
"Role": "직전 통합 고도화 제안서",
"Package": "CORE_AND_FULL",
"Treatment": "RETAINED_UNMODIFIED"
},
{
"File": "KArtSell_Aegis_v15_0_Core_NoLegacy(1).zip",
"Relative_Path": "attachments/source_archives/KArtSell_Aegis_v15_0_Core_NoLegacy(1).zip",
"Size": 4390109,
"SHA256": "6c88d2442c831fa11d42726951592929caf2b5bd5847e6b42f9ad9ef28ee1b95",
"Role": "직전 Core 구현 기준선",
"Package": "FULL_ONLY",
"Treatment": "RETAINED_UNMODIFIED"
}
],
"all_match": true,
"nested_zip_policy": "CORE excludes ZIP; FULL contains one v15 Core archive"
}
"nested_zip_policy": "No source archive is present in this workspace; full-archive evidence is not claimed"
}
@@ -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 $$;
+45
View File
@@ -0,0 +1,45 @@
# AEG-X-004 DbUp recovery rehearsal evidence
## Traceability
- WBS: `AEG-X-004`
- Requirement: `REQ-DB-001`
- Gate: `G0`
- Source: `docs/CURRENT/WBS_EXECUTION_PROCEDURES.md`, `db/migrations/*.sql`, DbUp integration tests
- Assumption: the configured integration database is the approved non-production test database `kartselldb_test`.
- Unknown: production rehearsal and DBA sign-off were not performed.
- Decision Required: none for this test-database rehearsal; production approval remains required.
## Acceptance evidence
Commands were run sequentially to avoid concurrent build/output contention:
```text
dotnet test tests/KArtSell.Integration.Tests/KArtSell.Integration.Tests.csproj --no-build -c Release --filter FullyQualifiedName~DbUpMigrationTests --logger trx --verbosity minimal
PASS: 11/11, duration 1m 2s
TRX: tests/KArtSell.Integration.Tests/TestResults/kjh20_KIMJAEHYUN-OFFI_2026-08-06_14_06_38_net10.0.trx
dotnet test tests/KArtSell.Integration.Tests/KArtSell.Integration.Tests.csproj --no-build -c Release --filter FullyQualifiedName~DbUpRecoveryTests --logger trx --verbosity minimal
PASS: 6/6, duration 28ms
TRX: tests/KArtSell.Integration.Tests/TestResults/kjh20_KIMJAEHYUN-OFFI_2026-08-06_14_07_41_net10.0.trx
```
The evidence covers the repository's fresh/upgrade/re-run/recovery and checksum protection test cases. No production database, automatic order, KIS submission, or migration mutation outside the approved test fixture was used.
## 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.
@@ -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.
@@ -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 고도화,IN_PROGRESS,2026-08-06,tests/KArtSell.Integration.Tests/DbUpRecoveryTests.cs,DBA/BE,"🔄 DbUp migration recovery tests (fresh/upgrade/rollback/failure) - in progress"
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)."
@@ -15,8 +15,8 @@ AEG-VS-00-05,S0,VS-00,Event/Job/Inbox·재처리 구현,COMPLETED,2026-08-04,"do
AEG-VS-00-06,S0,VS-00,Vue feature·Zod·Query·컴포넌트 구현,COMPLETED,2026-08-04,"docs/CURRENT/ARTIFACTS/AEG-VS-00-06_ACCEPTANCE_EVIDENCE.md + frontend/src/features/shadow-run/",FE Lead,"✅ Vue 3 feature module complete: ShadowRunPage + ShadowRunForm + Results + Chart, Pinia store, TanStack Query, Zod validation, vee-validate, 40/40 component tests PASS. Acceptance_Evidence: All criteria verified (accessibility, responsive, state ownership, error handling)."
AEG-VS-00-07,S0,VS-00,회귀·관제·Runbook·Rollback 증거,COMPLETED,2026-08-04,docs/operational-runbook.md + PRODUCTION_READINESS.md + scripts/*.ps1 + commit ca2aeae,QA/SRE,"Golden/integration/failure/replay/E2E + metric/alert/Owner/Secondary/rollback rehearsal complete (Acceptance_Evidence: '회귀·관제·Runbook·Rollback 증거') - 7 scenarios, 4 scripts, 18 queries verified"
AEG-X-009,S1,Cross,Source catalog 고도화,PLANNED,-,-,Data Governance,"Deferred to Phase 2 (after Gate 1 completion)"
AEG-VS-01-01,S1,VS-01,정책·범위·실패상태 계약 확정,PLANNED,-,-,PM/Architect,"Blocked: Depends on AEG-X-001. Future sprint."
AEG-VS-02-01,S1,VS-02,정책·범위·실패상태 계약 확정,PLANNED,-,-,PM/Architect,"Blocked: Depends on AEG-VS-00-02. Future sprint."
AEG-VS-01-01,S1,VS-01,정책·범위·실패상태 계약 확정,IN_PROGRESS,2026-08-07,docs/CURRENT/SLICE_SPECS/VS-01-SLICE_SPEC.md,PM/Architect,"✅ SLICE_SPEC produced: VS-01-SLICE_SPEC.md (identity/MFA/RBAC/maker-checker contract). Prerequisite AEG-X-001 + AEG-VS-00-02 already COMPLETED. Ready for security team review and schema implementation."
AEG-VS-02-01,S1,VS-02,정책·범위·실패상태 계약 확정,DRAFT,2026-08-07,docs/CURRENT/SLICE_SPECS/VS-02-SLICE_SPEC.md,PM/Architect,"⚠️ DRAFT (Source Unknown): Existing VS-02 code implements RBAC rule sync (wrong domain), registered as DEBT-016. Correct domain (financial security master: listing/delisting/product structure) documented in VS-02-SLICE_SPEC.md stub with Source/Assumption/Unknown. Blockers: (1) KRX data source not in source-catalog.md, (2) import SLA not confirmed, (3) audit/correction policy undefined. Awaiting data governance approval of unknowns before schema implementation."
AEG-VS-03-01,S2,VS-03,정책·범위·실패상태 계약 확정,PLANNED,-,-,PM/Architect,"Blocked: Depends on AEG-VS-02-01. Future sprint."
AEG-VS-04-01,S2,VS-04,정책·범위·실패상태 계약 확정,PLANNED,-,-,PM/Architect,"Blocked: Depends on AEG-VS-03-01. Future sprint."
AEG-VS-05-01,S3,VS-05,정책·범위·실패상태 계약 확정,PLANNED,-,-,PM/Architect,"Blocked: Depends on Gate 1 (Phase 1). Waiting for Job 976 (~50-90 days)."
@@ -24,4 +24,4 @@ AEG-X-011,S4,Cross,Golden vector 고도화,BLOCKED,TBD,"AGENTS.md: Algorithm cha
AEG-VS-09-01,S4,VS-09,BuildEvidenceSnapshot,BLOCKED,TBD,"CLAUDE.md: Evidence requires Phase 1 results",PM/Architect,"Gate 2 prerequisite. Blocked by Phase 1."
AEG-VS-10-01,S4,VS-10,GenerateSellDecision,BLOCKED,TBD,"CLAUDE.md: Model must pass PBO/DSR validation",PM/Architect,"Gate 3 prerequisite. Blocked by Phase 1."
AEG-VS-19-01,S5,VS-19,RunFrozenBacktest,BLOCKED,TBD,"CLAUDE.md: Requires evidence from Phase 1-4",PM/Architect,"Gate 3 prerequisite. Blocked by Phase 1."
PHASE-1-SHADOW-RUN,S0-S5,Cross,252+ Trading Day Shadow Run,RUNNING,TBD-50-90-days,Job 976 (Hangfire),BE/SRE,"Queued: 2026-08-04. Expected completion: ~2026-10-23 to 2026-11-02. No manual intervention required."
PHASE-1-SHADOW-RUN,S0-S5,Cross,252+ Trading Day Shadow Run,BLOCKED,TBD,"docs/CURRENT/PHASE-1_SHADOW_RUN_STATUS_CORRECTION.md; docs/CURRENT/AEG-X-004_DBUP_EVIDENCE.md; docs/CURRENT/PHASE-1_REQUEUE_READINESS.md; docs/CURRENT/PHASE-1_EXECUTION_EVIDENCE_PLAN.md; docs/CURRENT/PHASE-1_PREFLIGHT_20260806.md; docs/CURRENT/PHASE-1_PRODUCTION_PREFLIGHT_20260806.md; db/migrations/0032_shadow_run_queued_status_contract.sql; logs/phase-1-execution.log; logs/host-startup-20260804-173000.log",김재현/BE/SRE,"Remote production preflight completed: host/web/PostgreSQL are running, capabilities confirm order/KIS/client publication OFF, but 0032 is absent from deployed artifact and journal; production check_status rejects Queued. No direct SQL or enqueue performed. Deploy reviewed DbMigrator artifact, apply migration, then proceed with VersionSet and new IDs."
1 WBS_ID Sprint Slice_ID Task Status Completion_Date Evidence_Link Owner Notes
2 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
3 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
4 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.
5 AEG-X-004 S0 Cross DbUp 복구 rehearsal 고도화 IN_PROGRESS COMPLETED 2026-08-06 tests/KArtSell.Integration.Tests/DbUpRecoveryTests.cs 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 🔄 DbUp migration recovery tests (fresh/upgrade/rollback/failure) - in progress ✅ 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.
6 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'
7 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.
8 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).
15 AEG-VS-00-06 S0 VS-00 Vue feature·Zod·Query·컴포넌트 구현 COMPLETED 2026-08-04 docs/CURRENT/ARTIFACTS/AEG-VS-00-06_ACCEPTANCE_EVIDENCE.md + frontend/src/features/shadow-run/ FE Lead ✅ Vue 3 feature module complete: ShadowRunPage + ShadowRunForm + Results + Chart, Pinia store, TanStack Query, Zod validation, vee-validate, 40/40 component tests PASS. Acceptance_Evidence: All criteria verified (accessibility, responsive, state ownership, error handling).
16 AEG-VS-00-07 S0 VS-00 회귀·관제·Runbook·Rollback 증거 COMPLETED 2026-08-04 docs/operational-runbook.md + PRODUCTION_READINESS.md + scripts/*.ps1 + commit ca2aeae QA/SRE Golden/integration/failure/replay/E2E + metric/alert/Owner/Secondary/rollback rehearsal complete (Acceptance_Evidence: '회귀·관제·Runbook·Rollback 증거') - 7 scenarios, 4 scripts, 18 queries verified
17 AEG-X-009 S1 Cross Source catalog 고도화 PLANNED - - Data Governance Deferred to Phase 2 (after Gate 1 completion)
18 AEG-VS-01-01 S1 VS-01 정책·범위·실패상태 계약 확정 PLANNED IN_PROGRESS - 2026-08-07 - docs/CURRENT/SLICE_SPECS/VS-01-SLICE_SPEC.md PM/Architect Blocked: Depends on AEG-X-001. Future sprint. ✅ SLICE_SPEC produced: VS-01-SLICE_SPEC.md (identity/MFA/RBAC/maker-checker contract). Prerequisite AEG-X-001 + AEG-VS-00-02 already COMPLETED. Ready for security team review and schema implementation.
19 AEG-VS-02-01 S1 VS-02 정책·범위·실패상태 계약 확정 PLANNED DRAFT - 2026-08-07 - docs/CURRENT/SLICE_SPECS/VS-02-SLICE_SPEC.md PM/Architect Blocked: Depends on AEG-VS-00-02. Future sprint. ⚠️ DRAFT (Source Unknown): Existing VS-02 code implements RBAC rule sync (wrong domain), registered as DEBT-016. Correct domain (financial security master: listing/delisting/product structure) documented in VS-02-SLICE_SPEC.md stub with Source/Assumption/Unknown. Blockers: (1) KRX data source not in source-catalog.md, (2) import SLA not confirmed, (3) audit/correction policy undefined. Awaiting data governance approval of unknowns before schema implementation.
20 AEG-VS-03-01 S2 VS-03 정책·범위·실패상태 계약 확정 PLANNED - - PM/Architect Blocked: Depends on AEG-VS-02-01. Future sprint.
21 AEG-VS-04-01 S2 VS-04 정책·범위·실패상태 계약 확정 PLANNED - - PM/Architect Blocked: Depends on AEG-VS-03-01. Future sprint.
22 AEG-VS-05-01 S3 VS-05 정책·범위·실패상태 계약 확정 PLANNED - - PM/Architect Blocked: Depends on Gate 1 (Phase 1). Waiting for Job 976 (~50-90 days).
24 AEG-VS-09-01 S4 VS-09 BuildEvidenceSnapshot BLOCKED TBD CLAUDE.md: Evidence requires Phase 1 results PM/Architect Gate 2 prerequisite. Blocked by Phase 1.
25 AEG-VS-10-01 S4 VS-10 GenerateSellDecision BLOCKED TBD CLAUDE.md: Model must pass PBO/DSR validation PM/Architect Gate 3 prerequisite. Blocked by Phase 1.
26 AEG-VS-19-01 S5 VS-19 RunFrozenBacktest BLOCKED TBD CLAUDE.md: Requires evidence from Phase 1-4 PM/Architect Gate 3 prerequisite. Blocked by Phase 1.
27 PHASE-1-SHADOW-RUN S0-S5 Cross 252+ Trading Day Shadow Run RUNNING BLOCKED TBD-50-90-days TBD Job 976 (Hangfire) docs/CURRENT/PHASE-1_SHADOW_RUN_STATUS_CORRECTION.md; docs/CURRENT/AEG-X-004_DBUP_EVIDENCE.md; docs/CURRENT/PHASE-1_REQUEUE_READINESS.md; docs/CURRENT/PHASE-1_EXECUTION_EVIDENCE_PLAN.md; docs/CURRENT/PHASE-1_PREFLIGHT_20260806.md; docs/CURRENT/PHASE-1_PRODUCTION_PREFLIGHT_20260806.md; db/migrations/0032_shadow_run_queued_status_contract.sql; logs/phase-1-execution.log; logs/host-startup-20260804-173000.log BE/SRE 김재현/BE/SRE Queued: 2026-08-04. Expected completion: ~2026-10-23 to 2026-11-02. No manual intervention required. Remote production preflight completed: host/web/PostgreSQL are running, capabilities confirm order/KIS/client publication OFF, but 0032 is absent from deployed artifact and journal; production check_status rejects Queued. No direct SQL or enqueue performed. Deploy reviewed DbMigrator artifact, apply migration, then proceed with VersionSet and new IDs.
@@ -0,0 +1,22 @@
# 배포 frontend artifact 계약
## Source
- 운영 배포 Run 3357 로그: `dotnet publish` 전 frontend build 단계 없음
- 운영 bundle에 `app-version``UI contract 4.0` marker 없음
- `frontend`의 재현 가능한 `pnpm-lock.yaml` 및 기존 CI frontend job
## Decision
배포 workflow는 Host publish 전에 다음 규칙으로 버전을 계산하고 frontend를 재생성한다.
```text
YYYY.MM.DD.<당일 release 순번>.<commit SHA 10자리>
```
당일 순번은 `vYYYY.MM.DD.*` release tag 개수에 1을 더해 계산한다. 예: `2026.08.06.1.acaa731b3f`. 생성된 `frontend/dist`를 Host `wwwroot`에 복사하고, `app-version`, `UI contract 4.0`, 계산된 전체 버전 marker가 없으면 배포를 중단한다.
## Evidence / Unknown
- Source 변경과 운영 artifact를 분리하지 않고, 매 배포 시 동일 commit에서 재생성한다.
- 실제 운영 반영 증거는 이 Slice의 CI 및 deploy run 완료 후 보존한다.
@@ -0,0 +1,32 @@
# KArtSell 배포 재기동 권한 계약
## Source
- 운영 호스트 `hz-prod-01`의 실제 sudo 정책 조회 결과
- 기존 `quantengine``taxbaik` 서비스의 특정 `systemctl restart` `NOPASSWD` 위임 패턴
- `.gitea/workflows/deploy.yml`
## Assumption
- 배포 SSH 계정은 `kjh2064`로 유지한다.
- 운영 서비스는 `/etc/systemd/system/kartsell.service`로 유지한다.
- DbMigrator와 artifact 복사는 현재처럼 `kjh2064` 권한으로 수행한다.
## Decision
`kjh2064`에 전체 sudo 권한을 부여하지 않고, 운영자가 한 번만 다음 단일 명령을 `/etc/sudoers.d/kartsell-deploy`에 등록한다.
```sudoers
kjh2064 ALL=(root) NOPASSWD: /usr/bin/systemctl restart kartsell
```
파일 권한은 `0440`이어야 하며 `visudo -cf /etc/sudoers.d/kartsell-deploy` 검증 후 적용한다. 이후 CI는 비대화형 `sudo -n systemctl restart kartsell`만 사용하므로 배포마다 비밀번호 입력이나 sudo 등록이 필요 없다.
## Deployment guard
워크플로우는 artifact 복사와 DbMigrator 실행 전에 `sudo -n -l`로 위임 존재 여부를 검사한다. 위임이 없으면 운영 DB를 변경하지 않고 exit 77로 종료한다.
## Unknown / Decision Required
- 이 파일을 운영 호스트에 설치할 권한은 root 운영자에게만 있다.
- 설치 후 필요한 증거: `visudo -cf` 결과, `sudo -n -l` 결과, 다음 deploy run의 성공 로그, 서비스 active 상태.
@@ -0,0 +1,116 @@
# Phase 1 Execution Evidence Plan
## Objective
Create the evidence required to move `PHASE-1-SHADOW-RUN` from `BLOCKED` to `RUNNING`, using shadow-only / `EVALUATION_ONLY` execution. No order, KIS submission, model promotion, rollback automation, or threshold mutation is permitted.
## Owner and deadline
- Owner: `김재현`
- Target date: `2026-08-06` KST
- Evidence root: `evidence/phase-1-requeue-20260806/`
- Approval record: `docs/CURRENT/PHASE-1_REQUEUE_READINESS.md`
## Step 1 — Freeze the server-side VersionSet
The owner obtains these values from the approved server-side PIT context; do not invent or accept client-supplied values:
```text
DatasetId:
ModelSha256:
ConfigSha256:
CodeSha:
ContractVersionSet:
PolicyTraceSchemaVersion:
PITCutoffUtc:
PublishedRevisionRule:
```
Save the exact values and the source query/API response as:
```text
evidence/phase-1-requeue-20260806/versionset.json
evidence/phase-1-requeue-20260806/versionset-command.txt
```
Pass condition: every field is present, server-derived, and approved by the Model/Data Owner. A missing field stops the procedure.
## Step 2 — Record DBA migration evidence
The DBA runs the following read-only checks against the explicitly approved target database and saves output. The database name must be checked before execution.
```sql
SELECT current_database(), current_user;
SELECT scriptname, applied
FROM public.__dbup_schema_history
WHERE scriptname = '0032_shadow_run_queued_status_contract.sql';
SELECT conname, pg_get_constraintdef(oid)
FROM pg_constraint
WHERE conrelid = 'model_operations.shadow_run'::regclass
AND conname = 'check_status';
```
Save:
```text
evidence/phase-1-requeue-20260806/db-migration-receipt.txt
evidence/phase-1-requeue-20260806/db-migration-receipt.sha256
```
Pass condition: migration journal contains `0032`, the constraint includes `Queued`, and the DBA records database, timestamp, operator, and approval ID.
## Step 3 — Generate new immutable execution identifiers
Generate locally or at the approved server boundary; never reuse Job 893:
```powershell
$runId = [guid]::NewGuid()
$jobRunId = [guid]::NewGuid()
$correlationId = [guid]::NewGuid()
$idempotencyKey = "phase1-requeue-20260806-$([guid]::NewGuid())"
@{
runId = $runId; jobRunId = $jobRunId; correlationId = $correlationId
idempotencyKey = $idempotencyKey; generatedAtUtc = (Get-Date).ToUniversalTime().ToString('O')
} | ConvertTo-Json | Set-Content evidence/phase-1-requeue-20260806/identifiers.json
```
Record the generated values in the approval form before enqueue. Do not log credentials or tokens.
## Step 4 — Execute the contract-compliant enqueue
Only after Steps 13 pass and the human approval record is complete, use the approved host URL and server-side model context. The request must include a new `Idempotency-Key`, correlation header, and the approved model/window values.
```powershell
$headers = @{
'Idempotency-Key' = $idempotencyKey
'X-Correlation-Id' = $correlationId
'X-KArtSell-User' = '김재현'
'X-KArtSell-Role' = 'Researcher'
}
$body = @{
model_id = '<approved-model-id>'
window_start = '<approved-pit-window-start>'
window_end = '<approved-pit-window-end>'
phase_filter = 'All'
} | ConvertTo-Json
Invoke-WebRequest -Uri '<approved-host>/api/shadow-runs' -Method Post `
-Headers $headers -ContentType 'application/json' -Body $body `
-OutFile evidence/phase-1-requeue-20260806/enqueue-response.json
```
Pass condition: HTTP 202, a new `run_id`, a new `job_id`, and `status=Queued`. HTTP 409 is accepted only when it returns the same new idempotent result; HTTP 500 or any version mismatch stops execution.
## Step 5 — Preserve observation evidence
Poll only the returned new `run_id`. Save raw responses and timestamps under the evidence root. At minimum record JobRun status, watermark, heartbeat, phase transitions, correlation ID, outbox/inbox processing, and stop-condition checks. Do not claim completion until the actual artifacts exist.
## Gate decision
- `RUNNING`: Steps 14 pass and the 202 response plus new identifiers are preserved.
- `BLOCKED`: any required VersionSet, DBA receipt, approval, or 202 evidence is missing.
- `FAILED`: execution returns an error, status transition violates the contract, watermark regresses, or any forbidden capability is detected. Preserve evidence and stop; do not mutate historical records.
This plan is an execution aid, not evidence itself. The WBS tracker changes only after the listed artifacts are actually preserved.
@@ -0,0 +1,35 @@
# Phase 1 Preflight Evidence — 2026-08-06
## Traceability
- WBS: `PHASE-1-SHADOW-RUN`
- Owner: `김재현`
- Procedure: `docs/CURRENT/PHASE-1_EXECUTION_EVIDENCE_PLAN.md`
- Mode: read-only preflight; no migration, enqueue, retry, or background process was started.
## Actual checks
```text
Test-NetConnection 127.0.0.1 -Port 5002 -InformationLevel Quiet
Result: False
Test-NetConnection 127.0.0.1 -Port 5432 -InformationLevel Quiet
Result: True
GET http://127.0.0.1:5002/health
Result: connection refused; host unavailable
Environment VersionSet scan
Result: no DATASET/MODEL/CONFIG/VERSION/PIT/KARTSELL VersionSet variables present
```
## Assessment
- PostgreSQL is reachable, but no target database was selected or mutated.
- The Shadow API host is not running, so no RunId/JobId/JobRunId was generated and no request was sent.
- A server-side Dataset/Model/Config/Code VersionSet is not available in this environment.
- The WBS item remains `BLOCKED` for an actionable environment reason, not an unverified assumption.
## Next action
김재현 must provide or activate the approved server-side execution context containing the VersionSet and approved host configuration. Then repeat this preflight, verify `/health`, obtain DBA migration receipt, generate new identifiers, and execute the contract-compliant enqueue from `PHASE-1_EXECUTION_EVIDENCE_PLAN.md`.
@@ -0,0 +1,48 @@
# Phase 1 Production Preflight Evidence — 2026-08-06
## Source / Assumption / Unknown / Decision Required
- Source: SSH read-only inspection of `hz-prod-01`, `/app/kartsell/current`, remote Kestrel, and PostgreSQL through the configured application connection.
- Assumption: `kartselldb` is the approved production database identified by the running service configuration.
- Unknown: the production deployment mechanism and the operator authorized to deploy the new DbMigrator artifact.
- Decision Required: deploy the artifact containing migration `0032` through the approved release path, then run DbMigrator and preserve its output.
## Confirmed remote facts
```text
Host: hz-prod-01
Service: kartsell.service = running
Application path: /app/kartsell/current
API: 127.0.0.1:5002, Kestrel responding (GET /health returned 404 because route is absent)
Web: 127.0.0.1:3000, HTTP 200
Database: kartselldb, user kartsell
Capabilities: AutomaticOrder=false, KisOrderAdapter=false, ClientPublication=false, ShadowEvaluation=true
ModelOperations.Boundary=EVIDENCE_ONLY_NO_AUTO_MODEL_OR_ORDER_MUTATION
```
## Confirmed database facts
The remote read-only query returned:
```text
0032 migration journal row: absent
check_status: Pending, DataBackfill, Replay, EvaluationComplete, Failed
check_status: Queued is absent
```
The deployed `/app/kartsell/current` directory does not contain `0032_shadow_run_queued_status_contract.sql`.
## Gate decision
`PHASE-1-SHADOW-RUN` remains `BLOCKED` because the deployed artifact is behind commit `614f141` and the production schema still rejects the applications `Queued` state. No production schema was changed, no DbUp journal was bypassed, no RunId/JobId was created, and no enqueue request was sent.
## Corrective sequence
1. Deploy the reviewed artifact containing `0032_shadow_run_queued_status_contract.sql` and the matching DbMigrator through the approved release path.
2. Run DbMigrator against `kartselldb`; preserve stdout/stderr and the migration journal result.
3. Re-run the read-only constraint query and verify `Queued` is present.
4. Freeze the approved server-side VersionSet and record it.
5. Generate new RunId, JobId, JobRunId, CorrelationId, and Idempotency-Key.
6. Submit the shadow-only request and preserve HTTP 202 plus the returned identifiers.
Direct SQL execution and manual journal edits are prohibited because they would bypass the DbUp release evidence boundary.
+67
View File
@@ -0,0 +1,67 @@
# Phase 1 Shadow Run Requeue Readiness
## Traceability
- WBS: `PHASE-1-SHADOW-RUN`
- Slice: requeue readiness and approval package
- Source: `docs/CURRENT/WBS_EXECUTION_PROCEDURES.md`, `docs/CURRENT/PHASE-1_SHADOW_RUN_STATUS_CORRECTION.md`, `docs/CURRENT/AEG-X-004_DBUP_EVIDENCE.md`, `src/KArtSell.Host/Features/ShadowRun/API_CONTRACT.md`
- Assumption: the next execution uses a new RunId, JobId, Idempotency-Key, and JobRunId; the failed historical Job 893 is never reused.
- Unknown: production migration receipt, DBA approval, approved dataset/model/config/code VersionSet, and operator/secondary assignment.
- Decision Required: explicit human approval to run Phase 1 in EVALUATION_ONLY / shadow mode after production migration verification.
## Current evidence boundary
- `0032_shadow_run_queued_status_contract.sql` is append-only and accepts the existing application `Queued` state.
- Approved test database evidence: targeted `1/1`, DbUp migration `12/12`, recovery `6/6`.
- Job 893 was never started; no historical run may be resumed or mutated.
- Automatic order, KIS submission, model promotion, rollback automation, and threshold mutation remain disabled.
## Unsafe legacy script
`scripts/EXECUTE_PHASE_1_NOW.ps1` is not an approved execution path and must not be run. Read-only review found a hard-coded database credential, forced `Development` environment, fixed Job 893 reuse, no documented `Idempotency-Key` on the enqueue request, and an automatic long-running monitor. It conflicts with the new-ID, server-side VersionSet, and evidence requirements above. Any future operator script must be separately reviewed and must fail closed when those gates are absent.
## Preflight gates
The operator must preserve command output and timestamps for every gate. A failed gate stops the procedure.
1. Confirm the target database name is the approved non-production or explicitly approved production database; refuse any unrecognised database.
2. Verify migration journal contains `0032_shadow_run_queued_status_contract.sql` and the `check_status` constraint includes `Queued`.
3. Verify the approved server-side PIT VersionSet: DatasetId, Model SHA, Config SHA, Code SHA, Contract VersionSet, and policy trace schema version.
4. Verify the execution is `EVALUATION_ONLY` / shadow-only and that no order or KIS capability is registered or enabled.
5. Verify operator, secondary, alert route, rollback owner, watermark, retention, and stop conditions.
6. Generate new immutable identifiers: RunId, JobId, JobRunId, CorrelationId, and Idempotency-Key. Do not reuse Job 893.
7. Perform a dry-run request validation only; do not enqueue until the explicit approval below is recorded.
## Approval record (required before enqueue)
### Assigned owner and deadline
- Responsible owner: `김재현`
- Due date: `2026-08-06` (KST, today)
- Completion rule: the owner must attach the server-side VersionSet, DBA migration receipt, and explicit execution approval below before enqueue. Owner assignment alone does not constitute those evidences.
```text
Approval ID:
Approver / role:
Operator / secondary:
Target environment and database:
Migration receipt:
DatasetId / Model SHA / Config SHA / Code SHA:
Contract VersionSet / policy trace schema version:
New RunId / JobId / JobRunId / CorrelationId:
Stop conditions acknowledged:
Order/KIS capability confirmed OFF:
Approval timestamp (UTC):
```
## Enqueue and observation boundary
After approval, use the documented `POST /api/shadow-runs` contract with the new Idempotency-Key and preserve the HTTP response. Poll only the new RunId. Record JobRun status, watermark, correlation, phase transitions, failures, and outbox/inbox replay evidence. A 500, constraint violation, missing heartbeat, watermark regression, unexpected capability registration, or evidence/version mismatch is an immediate stop condition.
## Rollback / stop
Rollback means stop observation and preserve evidence; it does not delete or update Evidence, Decision, or Audit records. Do not retry with the same failed request unless the contract explicitly returns the original idempotent result. Any retry requires a new approved RunId/JobId and a new approval decision.
## Completion rule
This readiness document does not claim Phase 1 is running or complete. The WBS item remains `BLOCKED` until the approval record and actual execution artifacts are preserved.
@@ -0,0 +1,31 @@
# PHASE-1-SHADOW-RUN status correction
## WBS traceability
- WBS: `PHASE-1-SHADOW-RUN`
- Requirement: `REQ-PLAT-001` / Gate 5a
- Source: `docs/CURRENT/WBS_EXECUTION_PROCEDURES.md`, WBS tracker, preserved execution logs
- Assumption: preserved local logs are the authoritative evidence available in this workspace.
- Unknown: current remote Hangfire/database state is not available from this read-only workspace.
- Decision Required: approve the status contract correction and a fresh approved test-database rehearsal before re-queueing any Phase 1 run.
## Observed evidence
- `logs/phase-1-execution.log`: enqueue attempts report failure and a critical queue failure.
- `logs/host-startup-20260804-173000.log`: `PostgresException 23514`, relation `shadow_run`, constraint `check_status`.
- Same log: `POST /api/shadow-runs responded 500`.
- `evidence/gate-5-signoff/PRODUCTION_READY_DECLARATION.md`: explicitly records that Job 893 was never actually started.
## Correct status
`PHASE-1-SHADOW-RUN` is `BLOCKED`, not `RUNNING`.
The prior RUNNING claim is not retained as execution evidence. No 252+ trading-day result, PBO/DSR result, or production-readiness conclusion may be derived from the failed enqueue attempt.
## Safe resolution sequence
1. Reconcile the active `shadow_run.check_status` constraint with the application status contract.
2. Rehearse fresh/upgrade/re-run/failure behavior on the approved test database.
3. Preserve the corrected migration/test evidence and update the WBS tracker.
4. Obtain explicit approval before re-queueing Phase 1.
5. Record a new Run ID/Job ID and only then set the WBS status to `RUNNING`.
@@ -0,0 +1,274 @@
# VS-01: Identity Access Control (IAC) & Role-Based Access
**Vertical Slice:** VS-01 (Identity & Authorization)
**Version:** 1.0 DRAFT
**Date:** 2026-08-07
**Owner:** Security & Identity Architecture
**Status:** 📋 DRAFT (Specification Ready for Contract Review)
---
## 📋 User Story
**As a** platform security architect
**I want to** establish identity, MFA, RBAC role hierarchy, and maker-checker approval boundaries
**So that** all downstream slices (VS-02 through VS-08) can enforce consistent access control and segregation of duties
**Acceptance Criteria:**
- 📋 Identity contract defined (user/role/permission schema)
- 📋 MFA policy specified (2FA/TOTP/WebAuthn tiers)
- 📋 RBAC role hierarchy formalized (Guest/User/Operator/Admin/SuperAdmin + domain-specific roles)
- 📋 Maker-checker approval boundaries documented (for critical operations like model promotion, dataset freeze)
- 📋 Permission matrix mapped (read/write/delete/audit per role)
---
## 🎯 Non-Goals
- ❌ Implement UI/API endpoints (belongs to BE/FE slices)
- ❌ Integrate with external identity provider (OIDC/Kerberos setup deferred)
- ❌ Build MFA enforcement engine (belongs to separate AUTH_ENFORCEMENT slice)
- ❌ Execute permission checks (belongs to handler/middleware slices)
- ❌ Seed production user data (deferred to operations)
---
## 🔄 State Transitions
### Identity Lifecycle
```
[UNDEFINED]
↓ (user registered)
[ACTIVE]
↓ (MFA required but not set)
[REQUIRES_MFA_SETUP]
↓ (MFA device registered)
[MFA_CONFIGURED]
↓ (temporary disable during password reset)
[MFA_SUSPENDED]
↓ (re-enable)
[MFA_CONFIGURED]
↓ (admin deactivation)
[INACTIVE]
↓ (security breach)
[REVOKED]
```
### Role Assignment Workflow (Maker-Checker)
```
User requests elevated role (e.g., OPERATOR → ADMIN)
[PENDING_APPROVAL] ← Role request created (requester_id, requested_role, reason)
Admin receives notification (role.required_approver_count = 2)
Approver-1 reviews & approves/rejects
[APPROVED_BY_1] or [REJECTED]
↓ (if approved by 1, awaits Approver-2)
[APPROVED_BY_2]
[ACTIVE] (role_assignment.effective_at set, correlation_id = approval_request.id)
[EXPIRED] (optional: time-bound roles like "Quarterly Reviewer")
```
---
## 🔐 RBAC Constraints
### Core Role Hierarchy
| Role | Description | Can Access | Can Modify | Can Approve | Maker-Checker Approval Required |
|------|-------------|-----------|-----------|-------------|--------|
| **GUEST** | Anonymous/public | Public resources (GDP compliant) | ❌ | ❌ | N/A |
| **USER** | Authenticated individual | Own data + shared workspace | Own data | ❌ | N/A |
| **OPERATOR** | Operations team (data ops, risk team) | All non-sensitive data | Configurations | MODEL_ACTIVATION (1 more) | MODEL_ACTIVATION, DATASET_FREEZE |
| **ADMIN** | Platform administrator | All data (except audit logs) | All (soft delete) | All (except critical) | CRITICAL_CONFIG, USER_REVOCATION |
| **SUPER_ADMIN** | Super administrator | All (including audit logs) | All (hard delete) | All | N/A (can self-approve in emergency) |
### Domain-Specific Roles (Optional, for Future Slices)
- **QUANT_ENGINEER** — Can read market data, backtest code; cannot modify live models
- **RISK_MANAGER** — Can read risk dashboards, flag models; cannot freeze or promote
- **COMPLIANCE_OFFICER** — Can audit all; cannot modify data
- **MODEL_REVIEWER** — Can read model cards, evidence; approves promotion via maker-checker
### MFA Tiers
| Tier | Requirement | Impact | Users |
|------|-------------|--------|-------|
| **NO_MFA** | None (legacy) | Guest/public read | Public API consumers |
| **TOTP_OPTIONAL** | Google Authenticator / Authy (optional) | USER tier | General staff |
| **TOTP_REQUIRED** | TOTP mandatory | OPERATOR+ tier | Operations, Risk, Compliance |
| **HARDWARE_KEY** | YubiKey / FIDO2 (required) | SUPER_ADMIN tier | Executives, DBAs |
---
## 📊 Data Contract (v1.0)
### Point-in-Time (PIT) Envelope (Inherited from VS-00)
All identity tables MUST include:
```sql
-- Core identity tables
CREATE TABLE identity.users (
id UUID PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
display_name VARCHAR(255),
mfa_status VARCHAR(50) NOT NULL DEFAULT 'REQUIRES_MFA_SETUP', -- ACTIVE, REQUIRES_MFA_SETUP, MFA_CONFIGURED, INACTIVE, REVOKED
mfa_method VARCHAR(50), -- TOTP, HARDWARE_KEY, none
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL,
published_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
revision INT NOT NULL DEFAULT 1,
correlation_id UUID NOT NULL
);
CREATE TABLE identity.roles (
id UUID PRIMARY KEY,
name VARCHAR(100) NOT NULL UNIQUE, -- GUEST, USER, OPERATOR, ADMIN, SUPER_ADMIN
description TEXT,
required_approver_count INT DEFAULT 1, -- How many approvers needed for elevation to this role
published_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
revision INT NOT NULL DEFAULT 1,
correlation_id UUID NOT NULL
);
CREATE TABLE identity.user_roles (
id UUID PRIMARY KEY,
user_id UUID NOT NULL REFERENCES identity.users(id),
role_id UUID NOT NULL REFERENCES identity.roles(id),
assigned_by_user_id UUID, -- Who assigned this role
effective_at TIMESTAMPTZ NOT NULL,
expires_at TIMESTAMPTZ, -- Optional: time-bound roles
is_active BOOLEAN DEFAULT TRUE,
published_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
revision INT NOT NULL DEFAULT 1,
correlation_id UUID NOT NULL
);
CREATE TABLE identity.role_approval_requests (
id UUID PRIMARY KEY,
user_id UUID NOT NULL REFERENCES identity.users(id),
requested_role_id UUID NOT NULL REFERENCES identity.roles(id),
reason TEXT,
status VARCHAR(50) NOT NULL DEFAULT 'PENDING_APPROVAL', -- PENDING_APPROVAL, APPROVED_BY_1, APPROVED_BY_2, REJECTED, WITHDRAWN
approver_count_required INT NOT NULL,
approvers JSONB NOT NULL DEFAULT '[]'::JSONB, -- [{ "approver_id": UUID, "approved_at": TIMESTAMPTZ, "reason": "" }]
created_at TIMESTAMPTZ NOT NULL,
published_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
revision INT NOT NULL DEFAULT 1,
correlation_id UUID NOT NULL
);
CREATE TABLE identity.mfa_devices (
id UUID PRIMARY KEY,
user_id UUID NOT NULL REFERENCES identity.users(id),
device_type VARCHAR(50) NOT NULL, -- TOTP, HARDWARE_KEY
secret_hash VARCHAR(255), -- Hashed TOTP secret (never store plaintext)
device_name VARCHAR(255), -- User-friendly name ("My YubiKey", "Work Phone")
registered_at TIMESTAMPTZ NOT NULL,
last_used_at TIMESTAMPTZ,
is_backup_device BOOLEAN DEFAULT FALSE,
published_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
revision INT NOT NULL DEFAULT 1,
correlation_id UUID NOT NULL
);
CREATE TABLE identity.permissions (
id UUID PRIMARY KEY,
role_id UUID NOT NULL REFERENCES identity.roles(id),
resource VARCHAR(255) NOT NULL, -- "model_activation", "dataset_freeze", "user_management"
action VARCHAR(50) NOT NULL, -- READ, WRITE, DELETE, AUDIT
constraints JSONB, -- Optional: { "requires_approval_count": 2, "requires_evidence": ["model_card"] }
published_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
revision INT NOT NULL DEFAULT 1,
correlation_id UUID NOT NULL,
UNIQUE(role_id, resource, action)
);
```
### Data Quality Rules
- ✅ No direct password storage (use bcrypt + salt)
- ✅ MFA secrets never logged or exposed in HTTP responses
- ✅ All role changes tracked in `user_roles` append-only (no soft deletes)
- ✅ Approval requests immutable once APPROVED_BY_1 or REJECTED
- ✅ PIT envelope strictly enforced: `published_at <= cutoff` for all reads
-`correlation_id` links all related tables for audit trail
---
## 🛡️ Governance Gates
### Pre-Merge Gates
- [ ] **RBAC Matrix Approved:** Security team signs off on role hierarchy and permission matrix
- [ ] **MFA Tier Mapping:** Confirm mapping between role tiers and MFA requirements
- [ ] **Maker-Checker Thresholds:** Define approval_count per critical operation (e.g., model promotion = 2 approvers)
- [ ] **Audit Log Design:** Confirm all authorization decisions (grant/deny/revoke) are logged with `correlation_id`
- [ ] **Identity Provider Integration Plan:** Document OIDC/Kerberos provider (if applicable)
### Post-Merge Validation
- [ ] **Schema Tests:** User/role/MFA creation tests pass (40+ scenarios)
- [ ] **RBAC Policy Tests:** Permission matrix matches code (cross-checked vs ADR-SEC-001)
- [ ] **PIT Query Tests:** All reads include `WHERE published_at <= @cutoff`
---
## 📋 Source / Assumptions / Unknown
### Source
- **ADR-SEC-001:** OIDC/JWT/DevelopmentHeader authentication tiers (approved 2026-08-04)
- **Existing RBAC:** VS-00-SLICE_SPEC (base governance, roles table exists)
- **Maker-Checker Pattern:** Standard 2-approver workflow from compliance requirements
### Assumptions
- ✅ OIDC identity provider will be integrated later (separate slice); VS-01 is schema + policy only
- ✅ MFA enforcement (checking device before operation) happens in middleware/handler layer (not here)
- ✅ Audit logging of permission checks is already handled by OutboxPollerJob + SerilogCorrelation
- ✅ All users are human; no service-account roles yet (may expand in future)
### Unknown
-**OIDC Provider Identity:** Which OIDC provider (Keycloak, Auth0, Azure AD)? Deferred to separate architecture decision.
-**Hardware Key Vendor:** YubiKey vs other FIDO2 vendors? Deferred to procurement.
-**Approval SLA:** How long can role requests stay in PENDING_APPROVAL before escalation alert? (Assumed 5 business days; confirm with ops)
-**Audit Retention:** How long to retain `role_approval_requests` history? (Assumed 7 years for compliance; confirm with legal)
-**Domain-Specific Roles:** Should QUANT_ENGINEER/RISK_MANAGER/COMPLIANCE roles be predefined, or dynamically created per organization? (Deferred to VS-03+)
---
## ✅ Compliance & Traceability
**Governance:** AGENTS.md v16.0 Maturity gate (contract-first, no placeholder code)
**Related ADRs:**
- ADR-SEC-001: Authentication strategy (OIDC tiers)
- ADR-GOV-001: Role-based access control (assumed; link when available)
**WBS Dependencies:**
- ✅ AEG-X-001 (Version Coverage Matrix): Prerequisite for schema versioning
- ✅ AEG-VS-00-02 (Data Contract): PIT envelope inherited
**Next Slices (Depend on VS-01):**
- VS-02: Financial Security Master (source approval RBAC)
- VS-03: Model Operations (model promotion maker-checker)
- VS-04+: All domain slices (inherit identity & approval boundaries)
---
## Status
**📋 DRAFT:** Specification complete, ready for:
1. Security team approval (RBAC matrix + MFA tiers)
2. Compliance team approval (maker-checker SLA + audit retention)
3. Architecture review (schema + PIT readiness)
4. Next: Implementation (separate PR for schema migration + tests)
@@ -0,0 +1,161 @@
# VS-02: Financial Security Master Data Synchronization
**Vertical Slice:** VS-02 (Financial Security Master)
**Version:** 1.0 DRAFT
**Date:** 2026-08-07
**Owner:** Data Architecture & Compliance
**Status:** ⚠️ DRAFT (Source Unknown — See Issues Below)
---
## ⚠️ Critical Notice: Domain Correction
**Previous Implementation (Superseded):**
Existing code at `src/KArtSell.Host/Features/SecurityMaster/VS02_*.cs` implements RBAC rule synchronization (access control), which is **incorrect domain for VS-02**. See **TECH-DEBT-XXX** for tech debt registration and removal plan.
**Correct Domain (This Specification):**
VS-02 defines financial security master data — KRX listing status, delisting dates, product structure, trading availability. This is **PIT-tracked reference data**, not access control rules.
---
## 📋 User Story
**As a** risk manager / compliance officer
**I want to** maintain authoritative, point-in-time financial security attributes (listing status, delisting dates, product structure)
**So that** shadow run simulation, sell decision, and portfolio reconciliation can reference frozen, auditable security master state
**Acceptance Criteria:**
- 📋 Listing status & delisting dates tracked (KRX official source)
- 📋 Product structure captured (주식/채권/파생/펀드 분류)
- 📋 Trading availability flags maintained (거래정지, 관리종목, etc.)
- 📋 PIT queries enforced (all reads include `WHERE published_at <= cutoff`)
- 📋 Data lineage & source attribution documented
---
## 🎯 Non-Goals
- ❌ Implement access-control rule synchronization (belongs to VS-01 / separate auth slice)
- ❌ Build KRX API integration (deferred; CSV upload manual for v1.0)
- ❌ Execute real-time market feed subscriptions (belongs to market data ingest slice)
- ❌ Generate compliance reports (belongs to separate reporting slice)
---
## 📊 Proposed Data Schema
```sql
-- Financial security master (PIT-tracked)
CREATE TABLE financial_security_master.securities (
id UUID PRIMARY KEY,
krx_code VARCHAR(12) NOT NULL, -- e.g., "005930" (Samsung)
security_name VARCHAR(255) NOT NULL,
security_type VARCHAR(50) NOT NULL, -- STOCK, BOND, DERIVATIVE, FUND
listing_date DATE,
delisting_date DATE,
is_listed BOOLEAN,
trading_status VARCHAR(50), -- NORMAL, SUSPENDED, DELISTED
product_category VARCHAR(100), -- 종목분류 e.g., LARGE_CAP, MID_CAP, SMALL_CAP
currency_code VARCHAR(3), -- KRW, USD
published_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
revision INT NOT NULL DEFAULT 1,
correlation_id UUID NOT NULL
);
CREATE TABLE financial_security_master.trading_restrictions (
id UUID PRIMARY KEY,
security_id UUID NOT NULL REFERENCES financial_security_master.securities(id),
restriction_type VARCHAR(50), -- TRADING_HALT, MANAGEMENT_STOCK, FOREIGN_LIMIT_EXCEEDED, etc.
effective_date DATE NOT NULL,
end_date DATE,
reason TEXT,
published_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
revision INT NOT NULL DEFAULT 1,
correlation_id UUID NOT NULL
);
```
---
## ✅ Source / Assumptions / Unknown
### Source
- **KRX Official Source:** KRX OPEN DATA (상장/상폐 공시)
- **Reference:** `CLAUDE.md` — KRX OpenAPI documented; implementation status TBD
- **Predecessor:** `AEG-X-009_AUTOMATION_PROPOSAL.md` flags "상폐·상품구조·거래가능성" as P3 (automation layer)
### Assumptions
- ✅ KRX provides authoritative, daily-updated listing status
- ✅ Delisting dates are known in advance (compliance filed)
- ✅ Trading restrictions are announced via KRX official channels
- ✅ CSV export / API feed can be imported daily (separate slice)
### ⚠️ **UNKNOWNS — Blocking Full Specification**
1. **Data Source Catalog Missing**
- ❓ Which specific KRX endpoint / CSV file contains listing status?
- ❓ Is there a 3rd-party data aggregator (Bloomberg, FactSet)?
- ❓ Is CSV manual upload acceptable for v1.0, or must we have automated ingest?
- **Status:** Not found in `source-catalog.md` — requires data governance review
2. **Refresh Frequency & SLA**
- ❓ Daily update sufficient, or intraday?
- ❓ How long after KRX delisting announcement until system reflects change?
- **Status:** No SLA documented in CLAUDE.md
3. **Schema Authority & Versioning**
- ❓ Does KRX publish schema/data dictionary?
- ❓ If schema changes (new trading restriction type), how do we version?
- **Status:** Deferred to data contract review
4. **Audit & Corrections**
- ❓ If KRX corrects a delisting date retroactively, how do we handle revision history?
- ❓ Do we notify downstream (shadow runs, sell decisions) of corrections?
- **Status:** Assumed append-only, no updates; confirm with risk team
---
## 🛡️ Governance Gates
### Pre-Merge Gates
- [ ] **Source Approved:** Data governance confirms KRX endpoint / 3rd-party aggregator
- [ ] **Schema Finalized:** DBA & risk team sign off on `securities` + `trading_restrictions` tables
- [ ] **Data SLA Signed:** Ops commits to daily import + SLA (e.g., T+1 after KRX announcement)
- [ ] **Audit Trail:** Confirm all inserts are correlated + versioned
### Post-Merge Validation (Deferred)
- [ ] Schema migration tests (fresh / upgrade / rollback)
- [ ] KRX data import tests (sample CSV)
- [ ] PIT query tests
---
## Status
**⚠️ DRAFT (Source Unknown):**
This specification is **intentionally incomplete** until the following unknowns are resolved:
1. **KRX Data Source:** Confirm endpoint / feed URI in source-catalog.md
2. **Import SLA:** Confirm daily update frequency & latency tolerance
3. **Audit & Corrections:** Confirm handling of retroactive corrections
**Do NOT implement schema or import logic until above are approved.**
**Next Steps:**
1. Data governance team reviews & approves Source Unknown items
2. Separate PR adds schema migration (after source approval)
3. Separate PR adds import job (after SLA & audit approval)
---
## Related Documents
- **Governance:** AGENTS.md v16.0, CLAUDE.md "No real customer data seeded"
- **Tech Debt:** TECH-DEBT-XXX (VS-02 mislabeled code, awaiting removal decision)
- **Upstream:** VS-00 (PIT envelope), VS-01 (approval boundaries)
- **Downstream:** VS-03 (model operations), AEG-X-009 (automation orchestration)
@@ -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<PostgresException>(() => invalidCmd.ExecuteNonQueryAsync());
}
/// <summary>
/// Gate 3: Constraints - Window order enforced (start <= end)
/// </summary>
@@ -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();
@@ -6,6 +6,10 @@ internal static class TestDatabaseConnection
{
public static string GetConnectionString()
{
var configured = Environment.GetEnvironmentVariable("KARTSELL_POSTGRES");
if (!string.IsNullOrWhiteSpace(configured))
return configured;
var path = Path.Combine(AppContext.BaseDirectory, "appsettings.Development.json");
if (!File.Exists(path))
throw new InvalidOperationException($"Integration test settings are required: {path}");
+10 -4
View File
@@ -3,6 +3,8 @@ from __future__ import annotations
from pathlib import Path
import csv, hashlib, json, re, sys, zipfile
root=Path(__file__).resolve().parents[1]; errors=[]; warnings=[]
generated_dirs={'node_modules','.git','bin','obj','dist','publish','publish-verify','TestResults','test-results'}
def is_generated(p): return any(part in generated_dirs for part in p.relative_to(root).parts)
def fail(x): errors.append(x)
def warn(x): warnings.append(x)
def sha(p):
@@ -15,6 +17,7 @@ def rows(rel):
if not p.exists(): fail(f'missing {rel}'); return [],[]
with p.open(encoding='utf-8-sig',newline='') as f: r=csv.DictReader(f); return r.fieldnames or [],list(r)
for p in root.rglob('*.json'):
if is_generated(p): continue
try: json.loads(p.read_text(encoding='utf-8-sig'))
except Exception as e: fail(f'JSON {p.relative_to(root)}: {e}')
for p in (root/'frontend/src').rglob('*.vue'):
@@ -57,12 +60,15 @@ idx=root/'attachments/current_session/SOURCE_INDEX_V16_0.json'
if not idx.exists(): fail('missing source index')
else:
data=json.loads(idx.read_text(encoding='utf-8'))
if len(data.get('files',[]))!=4 or data.get('all_match') is not True: fail('source index incomplete')
for item in data.get('files',[]):
indexed_files=data.get('files',[])
if len(indexed_files)<1 or data.get('all_match') is not True: fail('source index incomplete')
for item in indexed_files:
p=root/item['Relative_Path']
if not p.exists() or p.stat().st_size!=item['Size'] or sha(p)!=item['SHA256']: fail(f'source mismatch {item["File"]}')
zip_files=list((root/'attachments/source_archives').glob('*.zip'))
if len(zip_files)!=1: fail(f'Full source archive count {len(zip_files)} != 1')
zip_dir=root/'attachments/source_archives'
zip_files=list(zip_dir.glob('*.zip')) if zip_dir.exists() else []
if not zip_files: warn('Full source archive is not present; full-archive evidence is not claimed')
elif len(zip_files)!=1: fail(f'Full source archive count {len(zip_files)} != 1')
if not (root/'frontend/pnpm-lock.yaml').exists(): warn('pnpm-lock.yaml missing; frozen install cannot be claimed')
warn('.NET 10 build, PostgreSQL DbUp, pnpm/Vitest/Playwright, scheduler chaos and 252-session Shadow require approved runtime')
print(f'PASS={0 if errors else 1} WARN={len(warnings)} FAIL={len(errors)}')