Compare commits

..

1 Commits

Author SHA1 Message Date
kjh2064 3e6f609dda feat(governance): add source-approval + dataset-freeze contract schema (AEG-X-009, gated)
Source governance schema: append-only source_approval table enforcing approval
before ingestion. Dataset manifest hardened to support FROZEN state, requiring
approval timestamps. Boundaries tested (6/6 passing). Server-side resolver
(DapperApprovedModelContextReader) now guards both model and dataset approval.

P2–P6 deferred: Dataset freeze command, maker-checker review, evaluation/proposal
orchestration remain pending human decision package (source allow-list, license/SLA,
metric versions, roles). No source/model seeded per CLAUDE.md governance.

Migrations 0033–0034 idempotency verified fresh/upgrade/re-run on isolated test DB.

AGENTS.md: Maturity (contract-first); Necessity (governance prerequisite).

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-08-07 13:32:25 +09:00
15 changed files with 653 additions and 438 deletions
-1
View File
@@ -48,7 +48,6 @@
|----|----------|--------|--------|--------|-------|-------|-----|
| 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 |
---
@@ -0,0 +1,65 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://kartsell.taxbaik.com/contracts/data/source-approval.v1.proposed.json",
"title": "Governed Data Source Approval Contract",
"description": "Proposal only. This contract does not authorize ingestion until a human approval record exists.",
"contractVersion": "source-approval.v1-proposed",
"status": "DESIGN_PROPOSAL",
"automationBoundary": {
"allowedModes": ["EVALUATION_ONLY", "PROPOSAL_ONLY", "DRILL_ONLY"],
"forbiddenEffects": [
"AUTO_MODEL_ACTIVATION",
"AUTO_MODEL_PROMOTION",
"AUTO_PARAMETER_CHANGE",
"AUTO_ORDER",
"KIS_SUBMISSION",
"CLIENT_PUBLICATION"
]
},
"type": "object",
"additionalProperties": false,
"required": [
"sourceId",
"sourceVersion",
"domain",
"owner",
"steward",
"licenseReference",
"availabilitySla",
"freshnessSla",
"timezone",
"calendarId",
"unitContract",
"schemaContractVersion",
"status",
"contentHash",
"approvedBy",
"approvedAt"
],
"properties": {
"sourceId": {"type": "string", "minLength": 1},
"sourceVersion": {"type": "string", "minLength": 1},
"domain": {"type": "string", "minLength": 1},
"owner": {"type": "string", "minLength": 1},
"steward": {"type": "string", "minLength": 1},
"licenseReference": {"type": "string", "minLength": 1},
"availabilitySla": {"type": "string", "minLength": 1},
"freshnessSla": {"type": "string", "minLength": 1},
"timezone": {"type": "string", "minLength": 1},
"calendarId": {"type": "string", "minLength": 1},
"unitContract": {"type": "string", "minLength": 1},
"schemaContractVersion": {"type": "string", "minLength": 1},
"status": {"enum": ["CANDIDATE", "APPROVED", "SUSPENDED", "RETIRED", "QUARANTINED"]},
"contentHash": {"type": "string", "pattern": "^[A-Fa-f0-9]{64}$"},
"approvedBy": {"type": "string", "minLength": 1},
"approvedAt": {"type": "string", "format": "date-time"},
"publishedAt": {"type": "string", "format": "date-time"},
"revision": {"type": "integer", "minimum": 1}
},
"allOf": [
{
"if": {"properties": {"status": {"const": "APPROVED"}}},
"then": {"required": ["publishedAt", "revision"]}
}
]
}
@@ -0,0 +1,53 @@
-- AEG-X-009 / ADR-DATA-001: append-only source approval boundary.
-- This migration authorizes governance records only. It does not authorize ingestion,
-- recommendation, model activation, client publication, order, or KIS submission.
create schema if not exists governance;
create table if not exists governance.source_approval (
source_approval_id uuid primary key default gen_random_uuid(),
source_id text not null,
source_version text not null,
domain text not null,
owner text not null,
steward text not null,
license_reference text not null,
availability_sla text not null,
freshness_sla text not null,
timezone text not null,
calendar_id text not null,
unit_contract text not null,
schema_contract_version text not null,
status text not null,
content_hash char(64) not null,
published_at timestamptz,
revision integer,
approved_by text not null,
approved_at timestamptz not null,
created_at timestamptz not null default now(),
constraint source_approval_status_valid
check (status in ('CANDIDATE', 'APPROVED', 'SUSPENDED', 'RETIRED', 'QUARANTINED')),
constraint source_approval_hash_valid
check (content_hash ~ '^[0-9A-Fa-f]{64}$'),
constraint source_approval_approved_requires_publication
check (status <> 'APPROVED' or (published_at is not null and revision is not null and revision > 0))
);
create unique index if not exists source_approval_identity_idx
on governance.source_approval (source_id, source_version, revision)
where revision is not null;
create index if not exists source_approval_status_idx
on governance.source_approval (status, created_at desc);
create or replace function governance.reject_source_approval_mutation()
returns trigger as $$
begin
raise exception 'governance.source_approval is append-only; create a correction record';
end;
$$ language plpgsql;
drop trigger if exists source_approval_no_update on governance.source_approval;
create trigger source_approval_no_update
before update or delete on governance.source_approval
for each row execute function governance.reject_source_approval_mutation();
@@ -0,0 +1,32 @@
-- AEG-X-009 / ADR-DATA-001: make dataset freeze explicit and append-only.
-- This migration does not create or seed a dataset. It only hardens the existing
-- evaluation.dataset_manifest boundary.
alter table evaluation.dataset_manifest
drop constraint if exists dataset_manifest_status_check;
alter table evaluation.dataset_manifest
add constraint dataset_manifest_status_check
check (status in ('PROPOSED', 'APPROVED', 'FROZEN', 'QUARANTINED', 'RETIRED'));
alter table evaluation.dataset_manifest
drop constraint if exists dataset_manifest_frozen_approval_check;
alter table evaluation.dataset_manifest
add constraint dataset_manifest_frozen_approval_check
check (
status <> 'FROZEN'
or (approved_by is not null and approved_at is not null and frozen_at is not null)
);
create or replace function evaluation.reject_dataset_manifest_mutation()
returns trigger as $$
begin
raise exception 'evaluation.dataset_manifest is append-only; create a correction record';
end;
$$ language plpgsql;
drop trigger if exists dataset_manifest_no_update on evaluation.dataset_manifest;
create trigger dataset_manifest_no_update
before update or delete on evaluation.dataset_manifest
for each row execute function evaluation.reject_dataset_manifest_mutation();
@@ -0,0 +1,176 @@
# AEG-X-009 Data/Model Proposal Automation — Design Proposal
## Status and traceability
- WBS: `AEG-X-009`
- Requirement: `REQ-DATA-SOURCE`
- Evidence class: `SOURCE+DESIGN_PROPOSAL`
- Status: `DESIGN_PROPOSAL`; not approved implementation
- Source: `contracts/schedules/model-operations.v3.json`, `contracts/schedules/execution-assurance.v1.json`, `contracts/model-governance/evaluation-promotion.v2.json`, `src/KArtSell.BuildingBlocks/Versioning/VersionSet.cs`, live read-only schema inspection on 2026-08-06
- Assumption: source ingestion and model evaluation are allowed to create immutable proposal/evidence records when their mode is `EVALUATION_ONLY` or `PROPOSAL_ONLY`.
- Unknown: approved source owners, source licenses/SLA values, model training implementation, retention period, and operator/secondary assignments.
- Decision Required: approve the proposal schema, job ownership, source allow-list, promotion review roles, and retention/alert contracts before implementation.
## Non-negotiable boundary
Automation may:
1. discover and validate an approved source;
2. ingest immutable raw records and create a content-addressed dataset manifest;
3. run deterministic evaluation against a frozen server-side VersionSet;
4. create EvidenceSnapshot and a human-review proposal;
5. notify the maker/checker queue and expose status/metrics.
Automation must never:
- activate or promote a model;
- mutate thresholds, policy, configuration, or source code;
- rollback a model automatically;
- publish to clients;
- submit an order or KIS request.
## Required state flow
```text
SOURCE_CANDIDATE
-> SOURCE_APPROVED (human owner + license/SLA/timezone/unit)
-> INGESTION_EVALUATION_ONLY
-> DATASET_QUARANTINED | DATASET_FROZEN
-> MODEL_EVALUATION_ONLY
-> EVIDENCE_SNAPSHOT_CREATED
-> PROPOSAL_ONLY_REVIEW
-> HUMAN_APPROVED | HUMAN_REJECTED | EXPIRED
-> HUMAN_CHANGE_APPLIED (separate release, never by scheduler)
```
`DATASET_QUARANTINED`, missing evidence, hash mismatch, PIT violation, or VersionSet drift is a terminal hold for that run. It is not a retryable transient failure.
## Required immutable records
### Source catalog entry
```text
source_id
source_version
owner / steward / secondary
license_reference
availability_sla / freshness_sla
timezone / calendar
unit / currency
schema_contract_version
approved_at / approved_by
status: CANDIDATE | APPROVED | SUSPENDED | RETIRED
```
### Dataset manifest
Use the existing `evaluation.dataset_manifest` table. A row is eligible for evaluation only when:
```text
status = FROZEN
dataset_id and content_hash are non-blank
source_catalog_version is approved
lineage_hash is present
frozen_at and approved_at are present
published_at/revision/PIT rules pass
```
### Evaluation VersionSet
Use the existing `VersionSet` contract. It must be loaded server-side and contain:
```text
DatasetId, DataHash, ModelVersion, ConfigVersion, CodeSha, ContractVersion
```
The client may submit scope and requested window only. The client must not submit evidence, hashes, model versions, or configuration versions as authoritative values.
### Proposal packet
The proposal must reference, without copying or mutating, the EvidenceSnapshot and VersionSet. It must contain:
```text
proposal_id / idempotency_key / scope_key / job_run_id
version_set / evidence_id / dataset_id / input_hash / output_hash
policy_id / policy_trace_schema_version / decision_contract_version
evaluation windows and metric definition versions
PBO / DSR / frozen OOS / double-cost / false-exit-reentry evidence
maker / checker / expiry / disposition
```
## Existing schedule mapping
Do not add a new schedule until ADR/Issue approval. Use the existing contract entries as follows:
| Existing job | Mode | Automated responsibility | Forbidden result |
|---|---|---|---|
| J25 SourceContractDriftCheck | EVALUATION_ONLY | detect source contract/license/SLA drift | no source activation |
| J26 MarketCalendarCompletenessCheck | EVALUATION_ONLY | detect calendar/timezone/unit gaps | no threshold mutation |
| J27 EvidenceChainAudit | EVALUATION_ONLY | validate lineage/hash/PIT chain | no evidence repair by overwrite |
| J28 ProjectionFreshnessCheck | EVALUATION_ONLY | validate read-model freshness | no client publication |
| J30 ReleaseEvidenceAssemble | PROPOSAL_ONLY | assemble a review packet | no release or activation |
The missing business flow is not a new automatic promotion job. It is the contract and application boundary that creates a frozen dataset and proposal packet for the existing review process.
## Repository catalog mapping
The following mapping is grounded in the current catalog and data contracts. It is a design mapping, not an authorization to ingest.
| Domain | Current catalog/source | Current logical tables/contracts | Automation entry condition | Current status |
|---|---|---|---|---|
| Market data | KRX OpenAPI | `market_data.prices`, `VS-03_DATA_CONTRACT.md` | source approval + calendar/unit/SLA + PIT/hash checks | CANDIDATE |
| Corporate/fundamental data | OpenDart API | `model_operations.disclosures`, `VS-05_DATA_CONTRACT.md` | license/redistribution approval + filing schema/DQ | CANDIDATE |
| Portfolio | User input | `portfolio.holdings`, `VS-04_DATA_CONTRACT.md` | authenticated owner input + audit + PIT | CANDIDATE |
| Model operations | computed/evaluation output | `evaluation.dataset_manifest`, `governance.model_version_registry`, `signal_engine.evidence_snapshot` | frozen dataset and approved model/config/code contract | BLOCKED until seed/approval |
| Shadow evaluation | Hangfire/shadow run | `model_operations.shadow_run`, result/evidence contracts | server-side VersionSet + EVALUATION_ONLY capability | BLOCKED until VersionSet |
The source catalog's logical table descriptions must be reconciled with active runtime SQL and the live schema before a migration or ingestion implementation. The catalog itself is not a substitute for runtime schema evidence.
## Existing debt and decision linkage
This proposal directly addresses, but does not close, the following open items:
- `TD-044`: approved Dataset Manifest and Model Registry initial data absent;
- `TD-063`: total-return/delisting/corporate-action golden data incomplete;
- `TD-099` / `TD-105`: market calendar/timezone source and SLA not approved;
- `TD-132`: current total-return source not approved;
- `DEC-037`, `DEC-038`, `DEC-079`: source/license/SLA and calendar ownership decisions required.
These items remain OPEN/DECISION_REQUIRED until their evidence is attached. No automation job may treat the catalog row as approved merely because the row exists.
## Proposed WBS decomposition (proposal only)
These rows must be approved before being added to `WBS_MASTER.csv`:
| Proposed ID | Scope | Acceptance evidence |
|---|---|---|
| AEG-X-009-P1 | Source allow-list and approval record | unapproved source cannot enter ingestion |
| AEG-X-009-P2 | Dataset manifest freeze command | same input produces same dataset/content hash; append-only |
| AEG-X-009-P3 | Server-side VersionSet resolver | client-supplied evidence/version values ignored |
| AEG-X-009-P4 | Evaluation/Proposal orchestration | idempotent JobRun/Watermark; modes fail closed |
| AEG-X-009-P5 | Human review packet/API/UI | maker-checker, expiry, reject, audit trail |
| AEG-X-009-P6 | Replay/failure/observability evidence | quarantine, replay hash, alert, runbook, rollback/stop evidence |
## Gate progression
| Gate | Required before next gate |
|---|---|
| G0 | contract, source owner, data semantics, WBS approval |
| G1 | approved source catalog + isolated fresh/upgrade/re-run rehearsal |
| G2 | frozen dataset + VersionSet resolver + golden/replay evidence |
| G3 | evaluation-only execution and EvidenceSnapshot proof |
| G4 | proposal packet + maker/checker review evidence |
| G5 | separate human change approval; no scheduler activation |
## Immediate decision package
Before code or migration work, approve these six values explicitly:
1. source allow-list and owner/steward;
2. license, SLA, timezone, calendar, unit, and currency contracts;
3. dataset freeze status and retention policy;
4. model evaluation metric definition versions and population/window rules;
5. maker/checker roles and proposal expiry;
6. alert, stop, runbook, and secondary owner.
Until these are approved, the correct behavior is `BLOCKED`/`QUARANTINED`, not synthetic data/model creation.
@@ -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,정책·범위·실패상태 계약 확정,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-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-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)."
1 WBS_ID Sprint Slice_ID Task Status Completion_Date Evidence_Link Owner Notes
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 정책·범위·실패상태 계약 확정 IN_PROGRESS PLANNED 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. Blocked: Depends on AEG-X-001. Future sprint.
19 AEG-VS-02-01 S1 VS-02 정책·범위·실패상태 계약 확정 DRAFT PLANNED 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. Blocked: Depends on AEG-VS-00-02. Future sprint.
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).
@@ -1,274 +0,0 @@
# 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)
@@ -1,161 +0,0 @@
# 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)
+89
View File
@@ -0,0 +1,89 @@
# ADR-DATA-001: Governed Source Approval and Dataset Freeze Pipeline
## Status
`APPROVED` — approved by the repository owner on 2026-08-06 for the Source Approval contract slice. Implementation remains limited to append-only governance records; model activation, orders, and KIS submission remain forbidden.
## WBS / contract traceability
- WBS: `AEG-X-009`
- Requirement: `REQ-DATA-SOURCE`
- Existing contracts: `contracts/schedules/model-operations.v3.json`, `contracts/schedules/execution-assurance.v1.json`
- Related proposal: `docs/CURRENT/AEG-X-009_AUTOMATION_PROPOSAL.md`
- Policy boundary: `EVALUATION_ONLY` / `PROPOSAL_ONLY` / `DRILL_ONLY`
## Context
The live database contains the model-operations schemas, but no approved/frozen `dataset_manifest`, model registry, EvidenceSnapshot, or release bundle records. The source catalog previously claimed operational approval without preserving the required owner, license, SLA, timezone, unit, and approval evidence. This prevents a compliant Phase 1 VersionSet from being resolved.
## Decision proposal
Introduce a governed, append-only approval boundary before ingestion or evaluation:
```text
SourceCandidate
-> SourceApproval (human owner/steward + contract evidence)
-> DatasetManifest (immutable content/lineage hash)
-> DatasetFreeze (human approval or approved governance command)
-> ServerSideVersionSetResolver
-> EvaluationOnly / ProposalOnly operation
```
The resolver must reject any source or dataset that is not approved and frozen. The client cannot supply authoritative evidence, hashes, model/config/code versions, or contract versions.
## Proposed data boundary
The implementation may add normalized append-only records only after this ADR is approved. Candidate records must include:
```text
source_id, source_version, owner, steward, license_reference,
availability_sla, freshness_sla, timezone, calendar, unit, currency,
schema_contract_version, status, approved_by, approved_at,
published_at, revision, content_hash, lineage_hash
```
No update/delete is permitted for approval, evidence, or freeze history. Corrections are new records/events.
## Automation boundary
Allowed:
- source contract drift checks;
- data-quality evaluation;
- immutable manifest creation;
- deterministic dataset freeze proposal;
- EvidenceSnapshot creation;
- proposal packet and maker/checker notification.
Forbidden:
- automatic model activation/promotion;
- automatic rollback;
- threshold/config/policy/code mutation;
- client publication;
- broker order or KIS submission.
## Acceptance evidence required before implementation is complete
1. Unapproved source cannot enter ingestion.
2. Approved source with missing license/SLA/timezone/unit is quarantined.
3. Dataset freeze is append-only and content-addressed.
4. Same input and VersionSet produce the same manifest/evaluation hash.
5. Client-supplied VersionSet/evidence is ignored or rejected.
6. Replay with the same scope/idempotency/watermark produces no duplicate side effect.
7. Proposal approval is maker/checker and does not activate a model.
8. Failure, alert, runbook, retention, and rollback/stop evidence are preserved.
## Alternatives rejected
- Trusting `source-catalog.md` as approval: no immutable approval evidence.
- Creating synthetic DatasetId/ModelVersion values to unblock Shadow Run: violates evidence and reproducibility rules.
- Reusing existing model-operation tables without an approval boundary: permits ambiguous ownership and incomplete lineage.
- Adding a scheduler that activates models: forbidden by AGENTS.md v12.4.
## Approval record
- Decision: APPROVED for the first Source Approval contract slice.
- Scope: append-only source approval record and validation boundary only.
- Explicit exclusions: dataset freeze execution, model activation, automatic promotion/rollback, threshold mutation, client publication, broker order, and KIS submission.
- Follow-up: Dataset Freeze requires a separate reviewed slice and evidence package.
@@ -0,0 +1,30 @@
# AEG-X-009 Source Approval Migration Rehearsal
## Traceability
- WBS: `AEG-X-009`
- ADR: `ADR-DATA-001` / `DEC-101`
- Migration: `db/migrations/0033_source_approval_contract.sql`
- Target: isolated `kartsell_migration_test`
- Production `kartselldb`: not modified
## Actual execution evidence
```text
Command: dotnet src/KArtSell.DbMigrator/bin/Release/net10.0/KArtSell.DbMigrator.dll
Target: Host=127.0.0.1;Port=5432;Database=kartsell_migration_test
Fresh run:
0032_shadow_run_queued_status_contract.sql -> executed
0033_source_approval_contract.sql -> executed
Upgrade successful
Exit code: 0
Re-run:
No new scripts need to be executed - completing.
Exit code: 0
```
## Boundary
This proves migration fresh/re-run behavior only. It does not authorize any source, create a Dataset Manifest, resolve a model VersionSet, activate a model, publish to clients, submit an order, or submit to KIS.
@@ -0,0 +1,24 @@
# AEG-X-009 Dataset Freeze Contract Rehearsal
## Traceability
- WBS: `AEG-X-009`
- ADR: `ADR-DATA-001` / `DEC-101`
- Migration: `db/migrations/0034_dataset_manifest_freeze_contract.sql`
- Target: isolated `kartsell_migration_test`
- Production `kartselldb`: not modified
## Actual execution evidence
```text
DbMigrator upgrade: 0034_dataset_manifest_freeze_contract.sql executed, exit code 0
DbMigrator re-run: No new scripts need to be executed, exit code 0
Journal: 0034_dataset_manifest_freeze_contract.sql present
Status constraint: PROPOSED, APPROVED, FROZEN, QUARANTINED, RETIRED
Frozen approval constraint: FROZEN requires approved_by, approved_at, frozen_at
Append-only trigger: dataset_manifest_no_update present
```
## Boundary
This rehearsal validates schema and migration behavior only. No dataset row was seeded, no source was authorized, no VersionSet was resolved, and no model evaluation or Shadow Run was started.
@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="utf-8"?>
<TestRun id="e40a6b61-cb08-464e-8994-c346702b8803" name="kjh20@KIMJAEHYUN-OFFI 2026-08-06 17:31:19" runUser="KIMJAEHYUN-OFFI\kjh20" xmlns="http://microsoft.com/schemas/VisualStudio/TeamTest/2010">
<Times creation="2026-08-06T17:31:19.1855656+09:00" queuing="2026-08-06T17:31:19.1855659+09:00" start="2026-08-06T17:31:17.1501437+09:00" finish="2026-08-06T17:31:19.1999864+09:00" />
<TestSettings name="default" id="e0c137c9-3376-48e5-80d3-8d7d74e1766c">
<Deployment runDeploymentRoot="kjh20_KIMJAEHYUN-OFFI_2026-08-06_17_31_19" />
</TestSettings>
<Results>
<UnitTestResult executionId="59c82802-50ae-4762-af59-3dcff366ebfb" testId="314a5e65-3e25-4434-eca3-b2b918f32928" testName="KArtSell.ModelOperations.UnitTests.ModelOperationExecutionBoundaryTests.Registry_definitions_are_unique_and_evidence_only" computerName="KIMJAEHYUN-OFFI" duration="00:00:00.0214049" startTime="2026-08-06T17:31:18.8572092+09:00" endTime="2026-08-06T17:31:18.8935419+09:00" testType="13cdc9d9-ddb5-4fa4-a97d-d965ccfc6d4b" outcome="Passed" testListId="8c84fa94-04c1-424b-9868-57a2d4851a1d" relativeResultsDirectory="59c82802-50ae-4762-af59-3dcff366ebfb" />
<UnitTestResult executionId="08b46df9-07c3-4d74-bfb6-6ae7408fbc5f" testId="68affc9c-1fdb-0235-bb8e-0f39c95758a3" testName="KArtSell.ModelOperations.UnitTests.PromotionGateEvaluatorTests.Passes_evidence_gate_but_still_requires_human_approval" computerName="KIMJAEHYUN-OFFI" duration="00:00:00.0227602" startTime="2026-08-06T17:31:18.8535529+09:00" endTime="2026-08-06T17:31:18.9037790+09:00" testType="13cdc9d9-ddb5-4fa4-a97d-d965ccfc6d4b" outcome="Passed" testListId="8c84fa94-04c1-424b-9868-57a2d4851a1d" relativeResultsDirectory="08b46df9-07c3-4d74-bfb6-6ae7408fbc5f" />
<UnitTestResult executionId="172709d6-ab15-46a9-b946-9e3452bb9783" testId="63f3a3a5-555e-c69b-517d-af3d3742c72d" testName="KArtSell.ModelOperations.UnitTests.ModelOperationRegistryTests.Improvement_and_promotion_packet_jobs_are_proposal_only" computerName="KIMJAEHYUN-OFFI" duration="00:00:00.0042338" startTime="2026-08-06T17:31:18.9223268+09:00" endTime="2026-08-06T17:31:18.9231131+09:00" testType="13cdc9d9-ddb5-4fa4-a97d-d965ccfc6d4b" outcome="Passed" testListId="8c84fa94-04c1-424b-9868-57a2d4851a1d" relativeResultsDirectory="172709d6-ab15-46a9-b946-9e3452bb9783" />
<UnitTestResult executionId="3f52f522-8748-4401-9db3-c567bdbcfb18" testId="f11f9f8d-5962-492f-5226-89f243398182" testName="KArtSell.ModelOperations.UnitTests.PromotionGateEvaluatorTests.Holds_when_any_operational_integrity_error_exists" computerName="KIMJAEHYUN-OFFI" duration="00:00:00.0028524" startTime="2026-08-06T17:31:18.9224268+09:00" endTime="2026-08-06T17:31:18.9227206+09:00" testType="13cdc9d9-ddb5-4fa4-a97d-d965ccfc6d4b" outcome="Passed" testListId="8c84fa94-04c1-424b-9868-57a2d4851a1d" relativeResultsDirectory="3f52f522-8748-4401-9db3-c567bdbcfb18" />
<UnitTestResult executionId="140ed97c-4a00-4b5b-9489-e894d5733b19" testId="9052c99d-50c0-412a-2f24-ad636ad7f995" testName="KArtSell.ModelOperations.UnitTests.ModelOperationExecutionBoundaryTests.Registry_never_contains_order_or_auto_promotion_operations" computerName="KIMJAEHYUN-OFFI" duration="00:00:00.0029169" startTime="2026-08-06T17:31:18.9223810+09:00" endTime="2026-08-06T17:31:18.9229487+09:00" testType="13cdc9d9-ddb5-4fa4-a97d-d965ccfc6d4b" outcome="Passed" testListId="8c84fa94-04c1-424b-9868-57a2d4851a1d" relativeResultsDirectory="140ed97c-4a00-4b5b-9489-e894d5733b19" />
<UnitTestResult executionId="91c100f4-d2e4-468a-a5cc-271e55b3a677" testId="3fc876d0-6833-57e4-2651-437b2244093b" testName="KArtSell.ModelOperations.UnitTests.ModelOperationRegistryTests.Operation_codes_are_unique_and_no_auto_promotion_mode_exists" computerName="KIMJAEHYUN-OFFI" duration="00:00:00.0284568" startTime="2026-08-06T17:31:18.8571332+09:00" endTime="2026-08-06T17:31:18.9215450+09:00" testType="13cdc9d9-ddb5-4fa4-a97d-d965ccfc6d4b" outcome="Passed" testListId="8c84fa94-04c1-424b-9868-57a2d4851a1d" relativeResultsDirectory="91c100f4-d2e4-468a-a5cc-271e55b3a677" />
</Results>
<TestDefinitions>
<UnitTest name="KArtSell.ModelOperations.UnitTests.ModelOperationExecutionBoundaryTests.Registry_never_contains_order_or_auto_promotion_operations" storage="c:\job_roomz\kartsell.aegis\tests\kartsell.modeloperations.unittests\bin\release\net10.0\kartsell.modeloperations.unittests.dll" id="9052c99d-50c0-412a-2f24-ad636ad7f995">
<Execution id="140ed97c-4a00-4b5b-9489-e894d5733b19" />
<TestMethod codeBase="C:\Job_Roomz\KArtSell.Aegis\tests\KArtSell.ModelOperations.UnitTests\bin\Release\net10.0\KArtSell.ModelOperations.UnitTests.dll" adapterTypeName="executor://xunit/VsTestRunner3/netcore/" className="KArtSell.ModelOperations.UnitTests.ModelOperationExecutionBoundaryTests" name="Registry_never_contains_order_or_auto_promotion_operations" />
</UnitTest>
<UnitTest name="KArtSell.ModelOperations.UnitTests.ModelOperationExecutionBoundaryTests.Registry_definitions_are_unique_and_evidence_only" storage="c:\job_roomz\kartsell.aegis\tests\kartsell.modeloperations.unittests\bin\release\net10.0\kartsell.modeloperations.unittests.dll" id="314a5e65-3e25-4434-eca3-b2b918f32928">
<Execution id="59c82802-50ae-4762-af59-3dcff366ebfb" />
<TestMethod codeBase="C:\Job_Roomz\KArtSell.Aegis\tests\KArtSell.ModelOperations.UnitTests\bin\Release\net10.0\KArtSell.ModelOperations.UnitTests.dll" adapterTypeName="executor://xunit/VsTestRunner3/netcore/" className="KArtSell.ModelOperations.UnitTests.ModelOperationExecutionBoundaryTests" name="Registry_definitions_are_unique_and_evidence_only" />
</UnitTest>
<UnitTest name="KArtSell.ModelOperations.UnitTests.PromotionGateEvaluatorTests.Passes_evidence_gate_but_still_requires_human_approval" storage="c:\job_roomz\kartsell.aegis\tests\kartsell.modeloperations.unittests\bin\release\net10.0\kartsell.modeloperations.unittests.dll" id="68affc9c-1fdb-0235-bb8e-0f39c95758a3">
<Execution id="08b46df9-07c3-4d74-bfb6-6ae7408fbc5f" />
<TestMethod codeBase="C:\Job_Roomz\KArtSell.Aegis\tests\KArtSell.ModelOperations.UnitTests\bin\Release\net10.0\KArtSell.ModelOperations.UnitTests.dll" adapterTypeName="executor://xunit/VsTestRunner3/netcore/" className="KArtSell.ModelOperations.UnitTests.PromotionGateEvaluatorTests" name="Passes_evidence_gate_but_still_requires_human_approval" />
</UnitTest>
<UnitTest name="KArtSell.ModelOperations.UnitTests.ModelOperationRegistryTests.Improvement_and_promotion_packet_jobs_are_proposal_only" storage="c:\job_roomz\kartsell.aegis\tests\kartsell.modeloperations.unittests\bin\release\net10.0\kartsell.modeloperations.unittests.dll" id="63f3a3a5-555e-c69b-517d-af3d3742c72d">
<Execution id="172709d6-ab15-46a9-b946-9e3452bb9783" />
<TestMethod codeBase="C:\Job_Roomz\KArtSell.Aegis\tests\KArtSell.ModelOperations.UnitTests\bin\Release\net10.0\KArtSell.ModelOperations.UnitTests.dll" adapterTypeName="executor://xunit/VsTestRunner3/netcore/" className="KArtSell.ModelOperations.UnitTests.ModelOperationRegistryTests" name="Improvement_and_promotion_packet_jobs_are_proposal_only" />
</UnitTest>
<UnitTest name="KArtSell.ModelOperations.UnitTests.PromotionGateEvaluatorTests.Holds_when_any_operational_integrity_error_exists" storage="c:\job_roomz\kartsell.aegis\tests\kartsell.modeloperations.unittests\bin\release\net10.0\kartsell.modeloperations.unittests.dll" id="f11f9f8d-5962-492f-5226-89f243398182">
<Execution id="3f52f522-8748-4401-9db3-c567bdbcfb18" />
<TestMethod codeBase="C:\Job_Roomz\KArtSell.Aegis\tests\KArtSell.ModelOperations.UnitTests\bin\Release\net10.0\KArtSell.ModelOperations.UnitTests.dll" adapterTypeName="executor://xunit/VsTestRunner3/netcore/" className="KArtSell.ModelOperations.UnitTests.PromotionGateEvaluatorTests" name="Holds_when_any_operational_integrity_error_exists" />
</UnitTest>
<UnitTest name="KArtSell.ModelOperations.UnitTests.ModelOperationRegistryTests.Operation_codes_are_unique_and_no_auto_promotion_mode_exists" storage="c:\job_roomz\kartsell.aegis\tests\kartsell.modeloperations.unittests\bin\release\net10.0\kartsell.modeloperations.unittests.dll" id="3fc876d0-6833-57e4-2651-437b2244093b">
<Execution id="91c100f4-d2e4-468a-a5cc-271e55b3a677" />
<TestMethod codeBase="C:\Job_Roomz\KArtSell.Aegis\tests\KArtSell.ModelOperations.UnitTests\bin\Release\net10.0\KArtSell.ModelOperations.UnitTests.dll" adapterTypeName="executor://xunit/VsTestRunner3/netcore/" className="KArtSell.ModelOperations.UnitTests.ModelOperationRegistryTests" name="Operation_codes_are_unique_and_no_auto_promotion_mode_exists" />
</UnitTest>
</TestDefinitions>
<TestEntries>
<TestEntry testId="314a5e65-3e25-4434-eca3-b2b918f32928" executionId="59c82802-50ae-4762-af59-3dcff366ebfb" testListId="8c84fa94-04c1-424b-9868-57a2d4851a1d" />
<TestEntry testId="68affc9c-1fdb-0235-bb8e-0f39c95758a3" executionId="08b46df9-07c3-4d74-bfb6-6ae7408fbc5f" testListId="8c84fa94-04c1-424b-9868-57a2d4851a1d" />
<TestEntry testId="63f3a3a5-555e-c69b-517d-af3d3742c72d" executionId="172709d6-ab15-46a9-b946-9e3452bb9783" testListId="8c84fa94-04c1-424b-9868-57a2d4851a1d" />
<TestEntry testId="f11f9f8d-5962-492f-5226-89f243398182" executionId="3f52f522-8748-4401-9db3-c567bdbcfb18" testListId="8c84fa94-04c1-424b-9868-57a2d4851a1d" />
<TestEntry testId="9052c99d-50c0-412a-2f24-ad636ad7f995" executionId="140ed97c-4a00-4b5b-9489-e894d5733b19" testListId="8c84fa94-04c1-424b-9868-57a2d4851a1d" />
<TestEntry testId="3fc876d0-6833-57e4-2651-437b2244093b" executionId="91c100f4-d2e4-468a-a5cc-271e55b3a677" testListId="8c84fa94-04c1-424b-9868-57a2d4851a1d" />
</TestEntries>
<TestLists>
<TestList name="목록에 없는 결과" id="8c84fa94-04c1-424b-9868-57a2d4851a1d" />
<TestList name="로드된 모든 결과" id="19431567-8539-422a-85d7-44ee4e166bda" />
</TestLists>
<ResultSummary outcome="Completed">
<Counters total="6" executed="6" passed="6" failed="0" error="0" timeout="0" aborted="0" inconclusive="0" passedButRunAborted="0" notRunnable="0" notExecuted="0" disconnected="0" warning="0" completed="0" inProgress="0" pending="0" />
<Output>
<StdOut>[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v3.1.5+1b188a7b0a (64-bit .NET 10.0.10)&#xD;
[xUnit.net 00:00:00.41] Discovering: KArtSell.ModelOperations.UnitTests&#xD;
[xUnit.net 00:00:00.52] Discovered: KArtSell.ModelOperations.UnitTests&#xD;
[xUnit.net 00:00:00.59] Starting: KArtSell.ModelOperations.UnitTests&#xD;
[xUnit.net 00:00:00.71] Finished: KArtSell.ModelOperations.UnitTests&#xD;
</StdOut>
</Output>
</ResultSummary>
</TestRun>
@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<TestRun id="23a5a7ea-e78f-494c-b3ff-6152d9abf1a7" name="kjh20@KIMJAEHYUN-OFFI 2026-08-06 19:55:12" runUser="KIMJAEHYUN-OFFI\kjh20" xmlns="http://microsoft.com/schemas/VisualStudio/TeamTest/2010">
<Times creation="2026-08-06T19:55:12.0855880+09:00" queuing="2026-08-06T19:55:12.0855884+09:00" start="2026-08-06T19:55:09.6932612+09:00" finish="2026-08-06T19:55:12.0937392+09:00" />
<TestSettings name="default" id="a66678cf-5da0-4e5d-8a14-ba23c18a9ed0">
<Deployment runDeploymentRoot="kjh20_KIMJAEHYUN-OFFI_2026-08-06_19_55_12" />
</TestSettings>
<Results>
<UnitTestResult executionId="69dbab6d-636e-4e99-a5eb-53cd68ba6c46" testId="72049d72-cc56-d9c2-d6a1-91fc3da97762" testName="KArtSell.ArchitectureTests.RepositoryRulesTests.Sql_does_not_use_select_star_or_unqualified_signal_tables" computerName="KIMJAEHYUN-OFFI" duration="00:00:01.2539924" startTime="2026-08-06T19:55:10.7094039+09:00" endTime="2026-08-06T19:55:11.9704810+09:00" testType="13cdc9d9-ddb5-4fa4-a97d-d965ccfc6d4b" outcome="Passed" testListId="8c84fa94-04c1-424b-9868-57a2d4851a1d" relativeResultsDirectory="69dbab6d-636e-4e99-a5eb-53cd68ba6c46" />
</Results>
<TestDefinitions>
<UnitTest name="KArtSell.ArchitectureTests.RepositoryRulesTests.Sql_does_not_use_select_star_or_unqualified_signal_tables" storage="c:\job_roomz\kartsell.aegis\tests\kartsell.architecturetests\bin\release\net10.0\kartsell.architecturetests.dll" id="72049d72-cc56-d9c2-d6a1-91fc3da97762">
<Execution id="69dbab6d-636e-4e99-a5eb-53cd68ba6c46" />
<TestMethod codeBase="C:\Job_Roomz\KArtSell.Aegis\tests\KArtSell.ArchitectureTests\bin\Release\net10.0\KArtSell.ArchitectureTests.dll" adapterTypeName="executor://xunit/VsTestRunner3/netcore/" className="KArtSell.ArchitectureTests.RepositoryRulesTests" name="Sql_does_not_use_select_star_or_unqualified_signal_tables" />
</UnitTest>
</TestDefinitions>
<TestEntries>
<TestEntry testId="72049d72-cc56-d9c2-d6a1-91fc3da97762" executionId="69dbab6d-636e-4e99-a5eb-53cd68ba6c46" testListId="8c84fa94-04c1-424b-9868-57a2d4851a1d" />
</TestEntries>
<TestLists>
<TestList name="목록에 없는 결과" id="8c84fa94-04c1-424b-9868-57a2d4851a1d" />
<TestList name="로드된 모든 결과" id="19431567-8539-422a-85d7-44ee4e166bda" />
</TestLists>
<ResultSummary outcome="Completed">
<Counters total="1" executed="1" passed="1" failed="0" error="0" timeout="0" aborted="0" inconclusive="0" passedButRunAborted="0" notRunnable="0" notExecuted="0" disconnected="0" warning="0" completed="0" inProgress="0" pending="0" />
<Output>
<StdOut>[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v3.1.5+1b188a7b0a (64-bit .NET 10.0.10)&#xD;
[xUnit.net 00:00:00.14] Discovering: KArtSell.ArchitectureTests&#xD;
[xUnit.net 00:00:00.19] Discovered: KArtSell.ArchitectureTests&#xD;
[xUnit.net 00:00:00.23] Starting: KArtSell.ArchitectureTests&#xD;
[xUnit.net 00:00:01.53] Finished: KArtSell.ArchitectureTests&#xD;
</StdOut>
</Output>
</ResultSummary>
</TestRun>
@@ -0,0 +1,20 @@
# AEG-X-009 Server-side VersionSet Resolver
## Traceability
- WBS: `AEG-X-009`
- Contract: `src/KArtSell.BuildingBlocks/Versioning/VersionSet.cs`
- Implementation: `src/KArtSell.Modules.ModelOperations/Infrastructure/DapperApprovedModelContextReader.cs`
- Test evidence: `evidence/AEG-X-009/versionset-resolver-boundary.trx`
## Change
The resolver now selects dataset manifests in `APPROVED` or `FROZEN` state only, requires dataset approval fields, and requires model registry approval fields. It continues to load all authoritative VersionSet values from the server-side database; client evidence/version values are not accepted.
## Verification
```text
Model Operations boundary tests: 6/6 passed
```
No dataset/model rows were seeded and no operation request or Shadow Run was created.
@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="utf-8"?>
<TestRun id="9717473c-44ec-4177-b948-a3aab6f9f902" name="kjh20@KIMJAEHYUN-OFFI 2026-08-06 20:00:05" runUser="KIMJAEHYUN-OFFI\kjh20" xmlns="http://microsoft.com/schemas/VisualStudio/TeamTest/2010">
<Times creation="2026-08-06T20:00:05.4579824+09:00" queuing="2026-08-06T20:00:05.4579826+09:00" start="2026-08-06T20:00:03.9646531+09:00" finish="2026-08-06T20:00:05.4699867+09:00" />
<TestSettings name="default" id="25c44927-8b33-47f2-a80a-3f25b18dfdca">
<Deployment runDeploymentRoot="kjh20_KIMJAEHYUN-OFFI_2026-08-06_20_00_05" />
</TestSettings>
<Results>
<UnitTestResult executionId="421b5d6f-3c04-4e62-a9c3-efb7137bf0ab" testId="314a5e65-3e25-4434-eca3-b2b918f32928" testName="KArtSell.ModelOperations.UnitTests.ModelOperationExecutionBoundaryTests.Registry_definitions_are_unique_and_evidence_only" computerName="KIMJAEHYUN-OFFI" duration="00:00:00.0156515" startTime="2026-08-06T20:00:05.2514618+09:00" endTime="2026-08-06T20:00:05.2794597+09:00" testType="13cdc9d9-ddb5-4fa4-a97d-d965ccfc6d4b" outcome="Passed" testListId="8c84fa94-04c1-424b-9868-57a2d4851a1d" relativeResultsDirectory="421b5d6f-3c04-4e62-a9c3-efb7137bf0ab" />
<UnitTestResult executionId="441ac26b-53fb-4136-a8d3-1d5d2356ee97" testId="3fc876d0-6833-57e4-2651-437b2244093b" testName="KArtSell.ModelOperations.UnitTests.ModelOperationRegistryTests.Operation_codes_are_unique_and_no_auto_promotion_mode_exists" computerName="KIMJAEHYUN-OFFI" duration="00:00:00.0198758" startTime="2026-08-06T20:00:05.2539412+09:00" endTime="2026-08-06T20:00:05.2957390+09:00" testType="13cdc9d9-ddb5-4fa4-a97d-d965ccfc6d4b" outcome="Passed" testListId="8c84fa94-04c1-424b-9868-57a2d4851a1d" relativeResultsDirectory="441ac26b-53fb-4136-a8d3-1d5d2356ee97" />
<UnitTestResult executionId="a8ddc0dc-6452-494f-b628-387a1f594eb1" testId="f11f9f8d-5962-492f-5226-89f243398182" testName="KArtSell.ModelOperations.UnitTests.PromotionGateEvaluatorTests.Holds_when_any_operational_integrity_error_exists" computerName="KIMJAEHYUN-OFFI" duration="00:00:00.0016648" startTime="2026-08-06T20:00:05.3014463+09:00" endTime="2026-08-06T20:00:05.3024698+09:00" testType="13cdc9d9-ddb5-4fa4-a97d-d965ccfc6d4b" outcome="Passed" testListId="8c84fa94-04c1-424b-9868-57a2d4851a1d" relativeResultsDirectory="a8ddc0dc-6452-494f-b628-387a1f594eb1" />
<UnitTestResult executionId="9d9fc6e8-5fe5-436c-81d8-6ec2911f145b" testId="63f3a3a5-555e-c69b-517d-af3d3742c72d" testName="KArtSell.ModelOperations.UnitTests.ModelOperationRegistryTests.Improvement_and_promotion_packet_jobs_are_proposal_only" computerName="KIMJAEHYUN-OFFI" duration="00:00:00.0022293" startTime="2026-08-06T20:00:05.3013661+09:00" endTime="2026-08-06T20:00:05.3027426+09:00" testType="13cdc9d9-ddb5-4fa4-a97d-d965ccfc6d4b" outcome="Passed" testListId="8c84fa94-04c1-424b-9868-57a2d4851a1d" relativeResultsDirectory="9d9fc6e8-5fe5-436c-81d8-6ec2911f145b" />
<UnitTestResult executionId="0f952546-00ab-472c-9348-3f047c494137" testId="9052c99d-50c0-412a-2f24-ad636ad7f995" testName="KArtSell.ModelOperations.UnitTests.ModelOperationExecutionBoundaryTests.Registry_never_contains_order_or_auto_promotion_operations" computerName="KIMJAEHYUN-OFFI" duration="00:00:00.0015493" startTime="2026-08-06T20:00:05.3014902+09:00" endTime="2026-08-06T20:00:05.3016767+09:00" testType="13cdc9d9-ddb5-4fa4-a97d-d965ccfc6d4b" outcome="Passed" testListId="8c84fa94-04c1-424b-9868-57a2d4851a1d" relativeResultsDirectory="0f952546-00ab-472c-9348-3f047c494137" />
<UnitTestResult executionId="0f9b3aef-9ec3-44be-83d9-f19ebb9e880a" testId="68affc9c-1fdb-0235-bb8e-0f39c95758a3" testName="KArtSell.ModelOperations.UnitTests.PromotionGateEvaluatorTests.Passes_evidence_gate_but_still_requires_human_approval" computerName="KIMJAEHYUN-OFFI" duration="00:00:00.0169984" startTime="2026-08-06T20:00:05.2540204+09:00" endTime="2026-08-06T20:00:05.2858863+09:00" testType="13cdc9d9-ddb5-4fa4-a97d-d965ccfc6d4b" outcome="Passed" testListId="8c84fa94-04c1-424b-9868-57a2d4851a1d" relativeResultsDirectory="0f9b3aef-9ec3-44be-83d9-f19ebb9e880a" />
</Results>
<TestDefinitions>
<UnitTest name="KArtSell.ModelOperations.UnitTests.ModelOperationExecutionBoundaryTests.Registry_never_contains_order_or_auto_promotion_operations" storage="c:\job_roomz\kartsell.aegis\tests\kartsell.modeloperations.unittests\bin\release\net10.0\kartsell.modeloperations.unittests.dll" id="9052c99d-50c0-412a-2f24-ad636ad7f995">
<Execution id="0f952546-00ab-472c-9348-3f047c494137" />
<TestMethod codeBase="C:\Job_Roomz\KArtSell.Aegis\tests\KArtSell.ModelOperations.UnitTests\bin\Release\net10.0\KArtSell.ModelOperations.UnitTests.dll" adapterTypeName="executor://xunit/VsTestRunner3/netcore/" className="KArtSell.ModelOperations.UnitTests.ModelOperationExecutionBoundaryTests" name="Registry_never_contains_order_or_auto_promotion_operations" />
</UnitTest>
<UnitTest name="KArtSell.ModelOperations.UnitTests.ModelOperationExecutionBoundaryTests.Registry_definitions_are_unique_and_evidence_only" storage="c:\job_roomz\kartsell.aegis\tests\kartsell.modeloperations.unittests\bin\release\net10.0\kartsell.modeloperations.unittests.dll" id="314a5e65-3e25-4434-eca3-b2b918f32928">
<Execution id="421b5d6f-3c04-4e62-a9c3-efb7137bf0ab" />
<TestMethod codeBase="C:\Job_Roomz\KArtSell.Aegis\tests\KArtSell.ModelOperations.UnitTests\bin\Release\net10.0\KArtSell.ModelOperations.UnitTests.dll" adapterTypeName="executor://xunit/VsTestRunner3/netcore/" className="KArtSell.ModelOperations.UnitTests.ModelOperationExecutionBoundaryTests" name="Registry_definitions_are_unique_and_evidence_only" />
</UnitTest>
<UnitTest name="KArtSell.ModelOperations.UnitTests.PromotionGateEvaluatorTests.Passes_evidence_gate_but_still_requires_human_approval" storage="c:\job_roomz\kartsell.aegis\tests\kartsell.modeloperations.unittests\bin\release\net10.0\kartsell.modeloperations.unittests.dll" id="68affc9c-1fdb-0235-bb8e-0f39c95758a3">
<Execution id="0f9b3aef-9ec3-44be-83d9-f19ebb9e880a" />
<TestMethod codeBase="C:\Job_Roomz\KArtSell.Aegis\tests\KArtSell.ModelOperations.UnitTests\bin\Release\net10.0\KArtSell.ModelOperations.UnitTests.dll" adapterTypeName="executor://xunit/VsTestRunner3/netcore/" className="KArtSell.ModelOperations.UnitTests.PromotionGateEvaluatorTests" name="Passes_evidence_gate_but_still_requires_human_approval" />
</UnitTest>
<UnitTest name="KArtSell.ModelOperations.UnitTests.ModelOperationRegistryTests.Improvement_and_promotion_packet_jobs_are_proposal_only" storage="c:\job_roomz\kartsell.aegis\tests\kartsell.modeloperations.unittests\bin\release\net10.0\kartsell.modeloperations.unittests.dll" id="63f3a3a5-555e-c69b-517d-af3d3742c72d">
<Execution id="9d9fc6e8-5fe5-436c-81d8-6ec2911f145b" />
<TestMethod codeBase="C:\Job_Roomz\KArtSell.Aegis\tests\KArtSell.ModelOperations.UnitTests\bin\Release\net10.0\KArtSell.ModelOperations.UnitTests.dll" adapterTypeName="executor://xunit/VsTestRunner3/netcore/" className="KArtSell.ModelOperations.UnitTests.ModelOperationRegistryTests" name="Improvement_and_promotion_packet_jobs_are_proposal_only" />
</UnitTest>
<UnitTest name="KArtSell.ModelOperations.UnitTests.PromotionGateEvaluatorTests.Holds_when_any_operational_integrity_error_exists" storage="c:\job_roomz\kartsell.aegis\tests\kartsell.modeloperations.unittests\bin\release\net10.0\kartsell.modeloperations.unittests.dll" id="f11f9f8d-5962-492f-5226-89f243398182">
<Execution id="a8ddc0dc-6452-494f-b628-387a1f594eb1" />
<TestMethod codeBase="C:\Job_Roomz\KArtSell.Aegis\tests\KArtSell.ModelOperations.UnitTests\bin\Release\net10.0\KArtSell.ModelOperations.UnitTests.dll" adapterTypeName="executor://xunit/VsTestRunner3/netcore/" className="KArtSell.ModelOperations.UnitTests.PromotionGateEvaluatorTests" name="Holds_when_any_operational_integrity_error_exists" />
</UnitTest>
<UnitTest name="KArtSell.ModelOperations.UnitTests.ModelOperationRegistryTests.Operation_codes_are_unique_and_no_auto_promotion_mode_exists" storage="c:\job_roomz\kartsell.aegis\tests\kartsell.modeloperations.unittests\bin\release\net10.0\kartsell.modeloperations.unittests.dll" id="3fc876d0-6833-57e4-2651-437b2244093b">
<Execution id="441ac26b-53fb-4136-a8d3-1d5d2356ee97" />
<TestMethod codeBase="C:\Job_Roomz\KArtSell.Aegis\tests\KArtSell.ModelOperations.UnitTests\bin\Release\net10.0\KArtSell.ModelOperations.UnitTests.dll" adapterTypeName="executor://xunit/VsTestRunner3/netcore/" className="KArtSell.ModelOperations.UnitTests.ModelOperationRegistryTests" name="Operation_codes_are_unique_and_no_auto_promotion_mode_exists" />
</UnitTest>
</TestDefinitions>
<TestEntries>
<TestEntry testId="314a5e65-3e25-4434-eca3-b2b918f32928" executionId="421b5d6f-3c04-4e62-a9c3-efb7137bf0ab" testListId="8c84fa94-04c1-424b-9868-57a2d4851a1d" />
<TestEntry testId="3fc876d0-6833-57e4-2651-437b2244093b" executionId="441ac26b-53fb-4136-a8d3-1d5d2356ee97" testListId="8c84fa94-04c1-424b-9868-57a2d4851a1d" />
<TestEntry testId="f11f9f8d-5962-492f-5226-89f243398182" executionId="a8ddc0dc-6452-494f-b628-387a1f594eb1" testListId="8c84fa94-04c1-424b-9868-57a2d4851a1d" />
<TestEntry testId="63f3a3a5-555e-c69b-517d-af3d3742c72d" executionId="9d9fc6e8-5fe5-436c-81d8-6ec2911f145b" testListId="8c84fa94-04c1-424b-9868-57a2d4851a1d" />
<TestEntry testId="9052c99d-50c0-412a-2f24-ad636ad7f995" executionId="0f952546-00ab-472c-9348-3f047c494137" testListId="8c84fa94-04c1-424b-9868-57a2d4851a1d" />
<TestEntry testId="68affc9c-1fdb-0235-bb8e-0f39c95758a3" executionId="0f9b3aef-9ec3-44be-83d9-f19ebb9e880a" testListId="8c84fa94-04c1-424b-9868-57a2d4851a1d" />
</TestEntries>
<TestLists>
<TestList name="목록에 없는 결과" id="8c84fa94-04c1-424b-9868-57a2d4851a1d" />
<TestList name="로드된 모든 결과" id="19431567-8539-422a-85d7-44ee4e166bda" />
</TestLists>
<ResultSummary outcome="Completed">
<Counters total="6" executed="6" passed="6" failed="0" error="0" timeout="0" aborted="0" inconclusive="0" passedButRunAborted="0" notRunnable="0" notExecuted="0" disconnected="0" warning="0" completed="0" inProgress="0" pending="0" />
<Output>
<StdOut>[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v3.1.5+1b188a7b0a (64-bit .NET 10.0.10)&#xD;
[xUnit.net 00:00:00.31] Discovering: KArtSell.ModelOperations.UnitTests&#xD;
[xUnit.net 00:00:00.39] Discovered: KArtSell.ModelOperations.UnitTests&#xD;
[xUnit.net 00:00:00.43] Starting: KArtSell.ModelOperations.UnitTests&#xD;
[xUnit.net 00:00:00.52] Finished: KArtSell.ModelOperations.UnitTests&#xD;
</StdOut>
</Output>
</ResultSummary>
</TestRun>