메일 알림 시스템 추가
This commit is contained in:
parent
ddf12a851c
commit
3bf38c50df
22 changed files with 1769 additions and 31 deletions
|
|
@ -531,7 +531,8 @@
|
|||
<p class="dg-note">95차 적용(2026-06-29): refactor-governance P2/P3로 M2 one-shot session digest worker 경계를 추가했다. <code>app/services/session_digest_worker.py</code>가 <code>CompressionJob</code>을 Node-compatible <code>GenerateRequest</code>/<code>EngineMessage</code>로 변환하고, 주입형 engine·<code>audit_hook</code>으로 단발 호출한 뒤 local quality gate를 통과한 결과만 <code>session_summary.digest/compressed_by/token_count</code>와 <code>case_profile.case_digest</code>에 idempotent 적용할 수 있게 한다. DB loader는 persisted fallback summary와 client-visible masked transcript만 재구성하며 raw text, evaluator-only turn, CCD, end_state는 prompt에 넣지 않는다. 이는 provider live run, background scheduler, 임상 골든셋 품질평가, 재압축 완료가 아니다. 검증: <code>py -3.11 -X utf8 -B -m pytest -p no:cacheprovider app/test_session_digest_worker.py app/test_session_memory.py -q</code> 25 passed, <code>py -3.11 -X utf8 -B -m pytest -p no:cacheprovider app/test_session_digest_worker.py app/test_session_memory.py app/test_session_turn_persistence.py app/test_orchestrator_masking.py app/test_runtime_policy.py -q</code> 82 passed.</p>
|
||||
<p class="dg-note">96차 적용(2026-06-29): M2 digest worker를 운영자가 명시 실행할 수 있도록 <code>scripts/run-session-digest-worker.py</code> dry-run/apply runner를 추가했다. 기본은 metadata-only dry-run이고, <code>--apply</code>를 줘야 accepted 후보를 DB에 반영한다. runner는 load → DB release → engine generate → short apply 순서로 동작해 LLM 호출 동안 DB transaction을 잡지 않는다. digest 본문은 민감할 수 있어 <code>--show-digest</code>를 명시할 때만 출력한다. 이는 실 provider 장시간 운영 완료가 아니다. 검증: <code>py -3.11 -X utf8 -B -m py_compile scripts\run-session-digest-worker.py apps\api\app\services\session_digest_worker.py apps\api\app\test_session_digest_worker.py</code>, <code>py -3.11 -X utf8 scripts\run-session-digest-worker.py --help</code>, M2 focused 25 passed.</p>
|
||||
<p class="dg-note">97차 적용(2026-06-29): <code>SESSION_DIGEST_WORKER_ENABLED=false</code> 기본값을 두고, opt-in일 때만 세션 종료 성공 뒤 digest worker를 background task로 예약한다. scheduler는 persisted fallback row를 load한 뒤 DB connection을 놓고 engine을 호출하며, accepted plan만 짧은 apply transaction으로 반영한다. 이미 <code>compressed_by</code>가 있는 세션은 loader에서 제외하고, apply도 <code>compressed_by IS NULL</code> CAS로 보호하며, fallback summary 재작성 시 compression metadata를 초기화한다. 이는 bounded/default-off scheduler 골격이며, 실 provider 장시간 운영·임상 골든셋 품질평가·재압축 정책 완료가 아니다. 검증: <code>py_compile</code>, runner <code>--help</code>, M2 focused 30 passed.</p>
|
||||
<p class="dg-note">최신 적용(99차, 2026-06-29): 대시보드 SSOT drift gate를 보강했다. <code>scripts/check-dev-dashboard-ssot.py</code>는 현재 카드 상태(<code>done=25</code>, <code>planned=0</code>, <code>doing=0</code>), CHECK 잔여 0, owner board counts(<code>block=2</code>, <code>decide=7</code>, <code>ext=2</code>), 외부 <code>GATE</code> 상세 6행, 결정 탭 7행, 자유연습 F-10 결정 문구, M2 30/87 검증 수치, default-off scheduler/CAS 문구, 실 provider·임상 골든셋·재압축 GATE 보존, stale 82/25/26 passed 및 낡은 결정 8건 문구 제거를 함께 확인한다. 검증: <code>py_compile</code>, <code>py -3.11 -X utf8 scripts\check-dev-dashboard-ssot.py --json</code> PASS. 아래 적용 노트 일부는 historical snapshot이라 당시 통과 수치를 그대로 보존한다.</p>
|
||||
<p class="dg-note">99차 적용(2026-06-29): 대시보드 SSOT drift gate를 보강했다. <code>scripts/check-dev-dashboard-ssot.py</code>는 현재 카드 상태(<code>done=25</code>, <code>planned=0</code>, <code>doing=0</code>), CHECK 잔여 0, owner board counts(<code>block=2</code>, <code>decide=7</code>, <code>ext=2</code>), 외부 <code>GATE</code> 상세 6행, 결정 탭 7행, 자유연습 F-10 결정 문구, M2 30/87 검증 수치, default-off scheduler/CAS 문구, 실 provider·임상 골든셋·재압축 GATE 보존, stale 82/25/26 passed 및 낡은 결정 8건 문구 제거를 함께 확인한다. 검증: <code>py_compile</code>, <code>py -3.11 -X utf8 scripts\check-dev-dashboard-ssot.py --json</code> PASS. 아래 적용 노트 일부는 historical snapshot이라 당시 통과 수치를 그대로 보존한다.</p>
|
||||
<p class="dg-note">100차 적용(2026-06-29): 관리자·교수자 메일링 시스템 1차를 추가했다. 신규 외부 로그인 pending 계정은 <code>account_pending_approval:{user_id}</code>, 회기 종료 후 평가/error record 저장 완료 세션은 <code>session_review_ready:{session_id}</code>, 명시 테스트 메일은 <code>admin_test_email:{uuid}</code> idempotency key로 <code>app.notification_event</code>/<code>app.notification_delivery</code>에 큐잉된다. 메일 HTML은 Vignette 톤앤매너(종이 배경, 세이지 CTA, 8px 카드)를 inline style로 유지하고, 본문에는 축어록·평가 전문을 넣지 않고 <code>/admin/users</code>, <code>/teach/session/:sessionId/review</code>, <code>/admin</code> 딥링크만 제공한다. <code>NOTIFICATION_EMAIL_PROVIDER=smtp</code>와 <code>SMTP_*</code>가 설정된 경우 실제 발송하며, <code>GET /admin/notifications</code>, <code>POST /admin/notifications/process</code>, <code>POST /admin/notifications/test</code>, <code>scripts/run-notification-worker.py</code>로 delivery 상태와 재시도를 운영한다.</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>
|
||||
|
|
@ -929,7 +930,8 @@
|
|||
<tr><td>Engine session reuse</td><td><code>engine_gateway.test_gateway_model</code></td><td>27 tests OK; shared engine contract, JSON Schema + golden fixture validation, Node.js artifact conformance runner, <code>GenerateResponse</code> response validation, structured payload fallback parser, current-turn prompt split, direct evaluator/live-coach parser ownership, SSE frames/decoder, live session_id reuse, <code>gateway-default</code> 기본 라우팅 sentinel 정규화, missing-user 400 guard, 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>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 access delegation</td><td><code>python -B -m pytest -p no:cacheprovider app/test_auth_providers.py app/test_runtime_policy.py app/test_rbac_idor.py app/test_teacher_dashboard.py app/test_learner_dashboard.py -q</code> / <code>npx playwright test e2e/admin.spec.ts --project=chromium-desktop --workers=1</code> / <code>npm run check:api-types</code> / <code>npm run typecheck</code> / <code>npm run build</code></td><td>70 backend focused passed + admin/auth/runtime 64 passed + admin E2E 8 passed. 실제 <code>admin</code> role은 학습자·교수자·관리자 공간을 모두 열고, <code>admin_access</code>만 받은 비관리자 계정은 관리자 콘솔 진입권만 얻는다. 관리자 sidebar는 운영 홈/사용자/권한/티켓과 교수자·학습자 작업 공간 링크를 함께 노출한다.</td></tr>
|
||||
<tr><td>Session read-model DB readiness</td><td><code>python scripts\check-deploy-preflight.py --skip-db --env-file infra\.env.example --allow-placeholder-secrets</code> / <code>https://api-vignette.chanpaca.net/health</code></td><td>2026-06-29 prod 503 원인은 운영 DB의 <code>app.turns.provider_events</code> 컬럼 누락이었다. 운영 DB hotfix 후 <code>/teacher/dashboard</code> code path는 <code>source=database</code>로 복구됐고, <code>db.healthcheck()</code>, runtime table readiness, deploy preflight DB mode가 <code>app.turns.provider_events</code>와 <code>app.session_review_status</code> worksheet 컬럼을 함께 검증한다. Public health는 <code>environment=prod</code>, <code>db=true</code>, <code>engine=true</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>
|
||||
<tr><td>Synthetic health sampler</td><td><code>record_admin_health_sample()</code>, <code>record-admin-health-sample.py</code>, <code>install-health-sampler-task.ps1</code></td><td>Backend focused 31 passed. Python compile/help passed; PowerShell parser + <code>-PrintOnly</code> passed. Local one-shot appended 5 service rows to <code>app.admin_health_event</code>: status ok, engine_mode claude_cli. This remains sample history, not an SLA claim.</td></tr>
|
||||
<tr><td>Health retention/rollup</td><td><code>admin_health_daily_rollup</code>, <code>maintain-admin-health-events.py</code>, <code>/admin/uptime</code></td><td>Backend focused 36 passed. Python compile/help passed. Local dry-run returned <code>rollup_event_count=0</code>, <code>prunable_event_count=0</code>. Uptime summary now combines raw samples with daily rollups when raw rows have been pruned; detailed events stay raw-only.</td></tr>
|
||||
|
|
@ -953,15 +955,15 @@
|
|||
<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/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>2026-06-29 manual deploy from committed source; preview <code>https://eb2ed257.vignette-b1q.pages.dev</code></td></tr>
|
||||
<tr><td>Custom domain assets</td><td><code>https://vignette.chanpaca.net/login?deploy=20260629</code></td><td>2026-06-29 recheck: 200, <code>index-9KEDuuBQ.js</code>, <code>index-DC83qEpU.css</code></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>2026-06-29 admin IA/readiness fix deploy from current working tree; preview <code>https://c67ca04a.vignette-b1q.pages.dev</code></td></tr>
|
||||
<tr><td>Custom domain assets</td><td><code>https://vignette.chanpaca.net/login?admin-fix=20260629-1</code></td><td>2026-06-29 recheck: 200, <code>index-Ba7CXeH_.js</code>, <code>index-DC83qEpU.css</code></td></tr>
|
||||
<tr><td>Legacy Live2D routes</td><td><code>/live2d/mao/* / /live2d/haru/* / /live2d/live2dcubismcore.min.js</code></td><td>404</td></tr>
|
||||
<tr><td>Compose template</td><td><code>docker compose -f infra\docker-compose.yml config --quiet</code></td><td>Template path is valid with dummy required env. API build context now uses repo root; stale <code>rag.server</code> sidecar removed.</td></tr>
|
||||
<tr><td>Docker image smoke</td><td><code>docker build -f apps/api/Dockerfile .</code> / <code>docker run ... python -c "import app.main"</code> / <code>docker build -f apps/web/Dockerfile apps/web</code></td><td>API/Web image build and API import smoke pass after packaging cleanup. Web build uses npm lockfile and ignores host <code>node_modules</code>; API image excludes local <code>.env</code> files.</td></tr>
|
||||
<tr><td>Deploy preflight</td><td><code>python scripts\check-deploy-preflight.py --skip-db --env-file infra\.env.example --allow-placeholder-secrets</code></td><td>Passed: exact-pinned API requirements, live coaching <code>data/kb</code> source pack, env template keys. DB mode can additionally check app-role DSN with <code>--require-app-role</code>.</td></tr>
|
||||
<tr><td>Deploy preflight</td><td><code>python scripts\check-deploy-preflight.py --skip-db --env-file infra\.env.example --allow-placeholder-secrets</code> / DB mode with local <code>DATABASE_URL</code></td><td>Passed: exact-pinned API requirements, live coaching <code>data/kb</code> source pack, env template keys, and DB readiness (<code>current_user=vignette</code>). DB mode can additionally check app-role DSN with <code>--require-app-role</code> and now verifies session read-model columns including <code>app.turns.provider_events</code> plus worksheet review columns.</td></tr>
|
||||
<tr><td>Fresh compose smoke</td><td><code>docker compose -p vignette-packaging-smoke -f infra/docker-compose.yml up -d --build</code> + proxy <code>/api/health</code></td><td>Passed with dummy production-safe env: API healthy, DB healthy, web/proxy up, <code>http://localhost:18080/api/health</code> 200 with <code>db:true</code>, <code>engine:true</code>, <code>engine_mode:"claude_cli"</code>. Smoke volumes/network removed after run.</td></tr>
|
||||
<tr><td>Actual deployment env</td><td><code>infra/.env</code> owner-secret fill-in</td><td>Remaining external step: deployment target must provide real <code>APP_DB_PASSWORD</code>, OAuth client id/secret, <code>OPENAI_API_KEY</code>, <code>SESSION_SECRET</code>, and production-safe engine/voice flags. Current local stray values such as <code>ENGINE_MODE=claude_p</code> and prod sample TTS must not be copied.</td></tr>
|
||||
<tr><td>Focused E2E</td><td><code>admin + db-persistence + voice-success</code></td><td>10 passed</td></tr>
|
||||
<tr><td>Focused E2E</td><td><code>admin + db-persistence + voice-success</code></td><td>Latest admin spec: 8 passed on chromium desktop, including all-workspace admin navigation, health dashboard, real server-known users, operation tickets, mobile controls, and tablet form containment.</td></tr>
|
||||
<tr><td>Admin manage-users</td><td><code>admin.spec.ts --grep manage real server-known users</code></td><td>desktop/mobile 2 passed</td></tr>
|
||||
<tr><td>Full E2E baseline</td><td><code>PLAYWRIGHT_PORT=5174 npm run e2e</code></td><td>2026-06-27/28 기준선 113 passed</td></tr>
|
||||
<tr><td>Public auth discovery</td><td><code>E2E_PUBLIC_AUTH=1 npx playwright test --list --project=chromium-public-auth</code></td><td>2 tests listed</td></tr>
|
||||
|
|
@ -970,8 +972,8 @@
|
|||
<tr><td>Persona auth boundary</td><td><code>GET /personas</code></td><td>verified local/public unauth 401</td></tr>
|
||||
<tr><td>Public login</td><td><code>auth.spec.ts --grep public login</code></td><td>1 passed</td></tr>
|
||||
<tr><td>Public runtime scripts</td><td><code>start/watch/install-public-runtime*.ps1</code></td><td>parser OK; watchdog check-only healthy. Default public checks exclude not-yet-live <code>api-vnet.18ka.net</code>; add future domains explicitly with <code>-AdditionalPublicHealthUrls</code>.</td></tr>
|
||||
<tr><td>Public API health</td><td><code>https://api-vignette.chanpaca.net/health</code></td><td>prod, db true, engine true</td></tr>
|
||||
<tr><td>Public/local/Tailnet login recovery</td><td><code>https://vignette.chanpaca.net/login</code> / <code>https://api-vignette.chanpaca.net/health</code> / <code>https://alpaca-home.taile93291.ts.net/login</code></td><td>2026-06-29 public recheck: public login 200 (<code>assets/index-9KEDuuBQ.js</code>, <code>assets/index-DC83qEpU.css</code>), public API health <code>status=ok</code>, <code>environment=prod</code>, <code>db=true</code>, <code>engine=true</code>, <code>engine_mode=claude_cli</code>, unauth public <code>/personas</code> 401. Earlier local/Tailnet checks remain recorded separately; vnet DNS A records are still 0.</td></tr>
|
||||
<tr><td>Public API health</td><td><code>http://127.0.0.1:8001/health</code> / <code>https://api-vignette.chanpaca.net/health</code></td><td>2026-06-29 after public API restart: both return <code>status=ok</code>, <code>environment=prod</code>, <code>db=true</code>, <code>engine=true</code>, <code>engine_mode=claude_cli</code>. Unauthenticated protected routes such as <code>/personas</code> and <code>/teacher/dashboard</code> return 401, not 503.</td></tr>
|
||||
<tr><td>Public/local/Tailnet login recovery</td><td><code>https://vignette.chanpaca.net/login</code> / <code>https://api-vignette.chanpaca.net/health</code> / <code>https://alpaca-home.taile93291.ts.net/login</code></td><td>2026-06-29 public recheck: public login 200 (<code>assets/index-Ba7CXeH_.js</code>, <code>assets/index-DC83qEpU.css</code>), public API health <code>status=ok</code>, <code>environment=prod</code>, <code>db=true</code>, <code>engine=true</code>, <code>engine_mode=claude_cli</code>, unauth public <code>/personas</code> 401. Earlier local/Tailnet checks remain recorded separately; vnet DNS A records are still 0.</td></tr>
|
||||
<tr><td>Local 5175 login</td><td><code>PLAYWRIGHT_BASE_URL=http://127.0.0.1:5175 auth.spec.ts</code></td><td>desktop/mobile passed</td></tr>
|
||||
<tr><td>Learner/readiness E2E</td><td><code>learner.spec.ts + readiness.spec.ts desktop/mobile</code></td><td>14 passed</td></tr>
|
||||
<tr><td>Learner screenshots</td><td><code>learn-empty-desktop.png / learn-empty-mobile-compact.png</code></td><td>document overflow 0, empty history visible</td></tr>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue