다크 UI 시안과 운영 문서 갱신
This commit is contained in:
parent
30441bd93b
commit
d9458d0e40
8 changed files with 269 additions and 41 deletions
|
|
@ -499,6 +499,16 @@
|
|||
<p class="dg-note">64차 적용(2026-06-28): refactor-governance 1차로 C1 생성 워크시트 section/item spec을 <code>CASE_WORKSHEET_SECTION_SPECS</code>와 <code>case_worksheet_template_item_keys()</code>로 올려 루브릭 validator/CLI/test가 더미 <code>ReviewTurn</code> 없이 같은 템플릿 키를 참조하게 했다. API/DB/frontend 응답 shape와 저장본 우선순위는 바꾸지 않았다. 검증: C1 focused 34 passed, rubric CLI PASS, py_compile, residual grep.</p>
|
||||
<p class="dg-note">65차 적용(2026-06-28): refactor-governance 2차로 프론트 inline CSS template literal을 제거했다. <code>Login</code>은 <code>pages/login/LoginBrand.tsx</code>, <code>LoginPanel.tsx</code>, <code>login.css</code>로 분리해 인증 상태 로직과 프레젠테이션을 나눴고, <code>PendingApproval</code>, <code>Onboarding</code>, <code>LearnerHome</code>, <code>Professor</code>, <code>PersonaStudio</code>, <code>ClientAvatar</code>의 <code>*_CSS</code> 상수는 인접 CSS 파일 import로 옮겼다. 검증: inline <code><style>{..._CSS}</style></code> residual 0건, <code>npm run typecheck</code>, <code>npm run build</code>, 로그인 Playwright 렌더 스모크.</p>
|
||||
<p class="dg-note">66차 적용(2026-06-28): H4 자연 발화형 한국어 이름 마스킹 보강으로 <code>제 이름은 김서연입니다</code>, <code>보호자 이름은 박민수입니다</code>, <code>저는 최하늘입니다</code> 패턴과 <code>이름은 중요하지 않다</code> negative control을 합성 fixture에 추가했다. <code>이름</code> stopword와 자기소개형 이름 패턴을 generic 성씨+조사 패턴보다 앞에 두어 라벨 단어만 마스킹하고 실명을 남기던 구멍을 막았다. 검증: H4 focused 43 passed, CLI fixture 15/15 pass, py_compile.</p>
|
||||
<p class="dg-note">67차 적용(2026-06-28): M2 다음 회기 회상 주입 증거를 route-level로 고정했다. REST submit과 SSE stream이 공통 <code>_prepare_turn_context()</code> 경계에서 seed recall, pinned fact, 최근 client-visible 턴, KB cue를 조립하게 했고, 새 테스트가 DB <code>case_digest</code>·직전 <code>session_summary</code>·client-visible <code>pinned_fact</code>가 다음 턴 EngineMessage의 L2/L4 블록에 들어가며 raw 이름은 <code>[NAME]</code>으로 마스킹되는지 검증한다. 검증: M2 focused 68 passed, py_compile.</p>
|
||||
<p class="dg-note">68차 적용(2026-06-28): refactor-governance P1 delete-only로 <code>routes/sessions.py</code>의 미사용 <code>_load_prev_case_summary()</code>와 죽은 <code>_LEARNER_VISIBLE_AI_ROLE</code> 상수를 제거했다. 동작 변경 없이 다음 M2 작업 표면을 줄인 정리다. 검증: residual grep 0건, M2 focused 68 passed, py_compile.</p>
|
||||
<p class="dg-note">69차 적용(2026-06-28): refactor-governance P3 계약 축소로 리뷰 도식 <code>ReviewPhaseSegment.key/label</code>, <code>SessionReviewResponse.reachedPhase</code>, 평가 조회 <code>EvaluationSummary.stage</code>, 평가 트리거 응답 stage, 교수 콘솔 <code>TeacherSessionSummary</code>/<code>TeacherGrowthPoint</code> stage를 OpenAPI union으로 좁혔다. 공유 카드 <code>reachedPhase</code>와 live-coach event stage는 저장 payload/legacy 호환 검토가 필요해 별도 후속으로 남겼다. 검증: backend focused 50 passed, <code>npm run check:api-types</code>, <code>npm run typecheck</code>, <code>npm run build</code>.</p>
|
||||
<p class="dg-note">70차 적용(2026-06-28): H4 마스킹 경계를 refactor-governance 기준으로 좁혀 <code>guardrail.mask_pii()</code> 내부에 선택형 한국어 PII recognizer adapter를 추가했다. adapter는 import-time hard dependency가 아니며 실패 시 예외를 삼키고 기존 regex fallback을 마지막 안전망으로 유지한다. fake adapter 회귀는 regex가 못 잡는 별명/기관 span을 <code>[NAME]</code>/<code>[ORG]</code>로 마스킹하고 같은 문장의 전화번호는 후단 regex가 <code>[PHONE]</code>으로 처리하는지, adapter 실패 시에도 regex fallback이 유지되는지 검증한다. span 중복 처리는 문자별 set 할당 대신 정렬된 <code>last_end</code> 비교로 정리했다. 검증: H4 focused 47 passed, CLI fixture 15/15 pass, py_compile.</p>
|
||||
<p class="dg-note">71차 적용(2026-06-28): refactor-governance P3 계약 축소 후속으로 공유 카드 <code>PublicSessionShareResponse.reachedPhase</code>와 <code>LiveCoachEvent.stage</code>도 OpenAPI enum/null 계약으로 좁혔다. 저장된 legacy JSON/TEXT 값은 <code>stage_label_or_none()</code> validator에서 한글 라벨 또는 기존 code key만 살리고, 빈 문자열·임의 문자열은 <code>null</code>로 낮춰 기존 공유 링크·live-coach 이력이 500으로 깨지지 않게 했다. 세션 UI 코칭 이력은 null stage를 <code>단계 미상</code>으로 표시한다. 검증: share/live-coach focused 43 passed, <code>npm run check:api-types</code>, <code>npm run typecheck</code>, <code>npm run build</code>.</p>
|
||||
<p class="dg-note">72차 적용(2026-06-28): refactor-governance P3 계약 SSOT 후속으로 stage 라벨과 리뷰 phase key 변환 규칙을 <code>app/stage_contract.py</code>로 분리했다. <code>turn_runtime.py</code>의 legacy code 정규화, <code>session_read_model.py</code>의 <code>StageLabel</code>/<code>ReviewPhaseKey</code>, <code>routes/eval.py</code>의 nullable summary stage membership 체크가 같은 모듈을 참조한다. OpenAPI 응답 shape는 유지하고, enum/legacy/unknown 처리만 한 곳으로 모았다. 검증: stage contract focused 42 passed, <code>npm run check:api-types</code>, <code>npm run typecheck</code>, <code>npm run build</code>.</p>
|
||||
<p class="dg-note">73차 적용(2026-06-28): C1 워크시트 교수자 수동 검수 골격을 추가했다. 기존 <code>app.session_review_status</code>에 <code>worksheet_status</code>/<code>worksheet_note</code>/<code>worksheet_reviewed_at</code>을 붙이고, <code>PUT /teacher/sessions/{id}/review-status</code>가 승인·수정요청·반려 결정을 저장한다. <code>/sessions/{id}/review</code>의 <code>teacherReview</code>는 교수자/관리자에게만 이 결정을 노출하고, <code>SessionReview</code> 교수자 카드에는 학습자 저장 워크시트가 있을 때만 수동 검수 버튼을 활성화한다. AI 채점·루브릭 점수·자동 승인·재제출 잠금 정책은 만들지 않았다. 검증: C1 focused 40 passed, API types/typecheck/build, session-review E2E 3 passed.</p>
|
||||
<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">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>
|
||||
|
|
@ -542,8 +552,8 @@
|
|||
<div class="dg-card-body"><b>로그인/진입</b><span>OAuth, 허용 도메인, 로컬 역할 선택, 교육용 고지를 신뢰 중심으로 정리한 진입 화면.</span><code>01-login-responsive.png</code></div>
|
||||
</article>
|
||||
<article class="dg-card">
|
||||
<img src="design-concepts/generated/02-learner-home-responsive.png" alt="학습자 홈 PC 태블릿 모바일 반응형 디자인 시안" loading="lazy">
|
||||
<div class="dg-card-body"><b>학습자 홈</b><span>내담자 선택, 회기 시작, 이전 회기 이어가기, 리뷰 가능 상태를 작업대처럼 배치.</span><code>02-learner-home-responsive.png</code></div>
|
||||
<img src="design-concepts/generated/02-learner-home-responsive-v2-dark-unified.png" alt="학습자 홈 PC 태블릿 모바일 다크 테마 디자인 시안" loading="lazy">
|
||||
<div class="dg-card-body"><b>학습자 홈</b><span>내담자 선택, 회기 시작, 이전 회기 이어가기, 리뷰 가능 상태를 다크 작업대 톤으로 배치.</span><code>02-learner-home-responsive-v2-dark-unified.png</code></div>
|
||||
</article>
|
||||
<article class="dg-card">
|
||||
<img src="design-concepts/generated/11-learner-dashboard-redesign-concept-b.png" alt="학습자 대시보드 회기 리캡 중심 재설계 시안" loading="lazy">
|
||||
|
|
@ -554,12 +564,12 @@
|
|||
<div class="dg-card-body"><b>학습자 대시보드 대안</b><span>마지막 내담자 반응과 내 마지막 반응을 더 강하게 드러낸 대안 레이아웃.</span><code>12-learner-dashboard-redesign-concept-b-alt.png</code></div>
|
||||
</article>
|
||||
<article class="dg-card">
|
||||
<img src="design-concepts/generated/03-session-responsive.png" alt="라이브 상담 세션 PC 태블릿 모바일 반응형 디자인 시안" loading="lazy">
|
||||
<div class="dg-card-body"><b>라이브 상담 세션</b><span>내담자 무대, 자막, 입력, 라이브 신호, 안전 상태를 몰입형 훈련 화면으로 구성.</span><code>03-session-responsive.png</code></div>
|
||||
<img src="design-concepts/generated/03-session-responsive-v2-dark-unified.png" alt="라이브 상담 세션 PC 태블릿 모바일 다크 테마 디자인 시안" loading="lazy">
|
||||
<div class="dg-card-body"><b>라이브 상담 세션</b><span>내담자 무대, 자막, 입력, 라이브 신호, 안전 상태를 다크 몰입형 훈련 화면으로 구성.</span><code>03-session-responsive-v2-dark-unified.png</code></div>
|
||||
</article>
|
||||
<article class="dg-card">
|
||||
<img src="design-concepts/generated/04-session-review-responsive.png" alt="회기 리뷰 PC 태블릿 모바일 반응형 디자인 시안" loading="lazy">
|
||||
<div class="dg-card-body"><b>회기 리뷰</b><span>감정 흐름, 축어록, 기법 태그, 교수자 콜아웃을 근거 기반 회고 화면으로 묶음.</span><code>04-session-review-responsive.png</code></div>
|
||||
<img src="design-concepts/generated/04-session-review-responsive-v2-dark-unified.png" alt="회기 리뷰 PC 태블릿 모바일 다크 테마 디자인 시안" loading="lazy">
|
||||
<div class="dg-card-body"><b>회기 리뷰</b><span>감정 흐름, 축어록, 기법 태그, 교수자 콜아웃을 다크 근거 기반 회고 화면으로 묶음.</span><code>04-session-review-responsive-v2-dark-unified.png</code></div>
|
||||
</article>
|
||||
<article class="dg-card">
|
||||
<img src="design-concepts/generated/05-professor-responsive.png" alt="교수 콘솔 PC 태블릿 모바일 반응형 디자인 시안" loading="lazy">
|
||||
|
|
@ -647,8 +657,8 @@
|
|||
<div class="tg-head"><h3>원천문서 갭 분석</h3><span class="tg-note">산학협력 원천문서 5종 정독 + 코드 교차조사 (2026-06-26). 근거: source-docs-gap-analysis-2026-06-26.md · critical 3 / high 4 / medium+ 6</span></div>
|
||||
<div class="scards">
|
||||
<article class="scard" data-status="done" data-cat="콘텐츠·산출물" data-owner="0">
|
||||
<button class="scard-head" aria-expanded="false"><span class="chip c-done">C1 3차 DONE</span><span class="scard-mid"><span class="scard-title">사례개념화·치료계획 워크시트 저장형 구조<span class="critbadge">critical</span></span><span class="scard-sum">학습자 워크시트 저장 경로와 임상팀 루브릭 외부화 scaffold를 연결했다. 실제 루브릭 콘텐츠는 외부 의존.</span></span><span class="caret" aria-hidden="true"></span></button>
|
||||
<div class="scard-body"><div class="kv"><b>현재</b><p>doc1/4/5가 1급 산출물로 요구하는 워크시트 축(탐색 11항목·호소 5영역·인지삼제·1/2차 감정·보호/방해 4사분면·생물심리사회 목표)을 <code>SessionReviewResponse.caseWorksheet</code>로 노출한다. 서버는 저장된 축어록만 근거로 규칙 기반 초안을 만들고, 프론트 리뷰 화면은 근거 턴으로 점프 가능한 워크시트를 편집할 수 있다. 저장본은 <code>PUT /sessions/{id}/review/worksheet</code> → <code>app.case_worksheet</code>에 저장되고, 이후 <code>GET /review</code>는 <code>saved_by_learner</code> 워크시트를 자동 초안보다 우선 반환한다. 3차에서는 임상팀 확정 루브릭을 코드 수정 없이 받을 수 있도록 <code>data/rubrics/case-worksheet-rubric.json</code>, <code>case-worksheet-rubric.schema.json</code>, <code>app.services.case_worksheet_rubric</code>, <code>scripts/check-case-worksheet-rubric.py</code>를 추가했다. 현재 rubric은 <code>status=scaffold_only</code>, <code>scoring_enabled=false</code>이고, 5개 section/28개 worksheet item key가 서버 생성 워크시트와 일치하는지만 검증한다. 64차에서는 템플릿 key source를 <code>CASE_WORKSHEET_SECTION_SPECS</code>/<code>case_worksheet_template_item_keys()</code>로 명시해 validator/CLI/test가 같은 생성 워크시트 spec을 참조한다.</p></div><div class="kv"><b>검증</b><p><code>pytest app/test_session_turn_persistence.py app/test_rbac_idor.py -q</code> 25 passed, <code>py -3.11 -X utf8 -m pytest -p no:cacheprovider app/test_case_worksheet_rubric.py -q</code> 4 passed, <code>py -3.11 -X utf8 scripts/check-case-worksheet-rubric.py --json</code> PASS(<code>items_total=28</code>, <code>scoring_enabled=false</code>, sha256 provenance), <code>py -3.11 -m py_compile apps/api/app/services/case_worksheet_rubric.py apps/api/app/test_case_worksheet_rubric.py scripts/check-case-worksheet-rubric.py</code>, <code>npm run check:api-types</code>, <code>npm run typecheck</code>, <code>npm run build</code> passed. 로컬 API smoke: 세션 생성→워크시트 저장→<code>GET /review</code>가 <code>saved_by_learner:local smoke saved worksheet</code> 반환.</p></div><div class="kv k-warn"><b>잔여</b><p>임상팀 확정 루브릭 콘텐츠, level anchor, 가중치, 컷오프, 예시 답안, AI 채점 적용/calibration, 교수자 승인·반려·수정요청 검수 플로우는 후속이다. 현재 scaffold는 형식 검증과 로드 경로만 보장하며, 초안/저장본은 숨은 정답키나 임상 점수 없이 학습자 편집 산출물로만 취급한다.</p></div></div>
|
||||
<button class="scard-head" aria-expanded="false"><span class="chip c-done">C1 4차 DONE</span><span class="scard-mid"><span class="scard-title">사례개념화·치료계획 워크시트 저장형 구조<span class="critbadge">critical</span></span><span class="scard-sum">학습자 저장, 임상팀 루브릭 외부화 scaffold, 교수자 수동 검수 상태 저장을 연결했다. 실제 루브릭 콘텐츠는 외부 의존.</span></span><span class="caret" aria-hidden="true"></span></button>
|
||||
<div class="scard-body"><div class="kv"><b>현재</b><p>doc1/4/5가 1급 산출물로 요구하는 워크시트 축(탐색 11항목·호소 5영역·인지삼제·1/2차 감정·보호/방해 4사분면·생물심리사회 목표)을 <code>SessionReviewResponse.caseWorksheet</code>로 노출한다. 서버는 저장된 축어록만 근거로 규칙 기반 초안을 만들고, 프론트 리뷰 화면은 근거 턴으로 점프 가능한 워크시트를 편집할 수 있다. 저장본은 <code>PUT /sessions/{id}/review/worksheet</code> → <code>app.case_worksheet</code>에 저장되고, 이후 <code>GET /review</code>는 <code>saved_by_learner</code> 워크시트를 자동 초안보다 우선 반환한다. 3차에서는 임상팀 확정 루브릭을 코드 수정 없이 받을 수 있도록 <code>data/rubrics/case-worksheet-rubric.json</code>, <code>case-worksheet-rubric.schema.json</code>, <code>app.services.case_worksheet_rubric</code>, <code>scripts/check-case-worksheet-rubric.py</code>를 추가했다. 현재 rubric은 <code>status=scaffold_only</code>, <code>scoring_enabled=false</code>이고, 5개 section/28개 worksheet item key가 서버 생성 워크시트와 일치하는지만 검증한다. 64차에서는 템플릿 key source를 <code>CASE_WORKSHEET_SECTION_SPECS</code>/<code>case_worksheet_template_item_keys()</code>로 명시해 validator/CLI/test가 같은 생성 워크시트 spec을 참조한다. 73차에서는 <code>app.session_review_status</code>에 워크시트 수동 검수 상태·메모·시각을 붙여 교수자/관리자가 승인·수정요청·반려 결정을 남길 수 있게 했다.</p></div><div class="kv"><b>검증</b><p><code>py -3.11 -X utf8 -m pytest -p no:cacheprovider app/test_session_turn_persistence.py app/test_teacher_dashboard.py app/test_rbac_idor.py app/test_case_worksheet_rubric.py -q</code> 40 passed, <code>py -3.11 -X utf8 scripts/check-case-worksheet-rubric.py --json</code> PASS(<code>items_total=28</code>, <code>scoring_enabled=false</code>, sha256 provenance), <code>py -3.11 -X utf8 -m py_compile</code> focused files, <code>npm run generate:api-types</code>, <code>npm run check:api-types</code>, <code>npm run typecheck</code>, <code>npm run build</code>, <code>session-review.spec.ts</code> 3 passed. 로컬 API smoke: 세션 생성→워크시트 저장→<code>GET /review</code>가 <code>saved_by_learner:local smoke saved worksheet</code> 반환.</p></div><div class="kv k-warn"><b>잔여</b><p>임상팀 확정 루브릭 콘텐츠, level anchor, 가중치, 컷오프, 예시 답안, AI 채점 적용/calibration, 승인 후 잠금·재제출 정책, 재귀학습 데이터셋 approved 연계는 후속이다. 현재 scaffold는 형식 검증과 로드 경로, 교수자 수동 판정 저장만 보장하며, 초안/저장본은 숨은 정답키나 임상 점수 없이 학습자 편집 산출물로만 취급한다.</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">C2 구조 DONE</span><span class="scard-mid"><span class="scard-title">위기 에스컬레이션·safety_events 배선</span><span class="scard-sum">crisis.risk_level→ideation_observed 전달 + escalate 시 safety_events 적재. 라이브 검증.</span></span><span class="caret" aria-hidden="true"></span></button>
|
||||
|
|
@ -664,7 +674,7 @@
|
|||
</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>
|
||||
<div class="scard-body"><div class="kv"><b>현재</b><p>텍스트 <code>/turn</code>·<code>/stream</code>·음성 경로에서 fast-loop 턴 평가를 <code>app.feedback_scores</code>, <code>app.turn_technique</code>, <code>app.turn_client_state</code>, <code>app.supervisor_comment</code>, <code>app.alternative_utterance</code>로 정규화 적재 후 리뷰 조회에서 hydrate한다. 이번 패스에서 <code>app/services/live_coach.py</code>, <code>data/kb/live_coaching_workbook_0615.json</code>, <code>data/kb/live_coaching_sources/*.json</code>, <code>POST /sessions/{id}/live-coach</code>, <code>GET /sessions/{id}/live-coach</code>, <code>POST /kb/live-coach/source-packs/sync</code>, <code>app.live_coach_events</code>를 추가했다. 세션 UI는 코칭 모드에서 AI 코치 아바타 말풍선으로 즉시 개입하고, "근거 보기" 모달과 학습자 발화 우측 <code>C</code> 마커를 통해 받은 코칭 이력을 채팅 위 스크롤 오버레이로 보여준다. 저장 payload는 PII 마스킹 excerpt + 코칭 구조화 JSON이며, 공식 자료는 허가된 source pack의 version/citation/summary로 넣고 장문 원문·공식 문항을 재현하지 않는다. 같은 source pack은 <code>app.services.source_pack_sync</code>를 통해 evaluator 전용 RAG에 증분 색인하며, hash 변경 시 최신 active document version+1로 새 문서를 만든다. 학습자 <code>SessionReview</code> 데스크톱은 좌측 축어록 타임라인과 우측 요약·감정·흐름·루브릭·강점·개선점·pre/post·워크시트·피드백 작업열의 2열 구조로 재배치했고, 교수자 검토 화면과 모바일/태블릿은 기존 반응형 규칙을 유지한다.</p></div><div class="kv"><b>검증</b><p><code>python -m compileall app -q</code>, 과거 <code>pytest app/test_live_coach_sources.py app/test_orchestrator_masking.py app/test_session_turn_persistence.py -q</code> 27 passed, source sync focused <code>python -B -m pytest -p no:cacheprovider app/test_live_coach_sources.py -q</code> 4 passed, <code>py -3.11 scripts\sync-persona-sources.py --help</code>, <code>npm run generate:api-types</code>, <code>npm run check:api-types</code>, <code>npm run typecheck</code>, <code>npm run build</code>, <code>npm run e2e -- e2e/layout-visual-gate.spec.ts e2e/session-layout.spec.ts</code> 15 passed. 이번 2열 UI 검증: <code>npm run typecheck</code>, <code>npx playwright test e2e/session-review.spec.ts --project=chromium-desktop --workers=1</code> 3 passed, <code>npx playwright test e2e/layout-visual-gate.spec.ts --project=chromium-single-run --workers=1</code> 9 passed, <code>session-review__1440-desktop.png</code> 직접 확인. 로컬 DB smoke: source pack sync 4 sources/15 chunks embedded, 재호출 <code>skipped_unchanged=4</code>, <code>official_suicide_risk_guidelines</code> source filter 검색 성공.</p></div><div class="kv k-warn"><b>잔여</b><p>임상팀 골든셋·라이브 코칭 루브릭 검수, 공식 지침/논문 source pack 추가와 임상 검수 상태 운영정책 보강이 남아 있다.</p></div></div>
|
||||
<div class="scard-body"><div class="kv"><b>현재</b><p>텍스트 <code>/turn</code>·<code>/stream</code>·음성 경로에서 fast-loop 턴 평가를 <code>app.feedback_scores</code>, <code>app.turn_technique</code>, <code>app.turn_client_state</code>, <code>app.supervisor_comment</code>, <code>app.alternative_utterance</code>로 정규화 적재 후 리뷰 조회에서 hydrate한다. 이번 패스에서 <code>app/services/live_coach.py</code>, <code>data/kb/live_coaching_workbook_0615.json</code>, <code>data/kb/live_coaching_sources/*.json</code>, <code>POST /sessions/{id}/live-coach</code>, <code>GET /sessions/{id}/live-coach</code>, <code>POST /kb/live-coach/source-packs/sync</code>, <code>app.live_coach_events</code>를 추가했다. 세션 UI는 코칭 모드에서 AI 코치 아바타 말풍선으로 즉시 개입하고, "근거 보기" 모달과 학습자 발화 우측 <code>C</code> 마커를 통해 받은 코칭 이력을 채팅 위 스크롤 오버레이로 보여준다. 저장 payload는 PII 마스킹 excerpt + 코칭 구조화 JSON이며, 공식 자료는 허가된 source pack의 version/citation/summary로 넣고 장문 원문·공식 문항을 재현하지 않는다. 같은 source pack은 <code>app.services.source_pack_sync</code>를 통해 evaluator 전용 RAG에 증분 색인하며, hash 변경 시 최신 active document version+1로 새 문서를 만든다. 74차에서는 로컬 source pack 캐시의 key/lifetime/무효화 경계를 명시해 live turn은 API 프로세스 snapshot을 재사용하고, 관리자 sync/CLI만 <code>refresh=True</code>로 repo 파일을 다시 읽게 했다. 학습자 <code>SessionReview</code> 데스크톱은 좌측 축어록 타임라인과 우측 요약·감정·흐름·루브릭·강점·개선점·pre/post·워크시트·피드백 작업열의 2열 구조로 재배치했고, 교수자 검토 화면과 모바일/태블릿은 기존 반응형 규칙을 유지한다.</p></div><div class="kv"><b>검증</b><p><code>python -m compileall app -q</code>, 과거 <code>pytest app/test_live_coach_sources.py app/test_orchestrator_masking.py app/test_session_turn_persistence.py -q</code> 27 passed, source sync focused <code>py -3.11 -X utf8 -m pytest -p no:cacheprovider app/test_live_coach_sources.py -q</code> 9 passed, H2 related <code>py -3.11 -X utf8 -m pytest -p no:cacheprovider app/test_live_coach_sources.py app/test_orchestrator_masking.py app/test_session_turn_persistence.py -q</code> 44 passed, <code>py -3.11 -X utf8 scripts\sync-persona-sources.py --help</code>, <code>py -3.11 -X utf8 -m py_compile</code> focused files, <code>npm run generate:api-types</code>, <code>npm run check:api-types</code>, <code>npm run typecheck</code>, <code>npm run build</code>, <code>npm run e2e -- e2e/layout-visual-gate.spec.ts e2e/session-layout.spec.ts</code> 15 passed. 이번 2열 UI 검증: <code>npm run typecheck</code>, <code>npx playwright test e2e/session-review.spec.ts --project=chromium-desktop --workers=1</code> 3 passed, <code>npx playwright test e2e/layout-visual-gate.spec.ts --project=chromium-single-run --workers=1</code> 9 passed, <code>session-review__1440-desktop.png</code> 직접 확인. 로컬 DB smoke: source pack sync 4 sources/15 chunks embedded, 재호출 <code>skipped_unchanged=4</code>, <code>official_suicide_risk_guidelines</code> source filter 검색 성공.</p></div><div class="kv k-warn"><b>잔여</b><p>임상팀 골든셋·라이브 코칭 루브릭 검수, 공식 지침/논문 source pack 추가와 임상 검수 상태 운영정책 보강이 남아 있다.</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">HIGH H3 10차 DONE</span><span class="scard-mid"><span class="scard-title">페르소나 스튜디오 + RAG 첨부 + 항목형 저작/검토</span><span class="scard-sum">JSON 패널을 별도 저작 스튜디오로 분리하고, 실제 기록·교재 자료 기반 초안·목록형 임상 필드·라벨형 프롬프트 검토를 제공한다.</span></span><span class="caret" aria-hidden="true"></span></button>
|
||||
|
|
@ -672,11 +682,11 @@
|
|||
</article>
|
||||
<article class="scard" data-status="done" data-cat="보안·PII" data-owner="0">
|
||||
<button class="scard-head" aria-expanded="false"><span class="chip c-done">H4 부분 DONE</span><span class="scard-mid"><span class="scard-title">PII 마스킹 + LLM 호출 감사 + 프로필 온보딩·동의 게이트</span><span class="scard-sum">metadata-only llm_call_log, 15-case summary-only 마스킹 평가 계약, 전역 URL 하드게이트.</span></span><span class="caret" aria-hidden="true"></span></button>
|
||||
<div class="scard-body"><div class="kv"><b>현재</b><p>정규식 폴백에 한국어 고위험 패턴 추가: 구체 날짜(2001.4.18→[DATE])·금액(1200원→[MONEY])·행정구역 주소(서울시 강남구 역삼동→[ADDR])에 더해, 이름/성명 라벨·성씨+이름+조사/호칭·대학교/학과/병원/센터 등 suffix 기반 한국어 NAME/ORG 로컬 휴리스틱 1차를 추가했다. 이번 패스에서 <code>app.services.pii_masking_eval</code>, <code>data/privacy/pii-masking-ko-fixtures.json</code>, <code>scripts/evaluate-pii-masking.py</code>를 확장해 합성 fixture 15개로 NAME/ORG/PHONE/EMAIL/RRN/NUMID/DATE/MONEY/ADDR/negative-control을 <code>source</code>/<code>category</code>/<code>severity</code> metadata와 함께 entity recall·forbidden substring removal·unexpected entity violation으로 평가한다. <code>data/privacy/pii-masking-eval-input.schema.json</code>과 <code>data/privacy/pii-masking-eval-report.schema.json</code>은 Node.js 전환 후에도 공유 가능한 입력/리포트 계약이며, 기본 리포트는 <code>evidence_text_included=false</code>로 <code>masked_text</code>/<code>forbidden_remaining</code> 원문 증거를 제외한다. fixture가 잡은 <code>소속</code>/<code>안내</code>/<code>이름</code> NAME 오탐은 stopword로 보정했고, 66차에서는 자연 발화형 이름 라벨·자기소개 케이스를 추가해 라벨 단어만 마스킹하고 실명을 남기던 구멍을 막았다. Presidio가 설치돼도 한국어 누락을 막기 위해 regex fallback을 후단에 한 번 더 태운다. 상담 생성(generate/stream)·fast/deep 평가의 외부 LLM 호출 직후 <code>audit.llm_call_log</code>에 provider/model/token/cost/inference_geo/latency만 적재한다. prompt/completion 본문은 저장하지 않는다. fast evaluator prompt의 내담자 응답과 DB에 저장되는 client turn <code>text_masked</code>도 마스킹본을 쓰게 보강했다. 로컬 dev-login 실제 <code>/turn</code> smoke에서 <code>audit.llm_call_log</code>가 8→11로 3행 증가했다. 이번 패스에서 <code>app_user</code>에 이름·소속·학과·학년/직위·연락처·주소/수령지·닉네임·자기소개·아바타 URL·약관/개인정보 동의 시각·버전을 추가했다. 로그인 후 <code>onboarding_completed_at</code>이 없으면 전역 <code>OnboardingGate</code>가 <code>/onboarding</code> 외 URL(<code>/learn</code>, <code>/settings</code>, <code>/admin</code>, <code>/dev/avatar-preview</code>, <code>/login</code> 포함)을 모두 온보딩으로 돌리고, 온보딩 중에는 공용 셸 메뉴 없이 가입 직후 사용자 정보 입력 폼만 보여준다. learner 회기 시작도 온보딩 완료 전에는 막는다. <code>/users/me/avatar</code>는 PNG/JPG/WebP MIME·시그니처·3MB 제한 후 파일을 <code>USER_UPLOAD_DIR/profile-avatars</code>에 저장하고 URL만 보관한다. 약관·개인정보 처리방침 초안은 <code>/users/legal-docs</code>에서 제공한다. 관리자/교수자 권한은 온보딩에서 받지 않고 allowlist/관리자 사용자 관리로만 부여한다. 검증: <code>py -3.11 -X utf8 -m pytest -p no:cacheprovider app/test_pii_masking_eval.py app/test_orchestrator_masking.py app/test_evaluation_persistence.py app/test_session_turn_persistence.py -q</code> 43 passed, <code>py -3.11 -X utf8 scripts/evaluate-pii-masking.py --json</code> 15/15 pass, <code>py -3.11 -X utf8 -m py_compile apps/api/app/services/guardrail.py apps/api/app/services/pii_masking_eval.py apps/api/app/test_pii_masking_eval.py scripts/evaluate-pii-masking.py</code>, 과거 온보딩/동의 검증은 <code>pytest app/ -q</code> 178 passed, <code>pytest engine_gateway/ -q</code> 11 passed, <code>npm run check:api-types</code>, <code>npm run typecheck</code>, <code>npm run build</code>, auth E2E 7 passed, 최종 <code>PLAYWRIGHT_PORT=5174 npm run e2e</code> 113 passed.</p></div><div class="kv k-warn"><b>잔여</b><p>한국어 이름/기관과 contact/national/quasi-identifier 합성 technical dry-run은 15-case summary-only harness까지 완료했지만, Presidio ko/spaCy ko/의료·학교 도메인 NER 기반 정밀화, 실제 운영 말뭉치 기반 오탐/미탐 평가, 미성년/guardian 및 법무 검토가 필요한 최종 서명 동의서·개인정보 처리방침·약관 evidence는 외부/후속 게이트다. 공개 Google OAuth 실제 <code>/turn</code> proof는 별도 운영 게이트.</p></div></div>
|
||||
<div class="scard-body"><div class="kv"><b>현재</b><p>정규식 폴백에 한국어 고위험 패턴 추가: 구체 날짜(2001.4.18→[DATE])·금액(1200원→[MONEY])·행정구역 주소(서울시 강남구 역삼동→[ADDR])에 더해, 이름/성명 라벨·성씨+이름+조사/호칭·대학교/학과/병원/센터 등 suffix 기반 한국어 NAME/ORG 로컬 휴리스틱 1차를 추가했다. 이번 패스에서 <code>app.services.pii_masking_eval</code>, <code>data/privacy/pii-masking-ko-fixtures.json</code>, <code>scripts/evaluate-pii-masking.py</code>를 확장해 합성 fixture 15개로 NAME/ORG/PHONE/EMAIL/RRN/NUMID/DATE/MONEY/ADDR/negative-control을 <code>source</code>/<code>category</code>/<code>severity</code> metadata와 함께 entity recall·forbidden substring removal·unexpected entity violation으로 평가한다. <code>data/privacy/pii-masking-eval-input.schema.json</code>과 <code>data/privacy/pii-masking-eval-report.schema.json</code>은 Node.js 전환 후에도 공유 가능한 입력/리포트 계약이며, 기본 리포트는 <code>evidence_text_included=false</code>로 <code>masked_text</code>/<code>forbidden_remaining</code> 원문 증거를 제외한다. fixture가 잡은 <code>소속</code>/<code>안내</code>/<code>이름</code> NAME 오탐은 stopword로 보정했고, 66차에서는 자연 발화형 이름 라벨·자기소개 케이스를 추가해 라벨 단어만 마스킹하고 실명을 남기던 구멍을 막았다. Presidio가 설치돼도 한국어 누락을 막기 위해 regex fallback을 후단에 한 번 더 태운다. 70차에서는 <code>guardrail.mask_pii()</code> 내부에 선택형 한국어 PII recognizer adapter 경계를 추가했다. adapter는 명시 등록 전에는 비활성이고, 실패해도 예외를 밖으로 내보내지 않으며, fake adapter 테스트가 adapter span 마스킹 뒤 후단 regex fallback이 전화번호를 계속 처리하는지 고정한다. 상담 생성(generate/stream)·fast/deep 평가의 외부 LLM 호출 직후 <code>audit.llm_call_log</code>에 provider/model/token/cost/inference_geo/latency만 적재한다. prompt/completion 본문은 저장하지 않는다. fast evaluator prompt의 내담자 응답과 DB에 저장되는 client turn <code>text_masked</code>도 마스킹본을 쓰게 보강했다. 로컬 dev-login 실제 <code>/turn</code> smoke에서 <code>audit.llm_call_log</code>가 8→11로 3행 증가했다. 이번 패스에서 <code>app_user</code>에 이름·소속·학과·학년/직위·연락처·주소/수령지·닉네임·자기소개·아바타 URL·약관/개인정보 동의 시각·버전을 추가했다. 로그인 후 <code>onboarding_completed_at</code>이 없으면 전역 <code>OnboardingGate</code>가 <code>/onboarding</code> 외 URL(<code>/learn</code>, <code>/settings</code>, <code>/admin</code>, <code>/dev/avatar-preview</code>, <code>/login</code> 포함)을 모두 온보딩으로 돌리고, 온보딩 중에는 공용 셸 메뉴 없이 가입 직후 사용자 정보 입력 폼만 보여준다. learner 회기 시작도 온보딩 완료 전에는 막는다. <code>/users/me/avatar</code>는 PNG/JPG/WebP MIME·시그니처·3MB 제한 후 파일을 <code>USER_UPLOAD_DIR/profile-avatars</code>에 저장하고 URL만 보관한다. 약관·개인정보 처리방침 초안은 <code>/users/legal-docs</code>에서 제공한다. 관리자/교수자 권한은 온보딩에서 받지 않고 allowlist/관리자 사용자 관리로만 부여한다. 검증: <code>py -3.11 -X utf8 -m pytest -p no:cacheprovider app/test_pii_masking_eval.py app/test_orchestrator_masking.py app/test_evaluation_persistence.py app/test_session_turn_persistence.py -q</code> 47 passed, <code>py -3.11 -X utf8 scripts/evaluate-pii-masking.py --json</code> 15/15 pass, <code>py -3.11 -X utf8 -m py_compile apps/api/app/services/guardrail.py apps/api/app/services/pii_masking_eval.py apps/api/app/test_pii_masking_eval.py apps/api/app/test_orchestrator_masking.py scripts/evaluate-pii-masking.py</code>, 과거 온보딩/동의 검증은 <code>pytest app/ -q</code> 178 passed, <code>pytest engine_gateway/ -q</code> 11 passed, <code>npm run check:api-types</code>, <code>npm run typecheck</code>, <code>npm run build</code>, auth E2E 7 passed, 최종 <code>PLAYWRIGHT_PORT=5174 npm run e2e</code> 113 passed.</p></div><div class="kv k-warn"><b>잔여</b><p>한국어 이름/기관과 contact/national/quasi-identifier 합성 technical dry-run은 15-case summary-only harness 및 선택형 ko recognizer adapter 배선 회귀까지 완료했지만, 실제 운영 모델/provider 선정, 운영 말뭉치 기반 오탐/미탐 평가, 미성년/guardian 및 법무 검토가 필요한 최종 서명 동의서·개인정보 처리방침·약관 evidence는 외부/후속 게이트다. 공개 Google OAuth 실제 <code>/turn</code> proof는 별도 운영 게이트.</p></div></div>
|
||||
</article>
|
||||
<article class="scard" data-status="planned" data-cat="리팩터·정리" data-owner="0">
|
||||
<button class="scard-head" aria-expanded="false"><span class="chip c-plan">MED M1~M3</span><span class="scard-mid"><span class="scard-title">비언어 임상이벤트(M1)·다회기 케이스 아크(M2)·SSO claim/식별자/감사(M3)</span><span class="scard-sum">M1 provider_events 보존+taxonomy+리뷰 파생 노출 4차, M2 case memory+pinned_fact/history/명시철회 contradiction+episodic embedding writer 6차 완료. M3는 cohort map + provider external_id 1차 완료, 기관 SAML/거버넌스는 남음.</span></span><span class="caret" aria-hidden="true"></span></button>
|
||||
<div class="scard-body"><div class="kv"><b>현재</b><p>M1: voice learner turn의 audio_ref, silence_ms, speech_rate, barge_in을 리뷰 API <code>nonverbal</code> 이벤트로 파생하고, 회기 리뷰 축어록에 침묵·발화 속도·끼어듦·음성 입력 칩으로 표시한다. 2차로 <code>provider_events</code> JSONB 보존 슬롯을 추가해 control/STT provider 이벤트를 allowlist·size limit 후 저장한다. 3차에서는 저장 이벤트에 내부 taxonomy <code>event_type</code>/<code>category</code>를 추가해 sigh/cry/laugh/voice_activity/noise 등 canonical key를 남긴다. 4차에서는 인증된 회기 리뷰에 한숨·울음·웃음·호흡·운율·배경소음 계열만 한글 label/detail 칩으로 파생 노출한다. raw transcript/text payload, provider/source/raw type, 공개 공유 카드 노출은 제외했다. 실제 provider 기반 한숨·울음 감지와 live 마이크/WSS 장시간 실측은 아직 별도다. M2: <code>case_profile</code>을 learner-persona 안정 case로 upsert하고, <code>session_no</code>를 트랜잭션 안에서 원자 증가시킨다. 세션 종료 시 마스킹된 client-visible 축어록으로 fallback <code>session_summary.digest</code>와 case-level <code>case_digest</code>를 갱신하고, <code>rapport_trajectory</code>/<code>alliance_level</code>을 결정론 상태 수치에서 갱신한다. 종료 시점에는 마스킹된 client-visible 발화에서 <code>[NAME]</code>/<code>[ORG]</code> identity와 명시적 상담 약속만 <code>pinned_fact</code>로 upsert한다. 삽입 또는 값 변경 시에는 <code>pinned_fact_history</code>에 append-only 이력을 남기고, 같은 값 재확인과 <code>locked</code> fact는 이력을 늘리지 않는다. 5차에서는 명시적 상담 약속 철회/부정 문장만 기존 non-locked <code>agreement:counseling</code> fact를 <code>contradicted</code>로 격리하고 history reason <code>contradiction</code>을 남긴다. 새 contradicted fact를 임의 생성하거나 관계갈등·위기·임상 추론을 자동 모순 처리하지 않는다. 6차에서는 종료 저장 성공 뒤 background task로 마스킹된 client-visible 내담자 발화만 <code>app.turn_embedding</code>에 idempotent 색인한다. 시작/턴/voice 경로는 다음 회기 seed recall에서 <code>case_digest</code>, 직전 <code>session_summary</code>, client-visible non-contradicted <code>pinned_fact</code>, episodic recall 단편을 조립해 사용한다. M3: Google/SAML/dev-login이 <code>AUTH_EMAIL_COHORT_MAP</code>/<code>AUTH_DOMAIN_COHORT_MAP</code> 및 SAML cohort claim을 <code>cohort_ids</code>로 전달하고, DB <code>app_user.external_id</code>는 provider subject(<code>google:</code>/<code>saml:</code>/<code>dev:</code>) 기반으로 저장한다.</p></div><div class="kv k-warn"><b>권고</b><p>M1 실제 provider 기반 한숨·울음/억양 감지, 장시간 마이크/WSS 실측과 역량 지표화 정책, M2 관계/임상 fact 승격 기준·LLM digest 압축, M3 운영 SAML 서명검증·기관 claim schema/test tenant·deprovisioning audit는 후속이다.</p></div></div>
|
||||
<button class="scard-head" aria-expanded="false"><span class="chip c-plan">MED M1~M3</span><span class="scard-mid"><span class="scard-title">비언어 임상이벤트(M1)·다회기 케이스 아크(M2)·SSO claim/식별자/감사(M3)</span><span class="scard-sum">M1 provider_events 보존+taxonomy+리뷰 파생 노출 4차, M2 case memory+pinned_fact/history/명시철회 contradiction+episodic embedding writer+다음 턴 주입 증거 7차 + digest 계약 경계 완료. M3는 cohort map + provider external_id 1차 완료, 기관 SAML/거버넌스는 남음.</span></span><span class="caret" aria-hidden="true"></span></button>
|
||||
<div class="scard-body"><div class="kv"><b>현재</b><p>M1: voice learner turn의 audio_ref, silence_ms, speech_rate, barge_in을 리뷰 API <code>nonverbal</code> 이벤트로 파생하고, 회기 리뷰 축어록에 침묵·발화 속도·끼어듦·음성 입력 칩으로 표시한다. 2차로 <code>provider_events</code> JSONB 보존 슬롯을 추가해 control/STT provider 이벤트를 allowlist·size limit 후 저장한다. 3차에서는 저장 이벤트에 내부 taxonomy <code>event_type</code>/<code>category</code>를 추가해 sigh/cry/laugh/voice_activity/noise 등 canonical key를 남긴다. 4차에서는 인증된 회기 리뷰에 한숨·울음·웃음·호흡·운율·배경소음 계열만 한글 label/detail 칩으로 파생 노출한다. raw transcript/text payload, provider/source/raw type, 공개 공유 카드 노출은 제외했다. 실제 provider 기반 한숨·울음 감지와 live 마이크/WSS 장시간 실측은 아직 별도다. M2: <code>case_profile</code>을 learner-persona 안정 case로 upsert하고, <code>session_no</code>를 트랜잭션 안에서 원자 증가시킨다. 세션 종료 시 마스킹된 client-visible 축어록으로 fallback <code>session_summary.digest</code>와 case-level <code>case_digest</code>를 갱신하고, <code>rapport_trajectory</code>/<code>alliance_level</code>을 결정론 상태 수치에서 갱신한다. 종료 시점에는 마스킹된 client-visible 발화에서 <code>[NAME]</code>/<code>[ORG]</code> identity와 명시적 상담 약속만 <code>pinned_fact</code>로 upsert한다. 삽입 또는 값 변경 시에는 <code>pinned_fact_history</code>에 append-only 이력을 남기고, 같은 값 재확인과 <code>locked</code> fact는 이력을 늘리지 않는다. 5차에서는 명시적 상담 약속 철회/부정 문장만 기존 non-locked <code>agreement:counseling</code> fact를 <code>contradicted</code>로 격리하고 history reason <code>contradiction</code>을 남긴다. 새 contradicted fact를 임의 생성하거나 관계갈등·위기·임상 추론을 자동 모순 처리하지 않는다. 6차에서는 종료 저장 성공 뒤 background task로 마스킹된 client-visible 내담자 발화만 <code>app.turn_embedding</code>에 idempotent 색인한다. 7차에서는 시작 경로가 seed recall을 cache에 적재하고, REST submit/SSE stream은 공통 <code>_prepare_turn_context()</code>에서 recall, pinned fact, 최근 client-visible 턴, KB cue를 TurnContext/EngineMessage로 조립한다. route-level 테스트는 DB <code>case_digest</code>·직전 <code>session_summary</code>·client-visible <code>pinned_fact</code>가 다음 턴 L2/L4 블록에 들어가고 raw 이름이 <code>[NAME]</code>으로 마스킹되는지 고정한다. 76차에서는 <code>SessionDigestInput</code>/<code>SessionDigestResult</code>/<code>SessionSummaryWrite</code>로 종료 digest 입력·fallback 결과·DB write 인자 경계를 명시했다. future LLM digest worker 입력은 client-visible masked turn과 open thread만 받으며, 종료 상태 수치·평가 payload·CCD·raw text는 압축 prompt에 넣지 않는다. <code>digest_pending</code>은 CompressionJob 생성 여부 신호로 유지한다. M3: Google/SAML/dev-login이 <code>AUTH_EMAIL_COHORT_MAP</code>/<code>AUTH_DOMAIN_COHORT_MAP</code> 및 SAML cohort claim을 <code>cohort_ids</code>로 전달하고, DB <code>app_user.external_id</code>는 provider subject(<code>google:</code>/<code>saml:</code>/<code>dev:</code>) 기반으로 저장한다.</p></div><div class="kv k-warn"><b>권고</b><p>M1 실제 provider 기반 한숨·울음/억양 감지, 장시간 마이크/WSS 실측과 역량 지표화 정책, M2 관계/임상 fact 승격 기준·LLM digest worker 실행/품질평가/재압축, M3 운영 SAML 서명검증·기관 claim schema/test tenant·deprovisioning audit는 후속이다.</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">TRACK X1·X2 2차·L1</span><span class="scard-mid"><span class="scard-title">재귀학습 export(X1)·API 비용 관측(X2)·스택 정합성·일정(L1)<span class="ownbadge">내 차례</span></span><span class="scard-sum">X1 dry-run export, X2 비용 관측·모델 override·evaluator semantic cache·모델별 비용 리포트 완료. L1은 Node 방향 결정, gateway 계약, schema-backed golden fixture, Node conformance runner, session/persona read-model 분리 완료. 등재 문서 반영은 외부 gate.</span></span><span class="caret" aria-hidden="true"></span></button>
|
||||
|
|
@ -684,9 +694,9 @@
|
|||
</article>
|
||||
</div>
|
||||
<div class="detail-blocks">
|
||||
<div class="detail-block"><h4>즉시 착수 가능(내부 코드, 외부 합의 불요)</h4><ul><li>C1은 저장형 워크시트와 외부 루브릭 schema/loader/validation scaffold까지 3차 완료. C2는 1차 구조 완료, C3은 이론모드 배선과 명시 선택 UI까지 2차 완료, H2는 라이브 코칭 1차와 source pack version sync 및 학습자 리뷰 2열 UI 1차, H3은 페르소나 저작 CRUD/RAG 생성/seed materializer runner/source pack sync runner/raw source hash-only isolation/항목형 목록 저작 UI/프롬프트 미리보기 de-JSON까지 완료. 후속은 임상 스크립트, CBT 체인, 루브릭·이론 콘텐츠 외부화처럼 데이터·정책 고도화 영역이다.</li><li>H4(부분): 한국어 날짜/금액/주소 및 이름/기관 로컬 휴리스틱 마스킹, 15-case 합성 fixture 평가 harness와 입력/report schema, metadata-only LLM call audit 경로, 로컬 live row proof, learner 동의 수락/철회/회기 시작 하드게이트 골격은 완료. 실제 운영 말뭉치 기반 오탐/미탐 평가는 외부 데이터·reviewer 라벨·법무/guardian evidence가 필요하므로 gate로 남긴다. 내부 후보였던 합성 fixture 확장과 운영 평가 입력/report schema·summary-only dry-run 준비는 62차에서 완료했고, 자연 발화형 이름 누수 보강은 66차에서 완료했다. 남은 내부 후보는 모델 기반 정밀 ko NER adapter 검토다. X1 dry-run export 1차와 X2 비용 관측·예산 경고/evaluator 모델 override·semantic cache·hit-rate 관측·일별 비용 추이·모델별 비용 검증 리포트는 완료했고, 자동 차단·한도 enforcement는 정책 결정 전까지 보류한다. M1 provider_events 보존·taxonomy·인증 리뷰 파생 칩 4차, M2 case memory+pinned_fact 보수적 실적재·append-only history·명시적 상담약속 철회 contradiction·episodic embedding writer 6차, M3 claim 1차, L1 schema-backed engine gateway golden fixture·Node.js conformance runner·session/persona read-model 분리는 완료. 운영 SAML 서명검증은 기관 IdP 의존이다.</li></ul></div>
|
||||
<div class="detail-block"><h4>즉시 착수 가능(내부 코드, 외부 합의 불요)</h4><ul><li>C1은 저장형 워크시트와 외부 루브릭 schema/loader/validation scaffold까지 3차 완료. C2는 1차 구조 완료, C3은 이론모드 배선과 명시 선택 UI까지 2차 완료, H2는 라이브 코칭 1차와 source pack version sync 및 학습자 리뷰 2열 UI 1차, H3은 페르소나 저작 CRUD/RAG 생성/seed materializer runner/source pack sync runner/raw source hash-only isolation/항목형 목록 저작 UI/프롬프트 미리보기 de-JSON까지 완료. 후속은 임상 스크립트, CBT 체인, 루브릭·이론 콘텐츠 외부화처럼 데이터·정책 고도화 영역이다.</li><li>H4(부분): 한국어 날짜/금액/주소 및 이름/기관 로컬 휴리스틱 마스킹, 15-case 합성 fixture 평가 harness와 입력/report schema, metadata-only LLM call audit 경로, 로컬 live row proof, learner 동의 수락/철회/회기 시작 하드게이트 골격은 완료. 실제 운영 말뭉치 기반 오탐/미탐 평가는 외부 데이터·reviewer 라벨·법무/guardian evidence가 필요하므로 gate로 남긴다. 내부 후보였던 합성 fixture 확장과 운영 평가 입력/report schema·summary-only dry-run 준비는 62차에서 완료했고, 자연 발화형 이름 누수 보강은 66차에서 완료했다. 70차에서는 선택형 ko recognizer adapter 인터페이스와 fake/failure 회귀를 추가해, 실제 모델/provider를 고르기 전에도 같은 <code>mask_pii()</code> 경계에서 adapter→regex fallback 순서를 검증할 수 있게 했다. X1 dry-run export 1차와 X2 비용 관측·예산 경고/evaluator 모델 override·semantic cache·hit-rate 관측·일별 비용 추이·모델별 비용 검증 리포트는 완료했고, 자동 차단·한도 enforcement는 정책 결정 전까지 보류한다. M1 provider_events 보존·taxonomy·인증 리뷰 파생 칩 4차, M2 case memory+pinned_fact 보수적 실적재·append-only history·명시적 상담약속 철회 contradiction·episodic embedding writer·다음턴 주입회귀 7차/digest contract-only 8차, M3 claim 1차, L1 schema-backed engine gateway golden fixture·Node.js conformance runner·session/persona read-model 분리는 완료. 운영 SAML 서명검증은 기관 IdP 의존이다.</li></ul></div>
|
||||
<div class="detail-block"><h4>소유자 결정 / 외부(임상팀·기관) 의존</h4><ul><li>임상팀 산출물: C1 루브릭 항목·척도·가중치·anchor 예시·컷오프·검수 기준, C3 CBT 콘텐츠·체인, C2 위기 스크립트·서약 문안, H2 골든셋 코딩.</li><li>소유자 평가설계: H1 실험/통제군·3척도 문항·50분 흐름, κ/ICC·환각률 목표(doc4 미명시).</li><li>기관(한신 IT): M3 SSO IdP·test tenant·claim 스키마·인증서, 거버넌스 증거.</li><li>거버넌스: L1 Node 전환 방향은 결정됨. 남은 것은 FastAPI 유지 사유와 계약 우선 전환 계획의 저작권 등재 문서화·IP 협의.</li></ul></div>
|
||||
<div class="detail-block"><h4>판독 한계 (정직성)</h4><ul><li>핵심 축어록 2단 표는 PDF 시각 판독이 1차 근거(txt 일부 누락), OCR 잔재 존재.</li><li>doc4 κ/ICC·환각률은 신청서 본문 미명시 — 계약 확정 지표로 단정 금지.</li><li>'현재상태'는 grep/코드 사실 기반에서 시작했으나 C1·C2·C3 2차 UI·H1~H4·M1 provider_events 보존/taxonomy/인증 리뷰 파생 노출·M2 case memory/pinned_fact/history/명시철회 contradiction/episodic embedding writer·M3·X1·X2·L1 Node conformance와 session/persona read-model 분리는 구현 후 재검증 완료 기준이다. H3 원문 격리는 hash-only artifact와 RAG index 차단까지이며, 암호화 blob/vault 원문 실저장은 아직 후속이다. 실제 provider 기반 한숨·울음 감지, H4 legal/guardian evidence, M3 운영 IdP, L1 외부 거버넌스는 아직 게이트다.</li></ul></div>
|
||||
<div class="detail-block"><h4>판독 한계 (정직성)</h4><ul><li>핵심 축어록 2단 표는 PDF 시각 판독이 1차 근거(txt 일부 누락), OCR 잔재 존재.</li><li>doc4 κ/ICC·환각률은 신청서 본문 미명시 — 계약 확정 지표로 단정 금지.</li><li>'현재상태'는 grep/코드 사실 기반에서 시작했으나 C1·C2·C3 2차 UI·H1~H4·M1 provider_events 보존/taxonomy/인증 리뷰 파생 노출·M2 case memory/pinned_fact/history/명시철회 contradiction/episodic embedding writer/digest contract-only·M3·X1·X2·L1 Node conformance와 session/persona read-model 분리는 구현 후 재검증 완료 기준이다. M2는 fallback digest와 <code>digest_pending</code> 응답 계약까지이며, LLM digest worker 실행·품질평가·재압축은 완료로 보지 않는다. H3 원문 격리는 hash-only artifact와 RAG index 차단까지이며, 암호화 blob/vault 원문 실저장은 아직 후속이다. 실제 provider 기반 한숨·울음 감지, H4 legal/guardian evidence, M3 운영 IdP, L1 외부 거버넌스는 아직 게이트다.</li></ul></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -711,8 +721,8 @@
|
|||
<div class="scard-body"><div class="kv"><b>현재</b><p><code>app/turn_runtime.py</code>를 추가해 <code>load_owned_session</code>, <code>record_completed_turn</code>, <code>append_completed_turn</code>, <code>update_session_state</code>, <code>record_safety_event</code>를 공통화했다. 이번 패스에서 <code>finalize_completed_turn</code>을 추가해 <code>record_completed_turn</code> + <code>record_safety_event</code> 호출쌍을 REST submit/stream과 voice WS 라우트에서 제거했다. stream fast-loop 평가 누락, voice 세션 recall cache, visible_to/evaluator turn 분리는 보존.</p></div><div class="kv"><b>검증</b><p><code>pytest app/test_session_turn_persistence.py app/test_voice_ws.py engine_gateway/test_gateway_model.py app/test_phase3_artifact_checker.py app/test_dataset_export.py -q</code> 40 passed.</p></div><div class="kv k-warn"><b>잔여</b><p>더 큰 <code>finalize_turn</code>/<code>SessionRepository</code> 경계, 장기적으로는 세션 저장소 인터페이스 SSOT화가 남아 있다.</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">REFACTOR 2 4차 DONE</span><span class="scard-mid"><span class="scard-title">백엔드 Pydantic ↔ 프론트 <code>lib/api.ts</code> 계약 SSOT화</span><span class="scard-sum">OpenAPI→<code>api.gen.ts</code> 생성 + CI stale 체크 게이트. 세션 계열 수기 DTO도 생성 타입 alias로 축소.</span></span><span class="caret" aria-hidden="true"></span></button>
|
||||
<div class="scard-body"><div class="kv"><b>현재</b><p><code>apps/api/scripts/export-openapi.py</code>와 <code>apps/web/scripts/generate-api-types.mjs</code>로 FastAPI OpenAPI를 <code>apps/web/src/lib/api.gen.ts</code>에 생성한다. <code>npm run check:api-types</code>가 재생성 누락을 잡는다. <code>MeResponse</code>, <code>AuthConfigResponse</code>, <code>ConsentResponse</code>, <code>PersonaSummary</code>, <code>PersonaReviewSummary</code>, admin/user/engine DTO, review leaf DTO, <code>CrisisResourceResponse</code>, teacher safety/growth leaf DTO, <code>LearnerSessionsResponse</code>, <code>SessionReviewResponse</code>, worksheet aggregate, <code>TeacherDashboardResponse</code>, <code>TeacherSessionSummary</code>, <code>TeacherLearnerGrowth</code>에 이어 <code>SessionStartResponse</code>, <code>SessionDetailResponse</code>, <code>SessionDetailTurn</code>도 <code>ApiSchema<...></code> alias로 전환했다. 세션 응답 stage는 backend <code>StageLabel</code> enum으로 고정해 OpenAPI가 <code>라포|탐색|개입|정리</code> union을 생성한다. generated optional/default 차이는 화면 form state, notification default helper, 배열 렌더링 fallback으로 흡수한다. <code>.github/workflows/api-contract.yml</code>은 PR/master push에서 같은 stale check를 실행해 OpenAPI type drift를 CI에서 막는다.</p></div><div class="kv"><b>검증</b><p><code>npm run check:api-types</code>, <code>npm run typecheck</code>, <code>npm run build</code> passed. <code>python -m pytest app/test_session_turn_persistence.py -q</code> 16 passed. 영향 범위 E2E: <code>learner.spec.ts</code> + <code>session-review.spec.ts</code> + <code>teacher.spec.ts</code> + <code>layout-visual-gate.spec.ts</code> 32 passed.</p></div><div class="kv k-warn"><b>잔여</b><p>평가 조회·리뷰 도식·교수 콘솔 등 아직 자유 문자열로 남긴 stage-like 필드는 각 도메인 의미를 확인한 뒤 별도 축소한다.</p></div></div>
|
||||
<button class="scard-head" aria-expanded="false"><span class="chip c-done">REFACTOR 2 7차 DONE</span><span class="scard-mid"><span class="scard-title">백엔드 Pydantic ↔ 프론트 <code>lib/api.ts</code> 계약 SSOT화</span><span class="scard-sum">OpenAPI→<code>api.gen.ts</code> 생성 + CI stale 체크 게이트. 세션·리뷰·평가·교수·공유·live-coach stage 계약과 라벨 정규화를 union/null SSOT로 축소.</span></span><span class="caret" aria-hidden="true"></span></button>
|
||||
<div class="scard-body"><div class="kv"><b>현재</b><p><code>apps/api/scripts/export-openapi.py</code>와 <code>apps/web/scripts/generate-api-types.mjs</code>로 FastAPI OpenAPI를 <code>apps/web/src/lib/api.gen.ts</code>에 생성한다. <code>npm run check:api-types</code>가 재생성 누락을 잡는다. <code>MeResponse</code>, <code>AuthConfigResponse</code>, <code>ConsentResponse</code>, <code>PersonaSummary</code>, <code>PersonaReviewSummary</code>, admin/user/engine DTO, review leaf DTO, <code>CrisisResourceResponse</code>, teacher safety/growth leaf DTO, <code>LearnerSessionsResponse</code>, <code>LearnerDashboardResponse</code>, <code>SessionReviewResponse</code>, worksheet aggregate, <code>TeacherDashboardResponse</code>, <code>TeacherSessionSummary</code>, <code>TeacherLearnerGrowth</code>에 이어 <code>SessionStartResponse</code>, <code>SessionDetailResponse</code>, <code>SessionDetailTurn</code>도 <code>ApiSchema<...></code> alias로 전환했다. 세션 응답 stage는 backend <code>StageLabel</code> enum으로 고정해 OpenAPI가 <code>라포|탐색|개입|정리</code> union을 생성한다. 69차에서는 리뷰 도식 <code>ReviewPhaseSegment.key</code>를 <code>rapport|explore|intervene|closing</code>, <code>ReviewPhaseSegment.label</code>과 <code>SessionReviewResponse.reachedPhase</code>를 <code>StageLabel</code>로 좁혔다. 평가 조회 <code>EvaluationSummary.stage</code>는 평가 미생성 시 <code>null</code>, 평가 트리거 응답 stage와 교수 콘솔 <code>TeacherSessionSummary</code>/<code>TeacherGrowthPoint</code> stage는 <code>StageLabel</code> union으로 생성된다. 71차에서는 공개 공유 응답 <code>PublicSessionShareResponse.reachedPhase</code>와 live-coach 이력 <code>LiveCoachEvent.stage</code>도 <code>StageLabel | null</code> 계약으로 축소했다. 72차에서는 <code>app/stage_contract.py</code>가 <code>StageLabel</code>, <code>ReviewPhaseKey</code>, legacy stage code 정규화, unknown→null 판단, 리뷰 phase key 매핑을 소유하게 해 <code>turn_runtime.py</code>, <code>session_read_model.py</code>, <code>routes/eval.py</code>의 중복 규칙을 제거했다. generated optional/default 차이는 화면 form state, notification default helper, 배열 렌더링 fallback, live-coach <code>단계 미상</code> 표시로 흡수한다. <code>.github/workflows/api-contract.yml</code>은 PR/master push에서 같은 stale check를 실행해 OpenAPI type drift를 CI에서 막는다.</p></div><div class="kv"><b>검증</b><p><code>py -3.11 -X utf8 -m pytest -p no:cacheprovider app/test_evaluation_persistence.py app/test_evaluator_model_routing.py app/test_teacher_dashboard.py app/test_rbac_idor.py app/test_session_turn_persistence.py -q</code> 50 passed. 공유/live-coach 후속 검증: <code>py -3.11 -X utf8 -m pytest -p no:cacheprovider app/test_session_share.py app/test_live_coach_sources.py app/test_session_turn_persistence.py app/test_orchestrator_masking.py -q</code> 43 passed. Stage contract SSOT 검증: <code>py -3.11 -X utf8 -m pytest -p no:cacheprovider app/test_stage_contract.py app/test_session_share.py app/test_session_turn_persistence.py app/test_evaluation_persistence.py app/test_teacher_dashboard.py -q</code> 42 passed. <code>py -3.11 -X utf8 -m py_compile apps/api/app/stage_contract.py apps/api/app/turn_runtime.py apps/api/app/session_read_model.py apps/api/app/routes/eval.py apps/api/app/routes/sessions.py apps/api/app/routes/share.py apps/api/app/services/live_coach.py</code>, <code>npm run check:api-types</code>, <code>npm run typecheck</code>, <code>npm run build</code> passed. 과거 영향 범위 E2E: <code>learner.spec.ts</code> + <code>session-review.spec.ts</code> + <code>teacher.spec.ts</code> + <code>layout-visual-gate.spec.ts</code> 32 passed.</p></div><div class="kv k-warn"><b>잔여</b><p>남은 자유 문자열 stage는 evaluator 내부 payload, DB 저장 컬럼, dataset export, runtime store 같은 저장·분석 payload다. 라벨/phase key 변환 규칙은 <code>app/stage_contract.py</code> 하나로 이동했고, 브라우저-facing OpenAPI 응답 stage 계약은 현재 확인된 session/review/eval/teacher/share/live-coach 표면에서 enum/null로 고정했다.</p></div></div>
|
||||
</article>
|
||||
<article class="scard" data-status="done" data-cat="인프라·스택·RAG" data-owner="0">
|
||||
<button class="scard-head" aria-expanded="false"><span class="chip c-done">SEO/GEO 공유</span><span class="scard-mid"><span class="scard-title">검색 메타 + 회기 리뷰 URL 공유 카드</span><span class="scard-sum">robots/sitemap/llms + OG/JSON-LD. 종료 회기는 토큰 공유 URL로 교수자에게 요약·썸네일 전달.</span></span><span class="caret" aria-hidden="true"></span></button>
|
||||
|
|
@ -803,7 +813,7 @@
|
|||
<div class="task-list">
|
||||
<div class="task-row"><div><span class="task-status s-done">DONE</span></div><div><b>LLMEngine 정규화 계약 고정</b><p>gateway SSE <code>token/done/error</code>와 generate cost 계약은 회귀화했다. 이번 패스에서 <code>app/contracts/engine_gateway.py</code>를 추가해 FastAPI <code>engine_client</code>와 현재 Python gateway가 같은 <code>GenerateRequest</code>/<code>GenerateResponse</code>/<code>Stream*Event</code> 계약을 import한다. 이어서 <code>EngineGatewaySseLineDecoder</code>로 token/done/error decode와 validation도 같은 계약 모듈에 모았다. 최신 패스에서는 raw SSE line 해석을 <code>EngineClient.stream_packets()</code> 경계로 올리고, Python gateway <code>/v1/generate</code>가 <code>GenerateResponse.model_dump()</code>로 응답하게 해 hand-mirrored dict drift를 줄였다. Node.js gateway로 갈아치울 때 보존해야 할 첫 wire contract다. claude_cli와 Messages API live parity는 <code>ANTHROPIC_API_KEY</code> 의존 B2 항목으로 분리한다.</p></div><div><b>산출물</b><p><code>app/contracts/engine_gateway.py</code>, <code>app/engine_client.py</code>, <code>app/services/orchestrator.py</code>, <code>engine_gateway/gateway.py</code>, <code>engine_gateway/test_gateway_model.py</code>, <code>engine_gateway/golden/*</code>, <code>scripts/check-engine-gateway-contract.mjs</code> shared contract + stream packet tests</p></div><div><b>검증</b><p><code>node scripts/check-engine-gateway-contract.mjs --json</code>, <code>python -B -m pytest -p no:cacheprovider engine_gateway/test_gateway_model.py -q</code> 19 passed, <code>python -B -m pytest -p no:cacheprovider engine_gateway/test_gateway_model.py app/test_orchestrator_masking.py app/test_session_turn_persistence.py -q</code> 50 passed.</p></div></div>
|
||||
<div class="task-row"><div><span class="task-status s-done">DONE</span></div><div><b>상주 <code>claude -p</code> 엔진풀 실증</b><p><code>session_id</code>가 살아 있는 gateway session을 재사용하고 missing session은 ephemeral로 닫히는 구조를 회귀화했고, live 게이트웨이(9099)로 실측까지 마쳤다. <code>probe-engine-gateway.py</code>가 단일 session_id로 2회 reused stream을 돌려 TTFT 1667–4199ms, cost 누적 0.068→0.123(turns 1→2), engine=claude_p, model=opus-4-8을 측정했다.</p></div><div><b>산출물</b><p><code>engine_gateway/test_gateway_model.py</code>, <code>scripts/probe-engine-gateway.py</code>, live probe JSON</p></div><div><b>검증</b><p>session reuse 7 tests OK; live probe 2 reused streams, TTFT/cost 실측, session_id 재사용 확인</p></div></div>
|
||||
<div class="task-row"><div><span class="task-status s-done">DONE</span></div><div><b>마스킹 게이트 + LLM call audit</b><p>current turn, recall, pinned fact, recent turns가 Presidio/가명처리 후에만 engine request로 들어가게 막았다. 한국어 이름/기관 로컬 휴리스틱 1차와 15-case summary-only 합성 fixture 평가 harness/input-report schema를 추가했고, fast evaluator prompt와 client turn <code>text_masked</code>도 마스킹본을 쓰게 보강했다. 66차에서는 자연 발화형 이름 라벨·자기소개 케이스를 추가해 라벨 단어만 마스킹하고 실명을 남기던 구멍을 막았다. 외부 LLM 호출은 <code>audit.llm_call_log</code>에 provider/model/token/cost/inference_geo/latency metadata만 남기고 prompt/completion 본문은 저장하지 않는다.</p></div><div><b>산출물</b><p><code>app/services/pii_masking_eval.py</code>, <code>data/privacy/pii-masking-ko-fixtures.json</code>, <code>data/privacy/pii-masking-eval-input.schema.json</code>, <code>data/privacy/pii-masking-eval-report.schema.json</code>, <code>scripts/evaluate-pii-masking.py</code>, <code>app/test_pii_masking_eval.py</code>, <code>app/test_orchestrator_masking.py</code>, <code>app/test_evaluation_persistence.py</code>, <code>app/test_session_turn_persistence.py</code>, <code>session_persistence.record_llm_call_audit</code></p></div><div><b>검증</b><p>phone/email/RRN 및 한국어 NAME/ORG raw 값이 generate/stream/evaluator payload에 없음, 감사 payload 본문 미포함, fixture 15/15 pass, focused 회귀 43 passed, 현재 전체 API 178 기준 유지, 로컬 live <code>/turn</code> smoke 후 <code>audit.llm_call_log</code> 8→11</p></div></div>
|
||||
<div class="task-row"><div><span class="task-status s-done">DONE</span></div><div><b>마스킹 게이트 + LLM call audit</b><p>current turn, recall, pinned fact, recent turns가 Presidio/가명처리 후에만 engine request로 들어가게 막았다. 한국어 이름/기관 로컬 휴리스틱 1차와 15-case summary-only 합성 fixture 평가 harness/input-report schema를 추가했고, fast evaluator prompt와 client turn <code>text_masked</code>도 마스킹본을 쓰게 보강했다. 66차에서는 자연 발화형 이름 라벨·자기소개 케이스를 추가해 라벨 단어만 마스킹하고 실명을 남기던 구멍을 막았다. 70차에서는 선택형 ko recognizer adapter를 <code>mask_pii()</code> 경계에 추가하고, adapter 성공/실패 모두 regex fallback이 마지막 안전망으로 남는지 회귀화했다. 외부 LLM 호출은 <code>audit.llm_call_log</code>에 provider/model/token/cost/inference_geo/latency metadata만 남기고 prompt/completion 본문은 저장하지 않는다.</p></div><div><b>산출물</b><p><code>app/services/guardrail.py</code>, <code>app/services/pii_masking_eval.py</code>, <code>data/privacy/pii-masking-ko-fixtures.json</code>, <code>data/privacy/pii-masking-eval-input.schema.json</code>, <code>data/privacy/pii-masking-eval-report.schema.json</code>, <code>scripts/evaluate-pii-masking.py</code>, <code>app/test_pii_masking_eval.py</code>, <code>app/test_orchestrator_masking.py</code>, <code>app/test_evaluation_persistence.py</code>, <code>app/test_session_turn_persistence.py</code>, <code>session_persistence.record_llm_call_audit</code></p></div><div><b>검증</b><p>phone/email/RRN 및 한국어 NAME/ORG raw 값이 generate/stream/evaluator payload에 없음, adapter fake span과 fallback phone 마스킹 경로 검증, adapter 실패 시 regex fallback 유지, 감사 payload 본문 미포함, fixture 15/15 pass, focused 회귀 47 passed, 현재 전체 API 178 기준 유지, 로컬 live <code>/turn</code> smoke 후 <code>audit.llm_call_log</code> 8→11</p></div></div>
|
||||
<div class="task-row"><div><span class="task-status s-done">DONE</span></div><div><b>P1 상담 완주 플로우</b><p>로컬 DB-backed 런타임에서 P1 선택, SSE 상담, 종료, 회기말 리뷰 피드백을 한 번에 통과시켰다. 실제 운영 로그인 proof는 P0 public OAuth blocker와 분리해 남긴다.</p></div><div><b>산출물</b><p><code>e2e/session-mvp.spec.ts</code>, turn telemetry 저장 경로</p></div><div><b>검증</b><p><code>npx playwright test e2e/session-mvp.spec.ts --project=chromium-single-run --workers=1</code> 1 passed.</p></div></div>
|
||||
<div class="task-row"><div><span class="task-status s-done">DONE</span></div><div><b>저항 엔진 정량 검증</b><p>공감 발화와 조언점프 발화 5턴 세트를 상태머신 회귀 테스트와 실제 API+DB smoke로 비교했다. live smoke는 dev-login→onboarding→P1 세션 2개→각 5턴을 수행하고 <code>app.session_state</code>, <code>app.turns</code>, <code>app.turn_client_state</code>를 직접 조회했다. 공감 세션은 3턴부터 <code>탐색</code>으로 열리고 openness가 <code>0.171</code>까지 상승했다. 조언점프 세션은 <code>라포</code>와 openness <code>0.0</code>을 유지했다.</p></div><div><b>산출물</b><p><code>app/test_state_machine_resistance.py</code>, <code>scripts/smoke-resistance-openness-db.py</code>, <code>docs/ops/resistance-openness-db-smoke-2026-06-28.json</code></p></div><div><b>검증</b><p><code>pytest app/test_state_machine_resistance.py app/test_evaluation_persistence.py -q</code> 9 passed; live DB smoke PASS.</p></div></div>
|
||||
<div class="task-row"><div><span class="task-status s-done">DONE</span></div><div><b>음성 왕복 1턴 PoC</b><p>Session UI 마이크 버튼 경로에서 synthetic <code>getUserMedia</code>/<code>MediaRecorder</code>로 실제 <code>/voice/ws</code>를 열고 audio_start, binary chunk, audio_end, transcript, reply, TTS 재생 상태까지 검증했다. 추가로 로컬 live API에서 실제 STT/TTS provider를 통과해 TTS 바이너리를 수신했다. 물리 마이크, 공개 WSS, 50분 양방향 실측은 별도 운영 smoke로 남아 있다.</p></div><div><b>산출물</b><p><code>apps/web/e2e/voice-success.spec.ts</code>, controlled OpenAI/engine providers, Web Audio browser voice UI probe, live <code>/voice/ws</code> smoke</p></div><div><b>검증</b><p><code>npx playwright test e2e/voice-success.spec.ts --project=chromium-single-run</code> 2 passed; live P2 WS smoke binary 66 chunks/266,496 bytes</p></div></div>
|
||||
|
|
@ -865,29 +875,29 @@
|
|||
<tr><td>Web typecheck</td><td><code>npm run typecheck</code></td><td>Passed</td></tr>
|
||||
<tr><td>Full Playwright E2E</td><td><code>PLAYWRIGHT_PORT=5174 npm run e2e</code></td><td>113 passed; desktop/mobile 병렬 + <code>@single-run</code> DB 영속화·음성 성공 경로까지 전체 통과.</td></tr>
|
||||
<tr><td>API typegen SSOT</td><td><code>npm run check:api-types</code></td><td>Passed; FastAPI OpenAPI → <code>src/lib/api.gen.ts</code> stale check</td></tr>
|
||||
<tr><td>Contract SSOT aggregate DTO</td><td><code>npm run typecheck</code> / <code>npm run build</code> / <code>session persistence</code> / <code>learner + session-review + teacher + layout gate</code></td><td>Passed; learner sessions, session review/worksheet, teacher dashboard, session start/detail DTOs now use generated <code>ApiSchema</code> aliases with UI fallback. Stage responses are OpenAPI enum unions. Focused E2E 32 passed. <code>.github/workflows/api-contract.yml</code> now runs <code>npm run check:api-types</code> on API/Web contract changes.</td></tr>
|
||||
<tr><td>Contract SSOT aggregate DTO</td><td><code>py -3.11 -X utf8 -m pytest -p no:cacheprovider app/test_evaluation_persistence.py app/test_evaluator_model_routing.py app/test_teacher_dashboard.py app/test_rbac_idor.py app/test_session_turn_persistence.py -q</code> / <code>npm run check:api-types</code> / <code>npm run typecheck</code> / <code>npm run build</code></td><td>50 backend passed; learner sessions, session review/worksheet, teacher dashboard, session start/detail DTOs use generated <code>ApiSchema</code> aliases with UI fallback. Stage responses are OpenAPI enum unions, including review phase key/label, reached phase, evaluation summary/trigger responses, and teacher session/growth stage. <code>.github/workflows/api-contract.yml</code> runs <code>npm run check:api-types</code> on API/Web contract changes.</td></tr>
|
||||
<tr><td>SEO/GEO share cards</td><td><code>pytest app/test_session_share.py app/test_session_turn_persistence.py -q</code> / <code>npm run generate:api-types</code> / <code>npm run typecheck</code></td><td>21 passed; session share creates hashed-token public unfurl payload without raw transcript, revoked token returns 404, OpenAPI generated share DTOs, review screen share button typechecks. Static <code>robots.txt</code>/<code>sitemap.xml</code>/<code>llms.txt</code> added.</td></tr>
|
||||
<tr><td>Backend pytest</td><td><code>python -m pytest app/ -q</code></td><td>178 passed</td></tr>
|
||||
<tr><td>X2 evaluator routing/cache/cost trend</td><td><code>python -B -m pytest -p no:cacheprovider app/test_usage_report.py app/test_evaluator_model_routing.py app/test_runtime_policy.py app/test_admin_ops.py -q</code> / <code>scripts/report-ai-usage.py</code> sample CLI / prior web gates</td><td>42 backend passed + CLI sample OK; fast/deep evaluator model override, semantic cache hit/miss, admin usage budget/fail-closed, cache hit-rate observability, daily cost trend, and provider/model cost report schema covered. Prior API typecheck/build/admin E2E evidence remains valid because the API response shape was not changed.</td></tr>
|
||||
<tr><td>H3 raw source isolation</td><td><code>python -B -m pytest -p no:cacheprovider app/test_persona_review.py app/test_live_coach_sources.py -q</code></td><td>38 passed; persona source registration records raw hash-only artifacts separately from sanitized evaluator-only RAG chunks, and <code>rag.index_document()</code> rejects <code>sensitivity=3</code>/raw-marker chunks before DB access.</td></tr>
|
||||
<tr><td>H3 persona itemized authoring UI</td><td><code>npm run typecheck</code> / <code>npm run build</code> / <code>npm run check:api-types</code> / <code>npx playwright test e2e/teacher.spec.ts --project=chromium-single-run --workers=1</code> / <code>npx playwright test e2e/layout-visual-gate.spec.ts --project=chromium-single-run --workers=1</code> / <code>npx playwright test e2e/session-layout.spec.ts --project=chromium-desktop --project=chromium-mobile --workers=1</code></td><td>Typecheck/build/API type drift check passed; teacher PersonaStudio focused 8 passed including structured list payload and labeled prompt preview without raw JSON; visual gate 9 passed with the prompt tab opened; session-layout desktop/mobile 8 passed.</td></tr>
|
||||
<tr><td>H2 session review 2-column UX</td><td><code>npm run typecheck</code> / <code>npx playwright test e2e/session-review.spec.ts --project=chromium-desktop --workers=1</code> / <code>npx playwright test e2e/layout-visual-gate.spec.ts --project=chromium-single-run --workers=1</code></td><td>Typecheck passed; 3 focused E2E passed; 9 visual gates passed. Filled learner review uses a left transcript rail and right work column at desktop widths while teacher/mobile layouts remain on existing rules.</td></tr>
|
||||
<tr><td>M2 case memory</td><td><code>python -B -m pytest -p no:cacheprovider app/test_session_memory.py app/test_session_turn_persistence.py app/test_orchestrator_masking.py app/test_runtime_policy.py -q</code></td><td>67 passed; session end writes masked fallback digest, case_digest, rapport trajectory, alliance EWMA, conservative identity/agreement pinned_fact upserts, append-only pinned_fact_history on insert/change, explicit counseling-agreement withdrawal marks the existing non-locked fact as contradicted, and episodic writer stores only masked client-visible client turns into <code>app.turn_embedding</code> idempotently.</td></tr>
|
||||
<tr><td>M2 case memory</td><td><code>py -3.11 -X utf8 -m pytest -p no:cacheprovider app/test_session_memory.py app/test_session_turn_persistence.py app/test_orchestrator_masking.py app/test_runtime_policy.py -q</code> / <code>npm run check:api-types</code> / <code>npm run typecheck</code></td><td>74 backend passed; session end writes masked fallback digest, case_digest, rapport trajectory, alliance EWMA, conservative identity/agreement pinned_fact upserts, append-only pinned_fact_history on insert/change, explicit counseling-agreement withdrawal marks the existing non-locked fact as contradicted, episodic writer stores only masked client-visible client turns into <code>app.turn_embedding</code> idempotently, next-session route-level turn proof shows DB seed recall/pinned facts entering EngineMessage L2/L4 with raw names masked, and <code>SessionDigestInput</code>/<code>SessionDigestResult</code>/<code>SessionSummaryWrite</code> keep digest worker input/output and session_summary write shape contract-only. <code>SessionEndResponse.digest_pending</code> remains a boolean compression-needed signal in OpenAPI/generated types.</td></tr>
|
||||
<tr><td>L1 session read-model split</td><td><code>python -B -m py_compile app/session_read_model.py app/routes/sessions.py</code> / <code>python -B -m pytest -p no:cacheprovider app/test_session_turn_persistence.py app/test_evaluation_persistence.py app/test_session_share.py app/test_learner_dashboard.py app/test_rbac_idor.py app/test_teacher_dashboard.py -q</code> / <code>npm run check:api-types</code> / <code>npm run typecheck</code></td><td>py_compile passed; focused backend 48 passed; OpenAPI generated API type check and web typecheck passed. <code>app/session_read_model.py</code> now owns session list/dashboard/detail/review/share DTOs and deterministic builders while <code>routes/sessions.py</code> keeps auth, RLS-backed reads, persistence, and route decorators.</td></tr>
|
||||
<tr><td>L1 persona read-model split</td><td><code>python -B -m py_compile app/persona_read_model.py app/routes/personas.py</code> / <code>python -B -m pytest -p no:cacheprovider app/test_persona_review.py -q</code> / <code>python -B -m pytest -p no:cacheprovider app/test_persona_review.py app/test_session_turn_persistence.py -q</code> / <code>npm run check:api-types</code> / <code>npm run typecheck</code></td><td>py_compile passed; persona review/backend 33 passed; persona + session-start boundary 55 passed; OpenAPI generated API type check and web typecheck passed. <code>app/persona_read_model.py</code> now owns persona catalog/review/draft/source/evidence DTOs and deterministic mappers while <code>routes/personas.py</code> keeps auth, teacher/admin gates, DB repository calls, RAG source registration, LLM draft generation, and HTTP error mapping.</td></tr>
|
||||
<tr><td>M1 provider_events preservation</td><td><code>python -B -m pytest -p no:cacheprovider app/test_voice_ws.py app/test_session_turn_persistence.py app/test_voice_service.py -q</code></td><td>45 passed; voice control/STT provider events are allowlisted, size-limited, and tagged with internal <code>event_type</code>/<code>category</code> taxonomy into <code>app.turns.provider_events</code>. 인증된 review <code>nonverbal</code>은 정규화 taxonomy 일부만 파생 칩으로 노출하며 raw transcript/text/provider/source/raw type은 응답하지 않는다.</td></tr>
|
||||
<tr><td>C1 case worksheet</td><td><code>py -3.11 -X utf8 -m pytest -p no:cacheprovider app/test_case_worksheet_rubric.py app/test_session_turn_persistence.py app/test_rbac_idor.py -q</code> / <code>scripts/check-case-worksheet-rubric.py --json</code> / <code>npm run check:api-types</code> / <code>npm run typecheck</code> / <code>npm run build</code> / local API smoke</td><td>34 passed for worksheet persistence/RBAC plus rubric scaffold; session review exposes transcript-grounded <code>caseWorksheet</code>, learner-edited <code>saved_by_learner</code> worksheet persists through <code>app.case_worksheet</code>, and <code>case-worksheet-rubric@scaffold-2026-06-28</code> validates 5 sections/28 items with <code>scoring_enabled=false</code>. Local smoke returned <code>saved_by_learner:local smoke saved worksheet</code>. Stage labels are normalized at response boundaries.</td></tr>
|
||||
<tr><td>C1 case worksheet</td><td><code>py -3.11 -X utf8 -m pytest -p no:cacheprovider app/test_session_turn_persistence.py app/test_teacher_dashboard.py app/test_rbac_idor.py app/test_case_worksheet_rubric.py -q</code> / <code>scripts/check-case-worksheet-rubric.py --json</code> / <code>npm run check:api-types</code> / <code>npm run typecheck</code> / <code>npm run build</code> / <code>session-review.spec.ts</code> / local API smoke</td><td>40 passed for worksheet persistence/RBAC/teacher review plus rubric scaffold; session review exposes transcript-grounded <code>caseWorksheet</code>, learner-edited <code>saved_by_learner</code> worksheet persists through <code>app.case_worksheet</code>, teacher/admin <code>teacherReview</code> exposes manual <code>worksheetStatus</code>/<code>worksheetNote</code>/<code>worksheetReviewedAt</code>, and <code>case-worksheet-rubric@scaffold-2026-06-28</code> validates 5 sections/28 items with <code>scoring_enabled=false</code>. Local smoke returned <code>saved_by_learner:local smoke saved worksheet</code>. Stage labels are normalized at response boundaries.</td></tr>
|
||||
<tr><td>M3 auth claim mapping</td><td><code>python -B -m pytest -p no:cacheprovider app/test_auth_providers.py -q</code></td><td>30 passed; Google/SAML cohort maps, provider external_id, provider error reason, PKCE/state, HttpOnly cookie, SAML fixture ACS covered</td></tr>
|
||||
<tr><td>CrisisGate 109</td><td><code>python -m pytest app/test_session_turn_persistence.py app/test_voice_ws.py -q</code></td><td>20 passed; real crisis stops before engine, returns 109 resource, safety event DB insert payload covered</td></tr>
|
||||
<tr><td>Professor review workflow</td><td><code>python -m pytest app/test_rbac_idor.py app/test_teacher_dashboard.py -q</code> / <code>npx playwright test e2e/teacher.spec.ts --project=chromium-single-run --workers=1</code></td><td>9 backend passed + 4 E2E passed; teacher dashboard exposes learner_growth/safety queues and review-pending/recent rows now open <code>/teach/session/:id/review</code> read-only with learner worksheet editing blocked for teachers.</td></tr>
|
||||
<tr><td>Learner dashboard personalization</td><td><code>pytest app/test_learner_dashboard.py app/test_teacher_dashboard.py app/test_rbac_idor.py -q</code> / <code>npm run check:api-types</code> / <code>npm run typecheck</code> / <code>npm run build</code></td><td>12 backend passed; <code>GET /sessions/dashboard</code> uses shared <code>session_metrics</code> and feeds LearnerHome overview/growth/persona_progress/recent_feedback/last-session recap. Focused learner/review/teacher/layout/session E2E evidence recorded in backlog.</td></tr>
|
||||
<tr><td>Teacher review status</td><td><code>pytest app/test_teacher_dashboard.py app/test_rbac_idor.py app/test_learner_dashboard.py -q</code> / <code>npm run typecheck</code> / <code>npm run build</code> / <code>teacher.spec.ts</code></td><td>12 backend passed + 4 E2E passed; <code>app.session_review_status</code>, teacher dashboard <code>review_status/review_note/reviewed_at</code>, and <code>PUT /teacher/sessions/{session_id}/review-status</code> are documented. 검토 큐 visible 회귀와 교수자 전용 review grid 영역도 테스트로 고정했다.</td></tr>
|
||||
<tr><td>Teacher review status</td><td><code>pytest app/test_teacher_dashboard.py app/test_rbac_idor.py app/test_learner_dashboard.py -q</code> / <code>npm run typecheck</code> / <code>npm run build</code> / <code>teacher.spec.ts</code></td><td>12 backend passed + 4 E2E passed; <code>app.session_review_status</code>, teacher dashboard <code>review_status/review_note/reviewed_at</code>, worksheet <code>worksheet_status/worksheet_note/worksheet_reviewed_at</code>, and <code>PUT /teacher/sessions/{session_id}/review-status</code> are documented. 검토 큐 visible 회귀와 교수자 전용 review grid 영역도 테스트로 고정했다.</td></tr>
|
||||
<tr><td>Persona authoring CRUD</td><td><code>python -B -m pytest -p no:cacheprovider app/test_persona_review.py -q</code></td><td>33 passed; teacher/admin draft create/read/update/review submit, approved persona revision clone, code-family archive delete, learner blocked, approved-only catalog maintained, RAG draft generation prompt bundle id/version/hash pinned in metadata/provenance, seed/version runner dry-run manifest and apply pool lifecycle fixed.</td></tr>
|
||||
<tr><td>Repo source pack sync</td><td><code>python -B -m pytest -p no:cacheprovider app/test_live_coach_sources.py -q</code> / <code>py -3.11 scripts\sync-persona-sources.py --help</code></td><td>4 passed; dry-run reports next version without writes, apply bumps changed hash to latest+1, identical hash skips indexing, CLI exposes DB-backed dry-run/apply modes.</td></tr>
|
||||
<tr><td>Repo source pack sync</td><td><code>py -3.11 -X utf8 -m pytest -p no:cacheprovider app/test_live_coach_sources.py -q</code> / <code>py -3.11 -X utf8 scripts\sync-persona-sources.py --help</code></td><td>9 passed; dry-run reports next version without writes, apply bumps changed hash to latest+1, identical hash skips indexing, CLI exposes DB-backed dry-run/apply modes, and admin/CLI sync boundaries refresh the process-local live-coach source pack cache before comparing repo files. File-change regression proves stale cached content_hash becomes a new hash and next document version when refreshed.</td></tr>
|
||||
<tr><td>P1 cost telemetry</td><td><code>app.test_session_turn_persistence.SessionTurnPersistenceTest</code></td><td>Passed; generate/stream client AI turns store provider/model/tokens/cost</td></tr>
|
||||
<tr><td>Engine stream contract</td><td><code>node scripts/check-engine-gateway-contract.mjs --json</code> / <code>python -B -m pytest -p no:cacheprovider engine_gateway/test_gateway_model.py -q</code> / <code>python -B -m pytest -p no:cacheprovider engine_gateway/test_gateway_model.py app/test_orchestrator_masking.py app/test_session_turn_persistence.py -q</code></td><td>Node conformance runner OK, gateway contract 19 passed, L1 focused 50 passed; <code>EngineClient.stream_packets()</code> owns raw SSE decode, <code>EngineGatewaySseLineDecoder</code> parses token/done/error from the shared contract, keeps legacy token text compatibility, treats provider <code>[DONE]</code> as an ignored compatibility line rather than completion, rejects unknown gateway events, validates schema-backed <code>engine_gateway_contract.v1.json</code>, and gateway error event does not create saved turns.</td></tr>
|
||||
<tr><td>Engine session reuse</td><td><code>engine_gateway.test_gateway_model</code></td><td>19 tests OK; shared engine contract, JSON Schema + golden fixture validation, Node.js artifact conformance runner, <code>GenerateResponse</code> response validation, SSE frames/decoder, live session_id reuse and ephemeral close fixed</td></tr>
|
||||
<tr><td>P1/H4 masking gate</td><td><code>app/test_pii_masking_eval.py app/test_orchestrator_masking.py app/test_evaluation_persistence.py app/test_session_turn_persistence.py</code></td><td>43 passed; phone/email/RRN 및 한국어 NAME/ORG raw 값이 generate/stream/evaluator payload와 client <code>text_masked</code>에 남지 않음. Synthetic ko fixture 15/15 pass, 자연 발화형 이름 라벨·자기소개와 negative control 포함, input/report schema validation, summary-only report(<code>evidence_text_included=false</code>), entity recall 1.0, forbidden substring removal 1.0, unexpected entity violations 0.</td></tr>
|
||||
<tr><td>P1/H4 masking gate</td><td><code>app/test_pii_masking_eval.py app/test_orchestrator_masking.py app/test_evaluation_persistence.py app/test_session_turn_persistence.py</code></td><td>47 passed; phone/email/RRN 및 한국어 NAME/ORG raw 값이 generate/stream/evaluator payload와 client <code>text_masked</code>에 남지 않음. Optional ko recognizer fake span은 <code>[NAME]</code>/<code>[ORG]</code>로 마스킹되고 같은 문장의 phone은 후단 regex가 처리하며, adapter 실패 시에도 regex fallback이 유지된다. Synthetic ko fixture 15/15 pass, 자연 발화형 이름 라벨·자기소개와 negative control 포함, input/report schema validation, summary-only report(<code>evidence_text_included=false</code>), entity recall 1.0, forbidden substring removal 1.0, unexpected entity violations 0.</td></tr>
|
||||
<tr><td>P2a RBAC/audit/visibility</td><td><code>app.test_rbac_idor</code></td><td>8 tests OK; other learner 403, read_session audit, evaluator-only hidden, teacher session review read allowed while learner worksheet write remains 403</td></tr>
|
||||
<tr><td>Admin access delegation</td><td><code>python -m pytest app/test_auth_providers.py app/test_admin_ops.py -q</code> / <code>npm run typecheck</code> / <code>npm run check:api-types</code></td><td>32 backend passed; <code>admin_access</code> and <code>super_admin</code> are exposed through auth/admin DTOs. Super admins can grant/revoke admin page access; configured super admins stay protected. Frontend role switching uses <code>canAccessRole</code>.</td></tr>
|
||||
<tr><td>Admin usage persistence</td><td><code>python -m pytest app/test_admin_ops.py app/test_runtime_policy.py -q</code> / authenticated local public-API smoke</td><td>26 passed; <code>/admin/usage</code> returns 200 with <code>source=database</code>, <code>durable=true</code>. Cloudflare blocked raw Python public smoke with 1010, so app-level HTTP was verified against <code>127.0.0.1:8001</code> using the same prod process.</td></tr>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue