799 lines
59 KiB
Markdown
799 lines
59 KiB
Markdown
# Vignette G0~G8 완료 추진 핸드오프
|
||
|
||
> Updated: 2026-08-09 KST (다른 에이전트 실행용 최종 인계 · G7 외부-proof P0 코드 완료 · current-source fixture 분할 GREEN/clean HEAD 재실행)
|
||
> Workspace: `D:\workspace\vignette`
|
||
> 이 문서는 다음 에이전트가 **G0~G8 아홉 목표 중 남은 G7 외부 게이트와 G8 NAS-origin 재검증을 닫기 위한** 실행 기준이다.
|
||
|
||
## 0. 현재 상태 한 줄 요약
|
||
|
||
**G0~G6의 내부 구현·증거는 종료됐다. 단, 최종 clean source 전체 회귀는 다시 실행해야 한다. G8 실제 rollback
|
||
receipt는 완료됐지만 current source clean-head 승격은 아직 0회다.
|
||
두 execute는 모두 NAS mutation 전에 안전하게 중단됐다. naive `started_at`의 9시간 오차 P0는 UTC/KST 계약과
|
||
실DB 브라우저 회귀로 수정됐고, 이제 새 clean commit 결속과 NAS-origin 전체 E2E가 남았다. G7 공개 런처의
|
||
local sidecar lifecycle·provider/model readiness·Uvicorn queue, fresh PID/source provenance, 3,120초 runner와
|
||
3,000초 공통 시간창·canonical exit 계약은 소스에서 완료됐지만, current source 공개 배포와 외부 4-artifact 게이트는 남아 있다.**
|
||
|
||
| Goal | 판정 | 완료 증거 |
|
||
|---|---|---|
|
||
| G0 Measurement Foundation | DONE | provenance census 29/29, unknown/missing/orphan/null/total violations 0 |
|
||
| G1 Alliance Core | DONE | prompt 1.2 read-skew fix, prediction 24/24 ready, direction 9/9, error 0, recall 94.4%, precision 73.9% |
|
||
| G2 Outcome Trajectory | DONE | benchmark accuracy/early-warning recall 1.0, false alert 0, 실 DB 10 sessions/30 measurements |
|
||
| G3 Rupture & Repair | DONE | macro-F1 1.0, status 1.0, gaming 0, missed→partial→resolved 실 DB/API |
|
||
| G4 Deliberate Practice | DONE | completed-session observer, actual API/DB attempts, teacher correction, returned-practice browser 폐루프 |
|
||
| G5 Calibration & Transfer | DONE | actual transfer execution, independent/model-run/instrument/evidence provenance, DB/browser idempotency |
|
||
| G6 Supervision & Research | DONE | safety event metadata-only projection, safety priority 1 > deterioration 2, raw detail 0 |
|
||
| G7 Multimodal Alliance | **internal DONE · external GATE** | consent/withdrawal, synthetic soak 59/59, G7 runner/checker/topology 86/86, launcher/sidecar 80/80, API voice 71/71 |
|
||
| G8 Continuous Improvement | **receipt DONE · runtime REVALIDATION** | 실제 image rollback 2회 + executed receipt 2건. timestamp 수정 실DB GREEN, 새 clean commit·NAS-origin E2E 미완료 |
|
||
|
||
대시보드 status count는 **32 done / 3 doing / 0 planned**다.
|
||
|
||
Git 기준선은 branch `master`, HEAD `94c681d450d2f3b4df55c036e333cd817db3f7fb`이고 `origin/master`보다 2 commit
|
||
앞서 있다. 공유 worktree에는
|
||
문서/대시보드/G7·G8 런북, 완결된 clean-head 릴리스 에이전트·테스트, UTC/KST session timestamp 수정과
|
||
browser 회귀가 tracked dirty로 남아 있다. Git 작업은 stage/commit/push하지 않았다.
|
||
|
||
### 이 문서가 인계된 정확한 중단점
|
||
|
||
- 사용자가 **현재 구현을 잠시 멈추고 다른 에이전트로 넘길 핸드오프를 만들라**고 요청했다. 핸드오프 정리 중
|
||
public/NAS/DB/마이크/프로세스의 수동 mutation은 0회다. 5분 Scheduled Task는 한 번씩 자동 실행됐으나 아래
|
||
source-pin gate에서 안전하게 exit 1로 중단됐다.
|
||
- active goal은 계속 `active`다. 목표는 “문서 작성”이 아니라 **G0~G8 아홉 목표를 실제 runtime과 E2E로
|
||
모두 닫는 것**이다. G7 canonical checker exit 0과 G8 current clean source의 NAS-origin 0-failure가 없으면
|
||
`goal complete`를 호출하지 않는다.
|
||
- 마지막 완료 작업은 G7 runner/checker/topology·fresh public launcher provenance P0, session timestamp UTC/KST 계약,
|
||
API full 921/921, gateway 58/58, Web API types·typecheck·build, 자기주도 route fixture desktop/mobile 6/6이다.
|
||
fixture desktop/mobile은 마지막 단일
|
||
120/120 뒤 dashboard spec/HTML이 다시 바뀌었고 그 변경의 dashboard 10/10을 별도로 확인했으므로,
|
||
**현재 트리는 불변 110 + 최신 dashboard 10의 분할 GREEN**이다. 새 clean HEAD에서 exact 120을 다시 실행한다.
|
||
- G7 Windows topology mode, 3,000초 공통 시간창, canonical checker-bound exit와 공개 런처의 local
|
||
Whisper/MeloTTS lifecycle·exact readiness·Uvicorn queue·fresh API/cloudflared provenance 계약은 코드와 회귀가
|
||
끝났지만 public launcher 실행과 capture는 수행하지 않았다.
|
||
현재 public API가 Git metadata가 없는 냉동 release 디렉터리에서 실행되므로 provenance gate가 의도적으로
|
||
닫혀 있다. 이 검증을 약화해서 통과시키면 안 된다.
|
||
- **public task source-pin P0:** watchdog·로그온 boot는 이제 detached clean release만 허용하고 Git
|
||
commit/tree와 watchdog|boot/start script SHA를 health·Docker·process mutation 전에 검증한다. hidden VBS도
|
||
shared source를 직접 실행하지 않고 구조적으로 pin된 task만 trigger한다. 회귀는 provenance 11개를 포함한
|
||
watchdog/start/cutover combined 22/22, PowerShell 5.1 parser·WSH syntax PASS다. 그러나 **실제 두 Scheduled Task action은
|
||
아직 shared worktree를 가리키며 pin 인자가 없다.** 5분 watchdog은 pin 부재로 반복 실행마다
|
||
`LastTaskResult=1`이고, 새 필수 pin 인자 부재 때문에 운영 프로세스를 건드리기 전에 의도대로 fail-closed했다.
|
||
18:02:55 KST에는 Docker Desktop UI가 active DB container에 직접 stop을 보내 public `db=false`가 재발했다.
|
||
같은 container ID와 recovered named volume만 18:05 KST 재시작했고, 공개 local/public health는 다시
|
||
`status=ok, db=true, engine=true`다. clean commit 뒤 두 task를 동일한 detached stable
|
||
release root로 재등록하고 action pin·watchdog result 0·로그온/reboot smoke를 확인하기 전에는 public runtime
|
||
자동복구를 DONE으로 부르지 않는다.
|
||
- 다음 에이전트의 첫 mutation은 **사용자 승인 아래 새 clean commit 생성**이어야 한다. 그 clean source만 G8 NAS
|
||
preview 또는 G7 public runtime의 배포 입력으로 쓰고, 두 환경을 동시에 변경하지 말며 mutation owner는 한 명만 둔다.
|
||
|
||
### 완료 조건 판정 (2026-08-09 재검증)
|
||
|
||
원래 active goal의 완료 선언 조건 네 가지를 실행으로 대조한 결과다.
|
||
|
||
| # | 조건 | 판정 | 근거 |
|
||
|---|---|---|---|
|
||
| 1 | G0~G8 각각의 코드 계약과 실제 runtime 증거 | **부분 충족** | G0~G6 충족. G8 receipt는 충족했지만 NAS-origin 회기 재검증이 남았고 G7 외부 GATE가 열려 있다 |
|
||
| 2 | 학생 홈→회기→리뷰→처방→재연습→전이 폐루프가 실제 API/DB/browser 통과 | **충족** | 일회용 클론에서 `returned-practice-db-closed-loop` desktop 2/2 + mobile 2/2, route mock 0 |
|
||
| 3 | current source 증거와 대시보드·TODO·backlog 일치 | **충족** | SSOT checker FAIL 0, unit 5/5, dashboard E2E 10/10, count 32/3/0이 카드와 일치 |
|
||
| 4 | G7을 가짜·합성 증거로 DONE 처리하지 않음 | **충족** | `check-g7-external-proof.py` exit 1 유지, G7 카드는 `GATE` |
|
||
|
||
재검증 수치: API **921 passed**, gateway **58**, executor **28**, probe **11**, helper launcher **37**,
|
||
release agent **30**, G7 runner/checker/topology 묶음 **86**, G7 public launcher/sidecar **80**, G7 통합 **166**,
|
||
API voice 통합 **71**,
|
||
MeloTTS 사이드카 **16**, local whisper 사이드카 **38**,
|
||
SSOT FAIL **0**,
|
||
dashboard E2E **10/10**, web api-types·typecheck·build 통과, ruff clean.
|
||
|
||
current-source fixture 묶음은 16:49 KST 단일 실행에서 **120/120 PASS(92.0s)**했다. dev dashboard file URL은 실행 cwd가
|
||
아니라 spec의 `import.meta.url`로 repo root를 찾으며, session-review fixture는 alliance pulse까지 소유한다. 관리자
|
||
live health 테스트는 React StrictMode 중복 조회의 첫 응답을 고정하지 않고 화면이 실제 반영한 성공
|
||
`/admin/users` 스냅샷과 KPI를 결속한다. 그 뒤 dev-dashboard spec/HTML이 변경됐고 최신 dashboard 10/10만 따로
|
||
재검증됐다. 따라서 current tree의 정직한 판정은 **110 불변 + 10 최신 분할 GREEN**이며, 새 clean commit에서
|
||
같은 120건을 한 번 재실행해 archive/source 결속 뒤의 결과를 고정한다.
|
||
|
||
**따라서 `update_goal complete`를 호출하지 않는다.** 조건 1이 G7과 G8 NAS-origin 재검증 때문에 미충족이고,
|
||
조건 4가 G7을 합성 증거로 메우는 것을 금지한다. G8 receipt 자체는 종료됐다(§3).
|
||
|
||
### G0~G8 목표별 인수 기준
|
||
|
||
| Goal | 원 계획의 종료 조건 | 현재 인정 가능한 증거 | 다음 에이전트가 최종 종료 전에 할 일 |
|
||
|---|---|---|---|
|
||
| G0 | 100% provenance, evaluator/client 누수 0, Python/TS/DB schema conformance, 기존 회기 무회귀 | current producer census 29/29, 위반 0, migrations 14~16 2회 멱등 | current HEAD 전체 backend/schema 회귀와 최종 census를 다시 실행 |
|
||
| G1 | goal/task/bond 독립 저장, 축별 UI, gold 근거 span, 평가 실패 fail-closed | prediction 24/24 ready, 방향 9/9, error 0, recall 94.4%, precision 73.9%, pre/mid/post UI | current HEAD의 alliance desktop/mobile·DB-backed session 경로 재실행 |
|
||
| G2 | 5회기 연속성, 조기 경보 recall, false alert/uncertainty, synthetic 비임상 경계 | accuracy/recall 1.0, false alert 0, 실제 DB 10 sessions/30 measurements | current HEAD outcome trajectory API/UI 회귀 재실행 |
|
||
| G3 | rupture 유형 benchmark, missed/partial/resolved E2E, gaming/암기 방지 | macro-F1/status 1.0, gaming 0, 실제 DB/API missed→partial→resolved | text/SSE 회기 및 rupture UI desktop/mobile 재실행 |
|
||
| G4 | 모든 코칭 카드의 실행 재연습, 전후 근거 저장, unseen 전이 전 mastery 금지 | 실제 API/DB attempt·observer·teacher correction, browser route mock 0 | disposable DB에서 returned-practice desktop/mobile 4/4와 멱등 재확인 |
|
||
| G5 | 자기평가 잠금, calibration 감소, unseen transfer 유지, subgroup drift | actual transfer execution과 independent/model-run/instrument/evidence provenance | G4와 같은 disposable DB에서 actual POST·read reload·1→1 멱등 재확인 |
|
||
| G6 | 3-click queue, 원장 drilldown, 버전 재현, 역할/RLS/audit | safety metadata-only, safety priority 1 > deterioration 2, raw detail 0 | current HEAD producer/store/API와 supervisor UI 회귀 재실행 |
|
||
| G7 | 시간 정렬, **물리 마이크+공개 WSS 장시간 E2E**, text-only 대비 음성 이득, 동의/삭제 정책 | 내부 계약·synthetic soak만 완료. canonical checker는 의도적으로 exit 1 | current 공개 배포→인증 WSS→명시 동의 mic 50분→동시 runtime/topology→독립 human pack→checker exit 0 |
|
||
| G8 | source→draft→red-team→benchmark→catalog 재현, 누수/PII/무근거 0, 자동 calibration+rollback, incident→test/backlog 환류 | agentic worker·human gate·실제 rollback/restore receipt 2건, timestamp focused 실DB/browser GREEN | 사용자 승인 아래 새 clean commit→NAS 배포→**실제 NAS origin** 전체 회기 E2E GREEN→SSOT 동기화 |
|
||
|
||
## 1. 작업 시작 전 반드시 읽을 파일
|
||
|
||
1. `AGENTS.md`
|
||
2. `docs/README.md`
|
||
3. `docs/HANDOFF.md` — 현재 문서
|
||
4. `docs/dev_dashboard.html` — 상태 SSOT
|
||
5. `docs/TODO.md` — 열린 작업 SSOT
|
||
6. `docs/ops/backlog-2026-06-26.md`
|
||
7. `docs/ops/outcome-os-g7-external-proof-readiness-2026-08-07.md` — **다음 작업의 핵심**
|
||
8. `docs/ops/nas-preview-g8-rollback-proof-runbook.md`
|
||
9. `docs/ops/nas-preview-deployment-evidence-2026-08-07.md`
|
||
10. `docs/ops/public-db-recovery-rehearsal-2026-08-07.md`
|
||
|
||
`docs/archive/`는 현재 상태 근거로 읽지 않는다.
|
||
|
||
## 2. 절대 보존 경계
|
||
|
||
- Windows 11 + PowerShell 환경이다. 실행 전 OS·셸·도구 경로를 다시 확인한다.
|
||
- 명령은 PowerShell 5.1 호환 문법을 기본으로 작성한다. NAS 원격 스크립트는 **UTF-8 base64 전달 방식**을 쓴다.
|
||
(`$()`가 PowerShell에서 먼저 확장되는 것을 막는다.)
|
||
- 이 핸드오프 작성 시 branch/HEAD는 `master` / `94c681d450d2f3b4df55c036e333cd817db3f7fb`이고 include 후보는
|
||
52개다: UTC/KST timestamp, G7 voice/topology/public launcher, pinned `psutil`, G8 clean-head controller, public task source pin,
|
||
학생 boot recovery, current fixture 안정화, dashboard/TODO/backlog/HANDOFF·런북 동기화다. 이 묶음들은 아직
|
||
commit에 들어 있지 않다.
|
||
- clean-head 구현은 unit 30/30, G7 runner/checker/topology는 86/86, G7 public launcher/sidecar는 80/80,
|
||
public task source pin은 provenance 11개를 포함한 combined 22/22, timestamp·voice 계약을 포함한 API는
|
||
921/921와 KST 실DB browser 1/1, current fixture는 110 불변 + 최신 dashboard 10 분할 GREEN,
|
||
SSOT는 FAIL 0와 dashboard E2E 10/10을 통과했다. 다음 에이전트는 최초 diff review 뒤 동일한 focused gate를
|
||
다시 실행하고, 서로 다른 lane의 파일을
|
||
누락한 채 clean commit을 만들지 않는다.
|
||
- 기존·watchdog 생성 엔진 로그 4개와 `apps/web/test-results/`의 PNG 2개는 commit에서 제외하고 보존한다. 신규
|
||
`apps/api/app/test_session_read_model.py`와
|
||
`docs/ops/evidence/g8-clean-head-material-milestone-2026-08-09.json`은 이번 목표 산출물이므로 clean source에
|
||
포함해야 한다.
|
||
시작 즉시 `git status --short`로 다시 확인하고 `git reset --hard`, `git checkout --`, 대규모 자동 포맷, 임의 삭제는 금지한다.
|
||
- Git commit/stage/push는 사용자가 요청하기 전에는 하지 않는다.
|
||
- 공개 복구 DB, 원본 volume, pre-recovery container, dump, NAS named volume, 이전 API/Web 이미지를 삭제하지 않는다.
|
||
- **로컬 포트 8001(공개 API origin) · 55432(공개 DB) · 9099/9100(엔진)은 공개 런타임이다.**
|
||
공개 Web은 Cloudflare Pages이고 5174는 `vnet.18ka.net`용 로컬 preview다. `scripts/dev-up.ps1`은 런타임
|
||
리스너를 정리할 수 있으므로 공개 런타임이 떠 있는 동안 실행하지 않는다.
|
||
- 비밀값, 이메일, cookie, 사용자 UUID가 포함된 raw response, `.env` 내용은 로그·증거·채팅에 출력하지 않는다.
|
||
|
||
## 3. G8 receipt — 종료됨 · NAS-origin runtime 재검증은 열림
|
||
|
||
### Gate6 계약 정정 (성립 불가능했던 이전 계약을 코드로 교체)
|
||
|
||
감사 대상 current API 이미지 `sha256:52e0e816…8b2d`가 `com.docker.compose.project=vignette-preview-20260807`,
|
||
`service=api`, `version=2.20.1` **image label**을 갖고 있다. 따라서 "helper의 `com.docker.compose.*` key 0개"는
|
||
감사되지 않은 다른 이미지를 쓰지 않는 한 성립하지 않는다. 계약을 **key 부재 → 소속(membership)** 으로 바꿔
|
||
구현했다.
|
||
|
||
- 코드: `scripts/launch-nas-preview-g8-helpers.py`
|
||
- 테스트: `scripts/test_launch_nas_preview_g8_helpers.py` — **37/37**
|
||
- 계약: image 상속 label을 baseline으로 읽고 container의 모든 compose label이 baseline과 같거나 선언된 격리
|
||
override인지 검사 · 최종 project ≠ target · 최종 service ∉ {api,web,db,proxy} · argv에 target project/service
|
||
label 미주입(fake-runner) · exact container name/ID/`docker inspect` 증거 보존
|
||
- 런북: `docs/ops/nas-preview-g8-rollback-proof-runbook.md`
|
||
|
||
### 실제 실행 결과
|
||
|
||
| plan | receipt | 활성화된 API/Web 이미지 |
|
||
|---|---|---|
|
||
| `rollback-old` | `nas-g8-723eeef22eab05e63e3fafb0` | `79ec…4450` / `c530…2f28` |
|
||
| `restore-current` | `nas-g8-2738846cf2cf4fbe8ce0fc26` | `52e0…8b2d` / `6fdb…f215` |
|
||
|
||
- release gate·approval 각각 2회 멱등, lifecycle `executed`, artifact/approval/receipt binding, control-plane 분리 검증
|
||
- DB: `audit.ci_lifecycle_event` `rollback/executed` 2 · `audit.ci_human_approval_event` `authorize_rollback` 2 ·
|
||
`app.ci_release_gate` 2(전부 `pending_human_approval`, silent auto-promotion 0) · `ci_gate_artifact` 8
|
||
- HMAC journal 6 records, `previous_hash` 체인 전수 PASS, SHA256 `a5594feb…0690`
|
||
- 최종 상태: health 3/3, OpenAPI 126, auth 401, Web 200, helper 0, listener 0, 비밀 env 파기
|
||
- **계획 이탈:** Windows SSH 터널 `18018→8018`은 NAS sshd가 `administratively prohibited`로 direct-tcpip를 거부해
|
||
쓸 수 없었다. **sshd 설정은 바꾸지 않았고**, 같은 격리 계약의 NAS-side probe 컨테이너에서 loopback으로
|
||
실행해 control-plane(8018)/preview(8088) origin 분리는 그대로 유지했다. 다음 에이전트도 NAS에서
|
||
포트 포워딩을 기대하지 말 것.
|
||
- 기계 판독 증거: `docs/ops/evidence/nas-preview-g8-actual-rollback-2026-08-07.json`
|
||
|
||
## 4. 공개 서비스 복구 상태 — 이미 완료, 다시 망가뜨리지 말 것
|
||
|
||
공개 계정·회기 DB 복구는 2026-08-07 18:26 KST에 완료됐다. 2026-08-09 14:03 KST에는 Docker Desktop이
|
||
중단돼 DB listener가 사라지면서 public health가 일시적으로 `db=false`가 됐고, 18:02:55 KST에는 Docker Desktop
|
||
UI가 active DB container에 직접 stop을 보내 같은 증상이 재발했다. 두 번 모두 보존된 동일 컨테이너·볼륨만
|
||
재기동해 복구했다. 새 컨테이너·볼륨 생성이나 데이터 복원 덮어쓰기는 없었다.
|
||
|
||
- Public Web `https://vignette.chanpaca.net` · Public API `https://api-vignette.chanpaca.net` (origin `127.0.0.1:8001`)
|
||
- active DB container `vignette-dev-db`, volume `vignette_recovered_prod_20260807`, restart `unless-stopped`
|
||
- cutover 기준 복구 집계: users 84, sessions 30, turns 705, Google users 16, orphan sessions 0
|
||
- 2026-08-09 18:05 KST 재기동 후 owner read-only 집계: users 356, sessions 193, turns 726, Google users 16,
|
||
Google-owned sessions 31. 조회는 `BEGIN READ ONLY` 뒤 `ROLLBACK`했고 PII를 출력하지 않았다.
|
||
- health `environment=prod`, `db=true`, `engine=true`, `engine_mode=claude_cli`, dev-login disabled, unauth `/auth/me` 401
|
||
- **2026-08-09 18:05 KST recovery snapshot:** local/public health는 `status=ok, db=true, engine=true`, Google OAuth
|
||
start는 302→`accounts.google.com`, 공개 OpenAPI는 119 paths다. `/voice/health`는 아직 OpenAI STT/TTS이고
|
||
local Whisper 9882·MeloTTS 9883 listener는 0이다.
|
||
- DB 컨테이너는 `vignette_recovered_prod_20260807` named volume과 `unless-stopped`를 유지하고 local/public
|
||
health는 `status=ok, db=true, engine=true`다. 다만 watchdog·로그온 task action은 아직 shared worktree이며
|
||
stable root/commit/tree pin 인자가 없다. 새 source-pin 코드가 5분 watchdog을 mutation 전에 차단해 반복 실행이
|
||
`LastTaskResult=1`이다. clean commit의 detached release root로 두 task를 재등록하기 전까지 자동복구가
|
||
의도적으로 비활성인 상태다.
|
||
|
||
보안상 과거 `auth_session`은 복원하지 않았다. 사용자는 Google 재로그인이 필요하다.
|
||
|
||
보존물: pre-recovery container `vignette-dev-db-pre-recovery-20260807-182642`과 원본/recovery Docker volumes.
|
||
과거 증거에는 pre-cutover dump `…20260807-091057Z.dump` SHA256 `6b84d5c8…af1f`, recovered dump
|
||
`…20260807-092630Z.dump` SHA256 `d7bcc396…68ed`, post-cutover dump `…20260807-095511Z.dump` SHA256
|
||
`660695c8…65b88`이 기록돼 있으나, **인계 시점 로컬에는 `D:\workspace\vignette-backups` 디렉터리가 없고
|
||
`D:\workspace\vignette-recovery`에도 이 dump들이 보이지 않는다.** 삭제로 단정하지 말고 off-host/NAS/다른 경로를
|
||
확인하기 전 restore·cleanup을 금지한다. NAS의 pre-proof/pre-clean-head dump는 §5 기준으로 실제 존재와 hash를
|
||
재확인했다.
|
||
|
||
공개 DB에는 current dirty source를 배포하지 않는다.
|
||
|
||
## 5. NAS 배포 기준선 — 현재 HEAD가 아니라 2026-08-07 릴리스
|
||
|
||
- **2026-08-09 17:07 KST read-only snapshot:** URL `http://100.116.83.60:8088` · project
|
||
`vignette-preview-20260807` · remote root `/volume1/docker/vignette-preview-20260807` · active release SHA
|
||
`6030a677af7e87cbfabc422b553d108d53414fd3c446548734a13b036d35c611`.
|
||
- API `sha256:52e0e816…8b2d` / Web `sha256:6fdbb646…f215` exact image가 실행 중이고 previous
|
||
`79ec731f…0450` / `c530213f…62f28`도 보존돼 있다. Compose `api/web/db/proxy` 4개는 모두 running,
|
||
restart policy는 `unless-stopped`다.
|
||
- DB volume `vignette-preview-20260807_pgdata` · pre-proof dump `vignette-preview-g8-preproof-20260807T110610Z.dump`
|
||
SHA256 `92ed4736…1570f`, 960,970 bytes · pre-clean-head dump
|
||
`vignette-preview-pre-clean-head-20260809T051611Z.dump` SHA256 `726982…5d41c`, 1,015,222 bytes.
|
||
- health 3/3(`db=true`, `engine=true`), auth 401, OpenAPI 126 + G1~G8 route, root/JS/CSS 200이다.
|
||
- helper container 0, NAS listener 8018/18149 0, Windows tunnel listener 18018 0이다.
|
||
- **P1 권한 관측:** remote release root와 Compose 파일이 mode `0777`이다. 다음 execute 전에 owner/mode를
|
||
최소권한으로 좁히고 release agent preflight가 이를 fail-closed로 검증해야 한다. 현재 상태를 안전한 배포 신뢰
|
||
경계로 간주하지 않는다.
|
||
- localhost candidate browser E2E 108/108(634.578s), postdeploy SSE→DB review PASS
|
||
- **G8 rollback/restore 이후 재검증:** 실제 브라우저 SSE→DB review 재실행 PASS(35.0s)
|
||
|
||
배포 중 첫 108 gate가 103/5로 fail-closed된 이력(G5 fixture drift, StrictMode retry fixture, `shell.css` manifest 누락)은
|
||
지우지 않는다.
|
||
|
||
### ⚠ 배포된 SHA는 비-secure origin 결함을 갖고 있다 (수정은 소스에만 있음)
|
||
|
||
배포 후 NAS 프리뷰(`http://100.116.83.60:8088`, 평문 HTTP·비-localhost)에 전체 회기 스펙을 돌려보니 24건이
|
||
실패했고, 원인은 단 하나였다. 페이지 스냅샷의 실제 예외는 `crypto.randomUUID is not a function`이다.
|
||
이 API는 **secure context(HTTPS 또는 localhost)에서만** 노출되는데 제품 코드 18곳이 fallback 없이 직접
|
||
호출했고, `RuptureRepairCard.tsx`는 렌더 시점(`useRef`)에 호출해 회기 리뷰 라우트 전체가 error boundary로
|
||
떨어졌다. 릴리스 게이트의 108/108은 **localhost 후보 스택**(secure context)에서 돌았기 때문에 이 경로를
|
||
한 번도 밟지 않았다.
|
||
|
||
- 수정: `apps/web/src/lib/uuid.ts`의 `randomUuid()`로 통일. fallback도 `crypto.getRandomValues`를 우선
|
||
사용해 idempotency key의 예측 불가능성을 유지하고, Web Crypto가 아예 없을 때만 `Math.random`으로 내려간다.
|
||
- 회귀: `apps/web/e2e/insecure-context-uuid.spec.ts` **6/6** (직접 호출 0건 검사 포함), typecheck·build 통과.
|
||
- **아직 NAS에 배포하지 않았다.** 배포된 SHA `6030a677…c611`은 여전히 결함 빌드다. 다음 배포 때
|
||
release agent로 승격하고, 승격 뒤에는 NAS origin에서 회기 스펙을 다시 돌려 24건이 사라지는지 확인한다.
|
||
- 교훈: candidate gate를 localhost에서만 돌리면 secure-context 전용 API 결함을 못 잡는다. 배포 대상과
|
||
같은 scheme/host 형태에서 최소 한 번은 회기 리뷰 라우트를 열어봐야 한다.
|
||
|
||
## 6. 다음 실행 순서 — G8 runtime 재검증 후 G7 외부 종료
|
||
|
||
### 6.1 G8: clean-head 릴리스 경로부터 완성
|
||
|
||
`scripts/run-outcome-os-release-agent.py`의 명시적 clean-head 경로는 구현됐다. 기존 patch mode를 기본값으로
|
||
보존하며, tracked-clean source worktree의 exact HEAD/tree/archive만 candidate로 사용한다. 수동 복사나 NAS 직접
|
||
빌드는 계속 금지한다.
|
||
|
||
구현된 **`--source-mode clean-head`** 계약은 다음과 같다.
|
||
|
||
1. tracked dirty가 있으면 fail-closed. untracked 로그·테스트 결과는 archive에서 제외한다.
|
||
2. exact Git HEAD, tree, `git archive` SHA를 증거에 기록한다.
|
||
3. 같은 HEAD의 archive를 두 번 만들어 SHA가 동일한지 검증한다.
|
||
4. candidate는 그 archive만 풀어 만들고 patch/manifest를 암묵 적용하지 않는다.
|
||
5. 기존 preflight·API full·Web type/build·release E2E·DB snapshot·rollback 절차는 그대로 유지한다.
|
||
6. Vite source 전용 `e2e/insecure-context-uuid.spec.ts`는 별도 localhost gate(5198)로 12/12 실행한다. UUID fallback
|
||
6건과 SHA-256 fallback·직접 호출 금지 6건을 함께 고정하고,
|
||
production candidate/NAS-origin은 실제 배포 번들에서 동작하는 11개 회기 스펙만 실행한다.
|
||
7. unit/fake runner가 tracked dirty, archive SHA drift, evidence binding, patch-mode 무회귀를 검증해야 한다.
|
||
|
||
검증: release-agent unit 30/30, source-only insecure-context gate 12/12, Ruff, py_compile, `git diff --check` PASS. 공유 worktree에는
|
||
인계 문서와 controller 변경이 있으므로 이 경로 자체가 의도대로 tracked-dirty를 차단한다. 실제 배포는 새 commit을
|
||
가리키는 별도 detached clean worktree를 만들고 `--source-repo-root <clean-worktree>`로 지정한 뒤
|
||
dry-run→execute 순서로 수행한다.
|
||
|
||
#### clean commit preflight — include 52 / exclude 6
|
||
|
||
2026-08-09 최종 경로 목록 SHA-256은 ordinal 정렬된 UTF-8 LF 경로를 줄바꿈으로 연결하고 마지막 개행 없이 계산해
|
||
include `4d94694ccf9def6945272c61f673cac0eaa9fb59f4f9b6c165d661d819f891a1`, exclude
|
||
`1ff8c26732772df6b5857bf84de01a53d7da9c3b7d52df08938e77f3a527f022`다. 아래 include 52개만
|
||
사용자 승인 후 **명시적으로** stage한다. 그 직전 `git status --porcelain=v1 -uall`로 목록·해시를 다시 계산하고,
|
||
새 경로가 하나라도 있으면 멈춘다. `git add -A`는 금지한다.
|
||
|
||
```text
|
||
apps/api/app/config.py
|
||
apps/api/app/routes/voice.py
|
||
apps/api/app/services/session_metrics.py
|
||
apps/api/app/services/voice.py
|
||
apps/api/app/session_read_model.py
|
||
apps/api/app/test_session_read_model.py
|
||
apps/api/app/test_voice_service.py
|
||
apps/api/app/test_voice_ws.py
|
||
apps/api/requirements.txt
|
||
apps/web/e2e/admin.spec.ts
|
||
apps/web/e2e/dev-dashboard.spec.ts
|
||
apps/web/e2e/self-directed-learning-loop.spec.ts
|
||
apps/web/e2e/session-mvp.spec.ts
|
||
apps/web/e2e/session-persistence.spec.ts
|
||
apps/web/e2e/session-review-fixture.ts
|
||
apps/web/e2e/session-review.spec.ts
|
||
apps/web/index.html
|
||
docs/HANDOFF.md
|
||
docs/TODO.md
|
||
docs/decisions/local-voice-stack.md
|
||
docs/dev_dashboard.html
|
||
docs/guides/local-development.md
|
||
docs/guides/testing.md
|
||
docs/ops/backlog-2026-06-26.md
|
||
docs/ops/evidence/g8-clean-head-material-milestone-2026-08-09.json
|
||
docs/ops/nas-preview-g8-rollback-proof-runbook.md
|
||
docs/ops/outcome-os-g7-external-proof-readiness-2026-08-07.md
|
||
docs/ops/public-runtime-watchdog.md
|
||
scripts/boot-public-runtime.ps1
|
||
scripts/capture-g7-topology-evidence.py
|
||
scripts/check-dev-dashboard-ssot.py
|
||
scripts/check-g7-external-proof.py
|
||
scripts/install-public-runtime-task.ps1
|
||
scripts/local-whisper-stt-server.py
|
||
scripts/melotts-server.py
|
||
scripts/probe-public-voice-sidecars.py
|
||
scripts/register-boot-task.ps1
|
||
scripts/run-g7-external-proof-window.py
|
||
scripts/run-outcome-os-release-agent.py
|
||
scripts/start-local-whisper-stt.ps1
|
||
scripts/start-public-runtime.ps1
|
||
scripts/test_g7_external_proof.py
|
||
scripts/test_g7_topology_evidence.py
|
||
scripts/test_local_whisper_stt_server.py
|
||
scripts/test_melotts_server.py
|
||
scripts/test_outcome_os_release_agent.py
|
||
scripts/test_public_runtime_watchdog_provenance.py
|
||
scripts/test_public_voice_sidecar_probe.py
|
||
scripts/test_run_g7_external_proof_window.py
|
||
scripts/test_start_public_runtime_contract.py
|
||
scripts/watch-public-runtime-hidden.vbs
|
||
scripts/watch-public-runtime.ps1
|
||
```
|
||
|
||
아래 6개와 ignored `infra/.env.nas-preview`는 절대 stage하지 않는다. NAS 실행 시 env는
|
||
`--nas-env-file D:\workspace\vignette\infra\.env.nas-preview`로 외부 주입한다.
|
||
|
||
```text
|
||
apps/api/engine.public.err.log.2026-08-07T2200.bak
|
||
apps/api/engine.public.err.log.20260809-160042.bak
|
||
apps/api/engine.public.out.log.2026-08-07T2200.bak
|
||
apps/api/engine.public.out.log.20260809-160042.bak
|
||
apps/web/test-results/g7-voice-consent-desktop.png
|
||
apps/web/test-results/g7-voice-consent-mobile.png
|
||
```
|
||
|
||
#### 2026-08-09 clean-head 실행 결과 — NAS mutation 0
|
||
|
||
- dry-run `D:\workspace\vignette-recovery\g8-clean-head-dry-run-20260809.json`: `ok=true`,
|
||
active `6030a677…c611`, desired archive `6b1b15bd…a3f7`, `deployment_mutated=false`.
|
||
- execute 1 `...\g8-clean-head-execute-20260809.json`: Windows Git archive의 CRLF 때문에 생성 API 타입 byte check가
|
||
실패했다. archive SHA 검증 후 candidate의 생성 타입/셸 스크립트만 LF로 정규화하도록 controller를 수정했다.
|
||
NAS mutation 0, rollback 불필요.
|
||
- execute 2 `...\g8-clean-head-execute-20260809-r2.json`: API/types/typecheck/build/candidate stack은 통과했고
|
||
Playwright는 **109 passed / 5 failed**였다. source-only UUID 스펙 4건은 production build에서
|
||
`/src/lib/uuid.ts`를 import할 수 없는 gate 배치 오류라 별도 Vite gate로 분리했다. 남은 1건은 아래 제품 버그다.
|
||
NAS mutation 0, rollback 불필요. 동일 전체 execute를 아직 다시 시도하지 않았다.
|
||
|
||
#### 2026-08-09 clean-head `5221f79e` 실행 결과 — NAS mutation 0
|
||
|
||
- exact include 52개를 Yun Chan으로 커밋했다. HEAD `5221f79e…1c69`, tree `e4f15001…308b`, 2회 동일 archive
|
||
`1109bf86…0f4b`(482,662,400 bytes)를 detached clean worktree에서 고정했다.
|
||
- 새 milestone과 dry-run은 active `6030a677…c611` → desired `1109bf86…0f4b`,
|
||
`deployment_mutated=false`로 통과했다. 실행 직전 NAS custom dump
|
||
`6f4b95a7…b529f`(1,015,222 bytes, TOC 1,752 / TABLE DATA 129, 전용 pgdata volume)를 생성했다.
|
||
- execute는 API 921, API types, typecheck, build, insecure-context 6/6, candidate stack과 DB-backed
|
||
single-run 후반부까지 통과했지만 **110 passed / 2 failed**에서 승격 전에 중단됐다. 실패는 제품 화면이 아니라
|
||
`React 부트가 실제로 비어 있으면 진단을 유지한다`의 desktop/mobile 두 복제였다.
|
||
- 원인은 테스트가 Vite 개발 entry `/src/main.tsx`만 차단해 production candidate의 hashed entry
|
||
`/assets/index-*.js`를 차단하지 못한 환경 계약 누락이다. 두 entry를 함께 차단하도록 수정했고 Vite source 2/2와
|
||
production preview 2/2를 각각 통과했다. 전체 execute를 즉시 반복하지 않고 이 수정의 새 clean commit·archive를
|
||
다시 결속한 뒤 한 번만 재실행한다.
|
||
- 후속 clean commit `21461ab3…6fde`, tree `5c5f12a8…524e`, 2회 동일 archive `20d49694…fa1a` execute는
|
||
candidate **112/112**를 통과하고 NAS 새 API/Web image `d5021950…`/`376a3aa8…`까지 올렸다. 그러나 실제 NAS-origin
|
||
postdeploy의 G4 deliberate-practice desktop/mobile 2건이 110/112에서 실패해 active-state commit 전에 이전
|
||
`52e0e816…`/`6fdbb646…`로 자동 rollback했고 health·auth 401·OpenAPI 126·G0~G8 route와 image ID를 재검증해
|
||
rollback `verified`로 종료했다.
|
||
- focused exact-image 평문 origin에서 요청이 API까지 가지 않고 일반 오류로 끝나는 것을 재현했다. 원인은
|
||
문장 원문을 보내지 않기 위한 fingerprint가 `crypto.subtle.digest`에만 의존해 insecure origin에서 예외가 난 것이다.
|
||
원문 외부 전송 없이 portable SHA-256 fallback을 추가하고 표준 digest
|
||
`c06db0f0…62aa`를 payload에서 exact 검증했다. localhost secure origin 2/2와 실제 Tailnet insecure origin 2/2가
|
||
같은 digest로 통과했다. 이 수정의 새 clean commit만 다시 결속해 전체 execute를 재개한다.
|
||
|
||
#### P0 수정 완료 — 회기 timestamp UTC 전송과 KST 표시 날짜
|
||
|
||
- 최초 실패 스펙: `session-persistence.spec.ts`의 `persists AI tutor coaching history through reload @single-run`.
|
||
- 코칭 생성·DB 저장·reload 후 `C` 마커까지는 성공했지만, 수정 전 화면이 `9:03:59 / 543:59` 경과로 계산해
|
||
`회기 시간이 종료됐어요` 모달을 띄웠고 그 모달이 코칭 마커 클릭을 가로막았다.
|
||
- 원인: `apps/api/app/session_read_model.py::iso()`가
|
||
`datetime.fromtimestamp(ts).isoformat(timespec="seconds")`로 timezone 없는 문자열을 반환한다.
|
||
`apps/web/src/pages/Session.tsx::elapsedFromSession()`은 `Date.parse(detail.started_at)`로 이를 브라우저 로컬 KST로
|
||
해석한다. Linux API UTC와 KST 브라우저 사이에서 약 9시간 오차가 생긴다.
|
||
- 수정: `session_read_model.iso()`와 dashboard용 `session_metrics.iso_datetime()`은 UTC `+00:00`을 반환한다.
|
||
회기 리뷰 달력 날짜는 서버 timezone이 아니라 고정 KST(+09:00)를 사용한다.
|
||
- backend 관련 65 passed, 후속 voice 계약을 포함한 API 전체 **921 passed**, Ruff·compile·web typecheck가 통과했다.
|
||
- 고유 Compose project/volume의 실제 DB/API/engine/browser에서 KST context로 focused 1/1 PASS했다. UTC suffix,
|
||
`status=active`, `ended_at=null`, 회기 나이 10분 미만, 화면 `00:00-09:59`, timebar/dialog 0,
|
||
coach mark 실제 클릭과 DB history/source-pack dialog를 모두 검증했다. 종료 뒤 container/volume/listener는 0이다.
|
||
- **테스트 강제 클릭이나 모달 닫기 우회는 사용하지 않았다.** 첫 clean source `5221f79e…1c69`에는 제품 수정과
|
||
controller 변경이 포함됐고, production entry 차단 회귀 2건의 테스트 수정만 후속 clean commit으로 다시 고정한다.
|
||
|
||
### 6.2 G8: NAS preview 배포와 실제 origin E2E
|
||
|
||
clean-head release mode가 GREEN이면 전용 NAS preview만 대상으로 백업→dry-run→배포→검증한다. 공개 DB·공개 API는
|
||
이 단계에서 건드리지 않는다. 성공 조건은 다음 전부다.
|
||
|
||
직전 새 backup은
|
||
`/volume1/docker/vignette-preview-20260807/backups/vignette-preview-pre-clean-head-20260809T051611Z.dump`,
|
||
SHA-256 `72698232d132a8f848d81ff9d8602430e7e623c7646cc236cb39f978aab5d41c`,
|
||
1,015,222 bytes, TOC 1,752다. 하지만 release agent 자체에는 `pg_dump`/`pg_restore` 실행 계약이 없다.
|
||
따라서 **매 execute 직전** 별도 승인된 백업 단계에서 fresh custom dump를 만들고 SHA-256·byte size·TOC parse/count와
|
||
current DB identity를 증거에 결속한 뒤에만 승격한다. 기존 dump 존재만으로 이 단계를 생략하지 않는다.
|
||
|
||
release agent의 자동 rollback 범위는 exact API/Web image, Compose 적용 상태, active-state 파일뿐이다. 이미 적용된
|
||
DB migration이나 데이터 변경은 자동 복구하지 않는다. migration/data restore는 별도 owner 승인·대상 DB identity·dump
|
||
hash를 갖춘 복원 절차로만 수행한다. 이미지 rollback 성공을 DB rollback 성공으로 기록하면 안 된다.
|
||
|
||
- 새로운 active SHA와 exact API/Web image ID, predeploy dump SHA, previous image 보존.
|
||
- health 3/3, db/engine true, auth 401, OpenAPI와 G0~G8 route 존재.
|
||
- `http://100.116.83.60:8088` **실제 평문 NAS origin**에서 §8의 회기 E2E 묶음 0 failure.
|
||
- `crypto.randomUUID` 예외 0, error boundary 0, SSE→DB review PASS.
|
||
- 학생 홈→회기→리뷰→G4/G5 반환 연습의 desktop/mobile·DB read reload·멱등·UUID 비노출 유지.
|
||
- 실패 시 자동 rollback과 previous image 복구를 검증하고, 성공 전에는 G8 카드 전체를 DONE으로 바꾸지 않는다.
|
||
|
||
### 6.3 G7: 외부 종료 Gate
|
||
|
||
G7 내부 소스 계약은 완료됐고 `scripts/check-g7-external-proof.py`는 현재 의도적으로 exit 1이다.
|
||
네 artifact를 **같은 public host, 겹치는 50분 시간창**으로 수집해야 한다.
|
||
|
||
먼저 닫아야 할 운영·코드 선행조건이 있다.
|
||
|
||
- 공개 API는 여전히 119 paths 구버전이며 `/admin/voice-runtime`이 없고(404), `/voice/health`는
|
||
`openai/gpt-4o-transcribe` + `openai/gpt-4o-mini-tts`다.
|
||
- local Whisper 9882와 MeloTTS 9883 listener는 아직 공개 호스트에 떠 있지 않다. 다만 current source의
|
||
`start-public-runtime.ps1`는 두 sidecar를 API보다 먼저 시작하고 `local_whisper/small/cpu-int8`,
|
||
`melotts/melotts-korean` exact provider/model·WS/HTTP readiness를 fail-closed로 확인하며, 기존 비정상 리스너를
|
||
임의 종료·재사용하지 않는다. Uvicorn도 `--ws websockets --ws-max-queue 4`로 고정했다. source 검증은
|
||
launcher/sidecar 80/80, API voice 71/71, PowerShell parse·Ruff·compile·diff-check PASS지만 공개 실행은 0회다.
|
||
- **외부 실행 전 코드 P0 3건은 완료:** (1) production 프로세스 exit 0은 canonical checker
|
||
`returncode == 0 && gate_closed == true`에 결속한다. (2) 실제 브라우저 Origin
|
||
`https://vignette.chanpaca.net`은 exact HTTPS allowlist로, API/WSS/admin/topology
|
||
`api-vignette.chanpaca.net`은 별도 transport host와 `wss`/`https` scheme으로 검증한다. (3) 실제 capture 기본·최소를
|
||
3,120초로 올리고 sampler를 ceil+terminal sample로 계산하며, checker가 voice/runtime/topology 공통 교집합
|
||
**3,000초 이상**을 강제한다. focused runner 37/37과 통합 G7 166/166을 통과했다.
|
||
- 현재 공개 topology는 Windows host Uvicorn + cloudflared다. 명시적 `windows_host` topology mode는 구현 완료됐다.
|
||
API/cloudflared의 PID·start time·executable name/SHA256·command-line SHA256·Git SHA·cwd를 pin하고, 각 sample 전후
|
||
identity와 Git HEAD drift를 검사한다. RSS/peak RSS/CPU/handles/threads, process TCP/established/listener,
|
||
API listen-port owner/conflict와 host TCP high-water도 수집한다. 기존 `linux_compose`의 api+caddy·fresh-container
|
||
규칙은 그대로 유지한다.
|
||
- Windows topology focused 검증은 G7 runner/checker/topology 86/86, Ruff, `py_compile`, `git diff --check`를 통과했고 실제
|
||
read-only adapter가 API 8001 owner=1/conflict=0, cloudflared established TCP>0를 읽었다. raw command line이나
|
||
endpoint는 저장하지 않았다.
|
||
- **남은 provenance blocker는 배포/실증:** 현재 API cwd는
|
||
`D:\workspace\vignette-outcome-g7-release-20260807\apps\api`이고 그 release 디렉터리에는 `.git`이 없다. 소스의
|
||
`-RequireFreshPublicProvenance` 모드는 detached-clean commit/tree와 Python/cloudflared/config SHA를 mutation 전에
|
||
검증하고, legacy API와 exact-config cloudflared를 bounded stop한 뒤 pinned release cwd에서 새 PID로 교체한다.
|
||
PID/start/exe SHA/command SHA/실제 cwd를 재검증한 safe receipt에는 raw command line·config 내용을 남기지 않는다.
|
||
topology artifact도 같은 commit/tree, runner/collector/checker SHA와 `psutil==6.1.1`을 매 sample 전후 결속한다.
|
||
이 코드는 atomic receipt contract 18/18을 포함한 G7 통합 166/166으로 GREEN이지만 **현재 public에는 아직 실행하지 않았다.**
|
||
clean commit 전 legacy cwd/Git pin을 약화하거나 기존 PID를 증거로 재사용하지 않는다.
|
||
- watchdog·로그온 boot의 detached-clean source pin 코드는 완료됐지만 실제 task action은 아직 legacy shared
|
||
source다. clean commit 뒤 `docs/ops/public-runtime-watchdog.md` 순서대로 같은 stable release root에 두 task를
|
||
재등록하고 commit/tree/script hash marker와 `LastTaskResult=0`을 확인한다. 그 전에는 current
|
||
`LastTaskResult=1`을 실패가 아니라 안전 차단으로 유지하고, 단일 mutation owner가 public 배포 시간창을 소유한다.
|
||
- 운영 Python에 `psutil`이 없으면 collector는 `command_unavailable:psutil`로 fail-closed한다. current
|
||
`apps/api/requirements.txt`와 명시적 Python 3.11 모두 `psutil==6.1.1`로 고정됐다.
|
||
- 현재 운영 STT 모델은 이 호스트에서 실측된 CPU int8 `small`로 고정한다. cuDNN 9가 설치되고 별도 성능·정확도
|
||
gate를 통과하기 전까지 launcher·API runtime metadata·50분 runner/checker expected model을 모두 `small`로 유지한다.
|
||
|
||
```powershell
|
||
& $py -X utf8 -B scripts/check-g7-external-proof.py `
|
||
--voice-soak <soak.json> --runtime <runtime.json> `
|
||
--topology <topology.json> --human-voice-gain <pack.json>
|
||
```
|
||
|
||
1. `scripts/soak-public-voice-websocket.py` v4 — 운영 기본값 `local_whisper`/`melotts`의 ready metadata가
|
||
실제 provider/model과 정확히 일치하는 authenticated public WSS,
|
||
**명시 동의 물리 마이크** 50분 양방향 `passed`. `--confirm-physical-capture` 없이는 장치 열거·캡처를 하지 않는다.
|
||
2. `scripts/capture-g7-runtime-evidence.py` — 같은 시간창의 관리자 endpoint worker/Uvicorn queue high-water.
|
||
3. `scripts/capture-g7-topology-evidence.py` — 같은 host·exact image의 50분 cgroup/proc/Docker/TCP high-water.
|
||
4. 독립 blind human voice-gain pack — held-out 30명 외 calibration split 참가자 포함 총 최소 31명 /
|
||
held-out 50회기 / 150 paired axis / blind evaluator 2인 /
|
||
ICC(A,1) ≥ 0.75 · κ ≥ 0.70 · gain ≥ 0.01 · participant-cluster bootstrap 10,000회 95% CI lower > 0.
|
||
|
||
준비 상세는 `docs/ops/outcome-os-g7-external-proof-readiness-2026-08-07.md`.
|
||
|
||
G7 Windows topology 회귀 명령:
|
||
|
||
```powershell
|
||
Set-Location D:\workspace\vignette
|
||
$env:PYTHONPATH='D:\workspace\vignette\apps\api'
|
||
& $py -X utf8 -m unittest scripts/test_g7_external_proof.py scripts/test_g7_topology_evidence.py `
|
||
scripts/test_g7_runtime_evidence.py scripts/test_g7_external_voice_soak.py `
|
||
scripts/test_run_g7_external_proof_window.py apps/api/app/test_g7_voice_gain_evidence.py
|
||
& $ruff check scripts/capture-g7-topology-evidence.py scripts/check-g7-external-proof.py `
|
||
scripts/run-g7-external-proof-window.py scripts/test_g7_topology_evidence.py `
|
||
scripts/test_g7_external_proof.py scripts/test_run_g7_external_proof_window.py
|
||
```
|
||
|
||
**사용자에게 받아야 하는 것 (코드로 대체 불가):**
|
||
|
||
- authenticated learner/admin session 또는 안전한 `storageState` 생성 협조
|
||
- 물리 마이크 50분 실행에 대한 **명시적 동의**
|
||
- 실제 참가자·독립 평가자 운영 승인
|
||
|
||
합성 label이나 무동의 mic probe로 대체하지 않는다. `check-g7-external-proof.py` exit 0 전에는 G7 메인 상태를
|
||
DONE으로 바꾸지 않는다.
|
||
|
||
## 7. 학생 자기주도 학습 폐루프
|
||
|
||
```text
|
||
학습자 홈 추천 → 새 회기/사전 설정 → 실제 Session → 종료 리뷰 → G4 처방 또는 G5 전이 의도
|
||
→ 별도 재연습 회기 → completed-session observer → 실제 G4 attempt / G5 transfer execution
|
||
→ read-model reload → before/after·진행도·멱등 재확인
|
||
```
|
||
|
||
실제 DB-backed browser 증거: `apps/web/e2e/returned-practice-db-closed-loop.spec.ts` +
|
||
`apps/web/e2e/harness/prepare-returned-practice-db.py`, desktop 2/2 + mobile 2/2, route mock 0,
|
||
visible raw UUID 0, getUserMedia/enumerateDevices 0, horizontal overflow 0.
|
||
|
||
이 harness는 **disposable clone**(전용 DB container + 전용 API 포트 + 전용 vite 포트)에서만 실행한다.
|
||
공개 런타임 포트를 재사용하지 않는다. `--api-base-url`, `--database-url`, `--practice-internal-token`,
|
||
`--transfer-internal-token`이 필요하고 API health의 `db`·`engine`이 모두 true여야 한다.
|
||
|
||
2026-08-07 재실행 절차(그대로 재현 가능):
|
||
|
||
1. `docker run -d --name vignette-g8-e2e-db-<날짜> -p 127.0.0.1:55439:5432` + `infra/db/init` 마운트,
|
||
`POSTGRES_USER=vignette_owner` / `APP_DB_USER=vignette_app`. `app.ci_regression_dag_node`가 생기면 준비 완료.
|
||
2. 별도 엔진 게이트웨이를 **새 포트**(예: 9199)에 띄운다. 상주 게이트웨이의 claude 세션이 죽어 있으면
|
||
`engine=false`가 되므로 공개용 9099를 재사용하지 않는다.
|
||
3. uvicorn API를 8021에, vite를 5199에 띄우고 `ENGINE_URL`을 2번 게이트웨이로 지정한다.
|
||
4. harness 실행 → `E2E_RETURNED_PRACTICE_DB_CLOSED_LOOP=1` + `E2E_RETURNED_PRACTICE_FIXTURE`로 spec 실행.
|
||
5. 결과 기준: desktop 2/2 + mobile 2/2 = **4 passed**. 끝나면 컨테이너·프로세스를 모두 정리한다.
|
||
|
||
G4/G5 핵심 production bug 수정은 보존한다.
|
||
|
||
- G4 runtime SQL `digest(...)` → `app.digest(...)`
|
||
- G5 JSONB bind는 `json.dumps` string이 아니라 dict/list object 전달
|
||
- route는 nested Pydantic suite를 `body.model_dump()` dict로 깨지 않고 typed object로 전달
|
||
|
||
## 8. 최종 검증 명령과 현재 기준선
|
||
|
||
```powershell
|
||
$py = 'C:\Users\encep\AppData\Local\Programs\Python\Python311\python.exe'
|
||
$ruff = 'C:\Users\encep\AppData\Local\hermes\hermes-agent\venv\Scripts\ruff.exe'
|
||
```
|
||
|
||
### Backend full — 기준 API **921 passed**, gateway **58 passed**
|
||
|
||
```powershell
|
||
Set-Location D:\workspace\vignette\apps\api
|
||
& $py -X utf8 -B -m pytest -p no:cacheprovider app -q
|
||
& $py -X utf8 -B -m pytest -p no:cacheprovider engine_gateway -q
|
||
```
|
||
|
||
### G8 executor/probe/helper/release governance — 기준 28 / 11 / 37 / 30
|
||
|
||
```powershell
|
||
Set-Location D:\workspace\vignette
|
||
& $py -X utf8 -B -m unittest scripts/test_serve_nas_preview_rollback_executor.py
|
||
& $py -X utf8 -B -m unittest scripts/test_probe_nas_preview_g8_rollback.py
|
||
& $py -X utf8 -B -m unittest scripts/test_launch_nas_preview_g8_helpers.py
|
||
& $py -X utf8 -B -m pytest -p no:cacheprovider scripts/test_outcome_os_release_agent.py -q
|
||
& $ruff check scripts/serve-nas-preview-rollback-executor.py scripts/test_serve_nas_preview_rollback_executor.py scripts/probe-nas-preview-g8-rollback.py scripts/test_probe_nas_preview_g8_rollback.py scripts/launch-nas-preview-g8-helpers.py scripts/test_launch_nas_preview_g8_helpers.py scripts/run-outcome-os-release-agent.py scripts/test_outcome_os_release_agent.py
|
||
```
|
||
|
||
### Web contract/type/build
|
||
|
||
```powershell
|
||
Set-Location D:\workspace\vignette\apps\web
|
||
npm run check:api-types
|
||
npm run typecheck
|
||
npm run build
|
||
```
|
||
|
||
### 현재 HEAD 학생 UX·접근성 focused — 기준 **20/20**
|
||
|
||
2026-08-09 읽기 전용 재감사에서 `check:api-types`·typecheck·build와 아래 fixture-only 묶음이 desktop/mobile
|
||
20/20을 통과했다: Alliance Pulse 8, 자기주도 폐루프 2, insecure-origin UUID 6, pre/mid 자기점검 2,
|
||
시간만료 경고·종료 모달 2. 390×844·320×568 overflow 0, tab 키보드, Enter CTA, 44px 조작부,
|
||
4.5:1 대비, raw UUID/theory key 비노출, 동의 전 `getUserMedia`/`enumerateDevices` 0도 확인했다.
|
||
|
||
추가 회귀에서 새 음성 동의 modal을 실제로 수락한 뒤 위기 음성 종료까지 가는 desktop/mobile, 모바일 review
|
||
filter 44px 계약을 desktop/mobile로 검증해 4/4 통과했다. dashboard의 file-origin은 spec-relative repo root로
|
||
고정했고, session-review fixture 소유권과 viewport assertion, admin live snapshot race를 교정했다. 16:49 단일 실행은
|
||
**120/120 PASS(92.0s)**였지만, 그 뒤 dashboard 변경분은 dashboard 10/10으로만 따로 통과했다. 따라서 현 트리는
|
||
110 불변 + 10 최신 분할 GREEN이고, 최종 clean source에서 아래 exact 묶음을 다시 단일 실행해 HEAD/tree/archive
|
||
결속 뒤 120/120을 요구한다.
|
||
|
||
#### current-source fixture exact 120 — clean HEAD 재현 명령
|
||
|
||
정확한 수집 구성은 `dev-dashboard` 10 + `session-mvp` 24 + `session-review` 24 + `learner` 14 +
|
||
`session-layout` 8 + `admin` 40 = desktop/mobile 120이다. `127.0.0.1:8000`은 반드시 복구 dump에서 만든
|
||
**disposable API/DB/engine 전용 스택**이어야 한다. 이 묶음은 dev-login·세션 쓰기를 포함할 수 있으므로 public
|
||
8001, 복구 원본 55432, candidate DB, NAS DB에 연결하지 않는다.
|
||
|
||
```powershell
|
||
$ErrorActionPreference = 'Stop'
|
||
[Console]::OutputEncoding = [Text.UTF8Encoding]::new($false)
|
||
$OutputEncoding = [Text.UTF8Encoding]::new($false)
|
||
$cleanRoot = 'D:\workspace\vignette-clean-<NEW_SHA>'
|
||
$cleanWeb = Join-Path $cleanRoot 'apps\web'
|
||
$tempRoot = Join-Path $env:TEMP 'vignette-clean-fixture-120'
|
||
$tempWeb = Join-Path $tempRoot 'apps\web'
|
||
$tempEvidence = Join-Path $tempRoot 'docs\ops\evidence'
|
||
if (Test-Path -LiteralPath $tempRoot) { throw "기존 TEMP와 충돌: $tempRoot" }
|
||
$pw = Join-Path $cleanWeb 'node_modules\.bin\playwright.cmd'
|
||
if (-not (Test-Path -LiteralPath $pw -PathType Leaf)) {
|
||
throw 'clean worktree에서 npm ci --ignore-scripts를 먼저 실행해 node_modules를 준비할 것'
|
||
}
|
||
$health = Invoke-RestMethod -Uri 'http://127.0.0.1:8000/health' -TimeoutSec 5
|
||
if ($health.status -ne 'ok' -or -not $health.db -or -not $health.engine) {
|
||
throw 'disposable API8000의 db/engine ready가 아님'
|
||
}
|
||
New-Item -ItemType Directory -Path $tempWeb -Force | Out-Null
|
||
New-Item -ItemType Directory -Path $tempEvidence -Force | Out-Null
|
||
$config = Join-Path $cleanWeb 'playwright.config.ts'
|
||
$files = @(
|
||
'e2e/dev-dashboard.spec.ts',
|
||
'e2e/session-mvp.spec.ts',
|
||
'e2e/session-review.spec.ts',
|
||
'e2e/learner.spec.ts',
|
||
'e2e/session-layout.spec.ts',
|
||
'e2e/admin.spec.ts'
|
||
)
|
||
$env:PLAYWRIGHT_PORT = '15374'
|
||
$env:VITE_API_PROXY_TARGET = 'http://127.0.0.1:8000'
|
||
Remove-Item Env:PLAYWRIGHT_BASE_URL -ErrorAction SilentlyContinue
|
||
Remove-Item Env:PLAYWRIGHT_SKIP_WEB_SERVER -ErrorAction SilentlyContinue
|
||
$code = 99
|
||
try {
|
||
Push-Location $tempWeb
|
||
try {
|
||
& $pw test @files ("--config={0}" -f $config) `
|
||
--project=chromium-desktop --project=chromium-mobile `
|
||
--workers=4 --reporter=line ("--output={0}" -f (Join-Path $tempRoot 'pw-results'))
|
||
$code = $LASTEXITCODE
|
||
} finally {
|
||
Pop-Location
|
||
}
|
||
} finally {
|
||
$listeners = @(Get-NetTCPConnection -State Listen -LocalPort 15374 -ErrorAction SilentlyContinue)
|
||
$listenerError = $null
|
||
if ($listeners.Count -ne 0) { $listenerError = "Playwright Vite listener 잔존: $($listeners.OwningProcess -join ',')" }
|
||
if (Test-Path -LiteralPath $tempRoot) {
|
||
$resolved = (Resolve-Path -LiteralPath $tempRoot).Path
|
||
if ($resolved -ne $tempRoot) { throw "unexpected TEMP target: $resolved" }
|
||
[IO.Directory]::Delete($tempRoot, $true)
|
||
}
|
||
if ($listenerError) { throw $listenerError }
|
||
}
|
||
if ($code -ne 0) { throw "clean fixture 120 failed: $code" }
|
||
```
|
||
|
||
과거 API-ready 없이 돌린 별도 Vite-only 혼합 실행의 `session-layout` 8건은 DB persona가 없어 제품 assertion 전에
|
||
`seed_fallback/degraded`로 중단됐다. 위 exact 120 명령은 이 사각지대를 닫기 위해 disposable API의 db/engine health를
|
||
선행 강제한다. 최종 완료 때는 `session-layout` 8/8과 returned-practice DB closed-loop 4/4를 모두 다시 통과시킨다.
|
||
|
||
### Dashboard SSOT + E2E — 기준 SSOT PASS(FAIL 0), unit 5/5, dashboard 10/10
|
||
|
||
```powershell
|
||
Set-Location D:\workspace\vignette
|
||
& $py -X utf8 -B scripts/check-dev-dashboard-ssot.py
|
||
& $py -X utf8 -B -m pytest -p no:cacheprovider scripts/test_dev_dashboard_ssot.py -q
|
||
|
||
Set-Location D:\workspace\vignette\apps\web
|
||
.\node_modules\.bin\playwright.cmd test e2e/dev-dashboard.spec.ts --project=chromium-desktop --project=chromium-mobile --workers=1 --reporter=line
|
||
```
|
||
|
||
status count를 바꾸면 `scripts/check-dev-dashboard-ssot.py`의 `EXPECTED_STATUS_COUNTS`와
|
||
`apps/web/e2e/dev-dashboard.spec.ts`의 `metrics.done`/`metrics.doing`을 **함께** 갱신한다.
|
||
|
||
### 격리 NAS 런타임 회기 E2E
|
||
|
||
```powershell
|
||
Set-Location D:\workspace\vignette\apps\web
|
||
$env:PLAYWRIGHT_BASE_URL='http://100.116.83.60:8088'
|
||
$env:PLAYWRIGHT_SKIP_WEB_SERVER='1'
|
||
node.exe .\node_modules\@playwright\test\cli.js test e2e/session-layout.spec.ts e2e/session-persistence.spec.ts `
|
||
e2e/self-directed-learning-loop.spec.ts e2e/alliance-pulse.spec.ts e2e/outcome-trajectory.spec.ts `
|
||
e2e/rupture-repair.spec.ts e2e/deliberate-practice.spec.ts e2e/calibration-transfer.spec.ts `
|
||
e2e/supervision-research.spec.ts e2e/multimodal-alliance.spec.ts e2e/continuous-improvement-admin.spec.ts `
|
||
--project=chromium-desktop --project=chromium-mobile --project=chromium-single-run --workers=1 --reporter=line
|
||
```
|
||
|
||
전체 Playwright inventory는 620 tests / 44 files다. 실행 환경/API/DB를 정확히 맞추지 않고 fixture failure를
|
||
제품 failure로 오인하지 않는다. 같은 blocker가 두 번 반복되면 전체 재시도 대신 원인·증거·수정 계획을 먼저 보고한다.
|
||
|
||
## 9. 핵심 변경 파일
|
||
|
||
- `scripts/launch-nas-preview-g8-helpers.py` · `scripts/test_launch_nas_preview_g8_helpers.py` (신규, Gate6 계약)
|
||
- `scripts/serve-nas-preview-rollback-executor.py` · `scripts/test_serve_nas_preview_rollback_executor.py`
|
||
- `scripts/probe-nas-preview-g8-rollback.py` · `scripts/test_probe_nas_preview_g8_rollback.py`
|
||
- `scripts/run-outcome-os-release-agent.py` · `scripts/test_outcome_os_release_agent.py`
|
||
- `scripts/check-dev-dashboard-ssot.py` · `scripts/test_dev_dashboard_ssot.py`
|
||
- `scripts/check-g7-external-proof.py` · `scripts/soak-public-voice-websocket.py` ·
|
||
`scripts/capture-g7-runtime-evidence.py` · `scripts/capture-g7-topology-evidence.py` ·
|
||
`scripts/run-g7-external-proof-window.py`와 대응 G7 테스트 3개
|
||
- `apps/web/src/lib/uuid.ts` · `apps/web/e2e/insecure-context-uuid.spec.ts` (신규, 비-secure origin 결함 수정)
|
||
- `apps/api/app/session_read_model.py` · `apps/api/app/services/session_metrics.py` ·
|
||
`apps/api/app/test_session_read_model.py` · `apps/web/e2e/session-persistence.spec.ts` (UTC/KST P0와 실DB browser 회귀)
|
||
- `apps/web/index.html` · `apps/web/e2e/self-directed-learning-loop.spec.ts` (SPA route 전환 중 boot diagnostic 회복)
|
||
- `apps/web/e2e/admin.spec.ts` · `apps/web/e2e/dev-dashboard.spec.ts` ·
|
||
`apps/web/e2e/session-review-fixture.ts` · `apps/web/e2e/session-review.spec.ts`
|
||
- `scripts/watch-public-runtime.ps1` · `scripts/boot-public-runtime.ps1` ·
|
||
`scripts/install-public-runtime-task.ps1` · `scripts/register-boot-task.ps1` ·
|
||
`scripts/watch-public-runtime-hidden.vbs` · `scripts/test_public_runtime_watchdog_provenance.py`
|
||
- `apps/web/e2e/returned-practice-db-closed-loop.spec.ts` ·
|
||
`apps/web/e2e/harness/prepare-returned-practice-db.py`
|
||
- `docs/dev_dashboard.html` · `docs/TODO.md` · `docs/README.md` · `docs/ops/backlog-2026-06-26.md`
|
||
- `docs/ops/nas-preview-g8-rollback-proof-runbook.md` ·
|
||
`docs/ops/evidence/nas-preview-g8-actual-rollback-2026-08-07.json` ·
|
||
`docs/ops/nas-preview-deployment-evidence-2026-08-07.md`
|
||
|
||
## 10. 최종 원칙
|
||
|
||
- source-only PASS를 runtime DONE으로 부르지 않는다.
|
||
- health만 보고 배포 완료라고 하지 않는다. auth, OpenAPI, assets, browser SSE→DB review를 함께 본다.
|
||
- synthetic control plane을 실제 NAS rollback으로 과장하지 않는다.
|
||
- G7 물리 마이크·사람 평가를 무동의/합성 데이터로 대체하지 않는다.
|
||
- current/previous images와 DB backup을 확인하기 전 destructive operation을 실행하지 않는다.
|
||
- 성공보다 정직한 fail-closed 증거가 우선이다.
|
||
|
||
## 11. 권장 멀티에이전트 실행 구조
|
||
|
||
사람 개발자 작업계획은 만들지 않는다. 구현·테스트·배포·증거 동기화는 에이전트가 수행하고, 사람에게는 코드로
|
||
대체 불가능한 동의·인증 세션·실제 참가자/평가자 승인만 요청한다. 동시 작업은 최대 세 하위 lane으로 나누되,
|
||
NAS/public/DB mutation owner는 항상 한 에이전트만 둔다.
|
||
|
||
1. **G8 release lane:** clean-head release mode 구현·unit·dry-run·NAS preview 배포·NAS-origin E2E. 이 lane만 NAS mutation.
|
||
2. **G7 proof lane:** current public route/config 감사, local provider readiness, `--rehearse`, 네 artifact 수집 준비.
|
||
실제 마이크는 사용자 명시 동의 전 0회, public mutation은 G8 lane과 시간 겹치지 않게 단일 owner에게 인계.
|
||
3. **E2E/SSOT lane:** local fixture, disposable DB browser, visual/accessibility, dashboard/TODO/backlog 정합을 읽기 전용으로 감사.
|
||
|
||
루트 에이전트는 각 lane의 증거 SHA와 실패 원인을 합쳐 게이트를 판정한다. 같은 blocker가 두 번 반복되면 세 번째
|
||
재시도 전에 원인·증거·수정 계획을 사용자에게 보고한다.
|
||
|
||
## 12. 새 에이전트에 그대로 줄 시작 프롬프트
|
||
|
||
```text
|
||
D:\workspace\vignette의 docs/HANDOFF.md를 인수인계 SSOT로 읽고, AGENTS.md → docs/README.md →
|
||
docs/dev_dashboard.html → docs/TODO.md → docs/ops/backlog-2026-06-26.md 순서로 현재 상태를 재확인해.
|
||
|
||
현재 master는 HEAD 94c681d…7fb이고 origin/master보다 2 commit 앞서며, timestamp·voice provider/model,
|
||
G7 topology·public launcher·G8 clean-head·SSOT가
|
||
아직 tracked dirty다. 기존 D:\workspace\vignette-clean-head-94c681d와 g8-focus-head-94c681d.tar는 이 최신 변경을
|
||
포함하지 않으므로 배포 입력으로 사용하지 마. 먼저 git status/diff와 각 lane의 focused test를 재확인해.
|
||
|
||
최종 목표는 G0~G8 아홉 목표를 코드-only가 아니라 실제 runtime 증거로 모두 닫는 것이다. G0~G6은 현재 내부 DONE
|
||
증거를 보존하되 새 clean HEAD 전체 회귀로 재확인해. G8 rollback receipt와 clean-head controller는 구현됐지만
|
||
2026-08-09 execute 두 번은 모두 NAS mutation 전에 fail-closed됐다. docs/HANDOFF.md §6.1의 naive
|
||
session timestamp 버그는 UTC/KST 계약과 focused disposable DB/API/browser E2E로 GREEN이다. 테스트 강제 클릭 우회는
|
||
없다. 사용자 승인을 받아 이 변경을 새 clean commit으로 만들고 HEAD/tree/archive를 다시 결속해 격리 NAS preview에
|
||
배포한 다음 실제 NAS HTTP origin의 전체 회기 E2E를 0 failure로 통과시켜. G7은 내부 구현만 DONE이다. current source 공개 배포,
|
||
local_whisper/melotts ready, authenticated public WSS, 사용자의 명시 동의를 받은 물리 마이크 50분 soak,
|
||
같은 시간창의 runtime/topology high-water, 독립 human voice-gain pack까지 모아 canonical checker exit 0을 만들어야 한다.
|
||
G7 runner exit의 checker exit 0/gate_closed 결속, browser Origin과 API/WSS host 분리, 3,120초 capture와
|
||
3,000초 공통 overlap, detached-clean HEAD/tree, runner/collector/checker SHA와 exact psutil pin은 소스에서 완료됐다.
|
||
API/cloudflared는 legacy PID를 재사용하지 말고 `-RequireFreshPublicProvenance`로 pinned release cwd에서 강제
|
||
재시작해 새 PID/start/exe/command SHA/cwd safe receipt를 결속해.
|
||
G7 runner/checker/topology는 86/86, public launcher/sidecar 계약은 80/80, 통합은 166/166, API voice 통합은 71/71로 구현됐지만
|
||
현재 public API의 Git 없는 냉동 release cwd는 provenance gate를
|
||
통과하지 못한다. current committed repo 기반 launcher/runtime으로 정렬하거나 동등하게 강한 provenance를 구현하고,
|
||
cwd/Git pin이나 기존 Linux Compose gate를 약화하지 마. 합성 증거로 G7을 DONE 처리하지 마.
|
||
public task source-pin 코드는 구현됐다. watchdog·로그온 boot는 detached clean release와 exact Git commit/tree,
|
||
watchdog|boot/start script SHA가 없으면 mutation 전에 실패한다. 하지만 실제 두 Scheduled Task action은 아직 shared
|
||
worktree와 legacy 인자를 가리키며, 5분 watchdog은 최신 실행에서 pin 인자 부재로 `LastTaskResult=1`이다. public은
|
||
현재 health ok지만 자동복구는 의도적으로 fail-closed다. clean commit 뒤 동일 stable release root로 두 task를
|
||
재등록하고 action pin·watchdog result 0·로그온/reboot smoke를 증명한 뒤에만 runtime DONE으로 바꿔.
|
||
|
||
NAS 17:07 기준선은 active 6030a677…c611, API/Web 52e0…8b2d/6fdb…f215, previous images 보존,
|
||
Compose 4개 running+unless-stopped, volume vignette-preview-20260807_pgdata, health/routes/assets GREEN, helper/listener 0이다.
|
||
release agent는 DB dump/restore를 지원하지 않으므로 execute 직전 fresh custom dump SHA/size/TOC와 DB identity를
|
||
별도로 결속해. 자동 rollback은 image/Compose/active-state뿐이고 migration/data restore는 별도 owner 승인이야.
|
||
remote release root/Compose mode 0777 P1도 최소권한 preflight로 닫기 전에는 execute하지 마.
|
||
|
||
학생 자기주도 폐루프는 홈 추천→회기→리뷰→G4 처방/G5 전이→별도 재연습→completed-session observer→
|
||
actual attempt/execution→read-model reload→before/after·멱등까지 desktop/mobile 실제 DB/browser로 검증해.
|
||
최종 검증은 API full, gateway full, schema/provenance, Web API types/typecheck/build, fixture E2E, disposable DB E2E,
|
||
NAS-origin E2E, visual/accessibility, auth/OpenAPI/assets/SSE→DB review를 포함해야 한다.
|
||
|
||
독립 작업은 서브에이전트로 병렬화하되 NAS/public/DB mutation은 단일 owner만 수행해. 공개 DB 복구 volume과
|
||
pre-recovery container/dumps, NAS named volume과 previous images는 삭제하지 마. 비밀/PII/raw UUID를 증거에 남기지 마.
|
||
과거 로컬 recovery dump 경로는 인계 시점에 보이지 않으므로 삭제로 단정하거나 그 파일을 전제로 복원하지 말고,
|
||
off-host/NAS 보관 위치와 새 backup 목적지를 먼저 확인해.
|
||
source-only PASS나 health-only를 DONE으로 부르지 말고, 대시보드·TODO·backlog·HANDOFF를 마지막 증거와 함께 동기화해.
|
||
모든 게이트가 실제로 닫히기 전에는 goal complete를 선언하지 마.
|
||
```
|