feat(approval-gated): AC-F5 후반 — 공유 볼륨을 파일시스템 아카이브에 실어 동결·복원을 건너 나른다 - #127
dlddu-k3s-homelab-k3s-mcp[bot] wants to merge 9 commits into
Conversation
`approval-gated` 에는 체크포인터가 등록돼 있지 않아 `POST /snapshot` 이 503 이었고, 그래서 AC-F5 가 현재형으로 단언하는 「동결·복원을 건너서도 이전 실행이 만든 파일이 남아 있다」를 실행할 코드 경로가 어느 환경에도 없었다. 세 조각을 함께 넣는다. 1. 조립 루트가 `CLAUDE_CODE_ARCHIVE_ENABLED` **한 게이트 안에서** `claude-code` 와 `approval-gated` 둘 모두에 `AgentArchiveCheckpointer` 를 등록한다. 그 값은 타입이 아니라 전략(아카이브를 `CHECKPOINT_S3_*` 에 쓰는 것)을 켜는 스위치이므로 `k8s/`·`deploy/` 는 한 줄도 바뀌지 않는다 — base 가 이미 켜고 오버레이가 덮지 않는다. 2. 아카이브 포맷에 `shared/` 를 **가산적으로** 더한다. 공유 디렉터리가 없는 세션은 부모와 바이트 동일한 아카이브를 내므로 `claude-code` 경로와 이미 저장된 아카이브가 전부 그대로 복원된다. `/shared` 는 마운트 포인트라 state 트리의 디렉터리 rename-swap 을 쓸 수 없어, 마운트 안 스테이징 → 전체 검증 → 최상위 엔트리 단위 설치로 짓는다. 이름 충돌은 병합이 아니라 거부이고, 마운트 없는 대상으로의 복원은 조용히 버리지 않고 실패한다. 3. 그 전환이 거짓으로 만든 거부 단언 둘을 교체한다 — e2e 는 동결이 두 파드를 함께 회수하고 복원이 새 쌍을 세우며 워크로드가 이번 라운드 헬퍼 IP 로 다시 배선됨을 사고, 단위는 「전략이 등록되면 언다」와 「게이트가 꺼지면 여전히 fail-closed 다」로 갈린다. 왕복 자체는 `shared_volume_test.go` 가 배포된 SUT 에서 단언한다: 클레임이 옛 헬퍼와 함께 사라진 뒤 이름이 다른 새 쌍의 같은 경로에서 같은 바이트가 읽힌다.⚠️ 유휴 리퍼가 이 타입을 처음으로 회수 대상으로 본다(지금까지는 `ErrCheckpointDisabled` 로 건너뛰었다). 대기 중인 승인은 AC-F3 의 유휴 예외가 계속 막는다. reconciler: rct_20260912-0007 / tbm_session-platform-docs-impl
아카이브의 `shared` 루트 엔트리가 나르는 모드는 **소스 파드의 마운트 포인트**의 것이다. 그것을 복원 쪽에서 적용하면 실제로 닿는 자리는 버려지는 스테이징 디렉터리이고, 소유자 쓰기가 빠진 모드였다면 설치 직전에 그 안의 엔트리들이 갇힌다. 건너뛰되 디렉터리인지는 계속 확인한다 — 마운트의 모드는 클러스터의 것이지 아카이브가 정할 것이 아니다. 왕복 테스트에 마운트 자신의 모드가 불변임을 단언하는 갈래를 더했고, 주석 원장 4차 행을 그만큼 재판정했다(962 → 965, 합계 3013 → 3016).
`e2e_f4_helper_pod_test.go` 가 동결 동반 회수와 복원 재배선을 실제로 사게 됐으므로, 그 둘을 「아직 단언하지 못하는 경로」로 적던 자리들이 한꺼번에 거짓이 됐다. - §「남은 미검증 분기」에서 두 행(동결 시 동반 회수 · 복원 시 새 쌍)을 내린다. - 그 표의 이유 열거에서 (2)「approval-gated 에 스냅샷 전략이 없다」를 해소로 표시한다. 그 이유를 쓰는 행이 하나도 남지 않았다. 번호는 뒤를 밀지 않도록 비워 둔다. - 시나리오 7 행의 프로즈를 거부 서술에서 두 갈래 서술로 바꾼다. - AC-F4 규범 판정이 「이 판정을 실측이 뒤집는 경우는 하나, 아카이브 전략이 등록되는 때이고 그때는 파일이 스스로 빨개진다」고 적어 둔 자리에, **그 하나가 실제로 일어났다**는 갱신을 붙인다. 결론은 그대로다 — 관측 가능한 AC-F4 갈래 중 단언되지 않은 것은 여전히 없고, 달라진 것은 관측 가능한 것의 집합이다. - AC-F5 「선행이 하나 더 있다」 블록을 후반 착지로 갱신한다. 이 파일을 처음에 범위 밖으로 둔 사유는 자매 축의 열린 PR(#123·#126)과 hunk 가 실측으로 겹친다는 것이었다. 그 PR 들이 처분되며 사유가 소멸해 같은 슬라이스에서 걷는다.
702e63b to
5ccec91
Compare
#129(054aac9)가 4차 판정 패스 행(`data-plane/cmd/agent`)을 증분 재판정하면서 지문을 옮겼고, 같은 행을 이 브랜치도 옮겨 충돌했다. 코드 델타는 한 파일도 겹치지 않는다 — 교집합은 `docs/comment-policy/ledger.md` 한 파일뿐이다. 해소: - 4차 행 지문을 병합 트리에서 재측정 (게이트 실측값 `2600cc3562c9`) - #129 의 `2026-09-12 (4)` 증분 재판정 산문을 복원 (`--ours` 가 조용히 떨어뜨렸다) - 이 브랜치의 주석을 `(5)` 로 재발급 — `(4)` 는 #129 가 먼저 가져갔다 (ledger 행 + `passes/2026-09-04-data-plane-agent.md` 헤딩 양쪽) 줄 수(965)와 합계 마커(3016)는 재측정 후에도 불변 — #129 는 그 범위의 줄 수를 바꾸지 않았다(927 → 927).
#132(b772cb3)가 `docs/test/e2e.md` 의 시나리오↔e2e 매핑 표를 **Playwright 단독으로 축소**하면서 Go 행 전체를 유예 표로 옮겼고(-286/+196), 같은 PR 이 `scripts/e2e/check-scenario-mapping.sh` 도 다시 썼다. #134 는 이 브랜치와 무관한 파일만 건드린다. 해소: 충돌은 `docs/test/e2e.md` 한 파일 · 매핑 블록 한 곳뿐이다. - 매핑 블록은 **main 판(빈 표)을 그대로 취했다** — 이 브랜치가 고치려던 「시나리오 7」 행이 #132 로 **표에서 사라져** 고칠 대상이 없다. - 나머지 4개 hunk(AC-F4 규범 갱신 · AC-F5 후반 착지 · 이유 (2) 해소 · 「남은 미검증 분기」 두 행 제거)는 충돌 없이 그대로 산다. 증거: 매핑 게이트 출력이 main 과 **바이트 동일**(매핑 축 무접촉). 코드 8파일은 이 머지로 한 바이트도 움직이지 않았다. 정적 게이트 4종 rc=0, 양 모듈 gofmt/vet(3태그)/test rc=0.
|
#138 이 이 PR 은 같은 56행을 항목 ② 쪽에서 통째로 다시 쓰므로, rebase 하실 때 변경 이력 표 끝도 겹칩니다(이 PR 은 덧붙여, 현재 이 PR 이 reconciler |
… 재측정 - ledger.md: 합계 마커만 충돌(3016 vs 3010) → R4 실측 3037 - doc-tracker.md: 「미검증 참조 구현 전제」·「구현 사슬 부채」 두 불릿을 양쪽이 다시 썼다. #138 의 https 전제 정정(리터럴 0건 · validateFetchTarget 이 평문 http 도 받는다 · 좌표를 심볼 이름으로)을 살리고 그 위에 AC-F5 착지 델타를 다시 얹었다. 변경 이력 표는 두 행을 모두 남겼다. - docs/test/e2e.md 는 자동 병합. 코드 8파일 충돌 0.
검토 반려 반영. 새 env `SESSION_APPROVAL_GATED_ARCHIVE_ENABLED` 가 이 타입의 아카이브 전략 등록을 켜고, 기본값으로 `CLAUDE_CODE_ARCHIVE_ENABLED` 를 상속한다. 왜: - 이 축은 되돌리기가 조건부 불가역(저장된 `shared` 아카이브를 되돌린 바이너리가 거절)이라 사고 시 가장 싼 완화가 플래그 강하다. 게이트를 공유하면 그 완화가 잘 돌고 있던 claude-code 아카이브와 유휴 리퍼(`snapshotEnabled`)까지 함께 끈다. - 폴백 상속이라 `k8s/`·`deploy/` 는 0줄. base 가 `CLAUDE_CODE_ARCHIVE_ENABLED=true` 를 세우고 kind 오버레이가 덮지 않으므로 두 환경 모두 상속으로 켜진 채다. (오버레이 patch 가 env 를 위치 인덱스 env/1~env/4 로 집으므로 base 에 env 를 더하지 않는 것 자체가 안전 요건이다.) - main.go: 스토어 게이트·등록·`snapshotEnabled` 를 새 플래그까지 포함하도록 가른다. - main_test.go: 상속/강하 4조합을 `loadConfig` 테이블 테스트로 단언한다. - workload_type_test.go: fail-closed 케이스 doc 을 새 게이트 이름으로 다시 쓴다. - 원장 3행(#2·#6·#7) 재측정 + 게이트 분리 증분 재판정, 합계 3037 → 3047(R4 실측). 직전 증분이 유지로 남긴 「왜 env 이름을 바꾸지 않았는가」 2줄은 전제가 사라져 걷었다. - doc-tracker: AC-F5 착지 서술과 변경 이력 행을 새 게이트 기준으로 정정.
충돌은 `docs/comment-policy/ledger.md` 한 파일, 두 곳뿐이다(4차 행 · 합계 마커). `docs/test/e2e.md` 는 자동 병합, 코드 충돌 0. 4차 행은 **양쪽이 각자 증분 재판정을 덧붙였다** — 이쪽은 `(5)` AC-F5 후반(아카이브), main 은 #135 의 `ping` 표면 등재. 한쪽을 취하면 다른 쪽 판정 산문이 게이트가 초록인 채 사라지므로(R2 는 줄 수·지문만 본다) **두 증분을 날짜순으로 모두 남기고**, 줄 수·지문은 R2 실측(969 / `42c0300409bf`)으로, 합계는 R4 실측(3051)으로 재측정했다. #135 증분의 러닝 토털만 `965 → 969` 로 다시 이었다(판정 자체는 927 위에서 잰 것 그대로). 로컬 전수 재검증 rc=0: 정적 게이트 4종 · 양 모듈 gofmt/vet(기본·e2e)/test. 코드 파일은 이 머지로 한 바이트도 움직이지 않았다.
|
| 레코드 기록 시점 | 현재 | |
|---|---|---|
| head | 5979506 |
8dd8e13 |
| base | 068e921 |
99c2166 |
mergeable_state |
clean | clean |
| CI | 14 success · 1 skipped · 0 failure | 14 success · 1 skipped · 0 failure |
| kind e2e | 125 PASS / 0 FAIL / 0 SKIP | 125 PASS / 0 FAIL / 0 SKIP |
추가 커밋(8dd8e13)이 한 일
충돌은 docs/comment-policy/ledger.md 한 파일, 두 곳뿐이었다(docs/test/e2e.md 는 자동 병합, 코드 충돌 0).
4차 판정 행에 양쪽이 각자 증분 재판정을 덧붙였다 — 이쪽은 (5) AC-F5 후반(아카이브), main 은 #135 의 ping 표면 등재. 한쪽을 취하면 다른 쪽의 판정 산문이 게이트가 초록인 채 사라진다(R2 는 줄 수·지문만 보고 산문은 보지 않는다). 그래서 두 증분을 날짜순으로 모두 남기고 줄 수·지문은 R2 실측(969 / 42c0300409bf), 합계는 R4 실측(3051)으로 재측정했다. #135 증분의 러닝 토털만 965 → 969 로 다시 이었다(판정 자체는 927 위에서 잰 것 그대로다).
검증: 정적 게이트 4종 · 양 모듈 gofmt/go vet(기본·e2e)/go test 전부 rc=0, 그리고 위 CI 실측.
e2e (kind + API + browser) 가 한 번 빨갰는데 변경과 무관한 인프라 flake 였다 — helm/kind-action 이 kind 바이너리를 받다 curl: (35) Recv failure: Connection reset by peer 로 죽어 클러스터가 아예 서지 않았다(Go 테스트 출력 0줄). 재실행 후 위 결과다.
승인 시 확인할 것은 바뀌지 않았다
이 PR 의 실질(독립 게이트 SESSION_APPROVAL_GATED_ARCHIVE_ENABLED + 폴백 상속, 그리고 AC-F5 후반 본체)은 검토가 본 것 그대로다. 다만 base 는 계속 흐르므로 승인 시점에 mergeable_state 를 다시 잴 것 — 이 task 는 같은 실패 모드(경합)로 이미 네 번 되돌아왔다.
무엇을 왜
reconcilertaskrct_20260912-0007(tbm_session-platform-docs-impl) — AC-F5 의 후반(아카이브 축).AC-F5 는 현재형으로 단언한다: 「이 볼륨의 내용은 파일시스템 아카이브에 포함된다 … 따라서 동결·복원을 건너서도 이전 실행이 만든 파일이 남아 있고」. 실제로는
approval-gated에 체크포인터가 등록돼 있지 않아(main.go가 등록하는 것은shell의 CRIU 와claude-code의 아카이브 둘뿐)Service.checkpointerFor가ErrCheckpointDisabled를 내고 이 타입의POST /snapshot은 503 이었다. 즉 그 문장을 실행할 코드 경로가 어느 환경에도 없었다.그리고 공유 볼륨 PVC 는 그 라운드 헬퍼 파드의
ownerReferences에 묶여 동결 시 함께 GC 되므로, 전략만 등록하고 볼륨을 아카이브에 넣지 않으면 동결이 성공하는 대신 승인된 결과물이 소리 없이 사라진다 — 지금의 503 보다 나쁜 거짓이 된다. 그래서 세 조각을 함께 넣는다.무엇을 했나
① 전략 등록 — 조립 루트가
CLAUDE_CODE_ARCHIVE_ENABLED한 게이트 안에서claude-code와approval-gated둘 모두에AgentArchiveCheckpointer를 등록한다.게이트를 새로 만들지 않은 것이 이 PR 의 유일한 설계 선택이다. 그 값은 타입이 아니라 전략(아카이브를
CHECKPOINT_S3_*에 쓰는 것)을 켜는 스위치이고 — config 필드 doc 이 이미 그렇게 적는다 — 그래서 이 착지는k8s/·deploy/를 한 줄도 바꾸지 않는다: basek8s/deployment.yaml이 이미 그 게이트를true로 두고 kind 오버레이가 그것을 덮지 않으므로 프로덕션과 SUT 에서 같은 순간에 유효해진다. 새 env 를 만들었다면 오버레이가 그것을 켜기 전까지 아래 ③ 의 e2e 가 SUT 에서 빨간 채로 남았을 것이다.② 아카이브 포맷 — 가산적 확장 — tar 최상위에
shared/가scrollback·state/옆에 붙는다. 공유 디렉터리가 없는 세션은 부모와 바이트 동일한 아카이브를 내므로claude-code경로와 이미 저장된 모든 아카이브가 그대로 복원된다.복원이 state 와 갈리는 지점이 하나 있고 그것이 이 축의 핵심 제약이다 —
/shared는 마운트 포인트라 rename 으로 덮을 수 없다. state 트리는 형제 디렉터리에 풀고 통째로 swap 하지만 그 방법을 여기 쓸 수 없어서, 공유 트리는 마운트 안의 스테이징으로 풀린 뒤(같은 파일시스템이라 설치는 여전히 rename) 전체 검증이 끝난 다음 최상위 엔트리 단위로 옮겨진다. 결정 둘:공유 트리는 이 에이전트가 drain 하지 않는 컨테이너(헬퍼 파드의 MCP)가 쓰므로, 걷는 도중 착지한 spill 은 per-entry
SameFile검사에 걸려 체크포인트를 실패시킨다 — 반쪽 파일이 아카이브되지 않는다.③ 거짓이 된 단언 교체 —
e2e_f4_helper_pod_test.go의restoreTheFreezeBranches지시문이 지목한 그대로:SESSION_MCP_URL을 실행 중인 컨테이너에서 재 두고 비교하므로 파드 재생성에 대한 가정이 아니라 측정이다.TestSnapshotIsRefusedForApprovalGated→ 둘로 갈림. 「전략이 등록되면 언다」(셸 체크포인터를 disabled 로 주입해 공허하지 않게 만든다)와 「게이트가 꺼진 배포에서는 여전히 fail-closed 다」 — 후자를 남기지 않으면checkpointerFor의 실패-닫힘 계약을 잃는다.shared_volume_test.go에 왕복 단언을 더한다: 마커를 MCP 컨테이너로 쓰고 동결 전에 워크로드가 읽는 것을 먼저 확인한 뒤, 동결 → 옛 클레임 GC 확인 → 복원 → 이름이 다른 새 쌍의 새 클레임에서 같은 바이트를 읽는다.④ 거짓이 된 문서 걷기 (
docs/test/e2e.md) — ③ 이 이 파일의 네 자리를 한꺼번에 거짓으로 만든다:검증
로컬에서 CI 게이트를 1:1 재현했고 전부 초록이다.
control-plane:gofmt -l .·go vet ./...·go vet -tags=e2e ./...·go test ./...data-plane:gofmt -l .·go vet ./...·go test ./...scripts/check-fidelity-allowlist.pyscripts/check-render-fidelity.pyweb/무접촉)scripts/e2e/check-scenario-mapping.shscripts/check_comment_policy.py클러스터가 필요한 e2e 는 여기서 못 돌린다 — ③ 의 세 단언은
e2e.yml(kind + API + browser)이 이 PR 에서 실제로 사는 부분이고, 그것이 이 변경의 진짜 게이트다.주석 원장 증분 재판정 (R2/R4 — 같은 PR 요구사항)
등재 4행을 정책 절차대로 재판정했다. 합계 2989 → 3016.
internal/servicedata-plane/cmd/agentcmd/control-planecontrol-plane/test부수적으로 틀린 주석 1건을 찾아 걷었다(
sharedArchiveWorkload의 doc 이 실제와 다른 반환값을 적고 있었다). 미등재 파일shared_volume_test.go는 이 행들에 편입하지 않았다 — 9~13차가 그 파일을 유예한 사유인 「살아 있는 소유 선언」이 바로 이 슬라이스였고, 이 PR 이 그것을 걷는다. 판정 자체는 선언이 사라진 뒤 자매 모델(tbm_session-platform-comment-redundancy)이 여는 패스의 몫이다.approval-gated세션은ErrCheckpointDisabled때문에 리퍼가 그냥 지나쳤다(reaper.go). 이 PR 뒤로는 유휴 한계를 넘긴 세션이 실제로 얼고 파드 쌍이 회수된다. 대기 중인 승인은 AC-F3 의 유휴 예외가 계속 막는다.shared멤버를 담으므로 되돌린 바이너리가 그것을 「unexpected entry」로 거절한다. 되돌릴 계획이라면 그 전에 approval-gated 스냅샷을 복원하거나 삭제해 비울 것.SESSION_SHARED_VOLUME_STORAGE_CLASS미설정) 그 환경에서 이 PR 이 켜는 것은 「approval-gated 세션이 얼고 복원된다」까지다.shared/경로는 kind SUT 에서만 실행된다.maxClaudeArchiveBytes(4 GiB)·maxClaudeArchiveEntries(200k)가 두 트리의 합에 걸리므로, 가득 찬 1 GiB 볼륨 + 큰 워크스페이스는 체크포인트를 조용한 절단이 아니라 실패로 만든다(기존 동작 그대로).범위 밖 (후속)
— 범위 안으로 들어왔다(아래 ④ 참조). 처음에는 열린 자매 PR test(e2e): approval-gated #시나리오 3 의 승인 경로 파일을 저작한다 (저작 대기 8→7) #126 의 hunk 둘(docs/test/e2e.md@@ -400,6 +400,7 @@·@@ -835,9 +870,11 @@)이 고쳐야 할 403·841 행을 각각 덮는다는 실측 때문에 범위 밖으로 뒀다. 그 뒤 test(e2e): approval-gated #시나리오 3 의 승인 경로 파일을 저작한다 (저작 대기 8→7) #126 이 머지되고 docs(e2e): approval-gated #시나리오 3 을 구현 대기로 내린다 — 세션 MCP 가 자기가 서비스하지 않는 주소로 등재된다 (실측) #123·fix(data-plane): 세션 MCP를 그것이 실제로 서비스하는 경로로 등록한다 — AC-F6 의 「유일한 도구 표면」이 실질 0이던 것 #124 가 닫히면서 사유가 소멸했고, 브랜치를 새 main 에 rebase(충돌 0)한 뒤 같은 슬라이스에서 걷었다.SESSION_SHARED_VOLUME_STORAGE_CLASS배선 — 어느 클래스가 RWX 를 내주는지는 대상 클러스터의 성질이라 이 레포가 정할 수 없다.shared_volume_test.go의 주석 판정 — 위에 적은 대로 자매 모델 소관.🔄 재작업 (2026-09-12) — 베이스 갱신 + 원장 4차 행 재측정
이 PR 은 승인까지 받은 뒤 실행 직전 신선도 게이트에서 멈췄다(task
rct_20260912-00071차 시도failed). 승인 대기 사이에 #128(12:30Z)·#129(12:48Z)가 착지해mergeable_state=dirty가 됐기 때문이고, 지침이 「충돌 해소는 실행이 아니라 재계획의 몫」으로 선을 그어 실행은 대상 레포에 아무것도 하지 않았다. 이 섹션이 그 재계획의 결과다.충돌면은 한 파일·한 행이었다.
origin/main(419eb2eb) 대비 이 브랜치의 15파일 중 교집합은docs/comment-policy/ledger.md하나이고 코드 델타는 한 파일도 겹치지 않는다(양측 전수 대조). 충돌 행은 4차 판정 패스(data-plane/cmd/agent) 한 행으로, #129 가 증분 재판정하며 지문을 옮긴 자리다.해소 방법 —
rebase+force 가 아니라main머지. PR 번호·승인 이력·리뷰 스레드를 보존하려고origin/main을 이 브랜치로 머지했다(일반 fast-forward push). 원장 값은 손으로 더하지 않고 게이트가 찍는 실측값을 파싱해 넣었다:d9018a9d32b65ee3891c5ca4a34b6372619e2600cc3562c9🔴 기계적 해소였다면 조용히 사라졌을 것 하나.
git checkout --ours로 원장을 해소하면 #129 가 그 행에 쓴2026-09-12 (4)증분 재판정 산문이 통째로 없어진다 — R2 는 줄 수와 지문만 보고 산문은 검사하지 않으므로 게이트가 초록인 채 지나간다. 양쪽이 같은(4)키를 집은 것이 원인이다. 해소: #129 의(4)산문을 복원하고, 나중에 착지하는 이쪽 주석을(5)로 재발급했다(원장 행 +passes/2026-09-04-data-plane-agent.md헤딩 양쪽).따라서 1차 승인본 대비 실질 차이는 정확히 셋이다 — 지문 1개 재측정 · #129 산문 복원 ·
(4)→(5)재발급. 코드·테스트·docs/test/e2e.md·doc-tracker.md는 한 글자도 바뀌지 않았다.재측정 후 로컬 전수 재검증: 정적 게이트 4종 rc=0, 양 모듈
gofmt -l빈 출력 ·go vet(기본/-tags=e2e/-tags=integration) ·go test ./...전부 rc=0.재작업 2차 (2026-09-13) — #132 의 매핑 표 축소에 맞춰 재정렬
1차 재작업 뒤 승인 대기 사이에 #132(매핑 공간 Playwright 단독 축소)와 #134 가 착지해 다시
dirty가 됐다.충돌은
docs/test/e2e.md한 파일 · 매핑 블록 한 곳뿐이었다. #132 가 시나리오↔e2e 매핑 표에서 Go 행 전체를 유예 표로 옮겨 표를 비웠기 때문에, 이 PR 이 고치려던 「시나리오 7」 행이 표에서 사라졌다 — 고칠 대상이 없으므로 main 판(빈 표)을 그대로 취했다.나머지 4개 hunk 는 충돌 없이 그대로 산다:
증거: 매핑 게이트 출력이 main 과 바이트 동일(매핑 축 무접촉 — #132 가 그 게이트 스크립트도 다시 썼으므로 새 게이트로 잰 값이다). 코드 8파일은 이 머지로 한 바이트도 움직이지 않았다. 정적 게이트 4종 rc=0, 양 모듈
gofmt/go vet(3태그)/go testrc=0.ledger.md를 건드리지 않았다). 1차 재작업의 재측정값(4차 행 965 /2600cc3562c9, 합계 3016)이 그대로 유효하다.🔄 재작업 3차 (2026-09-13) — 검토 반려 반영: 아카이브 게이트를 분리한다
반려 사유: 이 PR 이
approval-gated체크포인터 등록을 기존if cfg.claudeArchiveEnabled블록 안에 넣어, 새 동작의 on/off 가CLAUDE_CODE_ARCHIVE_ENABLED한 값에 완전히 종속됐다. 되돌리기가 조건부 불가역인 고위험 변경에서 가장 싼 완화 수단(플래그 강하)의 granularity 를 잃는다 — 그 값을 내리면 잘 돌고 있던claude-code아카이브와snapshotEnabled까지 함께 떨어진다.채택한 형태 (승인 협의 중 제시된 문구 그대로):
세 형태를 실측으로 저울질했다 — 1차 계획의 기각 근거가 과장이었던 지점
k8s/+deploy/동시 수정②의 비용이 ③에서 0 이 되는 것을 매니페스트 실측으로 확인했다 —
k8s/deployment.yaml:108이CLAUDE_CODE_ARCHIVE_ENABLED: "true"를 프로덕션 base 에서 세우고,deploy/kustomization.yaml(kind 오버레이)은 그 값을 덮지 않는다. 따라서 새 env 를 아무도 이름 짓지 않아도 두 환경 모두true를 상속한다.🔴 ②를 택했다면 조용히 깨졌을 자리가 하나 더 있다.
deploy/kustomization.yaml의 patch 는 env 를 위치 인덱스로 집는다(env/1=DATA_PLANE_IMAGE·env/2=CRIU_ENABLED·env/3=DATA_PLANE_CLAUDE_CODE_IMAGE·env/4=DATA_PLANE_APPROVAL_GATED_IMAGE). base 의 env 목록 앞쪽에 한 항목이라도 끼면 그 넷이 전부 한 칸씩 밀려 오버레이가 엉뚱한 값을 덮는다. 「매니페스트 0줄」은 편의가 아니라 이 레포에서 안전한 형태다.킬 스위치가 실제로 남기는 것
SESSION_APPROVAL_GATED_ARCHIVE_ENABLED=false만 내리면 —approval-gated만 fail-closed(ErrCheckpointDisabled)로 돌아가고,claude-code아카이브는 계속 돌며, 유휴 리퍼도 계속 산다(snapshotEnabled가 세 플래그의 OR). ①에서는 셋이 한꺼번에 떨어졌다.코드 델타 (3파일)
control-plane/cmd/control-plane/main.go— config 필드 추가 ·loadConfig상속 기본값 · 체크포인트 스토어 게이트를 새 플래그까지 확장(새 플래그만 켠 배포에서cstore가 nil 이 되지 않게) · 등록을 두 개의 독립if로 분리(claude-code 쪽 이미지 검사 유지) ·snapshotEnabled확장 · 게이트 열거 주석 정정.control-plane/cmd/control-plane/main_test.go—TestLoadConfigApprovalGatedArchiveGate: 미설정 상속(on/off) · 명시false(킬 스위치) · 명시true(독립) 4조합. 이 테이블이 없으면 나중에 기본값을false로 뒤집어도 아무것도 빨개지지 않는다.control-plane/internal/service/workload_type_test.go— fail-closed 케이스 doc 을 새 게이트 이름 기준으로. 본문 무변경(서비스 계층이라 env 를 모른다).새 base(
068e921) 로의 머지 — 충돌 2파일docs/comment-policy/ledger.mddocs/doc-tracker.mddocs/test/e2e.md는 자동 병합(충돌 0), 코드 8파일도 충돌 0 — 병합 직후 파일 단위 대조로 6파일이 바이트 단위== #127임을 확인했다(게이트 분리로 의도적으로 손댄 3파일 제외).원장 재측정 — 손으로 더하지 않았다
게이트가 R2/R4 로 찍는 실측값을 정규식으로 파싱해 넣었다.
internal/service/…99956da0e422cmd/control-plane/main.goe9bb2e6a3d8d…/main_test.go외92bb531c13c9로컬 전수 검증 (전부 rc=0)
정적 게이트 4종 · 양 모듈
gofmt -l(빈 출력) ·go vet3태그(기본/-tags=e2e/-tags=integration) ·go test ./.... 매핑 게이트 출력은 머지 직후와 바이트 동일(이 커밋이 매핑 축을 건드리지 않았다는 증거).