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 체커가 실패한다.
238 lines
11 KiB
JSON
238 lines
11 KiB
JSON
{
|
|
"schema_version": "vignette.nas-preview-g8-actual-rollback.v1",
|
|
"recorded_at_kst": "2026-08-07T21:15:00+09:00",
|
|
"run_id": "20260807g8a",
|
|
"scope": "isolated NAS preview only; public runtime untouched",
|
|
"target": {
|
|
"compose_project": "vignette-preview-20260807",
|
|
"remote_root": "/volume1/docker/vignette-preview-20260807",
|
|
"preview_url": "http://100.116.83.60:8088",
|
|
"health_url": "http://127.0.0.1:8088/api/health",
|
|
"db_ip": "172.22.0.2",
|
|
"db_role": "vignette_app",
|
|
"db_name": "vignette_preview"
|
|
},
|
|
"gate1_preconditions": {
|
|
"current_api_image": "sha256:52e0e816c47b1152368113b186d5cdb971123bc0961122541a7becf430b88b2d",
|
|
"current_web_image": "sha256:6fdbb6460a7617540fa3ceff7eba96a2b9d83678318f0e27f99ac4bd7d93f215",
|
|
"previous_api_image": "sha256:79ec731f6bcdb79850507f5b711930a710e2d8e82276e3ea61dc8e4a60e04450",
|
|
"previous_web_image": "sha256:c530213f4f8b23f228b2999df0c3653173cfa6939b6abb476f17df7488462f28",
|
|
"manifest_sha256": "eeb8edc060e920195e083399814b9594c4d88e3d534daa39facd6c895f9e8ef2",
|
|
"rollback_old_plan_sha256": "1ba0af237b83ace5572b49d699b2fd81bc6b46752cb86317c4d013f8a8f6aa6f",
|
|
"restore_current_plan_sha256": "9ee8ecca9f712bce96592e9b88f30b24fc2c71c220022efcba95a85316cd3309",
|
|
"executor_script_sha256": "07ea4ff348b5b96959c9411123c0c8519f22cc972a7c78ecb5f5e40ae88093d7",
|
|
"probe_script_sha256": "74d4b80b031106c620f36dffc7d7a99667f1eda23333dbdb9e7e75e3ec5bab68",
|
|
"preproof_dump_sha256": "92ed4736c1b21081e7ce1dc16aa0d026456d7683fd550077136d3de736c1570f",
|
|
"preproof_dump_bytes": 960970,
|
|
"listeners_18149_8018": 0,
|
|
"helper_containers": 0,
|
|
"health": "ok/db=true/engine=true"
|
|
},
|
|
"gate6_helper_isolation": {
|
|
"contract": "membership, not compose-label absence",
|
|
"rationale": "the audited current API image carries com.docker.compose.* image labels, so a zero-key rule can never hold while the audited runtime image is reused",
|
|
"implementation": "scripts/launch-nas-preview-g8-helpers.py",
|
|
"tests": "scripts/test_launch_nas_preview_g8_helpers.py 37/37 PASS",
|
|
"image_baseline_compose_labels": {
|
|
"com.docker.compose.project": "vignette-preview-20260807",
|
|
"com.docker.compose.service": "api",
|
|
"com.docker.compose.version": "2.20.1"
|
|
},
|
|
"inherited_compose_labels": {
|
|
"com.docker.compose.version": "2.20.1"
|
|
},
|
|
"isolated_project": "vignette-g8-helper-20260807g8a",
|
|
"target_project_members_during_proof": [
|
|
"vignette-preview-20260807-api-1",
|
|
"vignette-preview-20260807-db-1",
|
|
"vignette-preview-20260807-proxy-1",
|
|
"vignette-preview-20260807-web-1"
|
|
],
|
|
"helpers": [
|
|
{
|
|
"role": "executor",
|
|
"container_name": "g8-rollback-executor-20260807g8a",
|
|
"container_id": "5f5f3295f57fe9e8ccb61b719d31d0cdfca54730510ab811d95fcc9163b2785c",
|
|
"isolated_service": "g8-rollback-executor",
|
|
"hardening": "network=host, readonly rootfs, cap-drop ALL, no-new-privileges, user 1028:100, group-add 101, pids-limit 256, restart=no",
|
|
"bind": "127.0.0.1:18149",
|
|
"unauth_healthz_status": 403,
|
|
"token_healthz_status": 200,
|
|
"unauth_rollback_post_status": 403
|
|
},
|
|
{
|
|
"role": "control-plane",
|
|
"container_name": "g8-rollback-control-plane-20260807g8a",
|
|
"container_id": "ba545c633dd2074b0a31889019b6d10fbe60776de8881b161bfb084f835025df",
|
|
"isolated_service": "g8-rollback-control-plane",
|
|
"bind": "127.0.0.1:8018",
|
|
"health_status": 200,
|
|
"unauth_auth_me_status": 401,
|
|
"producer_enabled": false,
|
|
"distinct_lifecycle_from_preview_api": true
|
|
},
|
|
{
|
|
"role": "probe",
|
|
"plan": "rollback-old",
|
|
"container_name": "g8-rollback-probe-20260807g8a",
|
|
"container_id": "15c385c86db05461a61001ecdf38d693f31e4cdd1d8f0e5e2c7f62933f2fa56e",
|
|
"isolated_service": "g8-rollback-probe",
|
|
"docker_socket_mounted": false,
|
|
"exit_code": 0
|
|
},
|
|
{
|
|
"role": "probe",
|
|
"plan": "restore-current",
|
|
"container_name": "g8-rollback-probe-20260807g8a",
|
|
"container_id": "af079155c9b217f6c271bda03ebfe49ac99a5d8febc769d9cb088ada42ad289f",
|
|
"isolated_service": "g8-rollback-probe",
|
|
"docker_socket_mounted": false,
|
|
"exit_code": 0
|
|
}
|
|
]
|
|
},
|
|
"planned_step_deviation": {
|
|
"planned": "Windows SSH tunnel 127.0.0.1:18018 to NAS 127.0.0.1:8018, probe executed from Windows",
|
|
"observed": "NAS sshd refused direct-tcpip with 'administratively prohibited: open failed'",
|
|
"action": "probe executed from an isolated NAS-side helper container against NAS loopback 127.0.0.1:8018",
|
|
"contract_preserved": "control-plane origin http://127.0.0.1:8018 differs from preview origin http://127.0.0.1:8088; the probe still enforces main_preview_self_rollback_forbidden",
|
|
"nas_sshd_config_changed": false
|
|
},
|
|
"executions": [
|
|
{
|
|
"plan": "rollback-old",
|
|
"probe_id": "nas-g8-probe-51c203e09baf9957a571e915",
|
|
"plan_sha256": "3cdbe07193ae17f96ead11f930db7bb085648de2b75aa19b595733650389bf27",
|
|
"executor_receipt_id": "nas-g8-723eeef22eab05e63e3fafb0",
|
|
"artifact_id": "vignette-preview-release-a8c27b0aadcb9dac",
|
|
"artifact_sha256": "a8c27b0aadcb9dacea86c4be5e62e19277b9cc2678ebca51aa02998d2c2d1a0d",
|
|
"lifecycle_status": "executed",
|
|
"release_gate_idempotent_replay": true,
|
|
"approval_idempotent_replay": true,
|
|
"binding_verified": true,
|
|
"control_plane_separation_verified": true,
|
|
"started_at_utc": "2026-08-07T12:04:31+00:00",
|
|
"executed_at_utc": "2026-08-07T12:05:11+00:00",
|
|
"images_before": {
|
|
"api": "sha256:52e0e816c47b1152368113b186d5cdb971123bc0961122541a7becf430b88b2d",
|
|
"web": "sha256:6fdbb6460a7617540fa3ceff7eba96a2b9d83678318f0e27f99ac4bd7d93f215"
|
|
},
|
|
"images_after": {
|
|
"api": "sha256:79ec731f6bcdb79850507f5b711930a710e2d8e82276e3ea61dc8e4a60e04450",
|
|
"web": "sha256:c530213f4f8b23f228b2999df0c3653173cfa6939b6abb476f17df7488462f28"
|
|
},
|
|
"health_checks_after": 3,
|
|
"health_ok_after": 3,
|
|
"result_file_sha256": "54e156fee3fb2b37fe92e7fba845cc02db1887d29e5c3cfd2ca0045942ddf95e"
|
|
},
|
|
{
|
|
"plan": "restore-current",
|
|
"probe_id": "nas-g8-probe-3fc9052ae1f4577a316db010",
|
|
"plan_sha256": "050d2c72d176bf5e6426de837e0c6a98a85c08789411159d7681b4c4e5c07ca5",
|
|
"executor_receipt_id": "nas-g8-2738846cf2cf4fbe8ce0fc26",
|
|
"artifact_id": "vignette-preview-release-6030a677af7e87cb",
|
|
"artifact_sha256": "6030a677af7e87cbfabc422b553d108d53414fd3c446548734a13b036d35c611",
|
|
"lifecycle_status": "executed",
|
|
"release_gate_idempotent_replay": true,
|
|
"approval_idempotent_replay": true,
|
|
"binding_verified": true,
|
|
"control_plane_separation_verified": true,
|
|
"started_at_utc": "2026-08-07T12:06:06+00:00",
|
|
"executed_at_utc": "2026-08-07T12:06:42+00:00",
|
|
"images_before": {
|
|
"api": "sha256:79ec731f6bcdb79850507f5b711930a710e2d8e82276e3ea61dc8e4a60e04450",
|
|
"web": "sha256:c530213f4f8b23f228b2999df0c3653173cfa6939b6abb476f17df7488462f28"
|
|
},
|
|
"images_after": {
|
|
"api": "sha256:52e0e816c47b1152368113b186d5cdb971123bc0961122541a7becf430b88b2d",
|
|
"web": "sha256:6fdbb6460a7617540fa3ceff7eba96a2b9d83678318f0e27f99ac4bd7d93f215"
|
|
},
|
|
"health_checks_after": 3,
|
|
"health_ok_after": 3,
|
|
"result_file_sha256": "c2f535aa3bfb459ddd1395e21b8ebbea0c3ac6252bbb5cd4217c845cab443825"
|
|
}
|
|
],
|
|
"journal": {
|
|
"path": "/volume1/docker/vignette-preview-20260807/.rollback-executor/journal.jsonl",
|
|
"schema_version": "vignette.nas-preview-rollback-journal.v1",
|
|
"records": 6,
|
|
"events": ["started", "prepared", "executed", "started", "prepared", "executed"],
|
|
"hash_chain_verified": true,
|
|
"sha256": "a5594feb848d33e8ebfe52e55dc2802ba03d3161690b71724ec50fb779470690",
|
|
"integrity_key_id": "6d2f7a5169406e2a",
|
|
"record_hashes": [
|
|
"3a694e813d98811ef6132e159e65f6d980ff16f6495696b165079672734187f4",
|
|
"6748e27bbae655386ef4f73c8bea18cae30d6b9ccc0385a73e9b8df59acb4fab",
|
|
"7142488d0935b755af33f21675e938f5dda93bb1d268a99feb46bbf4f53ccea4",
|
|
"dba76366e9e544f8edfc3a5c532484d0de2b9d4d0b8f98b13e335c980401cd12",
|
|
"cd585370680b0882c8ac775e6cc870c70b005b5c146c17f16a283902fff39878",
|
|
"34ca44ba32dd9b03b630fc43301addb14bc1a711bbb5551a7081f721d417ac5c"
|
|
]
|
|
},
|
|
"db_lifecycle_aggregate": {
|
|
"measured_as": "table owner role, RLS bypassed for counting only",
|
|
"before": {
|
|
"app_user": 11,
|
|
"sessions": 10,
|
|
"turns": 14,
|
|
"auth_session": 12,
|
|
"g8_lifecycle_tables_nonzero": 0
|
|
},
|
|
"after": {
|
|
"app_user": 12,
|
|
"sessions": 10,
|
|
"turns": 14,
|
|
"auth_session": 14,
|
|
"app.ci_release_gate": 2,
|
|
"app.ci_gate_artifact": 8,
|
|
"app.ci_ingestion_submission": 4,
|
|
"audit.ci_human_approval_event": 2,
|
|
"audit.ci_lifecycle_event": 2,
|
|
"other_ci_tables": 0
|
|
},
|
|
"lifecycle_breakdown": {"rollback/executed": 2},
|
|
"approval_breakdown": {"authorize_rollback/release_gate": 2},
|
|
"release_gate_state": {"pending_human_approval/qualified": 2},
|
|
"silent_auto_promotion": 0,
|
|
"executor_receipt_ids_bound_in_db": [
|
|
"nas-g8-2738846cf2cf4fbe8ce0fc26",
|
|
"nas-g8-723eeef22eab05e63e3fafb0"
|
|
]
|
|
},
|
|
"post_state": {
|
|
"helper_containers": 0,
|
|
"listeners_18149_8018_18018": 0,
|
|
"target_project_members": 4,
|
|
"api_image": "sha256:52e0e816c47b1152368113b186d5cdb971123bc0961122541a7becf430b88b2d",
|
|
"web_image": "sha256:6fdbb6460a7617540fa3ceff7eba96a2b9d83678318f0e27f99ac4bd7d93f215",
|
|
"health_checks": 3,
|
|
"health_ok": 3,
|
|
"openapi_paths": 126,
|
|
"auth_me_status": 401,
|
|
"web_root_status": 200,
|
|
"override_temp_files": 0,
|
|
"helper_env_files_remaining": 0,
|
|
"compose_down_executed": false,
|
|
"volume_rm_executed": false,
|
|
"prune_executed": false,
|
|
"public_runtime_mutated": false,
|
|
"public_runtime_note": "the public stack (vignette.chanpaca.net, api origin 127.0.0.1:8001, container vignette-dev-db, volume vignette_recovered_prod_20260807) was only read, never restarted, reconfigured or written to"
|
|
},
|
|
"post_rollback_runtime_verification": {
|
|
"test": "session-persistence: persists the browser SSE stream into DB-backed review",
|
|
"project": "chromium-single-run",
|
|
"target": "http://100.116.83.60:8088",
|
|
"result": "passed",
|
|
"duration_seconds": 35.0,
|
|
"physical_microphone_used": false,
|
|
"route_mocks": 0,
|
|
"note": "run after restore-current, so the restored runtime is proven to serve a real browser session end to end"
|
|
},
|
|
"safety_scan": {
|
|
"secrets": 0,
|
|
"emails": 0,
|
|
"cookies": 0,
|
|
"non_deterministic_uuids": 0,
|
|
"note": "every UUID in the preserved probe results is a uuid5 value derived from the repo-fixed probe namespace and the plan suffix; no account, session or personal identifier is recorded"
|
|
}
|
|
}
|