Phase 3 KPI 계약 상수 분리

This commit is contained in:
Yun Chan 2026-06-29 00:01:24 +09:00
parent d9458d0e40
commit d18e4b6b4b
7 changed files with 69 additions and 53 deletions

View file

@ -509,6 +509,7 @@
<p class="dg-note">74차 적용(2026-06-28): refactor-governance P4로 H2 live-coach source pack 캐시 경계를 명시했다. <code>live_coach</code>의 로컬 source pack snapshot은 API 프로세스 생명주기 캐시로 유지하고, 관리자 <code>POST /kb/live-coach/source-packs/sync</code><code>scripts/sync-persona-sources.py</code><code>refresh=True</code>로 캐시를 비운 뒤 repo 파일을 다시 읽는다. live turn 경로에는 매 턴 파일 IO를 추가하지 않았고, RAG content_hash/version 비교와 evaluator-only payload 정책은 유지했다. 파일 변경 뒤 stale hash가 refresh로 새 hash/version bump에 반영되는 회귀를 추가했다. 검증: source pack focused 9 passed, H2 related 44 passed, sync CLI help, py_compile.</p>
<p class="dg-note">75차 계획(2026-06-28): 다크 UI v2 시안을 실제 코드 적용 라운드로 승격했다. 기준 이미지는 <code>03-session-responsive-v2-dark-unified.png</code>, <code>04-session-review-responsive-v2-dark-unified.png</code>, <code>02-learner-home-responsive-v2-dark-unified.png</code>이고, 실행 순서는 라이브 상담 세션 → 회기 리뷰 → 학습자 홈이다. 생성 PNG는 앱에 직접 넣지 않고 React/CSS 구현의 톤·밀도·반응형 기준으로만 사용한다. 추적 항목은 <code>docs/ops/backlog-2026-06-26.md</code>, 구현 체크리스트는 <code>docs/ops/dark-ui-refresh-implementation-plan-2026-06-28.md</code>에 둔다. DONE 표기는 실제 Playwright 캡처와 typecheck/build/focused E2E/layout gate 통과 뒤에만 한다.</p>
<p class="dg-note">76차 적용(2026-06-28): refactor-governance P2로 M2 digest 계약 경계를 고정했다. <code>SessionDigestInput</code>/<code>SessionDigestResult</code>/<code>SessionSummaryWrite</code>를 추가해 future LLM worker가 raw text, evaluator-only turn, 평가 payload, CCD, deterministic carry를 우회하지 못하게 했다. <code>/sessions/{id}/end</code><code>digest_pending</code>은 여전히 CompressionJob 생성 여부를 알리는 비동기 압축 필요 신호일 뿐이며, LLM digest worker 실행·압축 품질 검증 완료를 뜻하지 않는다. 검증: M2 focused 74 passed, <code>npm run check:api-types</code>, <code>npm run typecheck</code>, py_compile.</p>
<p class="dg-note">77차 적용(2026-06-28): H1 Phase 3 KPI report 계약을 <code>phase3_kpi_contract.py</code>로 모아 exporter/checker/test의 metric 이름·필수키·status 중복 선언을 줄였다. <code>kpi_report.json</code> metric은 <code>computed_prepost</code><code>design_pending</code> status를 구분하므로, 3척도 pre/post로 계산 가능한 파일럿 evidence와 평가설계 전이라 계산하지 않는 KPI를 기계적으로 분리한다. 공식 효과성 판정·통계해석·20명 파일럿 evidence는 계속 외부/파일럿 gate다. 검증: H1 KPI focused 10 passed, H1 broad 46 passed, exporter/checker help, py_compile.</p>
<p class="dg-note">20차 적용(2026-06-28): 신규 Google/SAML 사용자는 <code>account_status=pending</code>으로 시작하고 승인 전에는 <code>/pending</code> 안내 화면만 본다. <code>yunchan@twentyoz.kr</code>는 슈퍼 관리자 allowlist로 admin+approved를 받으며, <code>/admin/users</code>는 가입 승인 탭에서 pending 계정을 승인 또는 보류 처리한다.</p>
<p class="dg-note">21차 적용(2026-06-28): 관리자 페이지 진입권을 기본 역할과 분리해 <code>app_user.admin_access</code>로 저장한다. <code>AUTH_SUPER_ADMIN_EMAILS</code> 기본값은 <code>yunchan@twentyoz.kr</code>, <code>hoonjungkoo@hs.ac.kr</code>이며, 슈퍼 관리자는 학습자·교수자·관리자 공간 전환과 관리자 권한 부여/회수를 할 수 있다. 학생·교수 계정도 <code>admin_access=true</code>면 우측 상단 관리자 진입이 노출된다. 구성 슈퍼 관리자의 권한 회수와 계정 비활성화는 차단한다.</p>
<p class="dg-note">25차 적용(2026-06-28): <code>/admin/users</code>에서 허용 도메인 밖 이메일도 정확한 계정 단위로 강제 등록할 수 있다. Google/SAML/dev-login은 미리 등록된 이메일만 도메인 게이트 예외로 통과시키고, provider 로그인 시 기존 관리 row의 역할·코호트·승인 상태를 이어받는다. 미등록 외부 도메인 로그인은 계속 차단한다.</p>
@ -668,9 +669,9 @@
<button class="scard-head" aria-expanded="false"><span class="chip c-done">C3 2차 DONE</span><span class="scard-mid"><span class="scard-title">이론모드 스레딩 + 시작 전 명시 선택 UI</span><span class="scard-sum">theory_mode backend 배선, persona 기본값, humanistic/CBT/integrative segmented 선택과 POST 전송 검증.</span></span><span class="caret" aria-hidden="true"></span></button>
<div class="scard-body"><div class="kv"><b>현재</b><p>TurnContext+prepare_turn에 theory_mode, sessions/voice가 sess.theory_mode 전달, evaluator._theory_mode가 세션 이론(학습자 선택) 우선. <code>build_turn_messages</code><code>THEORY_MODE_GUIDANCE</code>를 통해 인간중심·CBT·통합 이론 프레이밍을 엔진 메시지에 넣는다. 프론트 <code>Session.tsx</code>는 기본값을 <code>persona.theory_target</code>에서 잡되, 세션 시작 전 <code>humanistic</code>/<code>cbt</code>/<code>integrative</code> segmented control로 학습자가 명시 선택할 수 있다. 선택값은 <code>POST /sessions</code><code>theory_mode</code>로 전송된다. 검증: <code>npm run typecheck</code>, <code>npm run build</code>, C3 backend focused 31 passed, <code>session-layout.spec.ts</code> desktop/mobile 8 passed, <code>session-mvp.spec.ts</code> single-run 1 passed, <code>layout-visual-gate.spec.ts</code> 9 passed.</p></div><div class="kv k-warn"><b>잔여(임상팀)</b><p>CBT 이론별 단계 프롬프트 체인 콘텐츠 + 이론부합 채점 루브릭은 후속이다. 이번 UI는 임상 문안·루브릭·백엔드 enum을 확장하지 않았다.</p></div></div>
</article>
<article class="scard" data-status="planned" data-cat="교수자·평가" data-owner="1">
<button class="scard-head" aria-expanded="false"><span class="chip c-plan">HIGH H1 2차</span><span class="scard-mid"><span class="scard-title">계약 평가 KPI(자기효능감·기술숙련도·수련만족도 사전사후) 수집·계산 1차<span class="ownbadge">내 차례</span></span><span class="scard-sum">3척도 pre-post evidence 원장, 학습자 입력 UI, Phase 3 CSV/KPI report scaffold 계산기가 연결됨. 공식 문항·통계해석은 평가설계 게이트.</span></span><span class="caret" aria-hidden="true"></span></button>
<div class="scard-body"><div class="kv"><b>현재</b><p>doc4가 요구한 3척도 pre-post에 대해 <code>app.learner_prepost_measure</code> 원장, 학습자 본인용 <code>GET/PUT /users/me/prepost-measures</code>, <code>SessionReview</code>의 파일럿 증거 원장 카드를 연결했다. 저장 shape는 <code>measure_name</code>(self_efficacy/skill_proficiency/training_satisfaction), <code>timepoint</code>(pre/post), raw/min/max score, instrument_version, item_count만 받으며, 문항 텍스트·효과크기·p-value·pass/fail은 만들지 않는다. 이번 패스에서 <code>app.services.phase3_kpi_export</code><code>scripts/export-phase3-kpi.py</code>를 추가해 원장 row를 Phase 3 evidence root의 <code>02-measures/prepost_measures.csv</code><code>02-measures/kpi_report.json</code>으로 산출한다. participant id는 <code>P3-###</code>로 가명화하고, 3척도 paired normalized mean pre/post/delta, complete/missing pair를 report에 채운다. 화면과 report 모두 공식 효과성·성적·수료 판정이 아니라 파일럿 evidence 계산임을 유지한다.</p></div><div class="kv"><b>검증</b><p><code>python -B -m py_compile app/services/phase3_kpi_export.py app/test_phase3_kpi_export.py scripts/export-phase3-kpi.py</code>, <code>python -B -m pytest -p no:cacheprovider app/test_phase3_kpi_export.py app/test_phase3_artifact_checker.py -q</code> 9 passed, <code>python -B -m pytest -p no:cacheprovider app/test_user_support_tickets.py app/test_runtime_policy.py app/test_admin_ops.py app/test_phase3_kpi_export.py app/test_phase3_artifact_checker.py -q</code> 45 passed, <code>python scripts/export-phase3-kpi.py --help</code> passed. 기존 API/UI 검증은 backend 36 passed, API types/typecheck, SessionReview focused E2E 3 passed, visual gate 2 passed.</p></div><div class="kv k-warn"><b>잔여</b><p>평가설계·공식 문항 확정, 실험/통제군 배정, 추이 시각화, 통계 검정 종류/alpha/결측 처리, 실제 20명 파일럿 evidence와 steward/legal/IAA 검수는 아직 남아 있다.</p></div></div>
<article class="scard" data-status="done" data-cat="교수자·평가" data-owner="0">
<button class="scard-head" aria-expanded="false"><span class="chip c-done">HIGH H1 3차 DONE</span><span class="scard-mid"><span class="scard-title">계약 평가 KPI(자기효능감·기술숙련도·수련만족도 사전사후) 수집·계산 2차</span><span class="scard-sum">3척도 pre-post evidence 원장, 학습자 입력 UI, Phase 3 CSV/KPI report 계산기와 metric status 계약까지 연결됨. 공식 문항·통계해석은 평가설계 게이트.</span></span><span class="caret" aria-hidden="true"></span></button>
<div class="scard-body"><div class="kv"><b>현재</b><p>doc4가 요구한 3척도 pre-post에 대해 <code>app.learner_prepost_measure</code> 원장, 학습자 본인용 <code>GET/PUT /users/me/prepost-measures</code>, <code>SessionReview</code>의 파일럿 증거 원장 카드를 연결했다. 저장 shape는 <code>measure_name</code>(self_efficacy/skill_proficiency/training_satisfaction), <code>timepoint</code>(pre/post), raw/min/max score, instrument_version, item_count만 받으며, 문항 텍스트·효과크기·p-value·pass/fail은 만들지 않는다. <code>app.services.phase3_kpi_export</code><code>scripts/export-phase3-kpi.py</code> 원장 row를 Phase 3 evidence root의 <code>02-measures/prepost_measures.csv</code><code>02-measures/kpi_report.json</code>으로 산출한다. participant id는 <code>P3-###</code>로 가명화하고, 3척도 paired normalized mean pre/post/delta, complete/missing pair를 report에 채운다. 77차에서는 <code>phase3_kpi_contract.py</code>가 metric 이름·필수키·status를 소유하게 해 exporter와 checker의 계약 drift를 줄였다. report metric status는 계산된 3척도 pre/post evidence를 <code>computed_prepost</code>, 공식 평가설계 전이라 계산하지 않는 지표를 <code>design_pending</code>으로 구분한다. 화면과 report 모두 공식 효과성·성적·수료 판정이 아니라 파일럿 evidence 계산임을 유지한다.</p></div><div class="kv"><b>검증</b><p><code>py -3.11 -X utf8 -m py_compile apps\api\app\services\phase3_kpi_contract.py apps\api\app\services\phase3_kpi_export.py apps\api\app\test_phase3_kpi_export.py apps\api\app\test_phase3_artifact_checker.py scripts\check-phase3-artifacts.py scripts\export-phase3-kpi.py</code>, <code>py -3.11 -X utf8 -m pytest -p no:cacheprovider app/test_phase3_kpi_export.py app/test_phase3_artifact_checker.py -q</code> 10 passed, <code>py -3.11 -X utf8 -m pytest -p no:cacheprovider app/test_user_support_tickets.py app/test_runtime_policy.py app/test_admin_ops.py app/test_phase3_kpi_export.py app/test_phase3_artifact_checker.py -q</code> 46 passed, <code>py -3.11 -X utf8 scripts\export-phase3-kpi.py --help</code>, <code>py -3.11 -X utf8 scripts\check-phase3-artifacts.py --help</code>. 기존 API/UI 검증은 API types/typecheck, SessionReview focused E2E 3 passed, visual gate 2 passed.</p></div><div class="kv k-warn"><b>잔여</b><p>평가설계·공식 문항 확정, 실험/통제군 배정, 추이 시각화, 통계 검정 종류/alpha/결측 처리, 실제 20명 파일럿 evidence와 steward/legal/IAA 검수는 아직 남아 있다.</p></div></div>
</article>
<article class="scard" data-status="done" data-cat="교수자·평가" data-owner="0">
<button class="scard-head" aria-expanded="false"><span class="chip c-done">H2 LIVE DONE</span><span class="scard-mid"><span class="scard-title">턴별 fast-loop + 라이브 코칭·이력</span><span class="scard-sum">평가 정규화에 더해 워크북·DSM·공식 지침/RAG 기반 코칭 아바타, 근거 모달, 발화별 이력 오버레이와 source pack RAG sync를 연결.</span></span><span class="caret" aria-hidden="true"></span></button>
@ -846,7 +847,7 @@
<div class="detail-head"><div><h3>Phase 3 교육용 파일럿 / 효과성 상세</h3><p>IRB 승인 게이트가 아니라 교육 활용 파일럿과 데이터/개인정보 게이트로 관리한다.</p></div><div class="mini-metrics"><span>주차: 18-20+</span><span>표본: 20명</span><span>first measure</span></div></div>
<div class="task-list">
<div class="task-row"><div><span class="task-status s-plan">GATE</span></div><div><b>20명 교육용 파일럿 운영</b><p>20명 파일럿을 실행하기 위한 roster, consent, session completion evidence gate를 문서화했다. checker는 CSV enum(<code>withdrawal_state</code>, <code>completion_state</code>, <code>timepoint</code>, withdrawal <code>scope/status</code>)을 검증한다. 실제 참여자 완주 증거는 아직 필요하다.</p></div><div><b>산출물</b><p><code>docs/phase3/pilot-runbook.md</code>, evidence root layout, operator gates, <code>scripts/check-phase3-artifacts.py</code></p></div><div><b>검증</b><p><code>python scripts/check-phase3-artifacts.py --help</code>, <code>pytest app/test_phase3_artifact_checker.py -q</code> 5 passed. 실제 evidence root validation은 파일럿 시점에 실행.</p></div></div>
<div class="task-row"><div><span class="task-status s-plan">GATE</span></div><div><b>효과성/KPI 측정</b><p>SUS, 자기효능감, κ/ICC, 환각률, 피드백 타당도 기준과 report schema를 고정했다. 3척도 pre/post aggregate score를 저장하는 <code>app.learner_prepost_measure</code>, <code>/users/me/prepost-measures</code> API, 학습자 <code>SessionReview</code> 입력 카드에 더해, <code>scripts/export-phase3-kpi.py</code>가 원장 row를 <code>prepost_measures.csv</code><code>kpi_report.json</code> scaffold로 산출한다. checker는 KPI report top-level keys와 metric 필수 필드(<code>value/threshold/pass/method/source_files</code> 등)를 error로 검증한다. 실제 수치 충족 여부와 통계 해석은 파일럿 데이터 이후 판단한다.</p></div><div><b>산출물</b><p><code>docs/phase3/kpi-measurement-plan.md</code>, KPI report schema, checker tests, <code>UserPrepostMeasure*</code> API contract, <code>PrepostMeasureCard</code>, <code>app/services/phase3_kpi_export.py</code>, <code>scripts/export-phase3-kpi.py</code></p></div><div><b>검증</b><p>checker가 <code>prepost_measures</code>, <code>sus_responses</code>, <code>kpi_report.json</code> evidence와 metric shape를 요구. H1 KPI export/backend focused 45 passed, exporter <code>--help</code> passed, API types/typecheck passed, SessionReview focused E2E 3 passed, session review visual gate 2 passed.</p></div></div>
<div class="task-row"><div><span class="task-status s-plan">GATE</span></div><div><b>효과성/KPI 측정</b><p>SUS, 자기효능감, κ/ICC, 환각률, 피드백 타당도 기준과 report schema를 고정했다. 3척도 pre/post aggregate score를 저장하는 <code>app.learner_prepost_measure</code>, <code>/users/me/prepost-measures</code> API, 학습자 <code>SessionReview</code> 입력 카드에 더해, <code>scripts/export-phase3-kpi.py</code>가 원장 row를 <code>prepost_measures.csv</code><code>kpi_report.json</code> scaffold로 산출한다. checker는 KPI report top-level keys와 metric 필수 필드(<code>value/threshold/pass/status/method/source_files</code> 등)를 error로 검증한다. metric status는 <code>computed_prepost</code>/<code>design_pending</code>으로 제한해 계산 가능한 evidence와 평가설계 미확정 지표를 분리한다. 실제 수치 충족 여부와 통계 해석은 파일럿 데이터 이후 판단한다.</p></div><div><b>산출물</b><p><code>docs/phase3/kpi-measurement-plan.md</code>, KPI report schema, checker tests, <code>UserPrepostMeasure*</code> API contract, <code>PrepostMeasureCard</code>, <code>app/services/phase3_kpi_contract.py</code>, <code>app/services/phase3_kpi_export.py</code>, <code>scripts/export-phase3-kpi.py</code></p></div><div><b>검증</b><p>checker가 <code>prepost_measures</code>, <code>sus_responses</code>, <code>kpi_report.json</code> evidence와 metric shape/status를 요구. H1 KPI contract focused 10 passed, exporter/checker <code>--help</code> passed, 기존 API types/typecheck passed, SessionReview focused E2E 3 passed, session review visual gate 2 passed.</p></div></div>
<div class="task-row"><div><span class="task-status s-done">DONE</span></div><div><b>재귀학습 데이터셋 1차 산출</b><p>masked-text JSONL dry-run exporter, PII scan, κ/ICC 계산, approved export 차단 게이트를 구현했다. 이번 패스에서 artifact checker도 approved manifest false-positive를 막도록 강화했다. 실제 approved export와 데이터 steward 검수는 남아 있다.</p></div><div><b>산출물</b><p><code>scripts/export-recursive-dataset.py</code>, <code>app.services.dataset_export</code>, <code>docs/phase3/dataset-export-manifest.md</code>, <code>app/test_phase3_artifact_checker.py</code></p></div><div><b>검증</b><p><code>pytest app/test_dataset_export.py app/test_phase3_artifact_checker.py -q</code> 10 passed, DB dry-run 5 rows → <code>technical_dry_run</code> manifest + sha256 생성, approved manifest는 PII pass·κ/ICC·withdrawn exclusion·consent scope·file sha256을 요구.</p></div></div>
<div class="task-row"><div><span class="task-status s-plan">GATE</span></div><div><b>개인정보/동의 감사</b><p>동의, 철회, privacy audit, 해외 전송 고지 evidence checklist를 만들었다. 실제 법무/개인정보 검토 서명은 파일럿 전 gate로 남아 있다.</p></div><div><b>산출물</b><p><code>docs/phase3/privacy-consent-audit.md</code>, withdrawal log contract</p></div><div><b>검증</b><p>동의 철회 후 session/export 제외 evidence를 checker 입력으로 요구</p></div></div>
</div>
@ -909,7 +910,7 @@
<tr><td>Postgres RLS/audit smoke checker</td><td><code>python scripts\check-postgres-rls-audit.py --help</code></td><td>py_compile/help/safe default OK; live DSN run still required for proof</td></tr>
<tr><td>OAuth/BFF cookie security</td><td><code>python -B -m pytest -p no:cacheprovider app/test_auth_providers.py -q</code></td><td>30 tests OK; PKCE/state, cookie-bound signed state restart recovery, state cookie mismatch reject, opaque HttpOnly/Secure cookie, no browser tokens, SAML AuthnRequest/dev-only fixture ACS, dev/Tailnet OAuth guard, public-origin dev-login closed</td></tr>
<tr><td>H4 consent gate</td><td><code>pytest app/test_auth_providers.py app/test_session_turn_persistence.py app/test_persona_review.py app/test_voice_ws.py -q</code></td><td>64 passed; learner consent accept/withdraw, non-learner/unaccepted reject, no-consent session start 403 <code>consent_required</code>, voice dev persona consent guard, frontend generated API contract updated</td></tr>
<tr><td>H1 pre/post evidence API + KPI export</td><td><code>app/test_user_support_tickets.py</code>, <code>app/test_runtime_policy.py</code>, <code>app/test_admin_ops.py</code>, <code>app/test_phase3_kpi_export.py</code>, <code>app/test_phase3_artifact_checker.py</code>, <code>npm run check:api-types</code>, <code>npm run typecheck</code>, <code>e2e/session-review.spec.ts</code></td><td>45 backend passed; <code>app.learner_prepost_measure</code> stores learner-scoped aggregate pre/post scores for self-efficacy, skill proficiency, and training satisfaction. <code>scripts/export-phase3-kpi.py</code> emits pseudonymous <code>prepost_measures.csv</code> and KPI report scaffold with paired normalized deltas. Generated API contract exposes <code>UserPrepostMeasure*</code>. 학습자 <code>SessionReview</code>는 1~5 aggregate score 조회/저장 카드를 표시하고, focused E2E 3 passed + visual gate 2 passed.</td></tr>
<tr><td>H1 pre/post evidence API + KPI export</td><td><code>app/test_user_support_tickets.py</code>, <code>app/test_runtime_policy.py</code>, <code>app/test_admin_ops.py</code>, <code>app/test_phase3_kpi_export.py</code>, <code>app/test_phase3_artifact_checker.py</code>, <code>npm run check:api-types</code>, <code>npm run typecheck</code>, <code>e2e/session-review.spec.ts</code></td><td>Current H1 broad 46 passed; KPI contract focused 10 passed. <code>app.learner_prepost_measure</code> stores learner-scoped aggregate pre/post scores for self-efficacy, skill proficiency, and training satisfaction. <code>scripts/export-phase3-kpi.py</code> emits pseudonymous <code>prepost_measures.csv</code> and KPI report scaffold with paired normalized deltas. <code>phase3_kpi_contract.py</code> owns metric names, required keys, and <code>computed_prepost</code>/<code>design_pending</code> status values used by exporter/checker/tests. Generated API contract exposes <code>UserPrepostMeasure*</code>. 학습자 <code>SessionReview</code>는 1~5 aggregate score 조회/저장 카드를 표시하고, focused E2E 3 passed + visual gate 2 passed.</td></tr>
<tr><td>Persona review workflow</td><td><code>app.test_persona_review</code></td><td>31 tests OK; approved-only catalog, teacher/admin queue, draft authoring, approved persona revision, archive audit, learner 403</td></tr>
<tr><td>Voice WS contract</td><td><code>app.test_voice_ws</code></td><td>8 tests OK; auth guard, audio_start/chunks/audio_end, text_turn, ping, max audio cap, DB persona voice map binding, explicit preset override</td></tr>
<tr><td>Voice preset/EOT</td><td><code>app.test_voice_service</code></td><td>14 tests OK; TTS payload, preset fallback, DB voice-map resolver, unsupported provider fallback, P1 sample TTS, EOT readiness</td></tr>
@ -921,7 +922,7 @@
<tr><td>Hanshin data/SSO gate</td><td><code>docs/ops/hanshin-data-governance-gate.md</code></td><td>artifact created; written external evidence still required</td></tr>
<tr><td>Resistance engine</td><td><code>app.test_state_machine_resistance</code> + <code>scripts/smoke-resistance-openness-db.py</code></td><td>9 focused tests OK; live DB smoke PASS. Empathy P1 curve <code>0→0→0.095→0.133→0.171</code> and DB final <code>stage=탐색</code>. Advice-jump curve stayed <code>0</code> and DB final <code>stage=라포</code>.</td></tr>
<tr><td>Python compile</td><td><code>python -m compileall app engine_gateway</code></td><td>Passed</td></tr>
<tr><td>Phase 3 artifact gates</td><td><code>python scripts\check-phase3-artifacts.py --help</code> + <code>pytest app/test_phase3_artifact_checker.py -q</code></td><td>Passed; checker now enforces CSV enums, KPI metric required fields, approved export PII/agreement/consent/withdrawal/file-hash gates. Actual pilot evidence still external.</td></tr>
<tr><td>Phase 3 artifact gates</td><td><code>python scripts\check-phase3-artifacts.py --help</code> + <code>pytest app/test_phase3_artifact_checker.py -q</code></td><td>Passed; checker now enforces CSV enums, KPI metric required fields/status, approved export PII/agreement/consent/withdrawal/file-hash gates. Actual pilot evidence still external.</td></tr>
<tr><td>Web build</td><td><code>npm run build</code></td><td>Passed</td></tr>
<tr><td>Pages production deploy</td><td><code>wrangler pages deploy dist --project-name vignette --branch main --commit-dirty=true</code></td><td><code>24c90784</code>, dirty local source deploy</td></tr>
<tr><td>Custom domain assets</td><td><code>https://vignette.chanpaca.net/login</code></td><td><code>index-HjXyC0JT.js</code>, <code>index-C8JKXhNP.css</code></td></tr>