174 lines
16 KiB
Markdown
174 lines
16 KiB
Markdown
# P0 실행 무결성 복구 — 설계/실행 스펙
|
|
|
|
status: approved
|
|
supersedes: (none)
|
|
applies-to-version: company-haness @ fix/p0-execution-integrity
|
|
date: 2026-07-10
|
|
|
|
## 배경 / 목표
|
|
|
|
외부 리뷰(2026-07-10)가 하네스의 P0(실행 무결성) 결함 6건을 지적했다. 검증 결과 모두 사실이다.
|
|
이 스펙은 그 6건 + 부속 도구(doctor·ref-linter·lifecycle 테스트)를 **파일 소유권이 겹치지 않는 6개 work-package**로
|
|
나눠, 각 package를 격리 subagent 1명이 구현하게 한다. 병렬 실행 중 어떤 두 에이전트도 같은 파일을 쓰지 않는다.
|
|
|
|
이 패스의 범위는 **P0만**이다. P1/P2는 이 패스 완료 후 별도로 논의한다.
|
|
|
|
핵심 한 줄(리뷰 인용):
|
|
> 올바른 프로젝트 문맥 → 실존하는 agent → 검증된 context → 실물 산출물 → 실제 실행 근거 → task-specific acceptance
|
|
|
|
## 대상 P0 결함
|
|
|
|
- **#1** 문서상 "강제"인 hook이 실제로 꺼져 있음(`.claude/settings.json` 부재; `settings.hooks.json`은 자동 로드 안 됨). Stop 미배선.
|
|
- **#2** `SubagentStop` 검증이 대부분 보고서를 못 찾고 fail-open. `agent_id`/`last_assistant_message` 미사용, 재귀 탐색 아님, YAML 오류 시 rc=1(차단 아님).
|
|
- **#3** 커맨드가 존재하지 않는 family agent(`fam-architecture-tech`/`fam-design`/`fam-data`/`fam-security`/`fam-product-mgmt`) 호출.
|
|
- **#4** cascade 커맨드가 필수 `context-package`를 만들지 않음(`/run-wave`만 만든다).
|
|
- **#5** 실제 회사·프로젝트 문맥 부재(`org-os/01-05,07` 없음). workspace 기본값이 test 프로젝트(`test-labs-documents`).
|
|
- **#6** validator가 헤더 모양만 검사. E4/E5 등급이 에이전트 자기신고이며 실제 실행 receipt와 대조되지 않음.
|
|
|
|
## Work-package 분해 (파일 소유권 = 충돌 매트릭스)
|
|
|
|
각 WP가 **쓰는(write/create) 파일**은 서로 배타적이다. 아래 목록 밖 파일은 그 WP가 수정하지 않는다.
|
|
|
|
| WP | 결함 | 쓰는 파일 (배타 소유) |
|
|
|---|---|---|
|
|
| WP-1 | #1 | `.claude/settings.json`(신규), `.claude/hooks/doctor.py`(신규), `.claude/commands/doctor.md`(신규, 선택) |
|
|
| WP-2 | #2 | `.claude/hooks/stop_validate.py`(재작성), `.claude/hooks/subagent_register.py`(신규), `.claude/tests/test_subagent_lifecycle.py`(신규) |
|
|
| WP-3 | #3 | `.claude/hooks/gen_agents.py`, `.claude/hooks/lint_refs.py`(신규), 생성물 `.claude/agents/fam-*.md`(gen_agents 출력) |
|
|
| WP-4 | #5 | `.claude/hooks/_workspace.py`, `.orgos-workspace`, `org-os/01-company/`..`07-knowledge-base/`(신규 스텁), `org-os/01-company/company-context.yaml`(신규), `org-os/00-role-registry/README` 무관 |
|
|
| WP-6 | #6 | `.claude/hooks/evidence_ledger.py`(신규), `.claude/hooks/validate_report.py`(재작성), `.claude/hooks/render_report.py`(게이트 추가), `.claude/schemas/*.json`(신규), `.claude/tests/test_enforcement.py`(갱신) |
|
|
| WP-5 | #4 | `.claude/hooks/context_package.py`(신규), `org-os/06-agent-work/context-package-spec.yaml`, `.claude/commands/decide.md`·`ground.md`·`design.md`·`spec.md`·`build.md` |
|
|
|
|
**문서 정직성 수정(CLAUDE.md/README.md)은 어느 WP도 하지 않는다** — Wave 3에서 오케스트레이터가 실제 구현 결과에 맞춰 한 곳에서 반영한다(문서 충돌 방지).
|
|
|
|
### 실행 순서 (waves)
|
|
|
|
- **Wave 1 (병렬):** WP-1, WP-2, WP-3, WP-4, WP-6. 파일 소유가 배타적이라 동시 실행 안전.
|
|
- **Wave 2 (Wave 1 이후):** WP-5. WP-4(회사문맥 스키마)·WP-6(report/evidence 계약)·WP-3(agent 존재)에 의존.
|
|
- **Wave 3 (오케스트레이터 직접):** 문서 정직성 반영, `doctor`+테스트 실행, 통합 검증.
|
|
|
|
## 공유 계약 (SHARED CONTRACTS — 모든 WP가 준수)
|
|
|
|
병렬 에이전트가 일관되게 맞물리도록, 아래 인터페이스는 **고정**이다. 임의로 바꾸지 말 것.
|
|
|
|
### C1. `_workspace.py` 공개 API (불변 시그니처)
|
|
`workspace_name()`, `work_root()`, `records_dir()`, `evidence_dir()`, `reports_dir()`, `state_dir()`, `slack_outbox()`, `slack_inbox()` — 함수명/반환(경로 문자열) 유지.
|
|
WP-4는 **해석 로직만** 바꾼다: 하드코딩 기본값(`test-labs-documents`) 제거. 미설정 시 `WorkspaceNotSetError`(명확한 안내 메시지)로 **중단**.
|
|
|
|
### C2. 불변 report 경로/포맷 (new_report.py — 변경 없음, 참조용)
|
|
- 경로: `<work_root>/completion-records/<workflow>/<role>-<UTCstamp>.report.yaml`
|
|
- 최상단 필드: `report-id`, `workflow-id`, `role-id`, `created-at`, `report-header{bottom-line, decision-needed, confidence, risks, evidence}`.
|
|
- 재귀 탐색 시 glob 패턴은 `completion-records/**/*.report.yaml`.
|
|
|
|
### C3. `validate_report.validate()` 시그니처 (WP-2 ↔ WP-6 경계)
|
|
```python
|
|
def validate(report: dict, report_path: str | None = None) -> list[str]:
|
|
# 반환: 위반 사유 문자열 리스트(빈 리스트 = 통과). 예외를 던지지 않는다.
|
|
```
|
|
- WP-6는 인자를 `(report, report_path=None)`로 **확장**하되 기존 호출부(`vr.validate(report)`)와 하위호환 유지.
|
|
- `report_path`가 주어지면 WP-6는 그 경로에서 workspace를 해석해 evidence-ledger(C5)를 대조한다.
|
|
- WP-2의 `stop_validate.py`는 계속 `vr.validate(report, report_path=path)`를 호출한다.
|
|
|
|
### C4. subagent 등록 레지스트리 (WP-1 배선 ↔ WP-2 구현)
|
|
- 파일: `<state_dir>/subagent-registry.jsonl` (append-only, 한 줄 = JSON).
|
|
- SubagentStart 레코드 필드(최소): `{agent_id, agent_type, workflow_id?, role?, expected_report_dir?, started_at}`.
|
|
- 값이 없으면 필드 생략 가능하나 `agent_id`는 필수.
|
|
- SubagentStop이 이 레지스트리에서 `agent_id`로 조회해 기대 보고서를 판정한다.
|
|
|
|
### C5. evidence-ledger receipt (WP-1 배선 ↔ WP-6 구현)
|
|
- 파일: `<evidence_dir>/ledger.jsonl` (append-only, 한 줄 = JSON receipt).
|
|
- receipt 필드: `{tool_use_id, tool_name, ts, cwd, command?, exit_code?, stdout_sha256?, artifact_path?, artifact_sha256?}`.
|
|
- Bash: `command`, `exit_code`, `stdout_sha256` 채움.
|
|
- Write/Edit: `artifact_path`, `artifact_sha256` 채움.
|
|
- validator(C6)는 이 파일을 읽어 E4/E5 주장과 대조한다. 파일 없으면 receipt 0개로 취급(주장 미검증 → 차단/강등).
|
|
|
|
### C6. evidence 등급 파생 규칙 (WP-6)
|
|
- 에이전트가 선언한 `grade`는 **주장**일 뿐, validator가 receipt로 **검증**한다.
|
|
- **E5/E4**: evidence 항목이 `command`+`exit-code:0`을 주장하면 ledger(C5)에 `command` 문자열이 일치하고 `exit_code:0`인 receipt가 있어야 한다. 없으면 위반(차단). 파일 산출을 주장하면 `artifact_sha256` receipt가 있어야 한다.
|
|
- 일반 파일 참조(예: 기존 `CLAUDE.md`)만으로는 E5 불가.
|
|
- 기존 헤더/BLUF/dissent 검사(C3의 validate 본문)는 유지·강화.
|
|
|
|
### C7. hook event 배선표 (WP-1이 `.claude/settings.json`에 작성)
|
|
아래 스크립트 경로/이벤트로 배선한다. 스크립트 구현은 각 소유 WP가 한다. 스크립트가 아직 없어도(병렬) settings.json 작성은 가능(다음 세션에 적용).
|
|
```
|
|
PreToolUse [Bash|Write|Edit|NotebookEdit] -> guard_tools.py (기존)
|
|
PostToolUse [Bash|Write|Edit] -> evidence_ledger.py (WP-6)
|
|
SubagentStart -> subagent_register.py (WP-2)
|
|
SubagentStop -> stop_validate.py (WP-2)
|
|
Stop -> stop_validate.py --main (WP-2: 메인 세션 최종 산출도 검증)
|
|
```
|
|
- `doctor.py`(WP-1)는 settings.json 존재·hook 배선·참조 스크립트 실존·python/pyyaml·workspace 설정 여부를 점검한다.
|
|
|
|
### C8. agent 이름 규약 (WP-3)
|
|
- fan-out-split family(멤버≥2, `lead-role-id` 없음) 10개 각각에 `fam-<family-id 소문자>` **router agent**를 추가 생성한다.
|
|
대상: FAM-PRODUCT-MGMT, FAM-UX-RESEARCH, FAM-DESIGN, FAM-ARCHITECTURE-TECH, FAM-ARCHITECTURE-BIZ, FAM-DATA, FAM-SECURITY, FAM-GTM-GROWTH, FAM-GTM-SALES, FAM-REVOPS.
|
|
- router는 멤버 role들의 관점을 담되(build_agent 스타일) fan-out 멤버 목록 + 종합/conflict 계약을 명시한다.
|
|
- 개별 worker agent(role-id.md)는 그대로 유지. router 이름(`fam-*`)은 worker 이름(role-id)과 충돌하지 않음.
|
|
|
|
---
|
|
|
|
## WP별 상세
|
|
|
|
### WP-1 — 설정 활성화 + doctor
|
|
**목표:** 문서상 "강제"를 실제로 켠다.
|
|
- `.claude/settings.json` 생성: C7 배선표대로. 기존 `settings.hooks.json`의 PreToolUse(guard_tools)를 포함하고 PostToolUse/SubagentStart/SubagentStop/Stop을 추가.
|
|
- `.claude/hooks/doctor.py`: 설정·hook·의존성·workspace·command→agent 참조(WP-3의 `lint_refs.py`가 있으면 호출) 점검 → 문제 시 비영점 종료 + 사람이 읽는 리포트.
|
|
- (선택) `.claude/commands/doctor.md`로 `/doctor` 노출.
|
|
**하지 않는 것:** CLAUDE.md/README 수정(Wave 3), hook 스크립트 구현(각 소유 WP).
|
|
**수용:** `python3 .claude/hooks/doctor.py`가 실행되고 현재 결함(스크립트 부재 등)을 정확히 보고. settings.json은 유효 JSON이며 C7과 일치.
|
|
|
|
### WP-2 — subagent 생명주기 (fail-closed 보고서 바인딩)
|
|
**목표:** #2를 닫는다.
|
|
- `subagent_register.py`(SubagentStart): stdin JSON 파싱, C4 레지스트리에 append. 예외/malformed JSON은 안전 처리하되 등록 실패를 로그.
|
|
- `stop_validate.py` 재작성:
|
|
- stdin에서 `agent_id`·`last_assistant_message`·(있으면) `agent_transcript_path` 사용.
|
|
- 보고서 경로 해석 우선순위: (1) `last_assistant_message`에 포함된 report-path, (2) `$CLAUDE_REPORT_PATH`, (3) 레지스트리 `expected_report_dir` 하위 최신, (4) `records_dir()/**/*.report.yaml` **재귀** 중 이 agent 소속.
|
|
- **fail-closed**: 등록된(보고서 산출 대상) agent인데 보고서 없음 → rc=2. YAML 파싱 오류 → rc=2. workspace 밖 경로 → rc=2. malformed hook JSON → rc=2.
|
|
- 읽기전용/면제 agent(레지스트리에 report 비대상으로 표기되거나 알려진 helper type)는 통과 허용(과잉차단 방지).
|
|
- `--main` 플래그: 메인 세션 Stop용(해당 workflow 최종 산출 검증). 메인엔 보고서가 없을 수 있으니 이 경우의 정책을 명확히(면제 or 최종 산출 존재 시 검증).
|
|
- `vr.validate(report, report_path=path)` 호출(C3).
|
|
- `test_subagent_lifecycle.py`: SubagentStart→Stop 페이로드를 스크립트에 파이프하는 E2E 유닛. 케이스: 유효 보고서 통과 / 보고서 없음 차단 / malformed YAML 차단 / 경로 이탈 차단 / **동시 2개 agent가 서로의 보고서를 오검증하지 않음**.
|
|
**수용:** 새 테스트 전부 통과. `stop_validate.py`가 rc 규약(2=block)을 지킴.
|
|
|
|
### WP-3 — family agent 참조 복구 + ref linter
|
|
**목표:** #3을 닫고 재발을 CI로 막는다.
|
|
- `gen_agents.py`: fan-out-split & lead 없음 family(C8의 10개)에 router agent(`fam-<id>`)를 **추가** 생성. 기존 worker/lead/family 로직은 보존. `--check` 개수 계약을 새 총계로 갱신(현재 60 → +10 router = 70; role/ lead/ family 카운트는 유지, `router` 종류 추가). `--check` 어서션·본문 검증도 router에 맞게 추가.
|
|
- `lint_refs.py`: `.claude/commands/*.md`(및 필요 시 agents/hooks)에서 참조하는 (a) agent 이름(`fam-*`, role-id), (b) hook 스크립트 경로, (c) 파일 경로를 추출해 실존 검증. 미해결 참조 있으면 비영점 종료(리스트 출력). CI/doctor에서 호출 가능.
|
|
**하지 않는 것:** commands/*.md 수정(WP-5 소유). CLAUDE.md 수정.
|
|
**수용:** `gen_agents.py`(무인자)로 `fam-architecture-tech/design/data/security/product-mgmt`(+나머지 5) 파일 생성됨. `gen_agents.py --check` 통과. `lint_refs.py`가 현 커맨드의 깨진 참조를 **수정 전엔 잡고, router 생성 후엔 통과**.
|
|
|
|
### WP-4 — workspace 강제 + 회사 문맥
|
|
**목표:** #5를 닫는다.
|
|
- `_workspace.py`: C1 유지. `DEFAULT_WORKSPACE` 하드코딩 제거. 미설정 시 `WorkspaceNotSetError`(명확 안내: ORGOS_WORKSPACE 또는 .orgos-workspace 지정)로 중단. 공개 함수 시그니처 불변.
|
|
- `.orgos-workspace`: test 프로젝트 고정 대신 로컬 개발용 명시 포인터로 취급. 값은 그대로 두되(로컬 편의), 코드가 "포인터 없으면 중단"을 강제. (필요 시 파일 상단 주석으로 "운영은 ORGOS_WORKSPACE 필수" 명기.)
|
|
- `org-os/01-company/ … 07-knowledge-base/`: README 스텁 디렉터리 생성(리뷰가 지적한 약속된 구조 실체화).
|
|
- `org-os/01-company/company-context.yaml` + 프로젝트 manifest 스키마: stack, build/test/lint/run 명령, 제품 목적, 사용자, 제약, 코드 규약, 민감도, 최근 결정 필드. (템플릿/스키마 수준으로 충분 — 실데이터 강요 아님.)
|
|
- **confidence 상한 규칙**: 실제 회사 자료가 없으면 해당 판단 confidence 상한 E1/E2 — 이 규칙을 관련 정책 문서(예: company-context.yaml 주석 또는 org-os/01-company/README)에 명문화.
|
|
**하지 않는 것:** new_report/validate 등 다른 hook 수정. CLAUDE.md 수정.
|
|
**수용:** ORGOS_WORKSPACE 미설정 상태에서 workspace 필요 hook이 명확 오류로 중단. `org-os/01-05,07` 실존. company-context 스키마 유효 YAML.
|
|
|
|
### WP-6 — receipt 기반 evidence + semantic validator
|
|
**목표:** #6을 닫는다.
|
|
- `evidence_ledger.py`(PostToolUse): C5 receipt를 `<evidence_dir>/ledger.jsonl`에 append. workspace 미설정 등 예외는 안전 처리(무한루프/크래시 금지).
|
|
- `validate_report.py` 재작성: C3 시그니처로 `report_path` 지원. C6 규칙으로 E4/E5 주장을 ledger와 대조. `report-type` 판별자 + `.claude/schemas/`의 JSON Schema로 유형별 필수 필드 검사. 기존 BLUF/decision/confidence/risks/evidence/synthesis 검사 유지.
|
|
- `render_report.py`: 렌더 전에 validator를 통과했는지 게이트(미통과면 렌더 거부/경고+비영점). 기존 인터페이스(CLI usage) 보존.
|
|
- `.claude/schemas/`: report 공통 + 유형별(decision/work/completion/review/blocked/design) 스키마.
|
|
- `test_enforcement.py` 갱신: **자기신고 E5 fixture(command+exit-code만, receipt 없음)는 이제 실패**. receipt를 시드한 fixture는 통과. 기존 통과하던 잘못된 케이스(존재하지 않는 linked report, conflicts:null 등)도 차단됨을 검증.
|
|
**하지 않는 것:** stop_validate.py 수정(WP-2 소유, 단 C3 시그니처만 맞춤). CLAUDE.md 수정.
|
|
**수용:** 자기신고 E5가 차단됨. receipt 뒷받침 E5는 통과. `validate_report.py`가 예외 없이 위반 리스트 반환.
|
|
|
|
### WP-5 (Wave 2) — context-package 컴파일러 + 커맨드 배선
|
|
**목표:** #4를 닫는다.
|
|
- `context_package.py`(신규): 모든 spawn이 거치는 컴파일러+validator. `context-package-spec.yaml` 필수 필드 + 신규 필수(`workspace`, `target-repo`, `acceptance-tests`, `non-goals`, `evidence-plan`)를 강제. workspace 미설정(C1)이면 중단.
|
|
- `context-package-spec.yaml`: 위 신규 필수 필드 추가.
|
|
- `decide.md`·`ground.md`·`design.md`·`spec.md`·`build.md`: 각 fan-out/spawn 단계가 `context_package.py`를 호출해 패키지를 만들고 검증한 뒤 워커를 호출하도록 배선. 커맨드마다 절차 중복 대신 공통 primitive 참조.
|
|
**수용:** 각 cascade 커맨드가 워커 spawn 전 context-package 컴파일·검증을 명시. `context_package.py`가 필수 필드 누락을 거부.
|
|
|
|
## 검증 계획 (Wave 3)
|
|
1. `CLAUDE_PROJECT_DIR="$PWD" ORGOS_WORKSPACE=<explicit> python3 .claude/hooks/gen_agents.py --check` — 70 agents 통과.
|
|
2. `python3 .claude/hooks/lint_refs.py` — 커맨드 참조 무결성 통과.
|
|
3. `CLAUDE_PROJECT_DIR="$PWD" ORGOS_WORKSPACE=<explicit> python3 .claude/tests/test_enforcement.py` 및 `test_subagent_lifecycle.py` 통과.
|
|
4. `python3 .claude/hooks/doctor.py` — 그린.
|
|
5. 각 hook 스크립트를 대표 페이로드로 수동 1회 실행해 크래시 없음 확인.
|
|
6. CLAUDE.md/README의 "강제" 서술을 실제 구현(설정 활성화 시 강제)로 정직하게 수정.
|
|
7. 커밋(사용자 승인 시).
|