vignette/docs/HANDOFF.md
Yun Chan 93dd8f82d7 G8 실제 rollback 증명 종료와 비-secure origin 회기 리뷰 크래시 수정
G8 마지막 게이트인 receipt-bound 실제 image rollback을 격리 NAS
vignette-preview-20260807 에서 실행해 종료했다.

Gate6 계약 정정:
감사 대상 current API 이미지가 com.docker.compose.project/service/version
image label 을 갖고 있어 "helper 의 compose label 0개" 계약은 감사되지 않은
다른 이미지를 쓰지 않는 한 성립하지 않는다. 계약을 key 부재가 아니라
소속(membership) 으로 바꿔 launch-nas-preview-g8-helpers.py 에 구현했다.
image 상속 label 을 baseline 으로 읽고 container 의 모든 compose label 이
baseline 과 같거나 선언된 격리 override 인지 검사하며, 최종 project 는
target 이 아니고 service 는 api/web/db/proxy 가 아니어야 한다. docker run
argv 에 target label 을 주입하면 fake-runner 테스트가 먼저 깨진다 (37/37).

실행 결과:
- rollback-old  receipt nas-g8-723eeef22eab05e63e3fafb0 -> 79ec../c530..
- restore-current receipt nas-g8-2738846cf2cf4fbe8ce0fc26 -> 52e0../6fdb..
- release gate/approval 각 2회 멱등, audit.ci_lifecycle_event rollback/executed 2,
  audit.ci_human_approval_event authorize_rollback 2, silent auto-promotion 0
- HMAC journal 6-record 체인 검증, health 3/3, OpenAPI 126, auth 401, Web 200
- helper 0, listener 0, 비밀 env 파기. down/volume rm/prune 미실행, 공개 런타임 미접촉
- 계획했던 Windows SSH 터널은 NAS sshd 가 direct-tcpip 를 거부해 사용할 수 없어
  sshd 설정 변경 대신 같은 격리 계약의 NAS-side probe 컨테이너로 실행했다

비-secure origin 크래시 수정:
배포된 NAS 프리뷰(평문 HTTP, 비-localhost)에 회기 스펙을 돌려 24건 실패를 확인했고
원인은 하나였다. crypto.randomUUID 는 secure context 전용인데 제품 코드 18곳이
fallback 없이 호출했고 RuptureRepairCard 는 렌더 시점 호출이라 회기 리뷰 라우트
전체가 error boundary 로 떨어졌다. 릴리스 게이트 108/108 은 localhost 후보 스택에서만
돌아 이 경로를 밟은 적이 없다. src/lib/uuid.ts 의 randomUuid() 로 통일하고 fallback 도
crypto.getRandomValues 를 우선 사용해 idempotency key 의 예측 불가능성을 유지했다.
회귀는 insecure-context-uuid.spec.ts 6/6 으로 고정했다(직접 호출 0건 검사 포함).
이 수정은 아직 NAS 에 배포하지 않았다.

검증:
API 898, gateway 58, executor 28, probe 11, helper launcher 37, release agent 21,
ruff clean, web api-types/typecheck/build, SSOT FAIL 0, SSOT unit 5/5,
dashboard E2E 10/10, 학생 폐루프 실 DB 브라우저 4/4(일회용 클론),
crypto 수정 후 기존 스펙 회귀 70/70, 복원된 NAS 실제 브라우저 SSE->DB 리뷰 PASS.

부수 발견(열린 항목):
공개 API 가 engine=false 로 degraded 인데 워치독이 이를 감지하지 못한다. engine 판정이
게이트웨이 /health 의 ok 만 보고 claude readiness probe 를 돌리지 않기 때문이다.
같은 .env 와 같은 CLI 로 새 게이트웨이를 다른 포트에 띄우면 즉시 ready 이므로 상주
프로세스의 세션만 죽은 형태다. TODO A절과 대시보드에 기록했다.

이 커밋은 파일 단위로 담겼다. 위 파일들에는 이전 세션의 미커밋 G0~G8 작업이 함께
들어 있으며, hunk 를 쪼개면 대시보드/체커/TODO 정합성이 깨져 SSOT 체커가 실패한다.
2026-08-07 22:17:20 +09:00

287 lines
18 KiB
Markdown

# Vignette G0~G8 완료 핸드오프
> Updated: 2026-08-07 KST (G8 실제 rollback 종료 반영)
> Workspace: `D:\workspace\vignette`
> 이 문서는 다음 에이전트가 **G0~G8 아홉 목표 중 마지막으로 남은 G7 외부 게이트를 닫기 위한** 실행 기준이다.
## 0. 현재 상태 한 줄 요약
**G0~G6·G8은 internal DONE + 실제 runtime 증거로 종료됐다. 남은 것은 G7 외부 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, public unauth WSS 1008, focused 119 PASS |
| G8 Continuous Improvement | **DONE** | 격리 NAS 실제 image rollback 2회 + executed receipt 2건 (아래 §3) |
대시보드 status count는 **33 done / 2 doing / 0 planned**다.
## 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에서 먼저 확장되는 것을 막는다.)
- worktree는 큰 규모로 dirty하다. `git reset --hard`, `git checkout --`, 대규모 자동 포맷, 임의 삭제 금지.
- Git commit/stage/push는 사용자가 요청하기 전에는 하지 않는다.
- 공개 복구 DB, 원본 volume, pre-recovery container, dump, NAS named volume, 이전 API/Web 이미지를 삭제하지 않는다.
- **로컬 포트 8001(공개 API) · 5173(공개 Web) · 55432(공개 DB) · 9099/9100(엔진)은 공개 런타임이다.**
`scripts/dev-up.ps1`은 8000/5173 리스너를 정리하므로 공개 런타임이 떠 있는 동안 실행하지 않는다.
- 비밀값, 이메일, cookie, 사용자 UUID가 포함된 raw response, `.env` 내용은 로그·증거·채팅에 출력하지 않는다.
## 3. G8 — 종료됨 (2026-08-07 21:04~21:12 KST)
### 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에 완료됐다.
- 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`
- 복구 집계: users 84, sessions 30, turns 705, Google users 16, orphan sessions 0
- health `environment=prod`, `db=true`, `engine=true`, `engine_mode=claude_cli`, dev-login disabled, unauth `/auth/me` 401
- 공개 OpenAPI 119 paths · watchdog `VignettePublicRuntimeWatchdog` 5분 주기
보안상 과거 `auth_session`은 복원하지 않았다. 사용자는 Google 재로그인이 필요하다.
보존물: pre-recovery container `vignette-dev-db-pre-recovery-20260807-182642`,
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`.
공개 DB에는 current dirty source를 배포하지 않는다.
## 5. current source NAS 배포 — 완료
- 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` (previous: `79ec731f…0450` / `c530213f…62f28`)
- DB volume `vignette-preview-20260807_pgdata` · pre-proof dump `vignette-preview-g8-preproof-20260807T110610Z.dump`
SHA256 `92ed4736…1570f`
- 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. **다음 에이전트의 유일한 남은 목표 — G7 외부 종료 Gate**
G7 내부 구현은 완료됐고 `scripts/check-g7-external-proof.py`는 현재 의도적으로 exit 1이다.
네 artifact를 **같은 public host, 겹치는 50분 시간창**으로 수집해야 한다.
```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 — 운영 Deepgram/OpenAI TTS, 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 — 최소 30명 / 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`.
**사용자에게 받아야 하는 것 (코드로 대체 불가):**
- 운영 Deepgram key·quota 사용 권한
- 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 **898 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 / 21
```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
```
### 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는 610 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`
- `apps/web/src/lib/uuid.ts` · `apps/web/e2e/insecure-context-uuid.spec.ts` (신규, 비-secure origin 결함 수정)
- `apps/web/e2e/dev-dashboard.spec.ts` · `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 증거가 우선이다.