# 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/health`는 `status=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 HEAD`가 `76d0b9ae9bb11b62af86ccd9a55e798bbedc9e31`인지 확인하고, 다음 순서로 검증한다. ```powershell 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/.env`의 `DATABASE_URL`을 쓰는 direct-runtime이다. 아래 검사가 비밀값을 출력하지 않고 통과했다. ```powershell 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의 미배포 증거로 다시 해석하지 않는다.