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 산출물은 커밋에서 제외했다.
125 lines
8 KiB
Markdown
125 lines
8 KiB
Markdown
# 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의 미배포 증거로 다시 해석하지 않는다.
|