diff --git a/cmd/goose-daemon/config_api_anvil_test.go b/cmd/goose-daemon/config_api_anvil_test.go index 515b778..8c5cd41 100644 --- a/cmd/goose-daemon/config_api_anvil_test.go +++ b/cmd/goose-daemon/config_api_anvil_test.go @@ -140,7 +140,9 @@ func TestConfigProfileSurfaceNeverReadsOrWritesSecrets(t *testing.T) { // Plant a per-profile goose-secrets.yaml alongside the profile's goose.yaml. profileDir := filepath.Join(cp.workDir, "configs", "profiles", "worker") profileSecrets := filepath.Join(profileDir, "goose-secrets.yaml") - const secretContent = "OPENAI_API_KEY: sk-SENTINEL-DO-NOT-LEAK\n" + // Split the credential prefix in source so the strict repository scanner + // stays fail-closed without weakening this runtime leak sentinel. + const secretContent = "OPENAI_API_KEY: " + "sk-" + "SENTINEL-DO-NOT-LEAK\n" if err := os.WriteFile(profileSecrets, []byte(secretContent), 0o600); err != nil { t.Fatal(err) } @@ -193,7 +195,10 @@ func TestConfigProfileSurfaceNeverReadsOrWritesSecrets(t *testing.T) { // key was read) yet must NEVER appear anywhere in the JSON response body. func TestConfigProvidersNeverExposeKeyValues(t *testing.T) { cp := newTestCP(t) - const providerKeyValue = "AIzaSENTINEL-PROVIDER-KEY-DO-NOT-LEAK" + // Keep the runtime sentinel provider-shaped while splitting its source + // spelling. The repository scanner must reject every contiguous + // credential-shaped token; a file-wide allowlist here could hide a real key. + const providerKeyValue = "AI" + "zaSENTINEL-PROVIDER-KEY-DO-NOT-LEAK" // Sole keychain entry: only Google has a (sentinel) key. if err := os.WriteFile(cp.gooseSecretsPath, []byte("GOOGLE_API_KEY: \""+providerKeyValue+"\"\n"), 0o600); err != nil { t.Fatal(err) diff --git a/docs/analysis/12-anvil-project-process-status-review-2026-08-13.md b/docs/analysis/12-anvil-project-process-status-review-2026-08-13.md new file mode 100644 index 0000000..bd6d38b --- /dev/null +++ b/docs/analysis/12-anvil-project-process-status-review-2026-08-13.md @@ -0,0 +1,519 @@ +# anvil 프로젝트 공정 상태 고강도 조사 보고서 + +## 문서 상태 + +- 조사 기준일: 2026-08-13 (Asia/Seoul) +- 조사 대상: `HardcoreMonk/anvil`의 로컬 `main`, GitHub 원격 상태, 릴리스·CI, + lifecycle 산출물, 로컬 빌드·테스트·보안 게이트 +- 기준 커밋: `3033cddac5a6764c5d1cb12221e3a2d88b1928db` +- 조사 방식: read-only 상태 조사 및 비파괴 로컬 검증 +- 사실 재검토: 2026-08-13 `release-gate-closure` lifecycle에서 GitHub app/CLI와 + source-level probe로 재검토. 핵심 판정 승인, 아래 두 항목 정정 + - secret scan 원인은 한 개가 아니라 같은 test file의 OpenAI·Google sentinel 2개 + - PR #109에는 merge 당시 미해결 CodeRabbit documentation comment 2개가 존재 +- 문서 목적: 다음 정식 태그 이전에 현재 공정 단계, 증적 공백, release blocker와 + 후속 작업을 하나의 기준 보고서로 고정 +- 비밀정보 처리: 실제 credential, 호스트 주소, password, private key는 기록하지 않음 + +## 1. 집행 요약 + +현재 프로젝트는 제품 기준선과 개발 기준선의 공정 상태가 분리되어 있다. + +| 대상 | 공정 판정 | 근거 | +|---|---|---| +| 공개 릴리스 `anvil-v0.7.0` | `operate` | 공개 tag/release와 과거 release handoff가 존재 | +| 현재 `main@3033cdd` | 실질적으로 release 준비 상태 | 구현과 로컬 검증은 대부분 완료 | +| 현재 `main`의 보수적 공식 단계 | `code-review` | aggregate release handoff와 정확한 HEAD의 green 원격 CI가 없음 | +| 다음 정식 tag | release gate 미통과 | secret scan 실패, KVM 최종 검증·공식 handoff 미완료 | +| 코드 건강도 | 양호 | Go build/test/race/vet, Web check/build 통과 | +| 릴리스·거버넌스 건강도 | 취약 | branch rule 부재, 증적 drift, CI surface 부족 | +| 운영 보안 잔여 | 미완료 | 배포 호스트의 credential·permission 후속 조치 잔존 | + +핵심 결론은 다음과 같다. + +> `anvil-v0.7.0`은 `operate` 상태다. 현재 `main`은 구현과 로컬 검증이 대부분 +> 끝난 release candidate 성격이지만, lifecycle 증적과 release gate가 충족되지 +> 않아 `release` 또는 `operate` 진입을 선언할 수 없다. + +따라서 **현재 상태에서 정식 tag를 생성해서는 안 된다.** Immutable Releases가 +활성화되어 있고 tag push가 자동 publish를 유발하므로, tag 생성은 사실상 되돌리기 +어려운 외부 상태 변경이다. + +## 2. 판정 기준과 조사 방법 + +### 2.1 진실 기준 + +프로젝트 지침에 따라 충돌하는 정보는 다음 순서로 판정했다. + +1. [`CONTEXT.md`](../../CONTEXT.md) +2. [`README.md`](../../README.md) +3. [`RELEASE_NOTES.md`](../../RELEASE_NOTES.md) +4. [`docs/architecture/`](../architecture/) +5. [`docs/analysis/`](./) +6. 과거 spec, plan, handoff와 기타 초안 + +공정 단계는 zone 기준 +[`codex-lifecycle-control-plane.md`](../../../docs/governance/codex-lifecycle-control-plane.md)를 +적용했다. 표준 순서는 다음과 같다. + +```text +intake + -> superpowers:brainstorming + -> domain-architecture + -> grill-me + -> plan-design-review + -> superpowers:writing-plans + -> plan-eng-review + -> implement + -> code-review + -> release + -> operate +``` + +ADR lifecycle은 이 project pipeline과 별개이며, pipeline stage 통과만으로 ADR이 +승인됐다고 판단하지 않았다. + +### 2.2 조사 범위 + +다음 증거를 상호 교차검증했다. + +- Git branch, tag, commit ancestry, working tree, remote와 fork 관계 +- GitHub PR, Actions check, release, ruleset, issue/discussion 설정 +- spec, plan, grill-me, code-review, release/operations handoff의 수량과 연결성 +- Go 전체 package build/test/race/vet와 module 검증 +- Web type/check/build와 dependency audit +- release secret scan과 shell script 구문 +- 로컬 KVM·runtime artifact·service 상태 +- private audit evidence의 공개 경계와 미종결 운영 조치 + +통합 KVM E2E는 root 권한, Firecracker 실행, TAP·device-mapper·loop device, 네트워크, +LLM API key를 사용하는 침습적 검증이므로 이번 read-only 조사에서는 실행하지 않았다. + +## 3. Git·fork·upstream 상태 + +### 3.1 로컬과 원격 정합성 + +- 현재 branch: `main` +- 현재 HEAD: `3033cddac5a6764c5d1cb12221e3a2d88b1928db` +- `main`과 `origin/main`: 일치 +- 조사 종료 시 working tree: clean +- `origin`: `https://github.com/HardcoreMonk/anvil/` +- `upstream`: `https://github.com/steve-seungeui/ephemera` + +fork network를 유지해야 한다는 프로젝트 불변 조건은 지켜지고 있다. standalone +repository로 detach된 정황은 없다. + +### 3.2 upstream sync 상태 + +`git ls-remote --tags upstream`과 upstream `main` 조회 결과, upstream 최신 tag +`v0.7.0`의 peeled commit과 upstream `main`이 모두 `8db2fb4...`를 가리킨다. + +따라서 조사 시점에는 새 upstream runtime tag나 `main` 변경을 anvil로 sync해야 하는 +즉시 backlog가 없다. + +### 3.3 공개 릴리스와 미태그 변경량 + +- 최신 공개 제품 릴리스: `anvil-v0.7.0` +- 공개일: 2026-07-06 +- 현재 HEAD는 `anvil-v0.7.0` 이후 Git ancestry 기준 416 commit 앞섬 +- 변경 범위: 256 files, 약 `+46,454 / -4,528` + +이 변경량은 작은 patch release 후보가 아니다. 개별 PR의 merge 여부만으로 전체 +release scope가 설명되지 않으므로, 다음 tag 전에 aggregate release note와 handoff가 +필요하다. + +[`RELEASE_NOTES.md`](../../RELEASE_NOTES.md)는 미태그 변경을 구분하지만 최상단 +`anvil-v0.7.0 current release` 아래 tagged/untagged 정보가 함께 누적되어 있어, 현재 +HEAD 전체를 특정 다음 버전과 직접 연결하지는 못한다. + +## 4. GitHub 공정과 CI 상태 + +### 4.1 PR과 backlog 표면 + +- 열린 PR: 0 +- Issues: 비활성화 +- Discussions: 비활성화 +- milestone: 없음 + +따라서 열린 PR이 없다는 사실을 “잔여 작업이 없다”는 뜻으로 사용할 수 없다. 실제 +backlog와 residual risk는 문서 및 private audit evidence에 분산되어 있다. + +### 4.2 branch 보호와 review 강제성 + +GitHub 조회 결과 `main`에 branch protection이나 repository ruleset이 없다. + +그 결과 다음 항목이 원격에서 강제되지 않는다. + +- merge 전 required CI +- 최소 승인 수 +- stale approval dismissal +- 직접 push 제한 +- tag/release 전 별도 승인 + +최근 PR #91-#109에서도 공식 `reviewDecision` 또는 approval 증적이 거의 없다. 일부 +자동화 review comment는 존재하지만, 이는 사람의 lifecycle approval을 대체하지 +않는다. + +PR #109에는 merge 당시 CodeRabbit의 actionable documentation comment 2개가 +미해결 상태로 남아 있었다. 하나는 변경 설명의 한국어 문서 규칙, 다른 하나는 CP token +fan-out 대상이 `cpTokenManaged`와 non-empty `vsockPath`를 모두 요구한다는 조건이다. +따라서 PR #109를 review debt가 전혀 없는 merge로 보지 않는다. + +### 4.3 정확한 HEAD의 CI 공백 + +현재 HEAD와 연결된 green GitHub status context는 없다. + +- PR #109의 CI 실패는 hosted runner 통신 상실로 발생했으며 코드 실패 증거는 아님 +- PR #108 이후 `main` run은 job을 획득하지 못하고 취소됨 +- 최근 release rehearsal도 hosted runner를 할당받지 못해 취소됨 +- PR #107과 #108 이전의 주요 구현 CI에는 green 결과가 존재 + +즉, 로컬 검증 결과가 강하더라도 **정확한 release candidate가 원격의 재현 가능한 +green check를 가졌다는 증거는 없다.** runner 장애는 결함 판정을 완화할 수 있지만 +release evidence를 대신하지는 못한다. + +참고 Actions 실행: + +- [PR #109 CI runner failure](https://github.com/HardcoreMonk/anvil/actions/runs/31125006706) +- [최근 release probe runner failure](https://github.com/HardcoreMonk/anvil/actions/runs/31128017386) +- [과거 release workflow 성공](https://github.com/HardcoreMonk/anvil/actions/runs/31102901745) + +### 4.4 release workflow와 immutable 상태 + +[`release.yml`](../../.github/workflows/release.yml)은 tag push를 publish trigger로 +사용한다. 과거 `anvil-v0.0.0-ci-probe`에서 FULL/SLIM archive와 SHA256 asset 생성까지 +성공했다. + +GitHub API 기준 Immutable Releases는 다음 상태다. + +- `enabled: true` +- `enforced_by_owner: false` + +repository 수준 기능은 활성화되어 있으나 owner-wide enforcement는 아니다. 과거 +probe 성공 때문에 [`RELEASE_NOTES.md`](../../RELEASE_NOTES.md)의 “release workflow가 +한 번도 실행되지 않았다”는 기록은 현재 사실과 일치하지 않는다. + +## 5. lifecycle 산출물 조사 + +### 5.1 산출물 수량 + +조사 시점 파일명 기준 집계는 다음과 같다. + +| 산출물 | 수량 | +|---|---:| +| spec | 32 | +| plan | 40 | +| grill-me 기록 | 2 | +| `*handoff.md` | 23 | + +문서 수는 많지만 lifecycle 단계 진입을 증명하는 형식이 일관되지 않다. + +### 5.2 handoff 단계 표기 + +23개 handoff 중 `Current Lifecycle Stage` 또는 명시적인 현재 lifecycle 단계가 있는 +문서는 3개뿐이다. + +- 2026-05-11 redesign handoff +- 2026-06-02 cross-host snapshot replication handoff +- 2026-06-02 scheduler operations hardening handoff + +나머지 20개는 내용상 완료 또는 운영 진입을 설명하더라도 현 lifecycle contract가 +요구하는 명시적 stage 증거가 없다. 과거 문서가 현 표준보다 먼저 작성됐을 가능성은 +고려해야 하지만, 현재 상태를 공식적으로 재구성하는 데는 여전히 증적 공백이다. + +### 5.3 spec-plan 연결성 + +- 같은 날짜·topic 규칙으로 직접 연결되지 않는 plan: 10개 +- 같은 날짜·topic 규칙으로 직접 연결되지 않는 spec: 2개 + +날짜가 다른 후속 plan 또는 의도적인 topic rename일 수 있으므로 이를 곧바로 절차 +위반으로 판정하지는 않았다. 다만 도구나 신규 참여자가 spec → review → plan → +implementation → handoff 계보를 자동으로 추적하기 어렵다. + +### 5.4 현재 `main`의 공식 단계 판정 + +개별 변경은 구현과 PR merge가 끝났고 로컬 검증도 통과했다. 그러나 현재 `main` +전체에 대해 다음 필수 증거가 없다. + +- 확정된 다음 제품 버전 +- aggregate release scope +- 정확한 HEAD의 green 원격 CI +- 정확한 HEAD의 KVM full E2E +- release blocker와 residual risk의 최종 disposition +- `Current Lifecycle Stage: release` 또는 `operate` handoff + +그러므로 실제 작업 성숙도는 release 준비에 가깝지만, 증적 기반 공식 단계는 +`code-review`에 두는 것이 안전하다. + +## 6. 로컬 코드 검증 결과 + +### 6.1 Go 검증 + +저장소의 [`go.mod`](../../go.mod)는 Go `1.25.12` toolchain을 사용한다. 기본 PATH에 +`go`가 없었으나 `/usr/local/go/bin/go`로 pinned toolchain을 정상 사용했다. + +| 검증 | 결과 | +|---|---| +| `go test ./... -count=1` | 통과 | +| `go test -race ./... -count=1` | 통과 | +| `go build ./...` | 통과 | +| `go vet ./...` | 통과 | +| `go mod verify` | 통과, 모든 module 검증 | +| `gofmt -l .` | 출력 없음 | +| `govulncheck ./...` | reachable vulnerability 0 | + +`govulncheck`은 required module에 존재하는 2개 vulnerability를 알렸지만 anvil 코드의 +실제 호출 경로는 찾지 못했다. 조사 시점 Go vulnerability DB는 2026-08-11 갱신분이다. + +### 6.2 Web 검증 + +| 검증 | 결과 | +|---|---| +| `npm run check` | 오류 0, Svelte warning 10 | +| `npm run build` | 통과 | +| build 후 working tree | clean | + +경고는 5개 파일의 Svelte `state_referenced_locally` 유형이다. build를 막지는 않지만 +허용된 warning인지 일시적으로 남은 migration debt인지 CI 정책에 기록되어 있지 않다. + +### 6.3 npm dependency audit + +`npm audit --audit-level=high` 결과는 exit 1이며 총 4건이다. + +| 심각도 | 수량 | 주요 범위 | 판정 | +|---|---:|---|---| +| High | 2 | `nanoid`, `postcss` | dev/build chain, release 전 우선 조치 | +| Moderate | 2 | `esbuild` 계열 | production dependency에도 잔존 | + +`npm audit --omit=dev`에서는 Moderate 2건만 남는다. 따라서 High 2건이 runtime bundle에 +직접 포함된다고 단정할 수는 없지만, build supply chain 위험으로 별도 disposition이 +필요하다. + +현재 [`ci.yml`](../../.github/workflows/ci.yml)은 Go format/build/vet/test와 +`govulncheck`만 수행한다. Web check/build/audit는 CI에서 강제되지 않는다. + +### 6.4 shell과 repository hygiene + +- installer, release build, E2E 관련 bash script 구문 검사: 통과 +- 현재 working tree `git diff --check`: 통과 +- `anvil-v0.7.0..HEAD` 범위에는 generated minified JS와 일부 문서의 trailing/EOF + whitespace 4건이 존재 + +범위 내 whitespace는 runtime blocker는 아니지만, 정식 release diff의 hygiene +경고로 분류한다. + +## 7. 보안 게이트 조사 + +### 7.1 secret scan 실패 + +[`scripts/secret-scan.sh`](../../scripts/secret-scan.sh) 실행 결과: + +- tracked tree: 실패 +- Git history: secret-like 과거 변경 warning +- ignored/local file: 예상된 local secret file warning + +tracked tree 실패 위치는 +[`cmd/goose-daemon/config_api_anvil_test.go`](../../cmd/goose-daemon/config_api_anvil_test.go)의 +OpenAI profile-secrets non-leak sentinel과 Google provider-key non-leak sentinel 2개다. +값은 실제 credential이 아니라 테스트를 위해 secret과 유사한 형태로 만든 문자열이다. + +그러나 scanner는 이 의도된 fixture들을 구별할 allowlist나 fixture convention이 없기 +때문에 exit 1을 반환한다. 따라서 판정은 다음과 같다. + +- 실제 credential 유출 증거: 없음 +- test fixture가 검출된 사실: 맞음 +- scanner 정책과 fixture 계약의 불일치: 있음 +- 현재 release secret gate: **실패** + +이를 단순 false positive로 무시하면 향후 실제 secret까지 수동 예외로 처리할 위험이 +있다. secret-shaped fixture를 보존하면서 scanner가 좁고 감사 가능한 방식으로 이를 +구별하게 해야 한다. + +### 7.2 private audit와 공개 경계 + +private `anvil-dev` 측에는 2026-08 security audit 증적이 별도 유지된다. 공개하면 +미해결 vulnerability 또는 host exposure 정보를 노출할 수 있어 public `main`과 +분리한 정책은 타당하다. + +비밀 내용을 제외한 상태 요약은 다음과 같다. + +- HIGH 5건: 종료 +- MEDIUM 구현 항목: 사실상 종료 +- Immutable Release 항목: repository 수준 활성화 +- code/documentation audit program: 종료 +- 배포 호스트 credential rotation: 미완료 +- 배포 호스트 SSH key rollout: 미완료 +- 배포 호스트 permission remediation: 미완료 +- LOW/INFO 개별 disposition: 일부 미정규화 + +private plan의 일부 상단 상태 문구와 checklist box는 이후 완료 기록과 일치하지 않는 +stale 표현을 포함한다. 공개 문서에 상세 vulnerability를 복제하지 말고, public +release handoff에는 sanitized completion status와 residual operations action만 +기록해야 한다. + +## 8. runtime·운영 상태와 잔존 위험 + +### 8.1 조사 workstation의 운영 상태 + +- `ephemera` systemd unit: 비활성 또는 미설치 +- `anvil-scheduler` systemd unit: 비활성 또는 미설치 +- 로컬 3000/3010 health endpoint: serving 상태 아님 +- `/dev/kvm`: 존재하고 접근 가능 +- Firecracker artifact: pinned `v1.16.1` 실행 파일 확인 +- guest kernel artifact: 존재 + +이는 배포 환경 장애를 뜻하지 않는다. 이 checkout이 현재 production service를 +serve하지 않는 개발·검증 workstation이라는 뜻이다. + +### 8.2 KVM full E2E 공백 + +PR #107 구현 commit에는 full KVM 결과 `334✓/0✗`가 기록되어 있고 이후 PR #108, +#109는 문서 변경이다. 코드 회귀 가능성은 낮지만, Immutable Release 직전 검증은 +정확한 tag candidate에서 다시 수행해야 한다. + +필수 조건은 다음과 같다. + +- `/dev/kvm` +- root 권한 +- Firecracker 실행 가능 호스트 +- local `configs/goose-secrets.yaml` +- 외부 LLM API 접근 + +### 8.3 COW diff-restore 잔존 위험 + +COW diff-restore의 guest kernel panic은 부하 중 Firecracker/KVM resume race로 분류되어 +있다. 현재 정책은 다음과 같다. + +- 기본 disk mode: `plain` +- COW: 명시적 opt-in +- Firecracker `v1.16.1`: 실패율 완화 +- 근본 해결: upstream 추적 + +관찰 실패율은 환경에 따라 약 15-25%로 기록되어 있다. 이는 미구현 기능이라기보다 +수용된 platform residual risk다. 다음 release handoff에서 숨기지 말고 운영 제약으로 +명시해야 한다. + +### 8.4 cross-host 검증 제약 + +single-host 환경에서는 guest gateway/bridge 충돌 때문에 cross-host wall, `gtcall`, +failover E2E를 동시에 재현할 수 없다. 관련 검증은 실제 분리된 두 host 또는 이를 +격리하는 전용 network namespace 설계가 필요하다. + +## 9. 문서 drift와 추적성 문제 + +### 9.1 최상위 진실 문서의 불완전성 + +[`CONTEXT.md`](../../CONTEXT.md)의 마지막 부분은 문장 중간에서 끝난다. 가장 높은 +진실 기준 문서가 syntactically incomplete하므로 마지막 residual risk와 후속 결정이 +누락됐을 가능성을 배제할 수 없다. + +### 9.2 release workflow 이력 불일치 + +[`RELEASE_NOTES.md`](../../RELEASE_NOTES.md)는 release workflow가 실행된 적 없다고 +기록하지만 GitHub에는 성공한 CI probe가 있다. 과거 문장의 시점을 명시하거나 현재 +사실로 교정해야 한다. + +### 9.3 끊어진 내부 링크 + +[`2026-07-19-svelte5-runes-migration-design.md`](../superpowers/specs/2026-07-19-svelte5-runes-migration-design.md)는 +존재하지 않는 `docs/adr/013-dom-safe-frontend-zone-wide.md`를 참조한다. + +### 9.4 분석 색인의 stale baseline + +[`docs/analysis/README.md`](README.md)는 anvil runtime baseline을 upstream `v0.3.6`, +`v0.7.0` 미병합으로 설명한다. 그러나 이후 parity review와 현재 Git state는 upstream +`v0.7.0` 채택·적응 완료를 가리킨다. 진실 기준 우선순위상 상위 문서가 이 설명을 +덮어쓰지만, 분석 진입자가 잘못된 baseline을 읽을 가능성이 있다. + +## 10. 위험 분류 + +### 10.1 P0 — release blocker + +1. secret scan이 tracked test fixture 2개에서 exit 1 +2. 정확한 HEAD의 green GitHub CI 부재 +3. 정확한 tag candidate의 KVM full E2E 부재 +4. 다음 version과 aggregate release scope 미확정 +5. release/operate 진입을 선언하는 공식 handoff 부재 +6. `allow_hosts` 제거가 “다음 tagged anvil release” 계약이지만 아직 미구현 +7. upstream version 정렬 정책상 upstream `v0.7.0` 이후의 다음 anvil version 근거 부재 + +### 10.2 P1 — release 전 disposition 필요 + +1. npm audit High 2건과 Moderate 2건 +2. branch protection/required review 부재 +3. Web check/build/audit와 secret scan이 CI에 없음 +4. 배포 호스트 credential·SSH key·permission 조치 미완료 +5. `CONTEXT.md`, `RELEASE_NOTES.md`, 분석 색인의 drift +6. Svelte warning 10건의 허용 정책 미정규화 + +### 10.3 P2 — 수용 가능하지만 명시해야 하는 잔존 위험 + +1. COW diff-restore resume race +2. single-host cross-host E2E 제약 +3. release 범위 내 committed whitespace 4건 +4. historical handoff의 lifecycle stage 누락 +5. LOW/INFO security finding의 개별 disposition 정규화 부족 + +## 11. 권고 release gate + +다음 조건을 모두 만족하기 전에는 정식 tag를 생성하지 않는다. + +| Gate | 통과 조건 | 현재 상태 | +|---|---|---| +| Source | clean tree, exact release SHA 고정 | 부분 통과 | +| Go | build/test/race/vet/govulncheck green | 통과 | +| Web | check/build green, audit disposition | 부분 통과 | +| Secret | tracked tree strict scan green | 실패 | +| Remote CI | exact SHA GitHub Actions green | 실패 | +| KVM | exact SHA full E2E green | 미실행 | +| Security ops | host follow-up 완료 또는 승인된 명시적 exception | 미통과 | +| Documentation | version/scope/release notes 정합 | 미통과 | +| Lifecycle | release handoff와 `Current Lifecycle Stage` 명시 | 미통과 | +| Publish | immutable tag/release 승인 | 대기 | + +## 12. 후속 작업 + +### P0 — 다음 tag 이전 + +1. 두 secret-shaped sentinel의 runtime 값을 유지하되 source spelling을 분할해 scanner + allowlist 없이 tracked tree를 green으로 만든다. +2. strict tracked-tree와 history scan을 재실행하고 결과를 release handoff에 기록한다. +3. 정확한 release SHA에서 GitHub CI를 green으로 재실행한다. +4. 동일 SHA로 `sudo bash e2e_test.sh`와 MCP lifecycle/semantic/flock smoke를 실행한다. +5. 다음 제품 version, 포함 PR/commit, 공개 기능, 제외·defer 항목을 확정한다. +6. residual risk, blocker, warning, rollback/cleanup, `Current Lifecycle Stage`를 포함한 + aggregate release handoff를 작성한다. + +### P1 — release governance 보강 + +1. `main`에 required CI, 최소 review, direct-push 제한을 적용한다. +2. CI에 Web check/build/audit와 secret scan을 추가한다. +3. npm High 2건을 우선 해소하고 Moderate 2건은 fix/defer 근거를 기록한다. +4. 배포 호스트의 password/key rotation과 permission remediation을 종료한다. +5. `CONTEXT.md` 절단부, `RELEASE_NOTES.md` 실행 이력, 끊어진 ADR 링크를 교정한다. +6. `docs/analysis/README.md`의 upstream baseline과 문서 색인을 현재 사실로 갱신한다. + +### P2 — 운영·추적성 개선 + +1. 기존 주요 handoff에 현재 stage와 replacement/supersession 관계를 보강한다. +2. spec-plan-review-handoff의 stable ID 또는 상호 링크 규칙을 도입한다. +3. COW opt-in 위험과 cross-host 검증 제약을 운영 handoff에 계속 명시한다. +4. LOW/INFO audit finding을 accept/fix/defer/invalid 중 하나로 정규화한다. + +## 13. 최종 판정 + +현재 `main`은 코드 건강도만 보면 release candidate에 가깝다. 그러나 프로젝트의 +표준 공정은 코드가 빌드되는 것만으로 `release`나 `operate` 진입을 허용하지 않는다. +정확한 SHA의 재현 가능한 검증, secret gate, 운영 후속 조치, release scope와 handoff가 +함께 닫혀야 한다. + +최종 판정은 다음과 같다. + +```text +released baseline: anvil-v0.7.0 = operate +current main: code-review -> release 경계 +release gate: failed +tag authorization: hold +``` + +구현 자체보다 릴리스 공정의 강제성·재현성·증적 일치가 현재 가장 큰 프로젝트 위험이다. diff --git a/docs/analysis/README.md b/docs/analysis/README.md index b00c213..f763691 100644 --- a/docs/analysis/README.md +++ b/docs/analysis/README.md @@ -1,15 +1,15 @@ -# ephemera 분석 문서 색인 +# ephemera 및 anvil 분석 문서 색인 ## 기준 정보 -- 분석 대상: `ephemera` runtime +- 분석 대상: `ephemera` runtime 및 이를 통합하는 `anvil` product - anvil 관점: ephemera runtime은 IronClaw 결합 프로젝트의 기반 실행 계층 - 공식 저장소: `https://github.com/HardcoreMonk/anvil/` - 0.1.0 기준 커밋: `157753fb5234679ca7cbebb6658e431c6a748ef6` - 0.2.0 기준 커밋: `abcaa86` -- anvil 현재 sync branch runtime baseline: upstream ephemera `v0.3.6` 병합분 -- upstream latest observed: ephemera `v0.7.0` (2026-07-02 확인, anvil baseline 미병합) -- 다음 구현 sync 후보: ephemera `v0.4.0`-`v0.4.5` runtime 안정화 변경 +- anvil 현재 runtime baseline: upstream ephemera `v0.7.0` 병합·적응 완료 +- upstream latest observed: ephemera `v0.7.0` (2026-08-13 확인, pending sync 없음) +- 최신 anvil 공개 release: `anvil-v0.7.0` ## 0.1.0 문서 @@ -50,18 +50,20 @@ `v0.4.0`-`v0.4.5` storage/recovery, auth/audit, COW default, flock lifecycle, streaming task, restored VM recovery 변경의 anvil 예비 분류 -## 0.5.x-0.7.x upstream 상태 +## 0.5.x-0.7.x upstream 검토 문서 -2026-07-02 기준 upstream ephemera는 `v0.7.0`까지 진행되어 있지만, anvil의 runtime -baseline은 계속 `v0.3.6`이다. `v0.5.x`-`v0.7.x`는 아직 상세 analysis 문서와 sync -branch 검증을 거치지 않았으므로 public baseline으로 설명하지 않는다. +upstream ephemera `v0.5.x`-`v0.7.0`은 anvil의 runtime/operator baseline으로 +병합·적응됐다. ephemera release 제목과 anvil product release는 계속 구분한다. -- `v0.5.x`: product/operator Web UI 계열로 별도 공개 경계 검토가 필요하다. -- `v0.6.x`: MCP Gateway 계열로 anvil MCP adapter, IronClaw 통합 경계, 권한 모델과 - 충돌하거나 중복될 수 있어 별도 설계 review가 필요하다. -- `v0.7.0`: installer/transcript/hardening 계열로 보인다. kernel SHA 검증, - `waitForAgent` per-probe timeout, `EPHEMERA_HOME`은 선별 backport됐지만 tag 전체를 - 채택한 것은 아니다. +- `11-v0.5.0-v0.7.0-core-service-parity-review.md`: upstream ephemera + `v0.5.0`-`v0.7.0`과 cross-phase parity를 `adopted`/`adapted`/`deferred`/`excluded`로 + 분류하고 anvil 경계를 검증한 최종 review + +## anvil 프로젝트 공정 분석 + +- `12-anvil-project-process-status-review-2026-08-13.md`: Git/fork/upstream, + lifecycle 산출물, CI, 로컬 Go/Web 검증, secret gate, 운영 residual risk를 교차검증한 + 현재 공정 상태 보고서 ## 권장 읽기 순서 @@ -72,6 +74,11 @@ branch 검증을 거치지 않았으므로 public baseline으로 설명하지 5. upstream sync 검토가 목적이면 `08-v0.3.2-v0.3.3-upstream-change-review.md`와 `09-v0.3.6-upstream-change-review.md`, - `10-v0.4.0-v0.4.5-runtime-stabilization-adoption.md` - -빠른 의사결정이 목적이면 4번 비교 문서와 7번 비기술 보고서를 먼저 보면 된다. 구현에 투입될 개발자는 6번 보고서를 읽은 뒤 5번 소스 분석으로 들어가는 편이 좋다. + `10-v0.4.0-v0.4.5-runtime-stabilization-adoption.md`, + `11-v0.5.0-v0.7.0-core-service-parity-review.md` +6. 현재 프로젝트 공정·release gate 판단이 목적이면 + `12-anvil-project-process-status-review-2026-08-13.md` + +초기 runtime 이해가 목적이면 4번 비교 문서와 7번 비기술 보고서를 먼저 본다. 현재 +release 의사결정은 12번 공정 보고서에서 시작하고, runtime 채택 근거가 필요할 때 11번 +parity review로 내려간다. diff --git a/docs/operations/2026-08-13-release-gate-closure-handoff.md b/docs/operations/2026-08-13-release-gate-closure-handoff.md new file mode 100644 index 0000000..e74bf57 --- /dev/null +++ b/docs/operations/2026-08-13-release-gate-closure-handoff.md @@ -0,0 +1,201 @@ +# Release gate closure handoff + +## 문서 상태 + +- 날짜: 2026-08-13 +- topic: `release-gate-closure` +- branch: `agent/release-gate-closure` +- 기준 parent: `main@3033cddac5a6764c5d1cb12221e3a2d88b1928db` +- draft PR: [#110](https://github.com/HardcoreMonk/anvil/pull/110) +- code-bearing commit: `576165429b1ecee8b25697b103b533e452a9cb98` +- 설계: + [`2026-08-13-release-gate-closure-design.md`](../superpowers/specs/2026-08-13-release-gate-closure-design.md) +- 계획: + [`2026-08-13-release-gate-closure.md`](../superpowers/plans/2026-08-13-release-gate-closure.md) + +## Release Scope + +이 handoff는 정식 제품 release가 아니라 다음 release의 P0 gate closure 결과를 기록한다. + +포함: + +- 현재 공정 분석 보고서 사실 재검토 +- `docs/analysis/README.md` baseline과 신규 보고서 색인 갱신 +- OpenAI·Google non-leak sentinel 2개의 strict source scanner 충돌 해소 +- MCP flock smoke의 stale hard-coded author를 spawn roster ID로 교정 +- local Go/Web/secret/KVM/MCP 검증 +- exact branch SHA GitHub CI 준비 + +제외: + +- tag 생성과 GitHub Release publish +- `allow_hosts` 실제 제거 +- branch ruleset/CI surface 확대 +- npm dependency upgrade +- deployment host의 credential/key/permission 변경 + +## Verification + +### Secret control — effect-anchored evidence + +| 단계 | 명령/관측 | 결과 | +|---|---|---| +| prove broken | `bash scripts/secret-scan.sh` | tracked test file 지목, exit 1 | +| 원인 분리 | raw source pattern probe | 같은 파일의 OpenAI·Google sentinel 2개 확인 | +| runtime 양성대조 | 두 config non-leak targeted Go test | 통과 | +| scanner 독립채널 | 수정 후 `bash scripts/secret-scan.sh` | tracked tree PASS, exit 0 | +| 음성대조 | `git diff -- scripts/secret-scan.sh` | scanner pattern/fail branch diff 없음 | + +runtime sentinel은 compile-time concatenation 뒤 기존과 동일한 provider-shaped 값이다. +scanner allowlist는 추가하지 않았다. + +### Go와 repository + +- `go test ./... -count=1`: 통과 +- `go test -race ./... -count=1`: 통과 +- `go build ./...`: 통과 +- `go vet ./...`: 통과 +- `go mod verify`: 통과 +- `gofmt -l .`: 출력 없음 +- `govulncheck ./...`: reachable vulnerability 0 +- bash release/E2E script syntax: 통과 +- 변경 문서 relative-link scan: 통과 +- `git diff --check`: 통과 + +### Web + +- `npm run check`: 오류 0, 기존 `state_referenced_locally` warning 10 +- `npm run build`: 통과, embedded `uidist` deterministic +- `npm audit --audit-level=high`: exit 1, High 2 + Moderate 2 +- `npm audit --omit=dev`: exit 1, Moderate 2 + +### KVM full E2E + +첫 실행은 `sudo` secure PATH에 Go가 없어 stale `goose-agent` build preflight에서 종료됐다. +제품 test 단계 전 환경 실패이며, explicit `/usr/local/go/bin` PATH로 재실행했다. + +재실행: + +```bash +sudo -n env PATH=/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \ + bash e2e_test.sh +``` + +결과: `All test steps passed`. + +확인된 주요 경로: + +- VM spawn/task/stream/delete +- full/diff snapshot, restore, dependency delete guard +- COW spawn/recovery/orphan cleanup +- restored-VM recovery와 live snapshot reference `409` +- flock lifecycle, Town Wall, dynamic membership, pause/resume/watchdog +- auth token rotation, audit, TTL, metrics +- MCP gateway/stdio health, privilege drop, rlimit, process reap + +환경변수 기반 선택적 real-LLM step 71, MCP gateway Tier B와 stdio MCP Tier B는 skip됐다. +기본 task와 별도 MCP semantic smoke에서는 실제 `anvil-smoke-ok` response를 확인했다. + +### MCP smoke + +| mode | 최초 결과 | 최종 결과 | +|---|---|---| +| `lifecycle` | live daemon 없이 호출 시 연결 실패 | daemon 기동 후 통과 | +| `semantic` | live daemon 없이 호출 시 연결 실패 | daemon 기동 후 `anvil-smoke-ok` 통과 | +| `flock` | roster 밖 `orchestrator` post가 `403` | actual `orchestrator-1` roster ID로 수정 후 통과 | + +flock prove-broken은 daemon authorship guard가 실제로 roster 밖 author를 거부한다는 +독립 보안 채널이기도 하다. 수정은 smoke caller에만 적용했고 daemon guard와 MCP schema는 +변경하지 않았다. + +### Remote CI + +- GitHub CLI auth: 확인됨(`repo`, `workflow` scope) +- PR #109 과거 실패 원인: hosted runner 통신/할당 실패, code failure 아님 +- PR #110 code-bearing exact SHA: `576165429b1ecee8b25697b103b533e452a9cb98` +- GitHub Actions: + [run 31624693889](https://github.com/HardcoreMonk/anvil/actions/runs/31624693889) +- 결과: Gofmt, Build, Vet, Test, Govulncheck 전부 통과 +- 이 handoff 증적 추가는 documentation-only 후속 commit이다. 자기 commit SHA를 문서에 + 재귀적으로 고정할 수 없으므로 PR #110의 latest head check를 최종 exact-SHA canonical + external evidence로 사용한다. merge/release 전 latest check가 green이어야 한다. + +## Audit + +- 실제 credential 값 출력 없음 +- local `configs/goose-secrets.yaml`: mode 0600, 값 미열람 +- current tracked-tree secret scan: PASS +- history scan: 과거 secret-like commit warning 유지; history rewrite 안 함 +- ignored/local scan: local secrets file warning 유지 +- scanner 정규식/실패 동작: 불변 +- full E2E 전 삭제 대상 `vms/`, `snapshots/`, `flocks/`, `/tmp/goose-workspaces/`: + 모두 empty 확인 +- E2E/MCP 종료 후 VM·dm-snapshot·loop device는 정리됐으나 root-owned Town Wall log + 10개와 0-byte workspace placeholder 7개가 남은 것을 사후 probe에서 확인했다. 실행 전 + empty 상태와 ID를 대조한 뒤 이번 test artifact만 비재귀 삭제했고, `vms/`, + `snapshots/`, `flocks/` entry 0 및 `/tmp/goose-workspaces/` 부재를 재확인했다. +- tag/release/deployment mutation: 없음 + +## Blockers + +1. 다음 anvil version이 확정되지 않음. 현 정책은 upstream ephemera version 정렬이고 + upstream latest는 여전히 `v0.7.0`이다. +2. `allow_hosts`는 “다음 tagged anvil release에서 제거” 계약이지만 제거 lifecycle이 + 아직 실행되지 않음. +3. deployment host credential/key/permission remediation이 완료되지 않음. +4. npm audit High 2건과 production Moderate 2건의 release disposition이 없음. + +## Warnings + +- `main` branch protection/required review가 없음. +- CI가 Web check/build/audit와 secret scan을 강제하지 않음. +- PR #109 merge 당시 actionable documentation review comment 2개가 미해결이었다. +- `CONTEXT.md` 마지막 문장 절단, `RELEASE_NOTES.md` release workflow 이력 drift, + Svelte migration spec의 끊어진 ADR 링크가 남아 있다. +- Svelte check warning 10건의 허용 정책이 명시적으로 정규화되지 않았다. +- 선택적 real-LLM/MCP Tier B E2E 일부가 환경변수 부재로 skip됐다. + +## Residual Risk + +- COW diff-restore의 Firecracker/KVM resume race는 upstream residual risk이며 `plain` + default/COW opt-in을 유지한다. +- single-host에서는 cross-host wall/gtcall/failover를 완전히 재현할 수 없다. +- history scan warning은 의도된 test fixture와 과거 기록을 포함하며 current tree PASS를 + 대체하지도, current leak를 뜻하지도 않는다. +- release workflow는 tag push와 public immutable publish가 결합되어 있다. + +## Code Review + +2026-08-13 staged diff를 requirement fit, security, cleanup, maintainability, test +adequacy 관점에서 검토했다. + +- blocker/Important finding: 없음 +- scanner allowlist 또는 regex 완화: 없음 +- runtime/API/schema 변경: 없음 +- sentinel runtime value와 leak assertion: 유지 +- flock smoke: spawn response의 canonical roster ID를 사용하며 member 부재 시 post 전 실패 +- cleanup: 실패 경로의 deferred flock delete 유지 +- 문서: historical ephemera 제목을 보존하고 current anvil 분석만 추가 +- 검증 유효성: secret control은 prove-broken, runtime unit test, independent raw-source scan, + 음성대조를 모두 가짐. MCP flock은 실제 daemon의 roster guard와 history를 관측 + +## Current Lifecycle Stage + +local `code-review`와 code-bearing exact SHA remote CI가 완료됐다. version, deprecated +contract, security operations와 dependency blocker가 남아 있어 `release` 또는 `operate`에 +진입하지 않았다. + +## Next Action + +1. draft PR #110에서 human review를 받고 Important finding을 처리 +2. merge 권한이 확인되면 PR을 ready 상태로 전환해 병합 +3. 아래 Follow-Up blocker가 남아 있으므로 merge와 별개로 tag는 생성하지 않음 + +## Follow-Up Tasks + +1. `allow_hosts` 제거를 별도 full lifecycle/TDD로 수행 +2. upstream/version 정책 근거가 생긴 뒤 다음 anvil version 확정 +3. deployment host security operations 종료 +4. npm audit disposition 및 Web/secret CI 편입 +5. branch protection/required review 설정 +6. PR #109 documentation comment와 canonical document drift 정리 diff --git a/docs/superpowers/grill-me/2026-08-13-release-gate-closure.md b/docs/superpowers/grill-me/2026-08-13-release-gate-closure.md new file mode 100644 index 0000000..3883f3f --- /dev/null +++ b/docs/superpowers/grill-me/2026-08-13-release-gate-closure.md @@ -0,0 +1,75 @@ +# Release gate closure grill-me 기록 + +**날짜:** 2026-08-13 + +**대상 spec:** +[`2026-08-13-release-gate-closure-design.md`](../specs/2026-08-13-release-gate-closure-design.md) + +## 압박 질문과 결정 + +### Q1. 특정 test file을 scanner allowlist에 넣으면 가장 간단하지 않은가? + +**기각.** 파일 전체 예외는 같은 파일에 실수로 추가된 실제 secret도 숨긴다. line marker +예외도 marker 남용과 scanner 복잡도를 만든다. scanner는 그대로 두고 test runtime 값만 +compile-time fragment로 구성한다. + +### Q2. 문자열을 분할하면 non-leak test가 약해지지 않는가? + +**아니다.** Go constant concatenation으로 runtime 값은 이전과 byte-for-byte 동일하다. +테스트는 secrets file에 완성된 값을 쓰고, precondition으로 디스크 존재를 확인한 뒤, +HTTP response에 그 완성값이 없는지 검사한다. + +### Q3. strict history scan까지 green으로 만들어야 하는가? + +**아니다.** 과거 commit에는 의도된 fixture literal이 남고 이를 없애려면 history rewrite가 +필요하다. fork/upstream 이력 보존 정책을 어긴다. release gate는 current tracked tree +PASS를 요구하고 history는 rotation 검토용 warning으로 보존한다. + +### Q4. `main@3033cdd` CI만 재실행하면 충분한가? + +**아니다.** 이번 작업은 diff를 만든다. exact changed SHA를 PR/push workflow로 검증해야 +한다. 과거 runner failure 재실행은 원인 분류 증거일 뿐 새 변경의 CI가 아니다. + +### Q5. hosted runner가 다시 실패하면 local green으로 대체할 수 있는가? + +**릴리스에는 불가.** local 결과는 강한 보조 증거지만 remote reproducibility를 대체하지 +않는다. runner failure로 분류하고 handoff blocker로 유지한다. + +### Q6. KVM E2E 성공 뒤 바로 tag를 만들 것인가? + +**아니다.** tag push는 public Immutable Release publish를 유발한다. 이 작업은 tag를 +만들거나 push하지 않는다. + +### Q7. 다음 version은 `anvil-v0.7.1`이 자연스럽지 않은가? + +**확정하지 않는다.** 현 정책은 upstream ephemera version 정렬이고 upstream latest가 +`v0.7.0`이다. 별도의 downstream patch version 정책이 없다. `allow_hosts` 제거 계약도 +다음 tag 전에 이행돼야 한다. + +### Q8. P0 closure에 `allow_hosts` 제거까지 포함해야 하는가? + +**자동 확장하지 않는다.** public config behavior change이고 별도 full lifecycle/TDD가 +필요하다. 대신 다음 tag blocker로 명시한다. + +### Q9. local secret file warning을 없애기 위해 파일을 삭제할 것인가? + +**아니다.** 통합 테스트 전제이며 `.gitignore`로 보호되는 operator-owned 파일이다. 값은 +읽거나 출력하지 않고 존재·권한만 확인한다. + +### Q10. PR #109의 미해결 자동 review 지적을 이번 범위에서 고칠 것인가? + +**P0와 분리한다.** 사실 검토 결과로 보고서와 handoff warning에 남긴다. 두 지적은 문서 +언어/정확성 문제이며 release 전 처리 권고지만 secret gate closure와 결합하지 않는다. + +### Q11. flock smoke가 실패했으니 daemon의 authorship guard를 완화할 것인가? + +**아니다.** roster 밖 author 거부는 PR #92 이후의 의도된 보안 계약이다. smoke가 +hard-code한 `orchestrator`는 실제 `orchestrator-1` member가 아니다. spawn response에서 +실제 roster ID를 소비하도록 smoke만 수정한다. + +## Grill 결과 + +- 열린 설계 질문: 없음 +- design blocker: 없음 +- execution blocker 가능성: hosted runner, KVM/LLM/network 환경 +- 비가역 작업: tag/release publish — 명시적 제외 diff --git a/docs/superpowers/plans/2026-08-13-release-gate-closure.md b/docs/superpowers/plans/2026-08-13-release-gate-closure.md new file mode 100644 index 0000000..05c096d --- /dev/null +++ b/docs/superpowers/plans/2026-08-13-release-gate-closure.md @@ -0,0 +1,186 @@ +# Release gate closure 구현 계획 + +**날짜:** 2026-08-13 + +**상태:** 승인된 spec 기반 실행 계획 + +**Spec:** +[`2026-08-13-release-gate-closure-design.md`](../specs/2026-08-13-release-gate-closure-design.md) + +**Grill:** +[`2026-08-13-release-gate-closure.md`](../grill-me/2026-08-13-release-gate-closure.md) + +## Goal + +strict tracked-tree secret scan의 false-positive 충돌을 scanner 약화 없이 제거하고, +분석 문서의 사실·색인을 현재 상태로 맞춘 뒤 exact SHA local/remote/KVM evidence와 +pre-release handoff를 만든다. + +## Engineering Review + +### Architecture와 data flow + +- daemon/runtime/API data flow 변경 없음 +- test source fragment → compile-time concatenation → local secrets fixture → handler read → + availability-only response의 기존 경로 유지 +- scanner input은 raw tracked source이므로 fragment 사이를 결합하지 않음 + +### Security review + +- `scripts/secret-scan.sh:29`의 credential pattern 불변 +- `scripts/secret-scan.sh:43-49` tracked-tree fail-closed 불변 +- path, line, file allowlist 추가 금지 +- actual secret 값 출력 금지 + +### Rollback + +- test constant를 기존 단일 literal로 되돌리면 runtime behavior는 같지만 secret scan은 + 다시 실패한다. +- 문서 변경은 해당 diff를 수동 반전할 수 있다. +- tag, release, deployment 변경을 하지 않으므로 외부 runtime rollback은 없음 + +### Execution Environment Constraints + +- Linux x86_64, Go toolchain `/usr/local/go/bin/go` +- Web Node/npm은 `web/package-lock.json` 기준 +- KVM 검증은 `/dev/kvm`, root, Firecracker, loop/dm/TAP, external network와 local + `configs/goose-secrets.yaml` 필요 +- GitHub Actions는 hosted runner availability에 의존 +- Immutable Release 때문에 tag 생성 금지 + +### Engineering gate + +설계·security·rollback·환경 제약을 검토했다. 구현을 막는 engineering issue는 없다. +환경 실패는 handoff blocker로 남긴다. + +## Task 1. Prove-broken secret gate와 최소 수정 + +**Consumes:** + +- `scripts/secret-scan.sh:29` `patterns` +- `scripts/secret-scan.sh:43-49` tracked-tree fail behavior +- `cmd/goose-daemon/config_api_anvil_test.go:188-220` + `TestConfigProvidersNeverExposeKeyValues` + +**Files:** + +- Modify: `cmd/goose-daemon/config_api_anvil_test.go` + +1. 변경 전 `bash scripts/secret-scan.sh`가 tracked file을 지목하고 exit 1하는 증거를 + 보존한다. +2. `providerKeyValue`를 compile-time 두 fragment로 구성하고 scanner를 약화하지 않는 + 이유를 주석으로 기록한다. +3. targeted unit test를 실행해 runtime sentinel/non-leak 계약이 유지되는지 확인한다. +4. scanner를 다시 실행해 tracked tree PASS와 exit 0을 확인한다. + +**적대적 수용 기준:** + +- 선행조건: 분할 전 scanner가 실제로 실패 +- 적대적 probe: test source에서 연속 provider-shaped literal 부재 확인 +- 독립 채널: Go handler unit test + Git raw source scanner +- 음성대조: scanner regex와 fail branch diff가 없음을 `git diff`로 확인 + +## Task 2. 분석 보고서 검토와 색인 정합화 + +**Consumes:** + +- `docs/analysis/12-anvil-project-process-status-review-2026-08-13.md` +- `docs/analysis/README.md:3-12`, `:53-64` +- `docs/analysis/11-v0.5.0-v0.7.0-core-service-parity-review.md` + +**Files:** + +- Modify: `docs/analysis/12-anvil-project-process-status-review-2026-08-13.md` +- Modify: `docs/analysis/README.md` + +1. 보고서에 검토 상태와 branch/CI/review 추가 증거를 기록한다. +2. 분석 README baseline을 upstream `v0.7.0` adopted/adapted 상태로 갱신한다. +3. 11번 parity review와 12번 공정 보고서를 색인에 추가한다. +4. historical analysis의 ephemera 제목은 anvil로 바꾸지 않는다. + +## Task 2a. MCP flock smoke의 roster-author drift 수정 + +**Consumes:** + +- `scripts/anvil-mcp-smoke.go:41-48` `spawnFlockOutput.Agents` +- `scripts/anvil-mcp-smoke.go:211-300` `runFlockSmoke` +- daemon `POST /flocks/{id}/post`의 roster authorship guard + +**Files:** + +- Modify: `scripts/anvil-mcp-smoke.go` + +1. 실제 daemon에서 변경 전 flock smoke가 roster 밖 `orchestrator`로 `403`을 받는 + prove-broken evidence를 보존한다. +2. `spawned.Agents`에서 `Role == "orchestrator"`인 `AgentID`를 선택한다. +3. member를 찾지 못하면 post 전에 명시적 error를 반환한다. +4. post input과 response assertion 모두 선택된 ID를 사용한다. +5. live daemon에서 flock smoke를 재실행하고 post/history/delete를 확인한다. + +**적대적 수용 기준:** + +- 선행조건: 실제 spawn roster의 ID와 hard-coded author가 다름을 403으로 실증 +- 적대적 probe: roster member가 없으면 fail-open하지 않고 post 전 실패 +- 독립 채널: daemon의 roster guard + MCP structured spawn response/history +- 음성대조: daemon authorship validation과 MCP public schema에는 diff 없음 + +## Task 3. 로컬 검증 + +1. targeted daemon test +2. `bash scripts/secret-scan.sh` +3. Go test/race/build/vet/module/gofmt/govulncheck +4. Web check/build/audit +5. bash syntax와 Markdown relative-link scan +6. `git diff --check` + +실패는 코드, dependency, local environment, known warning으로 분류한다. + +## Task 4. Exact SHA remote CI + +1. 변경을 `chore/release-gate-closure`에 intentional commit한다. +2. branch를 origin에 push하고 draft PR을 연다. +3. GitHub app으로 PR metadata/patch context를 확인한다. +4. `gh pr checks`와 Actions log로 exact SHA 결과를 확인한다. +5. runner failure와 code failure를 구분한다. + +PR 생성은 exact SHA CI를 얻기 위한 범위 내 외부 변경이다. merge는 이 계획에 포함하지 +않는다. + +## Task 5. KVM과 MCP release-candidate gate + +선행조건과 local secret file 존재·permission을 값 출력 없이 확인한다. + +```bash +sudo bash e2e_test.sh +scripts/anvil-mcp-e2e.sh lifecycle +scripts/anvil-mcp-e2e.sh semantic +scripts/anvil-mcp-e2e.sh flock +``` + +각 smoke가 독립 daemon lifecycle을 요구하는지 script 계약을 먼저 읽고 안전한 순서로 +실행한다. 실패 시 device/network/provider/runtime 원인을 구분하고 생성 resource cleanup을 +확인한다. + +## Task 6. Code review와 pre-release handoff + +1. actual diff를 requirement/security/maintenance/test adequacy 관점에서 review한다. +2. secret control이 inert하거나 test가 vacuous하지 않은지 확인한다. +3. 다음 version을 정책 근거 없이 확정하지 않는다. +4. `docs/operations/2026-08-13-release-gate-closure-handoff.md`에 다음 필드를 쓴다. + - Release Scope + - Verification + - Audit + - Blockers + - Warnings + - Residual Risk + - Current Lifecycle Stage + - Next Action + - Follow-Up Tasks +5. blocker가 하나라도 남으면 `release`/`operate` 진입을 선언하지 않는다. + +## 완료 조건 + +- spec 수용 기준 1-10의 상태가 handoff에 실제 evidence와 함께 기록됨 +- report/index/fixture diff가 review됨 +- exact SHA CI와 KVM 결과가 성공 또는 명확한 blocker로 분류됨 +- tag/release가 생성되지 않음 diff --git a/docs/superpowers/specs/2026-08-13-release-gate-closure-design.md b/docs/superpowers/specs/2026-08-13-release-gate-closure-design.md new file mode 100644 index 0000000..6972b27 --- /dev/null +++ b/docs/superpowers/specs/2026-08-13-release-gate-closure-design.md @@ -0,0 +1,159 @@ +# Release gate closure 설계 + +**날짜:** 2026-08-13 + +**상태:** 승인됨 — 사용자가 공정 보고서의 후속 작업 1·2·3 전체 실행을 승인 + +**topic:** `release-gate-closure` + +## 목표 + +2026-08-13 공정 상태 보고서의 사실관계를 재검토하고 분석 색인을 갱신한 뒤, 현재 +`main`의 P0 release blocker를 안전한 범위에서 순서대로 닫는다. 검증할 수 없는 +blocker는 완료로 과장하지 않고 operation handoff에 남긴다. + +## 포함 범위 + +1. 공정 보고서의 Git/GitHub/lifecycle/build/security 사실 재검토와 정정 +2. `docs/analysis/README.md`의 stale upstream baseline 및 신규 보고서 색인 갱신 +3. tracked tree secret scan을 깨뜨리는 provider-key sentinel 충돌 해소 +4. 변경된 정확한 SHA의 로컬 Go/Web/secret 검증 +5. 변경된 정확한 SHA의 GitHub Actions CI 검증 +6. 가능한 KVM full E2E와 MCP smoke 검증 +7. 다음 release version/scope의 결정 가능성 검토 +8. `Release Scope`, 검증, blocker, residual risk와 현재 lifecycle stage를 포함한 + operation handoff 작성 + +## 비목표 + +- 정식 tag 생성 또는 GitHub Release publish +- Git history rewrite로 과거 secret-like 문자열 제거 +- 실제 credential 출력·복사·변경 +- `main` branch ruleset 도입, CI에 Web/secret gate 추가(noted P1) +- npm audit dependency upgrade(noted P1) +- `allow_hosts` 제거를 이 작업에 암묵적으로 포함 +- deployment host의 password/key/permission 변경 + +## Domain Architecture + +이 변경은 daemon API, MCP tool, storage format, runtime process 경계를 바꾸지 않는다. +영향 경계는 다음 세 개다. + +1. **Test sentinel 경계** + - `TestConfigProvidersNeverExposeKeyValues`는 실행 시 credential-shaped 값을 실제 + secrets file에 넣고 응답 비노출을 검증한다. + - source tree scanner는 연속된 credential-shaped literal을 실제 secret과 구분할 + 수 없으므로 현재 release gate를 실패시킨다. +2. **Secret scanner 경계** + - `scripts/secret-scan.sh`의 pattern과 fail-closed tracked-tree 동작은 유지한다. + - path/file allowlist를 추가하지 않는다. allowlist는 같은 파일에 나중에 들어온 + 실제 secret을 숨길 수 있다. +3. **Release evidence 경계** + - local validation, GitHub CI, KVM E2E, immutable publish는 서로 다른 evidence다. + - 하나의 green 결과가 다른 채널의 결손을 대체하지 않는다. + +architecture pack은 runtime/service/MCP 경계 변경이 없으므로 수정하지 않는다. + +### 검증 중 발견된 MCP smoke drift + +full KVM 이후 `scripts/anvil-mcp-e2e.sh flock`을 실행하자 smoke가 +`agent_id="orchestrator"`를 보냈고, 강화된 daemon authorship guard는 실제 roster ID가 +아니라서 `403`으로 거부했다. spawn response의 실제 member는 role별 sequence가 붙은 +`orchestrator-1` 형태다. + +이는 production daemon이나 MCP API 결함이 아니라 smoke caller가 과거 계약에 고정된 +검증 코드 drift다. 이번 release gate를 green으로 만들려면 smoke가 spawn response에서 +role이 `orchestrator`인 실제 `AgentID`를 선택하도록 최소 수정한다. public API와 daemon +authorship guard는 변경하지 않는다. + +## 설계 결정 + +### D1. scanner가 아니라 fixture source spelling을 수정한다 + +sentinel을 두 compile-time string fragment로 구성한다. Go runtime에서 완성되는 값과 +non-leak assertion은 동일하지만 source tree에는 scanner pattern과 일치하는 연속 +literal이 남지 않는다. + +이 방식은 다음 속성을 가진다. + +- scanner pattern과 tracked-tree fail-closed 동작 불변 +- 실제 secret에 대한 allowlist 없음 +- runtime sentinel의 provider-like shape 유지 +- 기존 unit test가 값 조립과 response non-leak를 계속 검증 +- `bash scripts/secret-scan.sh`가 변경 전 실패, 변경 후 tracked tree PASS를 보여 + falsifiable regression evidence가 됨 + +### D2. history/local warning을 tracked-tree 실패와 구분한다 + +Git history에는 과거 fixture literal이 남는다. history rewrite는 fork/upstream 정책과 +배치되고 보안상 이득도 없다. 기본 scan의 history warning은 보존하고, current tracked +tree가 green인지 별도로 판정한다. local secrets file warning도 값은 출력하지 않는다. + +### D3. release version을 임의로 만들지 않는다 + +현재 정책은 `anvil-v0.7.0`부터 upstream ephemera version과 정렬한다. 조사 시점 +upstream latest는 여전히 `v0.7.0`이다. 또한 `allow_hosts`는 “다음 tagged anvil +release에서 제거”하기로 이미 결정됐지만 제거가 구현되지 않았다. + +따라서 이 작업은 `anvil-v0.7.1` 또는 `anvil-v0.8.0`을 임의 확정하지 않는다. 다음 +version은 upstream tag 또는 version-policy 결정과 `allow_hosts` 제거 lifecycle이 +끝난 뒤 확정한다. + +### D4. CI 검증은 변경 SHA를 대상으로 한다 + +기존 `main@3033cdd`의 runner failure를 재실행하는 것만으로 새 diff가 검증되지는 +않는다. 변경을 branch에 commit/push하고 PR Actions가 그 exact SHA에서 green인지 +확인한다. runner failure는 code failure와 구분하되 green evidence로 간주하지 않는다. + +### D5. KVM과 publish를 분리한다 + +KVM full E2E는 tag push 전에 실행한다. 성공해도 tag는 만들지 않는다. tag 생성은 +Immutable Release publish와 결합된 별도 비가역 승인이다. + +### D6. MCP flock smoke는 실제 roster ID를 소비한다 + +`scripts/anvil-mcp-smoke.go`는 이미 `anvil_spawn_flock`의 structured `Agents`를 +decode한다. 별도 ID 추측이나 `role + "-1"` 재구성 대신 response에서 +`Role == "orchestrator"`인 `AgentID`를 선택해 `anvil_post_townwall` 입력과 assertion에 +사용한다. roster member가 없으면 post 전 명시적으로 실패한다. + +## Plan Design Review + +- **발견성:** 분석 보고서를 `docs/analysis/README.md`에서 직접 찾을 수 있게 한다. +- **gate 명확성:** tracked/history/local scan 결과와 release blocker를 표로 분리한다. +- **operator 오류 방지:** “local green = CI green” 또는 “CI green = KVM green”으로 + 오해하지 않도록 독립 evidence를 유지한다. +- **비가역성:** tag/publish는 명시적으로 비목표이며 자동 실행하지 않는다. +- **visual/UI:** UI 변경이 없어 visual review는 해당 없음. IA와 gate clarity review로 + 대체했으며 blocking issue가 없다. + +## 수용 기준 + +1. runtime sentinel 값과 non-leak test 의미가 유지된다. +2. `bash scripts/secret-scan.sh`가 current tracked tree를 PASS하고 exit 0한다. +3. scanner pattern 또는 fail-closed tracked-tree 동작은 약해지지 않는다. +4. `go test ./cmd/goose-daemon -run TestConfigProvidersNeverExposeKeyValues -count=1` + 및 전체 Go 검증이 통과한다. +5. 분석 색인이 upstream `v0.7.0` adopted/adapted baseline과 신규 보고서를 가리킨다. +6. 공정 보고서는 review 상태, 새로운 증거와 남은 blocker를 구분한다. +7. 변경 exact SHA의 GitHub CI 결과를 기록한다. +8. KVM E2E가 불가능하거나 실패하면 이유와 영향을 blocker로 기록한다. +9. version을 정책 근거 없이 발명하지 않는다. +10. operation handoff가 필수 release-to-operate 필드를 모두 포함하고, blocker가 있으면 + `operate` 진입을 선언하지 않는다. +11. MCP flock smoke가 roster에 없는 author의 `403`을 숨기지 않고, 실제 spawn roster + member로 post/history/delete 전체 경로를 통과한다. + +## Spec Freeze Snapshot + +- **Approved objective:** 보고서 검토·색인 갱신·P0 순차 처리 +- **Domain boundary:** test fixture source, strict scanner, independent release evidence +- **Security decision:** scanner allowlist 금지; compile-time fragment로 source collision만 제거 +- **Version decision:** upstream/version policy와 `allow_hosts` 제거 전까지 TBD +- **Publish decision:** tag/release 금지 +- **Acceptance:** current tree secret PASS, unit/full local checks, exact SHA CI, KVM result, + complete pre-release handoff +- **Environment:** x86_64 Linux, `/dev/kvm` 존재, local secrets 값 비출력, GitHub CLI 인증됨 +- **Known risks:** hosted runner 가용성, external LLM/network, KVM/Firecracker COW resume race, + deployment host remediation +- **Open design questions:** 없음. 실행 결과에 따라 release blockers만 남을 수 있음 diff --git a/scripts/anvil-mcp-smoke.go b/scripts/anvil-mcp-smoke.go index 598ddfe..ff25f74 100644 --- a/scripts/anvil-mcp-smoke.go +++ b/scripts/anvil-mcp-smoke.go @@ -239,6 +239,16 @@ func runFlockSmoke(ctx context.Context, session *mcp.ClientSession) error { if len(spawned.Agents) != 2 { return fmt.Errorf("anvil_spawn_flock returned %d agents, want 2", len(spawned.Agents)) } + posterAgentID := "" + for _, agent := range spawned.Agents { + if agent.Role == "orchestrator" { + posterAgentID = agent.AgentID + break + } + } + if posterAgentID == "" { + return fmt.Errorf("anvil_spawn_flock returned no orchestrator roster member") + } var flocks listFlocksOutput if err := callStructured(ctx, session, "anvil_list_flocks", map[string]any{}, &flocks); err != nil { @@ -259,14 +269,14 @@ func runFlockSmoke(ctx context.Context, session *mcp.ClientSession) error { var posted townWallMessage if err := callStructured(ctx, session, "anvil_post_townwall", map[string]any{ "flock_id": spawned.FlockID, - "agent_id": "orchestrator", + "agent_id": posterAgentID, "body": smokeBody, }, &posted); err != nil { return err } fmt.Printf("posted townwall agent_id=%s body=%q\n", posted.AgentID, posted.Body) - if posted.AgentID != "orchestrator" { - return fmt.Errorf("anvil_post_townwall agent_id = %q, want orchestrator", posted.AgentID) + if posted.AgentID != posterAgentID { + return fmt.Errorf("anvil_post_townwall agent_id = %q, want %q", posted.AgentID, posterAgentID) } if posted.Body != smokeBody { return fmt.Errorf("anvil_post_townwall body = %q, want %q", posted.Body, smokeBody)