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 체커가 실패한다.
12 KiB
NAS 격리 프리뷰 배포 증거 — 2026-08-07
결과
- Tailnet 프리뷰:
http://100.116.83.60:8088 - Compose 프로젝트:
vignette-preview-20260807 - 원격 루트:
/volume1/docker/vignette-preview-20260807 - 실제 health:
status=ok,db=true,engine=true,engine_mode=claude_cli - 배포 SHA:
6030a677af7e87cbfabc422b553d108d53414fd3c446548734a13b036d35c611 - 이전 배포 SHA:
a8c27b0aadcb9dacea86c4be5e62e19277b9cc2678ebca51aa02998d2c2d1a0d - API 이미지:
sha256:52e0e816c47b1152368113b186d5cdb971123bc0961122541a7becf430b88b2d - Web 이미지:
sha256:6fdbb6460a7617540fa3ceff7eba96a2b9d83678318f0e27f99ac4bd7d93f215 - OpenAPI: 126 paths, G0~G8 Outcome/Alliance·Practice·Continuous Improvement 경로 포함
- 비인증
/api/auth/me: 401 - DB 계약:
vignette_app역할과 최종app.ci_regression_dag_node스키마 접근 gate 통과
기존 NAS 서비스와 포트·네트워크·볼륨을 공유하지 않는 별도 프리뷰다. 배포 작업은 이 전용 Compose 프로젝트만 대상으로 했으며 기존 프로젝트를 대상으로 한 중단·재생성 명령은 실행하지 않았다. 이 기록은 격리 구성과 실행 범위를 증명하지만 기존 서비스의 배포 전후 uptime 비교 증거로 확대 해석하지 않는다.
실제 브라우저 증거
- 학습자 경로: dev-login → 온보딩 → DB 페르소나 선택 → 새 회기 → Alliance pre 기준 잠금 → 세션 진입
- Playwright:
1 passed (13.1s)
- Playwright:
- 실제 엔진 1턴: 텍스트 발화 → SSE 내담자 응답 → 프로토콜 종료 → 후속 발화 가능 → DB-backed review
- Playwright:
1 passed (24.4s), 테스트 본문23.4s - 증거 세션:
c96922ab-0334-4864-ae48-df41a24aae14 - review: 학습자 1턴 + 내담자 1턴, 총 2턴
- 2026-08-07 08:29 KST 실제 NAS review API 재조회: HTTP 200, 같은 session id, 저장 축어록 2턴
- Playwright:
- 물리 마이크는 사용하지 않았다. 자동 TTS 재생만 제품 동작으로 확인했다.
- 최종 release-agent 브라우저 증거: 실제 NAS에서
session-persistence: browser SSE stream -> DB-backed review통과, stdout SHA-256b45111ce9f47d30e4c848362813940f3efd5464660b5046159b88befd9a59840.
시각 증거:
실배포에서 닫은 재발 방지 gate
- Synology BuildKit이 거부하던 루트
.dockerignore비ASCII 활성 패턴을 제거하고 ASCII 회귀 테스트를 추가했다. - Compose 2.20이 JSON 기본값을
{}}로 조립하던FRONTEND_ORIGIN_MAP을 필수 환경값으로 바꿨다. - DB health가 초기 SQL 실행 중 너무 일찍 통과하지 않도록 app role과 최종 G8 스키마 접근을 요구한다.
- API health는 HTTP 200이 아니라
status=ok,db=true,engine=true를 모두 요구한다. - 새 DB 전체 초기화에 5분
start_period를 둔다. - 프리뷰 dev-login은
hs.ac.kr,twentyoz.kr명시 allowlist만 허용한다. - 브라우저 SSE는 전송 계층 EOF가 아니라 애플리케이션
done이벤트에서 reader를 종료한다. - Windows clean candidate의 생성 API 계약과 Linux
*.sh를 LF로 정규화해bash\rDB init 실패를 차단한다. - 승격·롤백은 active snapshot의 canonical env 경로를 공유하고, 업로드 전에 존재를 검증한다.
- API/Web 교체 직후 502/503/504·연결 대기·초기 semantic health만 180초 안에서 재확인하고, OpenAPI/auth 계약 위반은 즉시 실패한다. 최종 승격은 readiness 3회째 통과했다.
최신 G7 hardening release-agent 승격
- material milestone과 release-only patch를 두 번 동일하게 생성하고 clean HEAD·canonical index 적용을 확인했다.
- 후보 API 전체, Web API 계약·typecheck·build, 격리 Compose와 회기 E2E를 통과한 뒤에만 NAS를 갱신했다.
- 첫 실제 교체는 즉시 502를 정상 실패로 판단해 이전 이미지로 자동 롤백했고, 이미지 ID·semantic health· OpenAPI 122·auth 401을 다시 확인했다. 이후 readiness window를 추가한 최종 실행은 3회째 health를 통과했다.
- 첫 실행은 기존 DB-backed mic fixture가 새 동의 모달을 거치지 않아 session E2E 23/24에서 fail-closed로 중단했고 NAS는 변경하지 않았다. 실제 사용자 순서대로 마이크→서버 동의 원장→synthetic capture로 고친 뒤 focused 1/1과 clean candidate session E2E 24/24를 통과했다.
- 최종 런타임은 OpenAPI 124, auth 401, G0~G8 계약과 실제 브라우저 SSE→DB review를 통과한 뒤 active-state를 위 exact SHA와 새 API/Web 이미지로 원자 커밋했다.
- 전체 후보 API 테스트, 최종 session E2E
24/24·505.578s, postdeploy browser review25.328s가 통과했다. 물리 마이크는 사용하지 않았다.
현재 G0~G8 통합 승격
- current source를 분류한 release-only patch는 세 번 동일 SHA로 생성됐고 manifest checker와 canonical
clean-index 적용을 통과했다. 모바일 learner 4열 navigation을 소유한
components/shell/shell.css도 runtime-critical related whole-file로 고정해 패키징 누락을 회귀 테스트로 막았다. - 후보 전체 browser E2E
108/108은634.578s에 통과했다. 실제 NAS 교체 뒤 일시적 502는 bounded readiness 두 번째 시도에서 회복됐고, 이후 독립 health3/3, OpenAPI 126 paths, auth 401, G0~G8 필수 route와 Web/CSS/JS 200을 확인했다. - postdeploy 실제 브라우저 SSE→DB-backed review는
32.437s에 통과했다. preview DB named volume은 그대로이며 browser smoke 뒤 users 11·sessions 10·turns 14·auth sessions 12다. 배포 전 custom dump SHA는47c8156e…958d이고 삭제·volume 교체·rollback은 없었다. - 기계 판독 요약: current 통합 배포 증거.
- 이 승격은 current 소스의 실제 런타임 증거지만 G8 receipt-bound image rollback 자체는 아니다. 그 증명은 아래 절에서 별도 loopback executor와 독립 control-plane API로 닫았다.
G8 실제 receipt-bound image rollback (2026-08-07 21:04~21:12 KST)
실행 절차와 Gate6 계약 정정은 G8 rollback 증명 런북,
기계 판독 증거는 evidence/nas-preview-g8-actual-rollback-2026-08-07.json이다.
helper 격리 (Gate6 정정)
감사 대상 current API 이미지 sha256:52e0e816…8b2d 자체가 com.docker.compose.project=vignette-preview-20260807,
service=api, version=2.20.1 image label을 갖는다. 따라서 helper의 compose label 개수를 0으로 요구하는
이전 계약은 감사되지 않은 다른 이미지를 쓰지 않는 한 성립하지 않는다. 계약을 key 부재가 아니라 소속으로
정정해 scripts/launch-nas-preview-g8-helpers.py에 구현하고 scripts/test_launch_nas_preview_g8_helpers.py
37/37로 고정했다.
- image 상속 label을 baseline으로 읽고 container의 모든
com.docker.compose.*가 baseline과 같거나 선언된 격리 override인지 검증한다. 실제 결과: 상속version=2.20.1, overrideproject=vignette-g8-helper-20260807g8a,service=g8-rollback-executor/g8-rollback-control-plane/g8-rollback-probe. - 최종 project는 target이 아니고, 최종 service는
api/web/db/proxy가 아니며,docker ps --filter label=com.docker.compose.project=vignette-preview-20260807멤버는 프리뷰 4개뿐이었다. docker runargv에 target project/service label을 주입하면 fake-runner 테스트가 먼저 실패한다.
| helper | container | ID |
|---|---|---|
executor 127.0.0.1:18149 |
g8-rollback-executor-20260807g8a |
5f5f3295f57f…785c |
control-plane 127.0.0.1:8018 |
g8-rollback-control-plane-20260807g8a |
ba545c633dd2…25df |
| probe (rollback-old) | g8-rollback-probe-20260807g8a |
15c385c86db0…a56e |
| probe (restore-current) | g8-rollback-probe-20260807g8a |
af079155c9b2…289f |
executor는 host network·read-only rootfs·cap-drop ALL·no-new-privileges·uid1028/gid100·supplemental
group101·pids-limit 256·restart no로 실행했고, unauth /healthz 403 · token /healthz 200 ·
unauth POST /rollback 403을 확인했다. control-plane은 producer disabled, preview DB 172.22.0.2의
vignette_app role, executor endpoint http://127.0.0.1:18149/rollback, unauth /auth/me 401이다.
계획 대비 이탈 — SSH 터널
계획했던 Windows SSH 터널 127.0.0.1:18018 → NAS 127.0.0.1:8018은 NAS sshd가 direct-tcpip를
administratively prohibited: open failed로 거부해 사용할 수 없었다. sshd 설정은 바꾸지 않았고,
같은 격리 계약의 NAS-side probe 컨테이너에서 loopback으로 실행했다. control-plane origin
http://127.0.0.1:8018과 preview origin http://127.0.0.1:8088이 다르므로 probe의
main_preview_self_rollback_forbidden 계약은 그대로 강제된다.
두 번의 executed receipt
| plan | receipt | artifact | 활성화된 API/Web 이미지 |
|---|---|---|---|
rollback-old |
nas-g8-723eeef22eab05e63e3fafb0 |
vignette-preview-release-a8c27b0aadcb9dac |
79ec…4450 / c530…2f28 |
restore-current |
nas-g8-2738846cf2cf4fbe8ce0fc26 |
vignette-preview-release-6030a677af7e87cb |
52e0…8b2d / 6fdb…f215 |
두 실행 모두 release gate 2회 멱등, approval 2회 멱등, lifecycle executed, artifact/approval/receipt
binding 검증, control-plane 분리 검증, 실행 뒤 health 3/3을 통과했다. 최종 상태는 OpenAPI 126 paths,
/api/auth/me 401, Web root 200이다.
원장·무결성
- HMAC journal 6 records —
started/prepared/executed× 2.previous_hash체인 전수 검증 PASS. SHA256a5594feb848d33e8ebfe52e55dc2802ba03d3161690b71724ec50fb779470690. - DB(owner role 집계):
app.ci_release_gate2,app.ci_gate_artifact8,app.ci_ingestion_submission4,audit.ci_human_approval_event2(전부authorize_rollback/release_gate),audit.ci_lifecycle_event2(전부rollback/executed), 나머지ci_*0. 두 executor receipt id가 DB lifecycle에 그대로 결속돼 있다. release gate는 둘 다pending_human_approval·qualified로 남아 silent auto-promotion은 0이다. - core 집계 변화: app_user 11→12, auth_session 12→14(probe dev-login 2회), sessions 10·turns 14 불변.
- probe 결과 파일 SHA256 —
rollback-old54e156fe…f95e,restore-currentc2f535aa…3825. - manifest
eeb8edc0…f8ef2, plan1ba0af23…aa6f/9ee8ecca…3309, executor script07ea4ff3…93d7, probe script74d4b80b…ab68는 실행 전후 동일하다.
정리와 보존
helper 3개 제거, listener 18149/8018/18018 = 0, override 임시 파일 0, helper env 3개는 shred -u로 파기했다.
journal·manifest·plan·evidence는 보존한다. docker compose down, docker volume rm, docker system prune은
실행하지 않았고 공개 런타임(vignette.chanpaca.net / vignette-dev-db)은 건드리지 않았다.
증거 안전 스캔은 이메일 0·비밀 힌트 0·plan 밖 UUID 0이다.
운영 및 남은 경계
- 자동화
vignette-e2e— Vignette 회기 E2E 정기 검증, 매일 04:30 KST, 실패 시만 알림, ACTIVE 구성 완료. material milestone이고 release-only patch 2회 결정성·manifest checker·canonical clean-index·NAS preflight가 통과하며 현재 배포 SHA와 다를 때만 같은 격리 프리뷰를 갱신한다. 이 증거 시점에는 첫 스케줄 실행 이력을 첨부하지 않았으며 실행 결과는 첫 실행 뒤 별도 기록한다. - G8은 내부 구현·격리 NAS runtime promotion·receipt-bound 실제 image rollback까지 모두 DONE이다. exact 배포 SHA와 두 receipt는 위에 고정했다.
- G7은 계속 BUILD다. 이 프리뷰는 물리 마이크 실증이나 Deepgram 운영 키 증거를 대신하지 않는다.
- 접근은 Tailnet 내부 HTTP 프리뷰다. 외부 공개 배포 완료 증거로 사용하지 않는다.