vignette/docs/ops/outcome-os-deploy-preflight-evidence-2026-08-07.md
Yun Chan 16e791e044 G0~G8 성과·동맹 측정 OS 작업 일괄 고정
8월 7일까지 워킹트리에만 남아 있던 미커밋 작업을 커밋한다. 여러 사본
폴더(worktree·clone)에 흩어져 있던 중간 스냅샷을 정리하기 전에 원본을
git 이력으로 고정하는 것이 목적이다.

- contracts/routes/services: measurement, outcome_trajectory, rupture_repair,
  deliberate_practice, calibration_transfer, supervision_research,
  multimodal_alliance, continuous_improvement 계열 신규 모듈과 테스트
- infra/db/init: 07~16 마이그레이션(측정 기반~calibration transfer 실행)
- apps/web: 세션 리뷰 카드·관리 화면·E2E 스펙 추가
- docs/ops: G0~G8 라이브 통합·배포·롤백 증거 문서와 evidence JSON/PNG
- scripts: smoke·ledger·릴리스 에이전트·NAS 프리뷰 운영 스크립트

engine.public 로그 .bak과 apps/web/test-results 산출물은 커밋에서 제외했다.
2026-08-08 01:30:53 +09:00

8 KiB

Outcome & Alliance OS 배포 프리플라이트 증거 — 2026-08-07

결론

G0~G8 구현을 운영 환경에 올리기 전에 누락된 migration과 내부 ingestion 비밀을 차단하는 fail-closed 프리플라이트를 추가했다. 앞부분은 배포 전 상태를 보존하며, 마지막 절은 실제 direct-runtime preflight와 공개 배포 결과를 기록한다.

검사 계약

  • G3~G8 내부 write 경계의 토큰 6개는 모두 존재하고 32자 이상이어야 한다.
  • 토큰은 change-me, replace-with, dummy, example 계열 예시 값이 아니어야 한다.
  • 여섯 토큰은 서로 다른 값이어야 하며 한 경계의 폐기가 다른 경계에 영향을 주지 않아야 한다.
  • Docker Compose는 여섯 토큰을 선택값이 아니라 필수 API 환경변수로 전달한다.
  • DB 프리플라이트는 기존 인증·회기·알림 테이블 외에 G0~G8 migration별 대표 원장 테이블을 검사한다.
  • --require-app-role 실행 시에는 기존과 같이 runtime role의 BYPASSRLS와 owner 권한도 거부한다.

실행 증거

2026-08-07 KST 로컬에서 다음을 확인했다.

검증 결과
python -X utf8 -m unittest scripts.test_deploy_preflight 6/6 passed
python -X utf8 scripts/check-deploy-preflight.py --skip-db --env-file infra/.env.example --allow-placeholder-secrets requirements, KB, example env OK
ruff check scripts/check-deploy-preflight.py scripts/test_deploy_preflight.py passed
docker compose --env-file .env.example config --quiet + 프로세스 한정 검증용 OAuth 값 passed
로컬 API 설정의 DB URL로 check_database(..., require_app_role=False) G0~G8 migration sentinel 전건 OK, current_user=vignette

Compose 정적 검증에는 실제 비밀 대신 프로세스 수명의 검증용 OAuth 값만 사용했다. 운영 .env를 읽거나 비밀 값을 출력하지 않았다. 로컬 DB 검사는 migration 존재만 증명하며 운영 app-role/RLS 증거로 승격하지 않는다. 운영 배포에서는 반드시 실제 app-role DSN과 --require-app-role로 다시 실행해야 한다.

배포 전 공개 상태 재확인

2026-08-07 00:37 KST에 https://api-vignette.chanpaca.net/healthstatus=ok, environment=prod였고 공개 OpenAPI는 전체 69개 경로 중 multimodal-alliance 포함 경로 0개, continuous-improvement 포함 경로 0개였다. 따라서 이 프리플라이트 완료만으로 G8의 실제 배포·public proof 게이트를 닫지 않는다.

01:16 KST 공개 release audit

동일 공개 런타임을 다시 읽기 전용으로 조회했다.

표면 결과 판정
/health status=ok, environment=prod, db=true, engine=true 기존 공개 런타임 자체는 건강함
/openapi.json 전체 69 paths, G5 calibration 0, G6 supervision-research 0, G7 multimodal-alliance 0, G8 continuous-improvement 0 G5~G8 release가 공개 API에 없음
인증 기준선 GET /sessions 401 not authenticated 공개 API의 인증 경계는 동작함
G5~G8 human read route 직접 GET 네 경로 모두 404 Not Found 인증 실패가 아니라 route 미배포
공개 WebSocket /voice/ws handshake 후 not authenticated, close 1008 WSS transport 존재만 확인; 인증 회기·물리 마이크·50분 증거 아님
공개 web asset index-BDlbUpT7.js, index-BbmNnVmr.css; JS에 G5~G8 API marker 없음 정적 asset도 현재 release를 포함하지 않음

직접 확인한 human read route는 /calibration/learners/me, /supervision-research/supervision-view, /sessions/00000000-0000-0000-0000-000000000001/multimodal-alliance, /continuous-improvement다. 로컬 현재 OpenAPI는 같은 분류가 각각 G5 8개, G6 9개, G7 9개, G8 8개라 공개본과 소스의 차이가 명확하다.

실제 infra/.env fail-closed 재현

비밀 값을 출력하지 않고 --env-file infra/.env --skip-db를 실행했을 때 exit 1이었다. ENVIRONMENT, Google OAuth client ID/secret, G3~G8 내부 토큰 6개가 누락됐고 ENGINE_MODE=claude_p는 허용 모드가 아니었다. 이 파일에는 DATABASE_URL 또는 app-role 식별자도 없어서 DB 포함 --require-app-role 검증을 실행할 운영 권한 자료가 없다.

따라서 당시 완료된 것은 구현·로컬 DB/API 증거와 fail-closed 검사기였다. 실제 소유자 secret/app-role 주입, 검증된 release-only patch 배포, 공개 route/assets/API/auth 재검증 전에는 G8과 AOS-011을 닫지 않는다.

release-only patch 적용 계약

release patch는 manifest의 base_commit 전용이다. 적용자는 먼저 clean checkout의 git rev-parse HEAD76d0b9ae9bb11b62af86ccd9a55e798bbedc9e31인지 확인하고, 다음 순서로 검증한다.

py -3.11 -X utf8 scripts/check-outcome-os-release-manifest.py --json
git apply --check docs/ops/evidence/outcome-os-release-only-2026-08-07.patch
git apply docs/ops/evidence/outcome-os-release-only-2026-08-07.patch
Set-Location apps/web
npm.cmd run check:api-types
npm.cmd run typecheck
npm.cmd run build

builder는 source worktree와 별도의 임시 clean checkout 적용 검사에 더해 임시 GIT_INDEX_FILE에 base tree를 읽고 git apply --cached --check를 실행한다. patch hunk는 canonical LF만 허용한다. 후보 산출물은 두 적용 검사와 source snapshot 불변 guard를 모두 통과한 뒤에만 기존 artifact를 원자적으로 교체한다. 따라서 다른 에이전트 편집이 빌드 중 발생하면 이전 검증 artifact를 보존한 채 fail-closed해야 한다.

통합 중 임시 artifact 상태 — 배포 금지

2026-08-07 01:28 KST 기준, line-ending guard를 추가한 첫 재생성 도중 다른 에이전트가 관련 소스 3개를 변경해 source snapshot guard가 실패했다. 수정 전 builder는 최종 guard 전에 destination을 기록했기 때문에 canonical 후보 patch가 기존 artifact를 덮어썼고, 현재 patch의 SHA/bytes는 manifest에 기록된 마지막 검증값과 일치하지 않는다. 현 artifact는 검증된 release-only 입력이 아니며 배포에 사용하면 안 된다.

builder는 이후 후보를 임시 경로에 유지하고 모든 guard 통과 뒤 원자 교체하며, 실패 후 이전 artifact를 복구하도록 수정됐다. 병렬 편집 통합과 dirty-path 재분류가 끝난 뒤 builder를 두 번 실행해 동일 SHA를 확인하고 manifest의 SHA/bytes를 갱신한 다음 checker가 green일 때만 이 임시 상태를 해제한다.

03:07~03:13 KST direct-runtime·공개 배포 종료 증거

임시 artifact 상태는 이후 전건 재분류와 두 번의 결정적 재조립으로 해제했다. 최초 공개 승격 입력은 229 files, 3,350,569 bytes, SHA-256 d9ac3c85c219640d14f74e705f766e6e1728464b0e32cc59e13acf8a45934d41이며 clean HEAD와 canonical index 적용 검사를 모두 통과했다.

현재 public runtime은 Compose 패키징이 아니라 apps/api/.envDATABASE_URL을 쓰는 direct-runtime이다. 아래 검사가 비밀값을 출력하지 않고 통과했다.

python scripts\provision-outcome-os-runtime-secrets.py --env-file apps\api\.env
python scripts\check-deploy-preflight.py --env-file apps\api\.env --deployment-profile direct-runtime --require-app-role

결과는 필수 direct-runtime key OK, current_user=vignette, non-owner app role이다. 이 clean worktree에서 backend 362, 회기·Outcome desktop/mobile 126, layout visual gate 15/15, typecheck·API contract·build를 통과한 뒤 production API 8001과 Cloudflare Pages를 승격했다. 공개 OpenAPI는 69→119 paths가 됐고 G1~G8 route, prod/db/engine health, auth 401, Google OAuth 302+state cookie, 새 custom-domain asset과 브라우저 로그인 렌더를 확인했다. 상세 public proof는 outcome-os-public-deployment-evidence-2026-08-07.md가 소유한다.

infra/.env의 Compose profile은 향후 컨테이너 패키징 전환용 별도 게이트로 남는다. 이것을 현재 direct-runtime의 미배포 증거로 다시 해석하지 않는다.