diff --git a/Case_02_Comparison_Research/YAML_Prompts/1. Stage_1/v.7/MEMORY.md b/Case_02_Comparison_Research/YAML_Prompts/1. Stage_1/v.7/MEMORY.md index 52d967c0..3fccc3c8 100644 --- a/Case_02_Comparison_Research/YAML_Prompts/1. Stage_1/v.7/MEMORY.md +++ b/Case_02_Comparison_Research/YAML_Prompts/1. Stage_1/v.7/MEMORY.md @@ -1565,3 +1565,241 @@ v.2 §2 의 지정 4건만 수행. 산출: ### 교훈 1. **이력 절은 개서하지 않고 시효 주석으로 닫는다** — §8 라운드 기록의 현재형 서술이 낡았을 때 원문 교체는 이력 위조, 방치는 오독 — "(…는 2026-08-20 회차에서 삭제됐다)" 부가가 정답. 2. **주석 확장(+2)과 코드 삭제(−2)가 상쇄돼도 중간 블록 행 범위는 이동한다** — A0/B 는 +2, R0 는 시작 +2·끝 0, R1 이후 무변. 파일 총행 무변을 "행 범위 무변"으로 착각하지 말 것. + + +--- + +## 2026-08-20 ㉗ — Default_Agent 프롬프트 크기 limit 삭제 (remove_limit_on_assets.md) + +한 줄 요약: `Default_Agent/` 자산에서 `utf8_bytes`·`unicode_scalars` 상한만 삭제 — 보유 파일은 `stage1_runtime/` 3본뿐(정책 JSON 2행 + 컴파일러 py/txt 각 17행, 총 삭제 36행·추가 0), 구본은 `extension_research/assets_outdated/*_old.*` 보존, sub-agent 검증 1회차 PASS·결함 0. + +- **삭제**: JSON `max_utf8_bytes: 96000` / `max_unicode_scalars: 24000` 2행. 컴파일러는 상한 조회 3행 + 판정·`raise PROMPT_SIZE_GUARD_EXCEEDED` 11행 + 매니페스트 `max_*` 2행·`"pass": not exceeded` 1행. +- **존치**: 계측(`scalar_count`·`estimate`·매니페스트 `utf8_bytes`/`unicode_scalars`/`legacy_advisory_estimate`), 고지 문구 2종, 그리고 **실패코드 레지스트리**(`overflow_fail_code`·`fail_codes.prompt_size_guard_exceeded`) — 수치 상한이 아니므로 "limit 외 무단절" 제약상 사문으로 남김. +- **검증**: `git diff --numstat` 세 파일 모두 insertions 0(삭제 전용·변경 헝크 0) · 구본에서 해당 행만 지운 결과가 개정본과 sha 완전 일치 · sub-agent가 scratchpad 사본에 한글 40,000자를 실제 투입해 구본 예외 발생 / 개정본 정상 완료를 실행 대조. +- **미처리 이월 2건**: ① `Default_Agent/runtime_manifest.json` 1644–1653행이 개정 전 sha를 물고 있어 무결성 재계산 시 3건 실패(읽는 코드는 현재 없음, 갱신은 제약 위반이라 보류) ② 형제 배포 트리 3곳(`ver_8_yaml_candidates/Default_Agent_Stage_1`·`00_배포트리/…`·`선행구축/…`)은 limit 존치 — 지정 경로 밖이라 범위 제외. + +### 교훈 +1. **설정 파일의 한도를 지워도 코드의 fallback 기본값이 한도를 되살린다.** 정책 JSON만 고쳤다면 `guard.get("max_utf8_bytes", 96000)`이 그대로 96,000을 강제했을 것 — "limit 삭제" 요청은 선언부와 강제부를 함께 훑어야 완결된다. +2. **삭제 요청에서 "계측"과 "한도"를 가르는 선을 먼저 긋는다.** `utf8_bytes` 값 산출은 남기고 `max_utf8_bytes` 비교만 없애는 것이 관측성을 지키면서 요청을 정확히 이행하는 지점 — 이름이 비슷하다고 함께 지우면 무단 축소가 된다. +3. **파생 필드는 원본 삭제 시 존치가 불가능할 수 있다.** `"pass": not exceeded`처럼 삭제 대상에만 의존하는 값은 남기려면 값을 **새로 지어내야** 하므로, 오히려 삭제가 "손대지 않는다" 제약의 준수 경로가 된다. +4. **sha 장부(`runtime_manifest.json`)를 둔 트리에서는 어떤 파일 편집도 장부를 시효 만료시킨다.** 제약이 장부 갱신을 막으면 그 사실 자체를 이월 항목으로 명시해야 한다 — 조용히 두면 다음 무결성 검사에서 원인 불명 실패로 나타난다. + +## 2026-08-20 ㉘ — runtime_manifest sha 갱신으로 limit 삭제 회차 봉인 (㉗ 이월 1건 해소) + +한 줄 요약: ㉗에서 이월했던 `Default_Agent/runtime_manifest.json` 시효 만료를 해소 — 개정된 3본의 sha256만 재계산해 교체(3행 치환, 추가·삭제 0), 전체 432항목 재계산 결과 **해시 불일치 0**으로 복귀. + +- **갱신 3건**: `stage1_runtime/prompt_compiler.py`·`.txt` `6922aa52…` → `d2f02564…`, `stage1_runtime/prompt_composition_policy.json` `06dbc4de…` → `8d3f003e…`. 값은 신뢰 없이 실물에서 재계산(스크립트가 `hashlib`로 직접 산출). +- **편집 방식**: 절대 행번호가 아니라 `"path": ""` 앵커로 위치를 찾아 **바로 다음 sha256 행의 값 문자열만** 치환 — `git diff --numstat` 3/3(값 교체분), 다른 필드·순서·서식 무변. `entries` 432 / `runtime_artifact_count` 432 / `program_release_status` 전부 그대로. +- **검증**: JSON 파싱 OK. 432항목 전수 재계산 → 일치 155 · **불일치 0** · 파일 없음 277. 155/0/277은 ㉖에서 기록한 트리 구성(교집합 155·불일치 0·277 무변)과 정확히 일치 — 즉 이번 갱신은 기존 장부 상태를 되돌린 것이지 새로 흔든 것이 아니다. +- **백업 미생성(의도)**: 이 파일은 git 추적 중이고 편집 전 HEAD와 동일했으므로 `HEAD`가 원본 보존판이다. ㉗과 달리 사용자 지시에 백업 요건이 없어 `assets_outdated/`에 사본을 만들지 않았다. + +### 교훈 +1. **sha 장부 갱신은 기존 값을 옮겨 적지 말고 실물에서 재계산한다.** 앞 회차 보고서·sub-agent 리포트에 적힌 해시를 복사하면 그 값이 틀렸을 때 장부가 틀린 채로 봉인된다 — 장부의 존재 이유(실물 대조)를 스스로 무력화하는 실수. +2. **JSON 장부 편집은 파싱→재직렬화 대신 앵커 기반 행 치환으로 한다.** `json.load` 후 `json.dump`는 들여쓰기·키 순서·공백을 재작성해 무관한 400여 행을 diff에 올린다 — 값 하나 바꾸려다 리뷰 불가능한 변경이 된다. +3. **장부 갱신 후에는 갱신 대상만이 아니라 전수 재계산으로 상태를 확인한다.** 155/0/277이 이전 회차 기록과 일치하는 것을 봐야 "되돌렸다"가 증명되고, 다른 항목까지 흔들렸는지도 같은 한 번에 걸러진다. + +## 2026-08-21 special_law_profiles 자산 부재 판단서 (assets_special_law_problem.md) +한 줄 요약: `stage_1_part_2_v.8.yml` 첫 task 실패의 유일한 실행 차단 원인은 **`Default_Agent/special_law_profiles/` 실물 부재 1건**이며, 함께 지적된 "인덱스 `[]` 불일치"와 "스키마 소문자 패턴"은 각각 미소비 필드·이미 수정된 과거 이력이어서 실행 차단이 아님을 실측으로 확정 — 산출물 `extension_research/assets_special_law_problem.md`. + +- **실사용 SLP는 3종뿐**: `SLP-PRODUCT-LIABILITY`(E-06)·`SLP-IP`(E-20)·`SLP-MEDIA-MEDIATION`(E-21). `SLP-AUTO-DAMAGE`·`SLP-STATE-COMPENSATION` 은 `domain_slice.schema.v2.json:101` **주석 안에만** 존재하고, 주석이 말하는 "9종"과 `special_law_profiles/_registry_index.json` 은 릴리스에 실체가 없다 → "9종" 추종 금지, 3종만 신설. +- **런타임이 이미 경로를 못박아 두었다**: `Stage_1_Registry_Runtime_v1.yml:473,492` 가 `Default_Agent/special_law_profiles//prompt_overlay.md` 로 `--profile` 을 조립한다. 신설 자산은 이 경로·릴리스 루트 하위(`compile_fragments` 의 `relative_to(release_root)` 검사)·정규화 sha 상이(중복 시 dedup 탈락)를 지켜야 한다. +- **raise 완화(2안) 기각 근거**: E-06 `seed_prompt_overlay.md:5,:34` 가 "제조물 사건은 이 profile 없이 종결 금지"를 프롬프트 본문에 명시 → 건너뛰면 프롬프트가 스스로 금지한 산출물이 나온다. 크기 가드(추정치 기반 보수 차단)와 달리 이 raise 는 법리 완결성 계약. +- **콘텐츠 부담은 작다**: `ensemble_v2.md` 상 특별법은 도메인 payload 플래그이고 시행일·경과규정·버전 pinning 은 SG-04 가 흡수 → overlay 는 조문·요율·기간 상수 없이 식별단서/요건슬롯/review code/SG-04 경계선언만 담는 얇은 조각으로 족하다. + +### 교훈 +1. **`registry_loader` 는 `config`(실물 로드본)와 `index_entry`(인덱스 원문)를 분리해 담고, 실행 경로는 전부 `config` 만 읽는다.** 인덱스 필드 불일치를 보고 "이것 때문에 죽는다"고 결론내기 전에 어느 쪽을 소비하는지부터 확인할 것 — 이번 건에서 인덱스의 `special_law_profiles: []` 는 아무 데서도 읽히지 않았다. +2. **스키마의 `$comment` 는 결함 기록이지 현재 상태가 아니다.** `:101` 주석은 "소문자 패턴이라 거부됐다"지만 바로 아래 `:104` 패턴은 이미 `SLP-[A-Z]…` 를 받는다 — 주석을 근거로 수정 대상을 정하지 말고 패턴을 실제 값으로 매치 테스트해서 판정할 것. +3. **선언·스키마·런타임 경로가 모두 갖춰졌는데 실물만 없는 유형의 장애는, 가드 완화가 아니라 자산 생성으로 닫는다.** 세 층이 일치한다는 것은 설계 의도가 확정돼 있다는 뜻이므로, 가드를 푸는 순간 세 층이 함께 거짓말을 하게 된다. + +## 2026-08-21 special_law_profiles 검증·전략·실행 3연속 (v.2 리포트 → 전략서 → 자산 신설) +한 줄 요약: v.1 판단서를 독립 재검증해 오류 3건을 정정한 `assets_special_law_problem_v.2.md`, DAG 기반 `special_law_profile_update.md` 전략서, 그리고 그 전략의 실제 실행(자산 5건 신설 + manifest 433) 까지 마쳐 **`stage_1_part_2_v.8.yml` 의 유일한 실행 차단이던 SLP 자산 부재를 해소**했다 — 조립·검증·봉인 스모크 전건 PASS. + +- **v.2 가 정정한 v.1 오류 3건**: ① 카탈로그 위치 — `special_law_profiles/_registry_index.json` 을 "두면 경고 해소"가 아니다. validator 는 인덱스 `reference_catalogs` 맵이나 CLI 주입으로만 카탈로그를 알며, 참조본 A0 가 이미 `platform/reference_catalogs/profile_ids.json` 로 정본 위치를 확정해 뒀다. ② 봉인 위치 — manifest 는 `domain_config.json`·`seed_prompt_overlay.md`·`domains/_registry_index.json` 을 **원래 싣지 않는다**(domains/ 51건은 module_role_projection 25 + structure_types 25 + common_worker_contract 1). 봉인 동형 위치는 자산군 자체 인덱스의 `prompt_overlay_sha256`. ③ "3개 파일 sha 상이"는 하드 계약이 아니다 — dedup 스코프는 도메인 1개 컴파일 내부. +- **신설 자산 5건**: overlay 3종(`SLP-PRODUCT-LIABILITY` 5652B / `SLP-IP` 5720B / `SLP-MEDIA-MEDIATION` 5605B), `special_law_profiles/_registry_index.json`(sha 봉인), `platform/reference_catalogs/profile_ids.json`(manifest 등재 +1, count 432→433). overlay 는 4절 골격(식별 단서 / 고유 요건 슬롯·증거 component / review code 발행 규칙 / SG-04 경계)으로 통일하고 법령 조문·요율·기간 상수는 0건(grep 검증). +- **검증 실측**: E-06·E-20·E-21 컴파일 `rc=0`·profile 조각 rank 40 부착·dedup 탈락 0·마커 검증(회귀 대조 E-05 도 rc=0), validator closure `missing: []`·경고 54→51, manifest 전수 재계산 **156/0/277**(직전 155/0/277 +카탈로그 1). +- **보류·별건**: G(절단 문자열 `SLP-PRODUCT-LIABI` 조사)는 `/Users/jsahn` 부재로 이 머신에서 수행 불가. H(인덱스 값 채움·스키마 주석)는 회귀 위험·드리프트 이유로 보류. 참조본 A0 요구 자산 3종(`registry_index.schema.json`·`domain_config.schema.json`·`signal_ids.json`) 여전히 부재. + +### 교훈 +1. **프롬프트 조각을 새로 쓸 때 "어느 출력 key 에 담아라"는 도메인별로 확인해야 한다.** `routing/extension_payload_key_declarations.v1.json` 이 도메인별 key 화이트리스트이고 `additional_properties_allowed: false` 인데, `special_law_profile_candidates` 를 선언하는 건 **E-06 뿐**이다(E-20·E-21 미선언). 세 profile 에 같은 key 를 지시했으면 두 도메인 worker 출력이 스키마 위반이 됐다 — 조각의 문장 하나가 스키마 계약을 깨뜨릴 수 있다. +2. **카탈로그·장부는 "파일을 만들었다"로 배선되지 않는다.** validator 의 kind 문자열은 `REFERENCE_FIELDS` 값(`special_law_profiles`)이어야 하며, 참조본 A0 의 `--reference-catalog profile=…` 표기로는 실제 주입해도 `unverified` 가 유지된다(경고 54 불변). 배선 성립 여부는 경고 개수 대조 같은 **실행 실측**으로만 확인된다. +3. **위생 작업이 회귀를 만들 수 있으면 실행 이득과 비교해 보류한다.** 인덱스의 `special_law_profiles: []` 채움은 실행 경로에서 읽히지 않아 이득 0인데, 워크스페이스 `registry_index.schema.json` 이 slice 스키마와 같은 소문자 패턴 결함을 가지면 `[]` 로 통과하던 A0 검증이 새로 깨진다 — "고쳐 두면 좋은 것"이 순이익이 아닐 수 있다. + +## 2026-08-21 ㉙ — Stage 1 Part 4 개정전략서 작성 (적대적 검증 12라운드 · 자발 중단) + +한 줄 요약: `extension_research/stage_1_part_4_개정전략서.md`(1,363행 · 175KB) 신규 작성 — Part 4는 diff가 아니라 **신작**(구본 F0가 기대하는 LES 계약과 P3 v.8 실산출이 배열키·색인·레코드 필드 전부 불일치), 게이트는 **조립본 사이드카 25개(127규칙 · registry 121 전건 커버)를 바이트 복사 배포**해 창작 0으로 켠다. 적대적 sub-agent **12라운드** 후 **자발 중단** — 종료 조건이 도달 불가능함을 인지하고 미해결분을 §9 이월 14건 · §10 결선 4건으로 넘겼다. + +- **핵심 설계**: task 3(F0 code · F1 LLM 조건부 · F2 code) · `Task_C_FL_*` 명명(P2 `Task_C_BO_*` · P3 `Task_C_LE_*` 계열) · 로직은 신설 모듈 `fact_ledger_compiler.py` + 정책 `fact_ledger_policy.v1.json`, YAML은 반입·게이트·봉인·기록만. 행 = **BO 1건당 1행**(`fact_id` = `F-{idx:03d}` 연속·경성 단언) · 구 4필드 → `domain_effects{도메인ID}` + `calculation_requests[]`(구 4필드는 한 버전 별칭 병기 = 29키). +- **게이트**: registry 121 = **구현 25 · 가시화 96**(str 90 + boundary 6) · 사이드카 고아 6. dict `condition`은 6종 키(any_review 13 · required_slots 6 · calc_operand 6 · boundary 6 · `*_attempted` 6). 사이드카 방언 **17축** — 정규화 리더를 먼저 만들고 그 리더로 잰 값만 근거로 삼는다. +- **골든 등가는 필드 4부류로 판정**: (가) BO 유래 **9** 값등가 · (나) LES·구signal 유래 **15** 재산출 대조 · (다) 증거 유래 **2** 값등가 · (라) 스켈레톤·게이트 **1** 차분 = 27, 무분류 0(`must_consider`는 (나)∩(라) 겸용). `EXPECTED_DELTA` **5건** 등록. +- **결정적 실측 6건**: ① 구본 LES 계약 ≠ P3 v.8(배열키 `legal_effect_structures`→`structure_records` · 색인 5종→3종) ② `downstream_read_sets.part4_F0` = SG-04·06·07·10·11(`signal_compiler.py` 168행 ↔ 전략서 163행 2중 근거) ③ 사이드카 25가 **조립본에 실재**(dangling 아니라 미배포) ④ **골든 BO의 `Legal_Keywords`가 한국어 법률용어라 registry `type_id`와 교집합 0** → 부착 0/59 ⑤ **골든-S2에 SG-01(11도메인)·봉인 `signal_manifest`·SG 13종·봉투 11이 실물로 존재**(손합성 불필요) ⑥ `legacy_declaration_mismatches`는 파생 목록이고 **실제 자기선언 누락은 4건**(E-09·X2·X1·X3). + +### 교훈 +1. **"검증 100% 통과까지 반복"은 종료 조건이 될 수 없다.** 적대적 검증자에게 "지적 0이라 쓰려면 25건 검증하라"고 요구하면 매 라운드 무언가가 나온다 — 종료는 **지적의 심각도 추이**로 판단해야 한다. 8~12라운드에서 BLOCKER가 3~5로 평평했던 것은 수렴이 아니라 **정체** 신호였고, 그때 멈췄어야 했다. +2. **큰 문서는 고칠수록 새 불일치가 생긴다.** 1,300행을 넘기자 한 절의 수정이 그 절을 참조하는 다른 절과 어긋나는 국면이 왔다. 그래서 규율 14(처방이 닿을 자리를 전수로 훑는다)와 규율 5(같은 수치·이름의 모든 출현을 기계 대조)를 세웠고, 실제로 라운드 지적의 절반가량이 그 기계 대조만으로 사전 차단 가능한 것이었다. +3. **자산이 이미 답을 갖고 있는데 요약본·이웃 파일만 보는 실수가 반복됐다** — 사이드카 "없음"(배포 트리만 봄) · 골든-S2 "손합성"(`ls` 안 함) · `legacy_declaration_mismatches`(파생 1건을 원본으로). **파생 목록을 근거로 쓰기 전에 원본을 전수로 다시 만들고, 픽스처를 합성하기 전에 그 디렉터리를 `ls` 한다.** +4. **규칙을 문서에 실었으면 "그 문서만 보고 재구현했을 때 기준선이 재현되는가"를 재야 한다.** (다) 증거력 산정을 4갈래로 옮겨 적었다가 29/59, 발췌만 쟀다가 54/59가 나왔다. 최종적으로 (가) 531/531 · (다) 59/59 재현을 실측으로 확인하고서야 규칙이 닫혔다 — 이 확인을 §8 0c-1 단계로 넣었다. +5. **등식은 차원을, 규칙은 피연산자의 단위를 확인하고 세운다.** 보존 등식이 "Σ 도메인 키 + BO 수 == 도달 총수"로 차원이 섞여 공집합 입력에서 즉사했고, 계산 등식은 행 항목 수와 원천 레코드 수를 한 식에 넣어 정상 실행에서 발화했다. 최종 5종으로 분리했다. + +## 2026-08-21 ㉚ — 특별법 profile 배선 장애 수정 (A0 즉시 실패 → 26도메인 조립 성공) + +한 줄 요약: 백엔드 `stage_1_part_2_v.8.yml` 첫 task `Task_C_BO_A0` 가 `RuntimeContractError: special-law profile prompt path missing: SLP-PRODUCT-LIABILITY` 로 즉사한 장애를 수정 — 원인은 **파일 부재가 아니라 인자 미전달**이었다. `collect_domain_fragments` 의 특별법 검사는 디스크를 보지 않고 호출자가 넘긴 `profile_paths` 에 키가 있는지만 보는데, YAML 이 그 인자를 아예 넘기지 않았다(전 파일에서 `profile_paths`·`special_law`·`SLP-` 출현 0). 적대적 sub-agent 2라운드(1차 FAIL → 2차 PASS·결함 0). + +- **YAML 3곳**(`ver_8_yaml_candidates/stage_1_part_2_v.8.yml`, 35+/1-): ① 상수 `SPECIAL_LAW_INDEX` ② `stage_text(COMMON_CONTRACT)` 직전에 지도 구축 블록 27행 — profile registry 를 읽어 각 항목이 선언한 `prompt_overlay_path` 를 따라가 `stage_text` 로 반입하고 `{profile_id: 논리경로}` 를 채운다. 파일 이름을 짓지 않고 기존 도메인 overlay 루프의 규약(`startswith("Default_Agent/")` 가드 + `posixpath.join(dirname(...))` + NFC)을 그대로 복제 ③ 호출부 `profile_paths=profile_paths`. +- **컴파일러**(`Default_Agent/stage1_runtime/prompt_compiler.py` + 바이트 동일 `.txt`, 13+/2-): 단일 모호 메시지를 두 갈래로 — 키 미전달(`not supplied by caller`, details 에 `supplied_profile_ids`) / 파일 부재(`file missing`, details 에 `resolved_path`, `is_file()` 신설). +- **매니페스트 2줄**: `prompt_compiler.py|.txt` sha `d2f02564…` → `bfd80e90…`. **1차 검증 FAIL 의 유일한 사유가 이것이었다.** +- **실측**: `assert_deployment()` 18자산 `unregistered/hash_mismatch/unreadable` 전부 공집합 PASS · `MODULE_MIRROR_HASH_MISMATCH` 없음 · 매니페스트 전수 **156/0/277**(직전 154/2/277) · **26 도메인 collect+compile 예외 0** · warn 0 · E-06 조각 6개(rank 40 에 `special_law_profile:SLP-PRODUCT-LIABILITY`) 조립 24,960 B, 조립 sha `29c0a24e…` · 무프로필 23 도메인 `specs`·`prompt_sha` 전건 불변 · E-20→SLP-IP, E-21→SLP-MEDIA-MEDIATION 부착. +- **배치**: 수정본은 배포 위치에 제자리 overwrite, 수정 전 원본 4종은 `ver_8_yaml_candidates/outdated/*.pre_special_law_wiring.*`. +- **이월**: ① **(높음) SLP 자산 4건이 `runtime_manifest.json` 미등재**(`grep -c special_law_profiles` → 0) — 훼손이 `assert_deployment()` sha 대조망에 안 걸린다. 등재 시 433→437 ② (중) 레지스트리 JSON 파손 시 `json.loads` 예외 미포착(480~481행, `try` 밖) — fail-closed 라 ①과 묶어 판단 ③ (낮음) 형제 배포 트리 드리프트: `ver_8_yaml_candidates/Default_Agent_Stage_1`·`00_배포트리/…` 는 `special_law_profiles/` 자체가 없고, `선행구축/Default_Agent_Stage_1` 은 46파일의 구 SLP 자산군(현행 3-profile 과 불일치) ④ (낮음) `stage1_runtime/__pycache__.zip` 릴리스 트리 오염, 556행 낡은 주석(상한 96,000 B) ⑤ 백엔드 `read_docs` 가 `special_law_profiles/` 를 실제 서빙하는지는 **실주행 1회로만 닫힌다**. + +### 교훈 +1. **"파일이 있는데 없다고 한다"는 대개 경로 문제가 아니라 배선 문제다.** 검사 코드가 디스크를 보는지 인자를 보는지 먼저 읽어야 한다. `collect_domain_fragments` 는 `if profile not in profile_map` 이었고 `profile_map` 은 호출자가 채우는 dict 였다 — 파일 존재 여부는 애초에 검사 대상이 아니었으므로, 배포 트리를 아무리 확인해도 원인이 나오지 않는다. **에러 문구("path missing")가 가리키는 곳과 실제 검사 대상이 다를 수 있다.** +2. **sha 장부를 둔 트리에서 파일 편집과 장부 갱신은 하나의 원자적 작업이다.** ㉗ 교훈 4를 알고 있었는데도 또 놓쳤고, 그 결과 **장애를 고친 게 아니라 더 이른 지점의 다른 장애로 바꿔 놓은** 상태로 1차 검증에 들어갔다(`assert_deployment()` 가 A0 `main()` 첫 문장이라 새 코드는 한 줄도 실행되지 않았다). "원인 해결 외 금지" 같은 좁은 지시 아래서도 **장부 갱신은 범위 밖이 아니라 원인 해결의 구성요소**다. +3. **검증자에게 "고친 것"이 아니라 "고쳐졌다고 주장하는 것"을 주고 반증시켜야 한다.** 이번 1차 라운드는 내가 검증 항목에 넣지도 않은 매니페스트를 검증자가 스스로 의심해 잡아냈다. 반대로 2차에서는 검증자가 YAML 함수를 **AST 로 원문 추출해 그대로 실행**했다 — 손으로 옮겨 적은 재현은 옮겨 적는 과정에서 결함을 지울 수 있다. +4. **회귀 검증의 대조군은 "안 건드린 것"에서 고른다.** 프로필 미선언 23개 도메인의 `specs`·`prompt_sha` 를 수정 전/후로 대조한 것이 이번 변경이 특별법 경로 밖으로 새지 않았음을 보인 가장 강한 증거였다. 고친 대상만 보면 "되더라"밖에 말할 수 없다. + +## 2026-08-21 ㉛ — A0 fan-out 전달 계약 수정 (B worker 0개 생성 → 26개) + +한 줄 요약: 실주행에서 A0 통과 후 `Task_C_BO_R0` 가 `read_docs failed for runtime/domain_seed_outputs/E-00.json` 로 죽은 장애를 수정 — 원인은 seed 파일 유실이 아니라 **도메인 worker 가 애초에 0개 생성된 것**. A0 반환 dict 에 `dynamic_fanout` 키 한 개 추가. + +- **서버 로그 실측**(ssh hpserver `docker logs agent-backend`): `[TASK:*]` 태그가 A0·R0·R1 **셋뿐**, `Task_C_B_domain_worker_*` 인스턴스 0개. 결정적 한 줄 — `WARNING [FANOUT] 'Task_C_BO_A0…' produced no fan-out items; setting aggregate events for ['Task_C_B_domain_worker_*'] to unblock downstream`. +- **원인**: 오케스트레이터 `agent.py:2328 _extract_fanout_items` 는 원천 task **반환 JSON 최상위 `dynamic_fanout` 리스트**(또는 최상위가 list)만 fan-out 항목으로 인정하고 파일은 보지 않는다. A0 는 계획을 `fanout/domain_fanout_plan.json` 에 쓰고 반환에는 `fanout_instance_count` 라는 **개수만** 실었다 → 항목 0 → 교착 회피 설계에 따라 wildcard 를 expanded 로 표시하고 aggregate event 를 set 해 **R0 를 그냥 통과**시켰다. R0 는 아무도 만들 지시를 받지 않은 seed 26개를 읽으러 가 첫 항목 E-00 에서 멈춘 것 — E-00 은 특별하지 않고 나머지 25개도 동일 부재. +- **수정 1곳**(`ver_8_yaml_candidates/stage_1_part_2_v.8.yml` 739~750행, A0 `main()` 반환문): `"dynamic_fanout": plan_root.get("task_instances") or []` 추가 + 사유 주석 3행. 계획 파일 기록·스키마 검증·`fanout_instance_count` 는 불변. 새 로직 0 — `domain_fanout_planner.py:125-138` 의 `task_instances` 원소가 B 템플릿 참조 5필드(`task_instance_id`·`domain_id`·`slice_path`·`compiled_prompt_path`·`expected_output_path`)를 이미 정확히 채우고 `EXPECTED_OUTPUT_PATH_MISMATCH` 경성 검사까지 갖췄다. +- **검증**: `code:` 블록 4개 전건 `ast.parse` PASS, A0 반환 dict 키를 **문자열 검색이 아니라 AST 로** 확인(10키, `dynamic_fanout` 포함). +- **배치**: 수정본 제자리 overwrite, 수정 전 원본 → `ver_8_yaml_candidates/outdated/stage_1_part_2_v.8_Aug_21.yml`(sha `f1d8d043…`). +- **미검증**: R0 이후 R1·F0·S0 는 이 워크스페이스에서 한 번도 실행된 적이 없다. 이 수정은 fan-out 장애를 제거할 뿐 Part 2 완주를 보장하지 않는다. Part 3 은 wildcard 가 없어 자체 결함은 없고, 입력(`BO.json`·`signals/*`)이 F0·S0 산출이라 Part 2 완주에 종속된다. + +### 교훈 +1. **"파일이 없다"는 오류가 실제로는 "그 파일을 만들 작업이 실행된 적 없다"일 수 있다.** 소비자(R0)가 내는 메시지는 생산자 부재를 가리키지 못한다. 산출물 부재를 만나면 소비자 코드를 파기 전에 **생산자 task 가 로그에 실재하는지 먼저 세어야 한다** — `[TASK:*]` 태그 uniq -c 한 번이 이번 진단의 전부였다. +2. **오케스트레이터가 task 간에 무엇을 어떻게 전달하는지는 YAML 이 아니라 백엔드 코드가 정한다.** A0 는 계획을 완벽히 만들어 파일로 남겼고 스키마 검증까지 통과했지만, 런타임 계약은 **반환 JSON 의 특정 키**였다. 자산·산출물 정합성만 검사하는 게이트는 이 종류의 불일치를 원리상 잡지 못한다. +3. **교착 회피 설계는 실패를 성공처럼 보이게 만든다.** `unblock downstream` 은 멈추지 않기 위한 합리적 선택이지만, 그 결과 A0 는 성공으로 기록되고 장애는 두 task 뒤에서 무관해 보이는 모습으로 터졌다. 경고 로그가 유일한 단서였다 — **WARNING 을 읽지 않으면 원인 지점과 발현 지점이 영영 연결되지 않는다.** + +## 2026-08-21 ㉜ — B worker 프롬프트 키 오선언 수정 (`prompt` → `prompts`, use_tools 추가) + +한 줄 요약: ㉛ 수정으로 fan-out 은 성공(14항목→14인스턴스)했으나 `Task_C_B_domain_worker_0~13` 전건이 `LLM API 오류: contents are required` 로 즉사 — 원인은 B worker task 가 프롬프트를 **단수 `prompt:` 스칼라**로 선언한 것. 백엔드는 **복수 `prompts:` 리스트**만 읽는다. + +- **서버 로그 실측**: `[FANOUT] produced 14 fan-out items → expanding` · `Expanded → 14 instances` (㉛ 수정 성립 확인). 이어 전 인스턴스 `ValueError: contents are required`, 그리고 `Current token count: 388 / 360,000` — 수백 행짜리 worker 프롬프트가 한 토큰도 안 실렸다는 증거. +- **원인**: `agent.py` 1957·2903·3739행 모두 `prompts=config.get("prompts", [])`. 단수 `prompt` 를 읽는 코드는 **없다** → `contents` 빈 배열 → Gemini 거부. 같은 파일 R1 은 `prompts:` 형식이라 정상 동작했다. +- **수정 2곳**(`ver_8_yaml_candidates/stage_1_part_2_v.8.yml` B worker 블록 758~889행): ① `prompt: |-` → `prompts:` / `- role: user` / `content: |-` (본문 115행은 +2 들여쓰기만, 공백 제외 전건 일치 확인 — 내용 불변) ② `use_tools: [localdocs]` 를 R1 과 동일 위치(llm_verbosity 다음)에 추가. +- **use_tools 를 함께 넣은 이유**: `agent.py:2893-2897` — `use_tools` 리스트도 `tools: true` 도 없으면 `task_mcp_manager = None` 이라 MCP 도구가 **아예 전달되지 않는다**. worker 의 본무가 `write_file(overwrite=true)` 로 `{{item.expected_output_path}}` 를 쓰는 것이므로, 이것 없이는 `contents` 오류만 넘기고 R0 의 seed 부재로 되돌아간다. +- **`preflight: true` 는 넣지 않았다**: `config.get("preflight", True)` — 기본값이 이미 True. 직전 답변에서 R1 대비 "세 가지가 없다"고 했으나 실제로 조치가 필요한 것은 두 가지였다. +- **검증**: YAML 파싱 OK · B worker 키 10종 · 단수 `prompt` 잔존 false · `prompts` 1항목(role=user, content 5,492 B/115행) · `{{item.*}}` 5종 보존 · 전 task 중 단수 `prompt` 사용 0건 · `code:` 블록 4개 AST PASS. +- **배치**: 제자리 overwrite, 수정 전 원본 → `outdated/stage_1_part_2_v.8_Aug_21_11pm.yml`(sha `abeaa8e7…`). +- **미검증**: worker 가 실제로 산출 파일을 쓰는지, R1·F0·S0 가 도는지는 다음 실주행에서만 확인된다. 인스턴스 14개(도메인 26개 아님)는 활성화 도메인 수로 보이며 별건. + +### 교훈 +1. **오케스트레이터가 읽는 키 이름은 YAML 작성자의 직관과 다를 수 있고, 틀려도 조용하다.** `prompt` 는 문법상 완전히 유효한 YAML 키라 파싱·스키마 검사를 전부 통과하고, 백엔드는 모르는 키를 그냥 무시했다. 발현은 두 단계 뒤 LLM 공급자의 `contents are required` 였다. **같은 파일에서 정상 동작하는 동종 task 와 키를 대조하는 것이 가장 빠른 판별법이다** — R1 과 나란히 놓자 즉시 드러났다. +2. **토큰 카운트는 프롬프트 전달 여부의 직접 증거다.** `Current token count: 388` 한 줄이 "프롬프트가 안 실렸다"를 확정했다. 수백 행 프롬프트를 선언해 놓고 세 자리 토큰이면 렌더링·전달 어딘가가 끊긴 것이다. +3. **기본값을 확인하지 않고 "없으니 추가"하면 불필요한 변경이 섞인다.** `preflight` 는 기본 True 라 추가가 무의미했다. 반대로 `use_tools` 는 없으면 도구가 0개가 되는 **의미 있는 부재**였다. 부재를 발견하면 그 필드의 **기본값부터 코드에서 확인**해야 추가할 것과 둘 것이 갈린다. + +## 2026-08-22 ㉝ — 산출물 빈약 원인 2건 수정 (worker 모델 복원 + 프롬프트↔투영정책 정렬) + +한 줄 요약: Part 2 실주행 성공했으나 결과물이 구버전보다 빈약(BO.json 11,602 B vs 이전 173,546 B, `Action`이 전부 `"event"|"state"`, `Legal_Keywords` 9건 모두 `[]`). 원인 두 가지를 실측으로 특정해 수정 — ① worker 모델이 최하위 등급 ② worker 프롬프트 출력 골격이 투영 정책의 1순위 출처 필드를 요구하지 않음. + +- **실측 경로**: 문서는 저장 시 암호화라 디스크 직독 불가 → localdocs MCP 를 `clientInfo.user_id|workspace_id` 해시로 호출해 복호화본을 읽었다(워크스페이스는 `find -newermt` 로 특정: `31d5b916…/8b14bcb4…`). +- **원인 ①**: v3 는 도메인 worker 5개가 전부 `gemini-3.1-pro-preview`(reasoning high). v.8 은 `Task_C_B_domain_worker_*` 가 `gemini-3.1-flash-lite` — v3 에서 flash-lite 는 단순 판정 R1(reasoning low) 전용 등급이었다. 실측: worker seed 13개 총합 **21,669 B**(이전 BO.json 하나의 1/8), 도메인당 후보 0~2건, **6개 도메인(E-06·E-07·E-08·E-11·X2·X3)은 후보 0건**, signal_manifest 29항목 중 record 보유 3개뿐(E-15:2·X1:4·lifecycle:6). +- **원인 ② — 내가 앞서 낸 진단을 정정한다.** "F0 투영이 v3 시절 필드명을 읽어 스키마와 어긋난다"는 **틀렸다**. 매핑은 `bo_surface_projection_policy.v1.json` 에 새 스키마 기준(`bo_type`·`juristic_act_type`·`legal_effect_candidates[].type_id`·`time_facts`·`object_refs`·`amount_facts`)으로 이미 선언돼 있고, R0(1459·1546행)·F0(2142행)가 실제로 불러 정상 작동했다 — 실주행 review_handoff 에 `schema_field_fallback` **22건**이 규정대로 발행된 것이 증거다. 진짜 어긋난 곳은 **worker 프롬프트의 출력 골격**이었다: 스키마 18필드 중 `juristic_act_type`·`party_roles`·`time_facts`·`object_refs`·`amount_facts` 5종이 골격에 없고 `extensions` 는 `{}` 로만 제시돼, `action_summary`·`action_type` 을 요구하는 문장이 프롬프트 전체에 **0회**였다. 모델이 채울 자리를 안내받지 못했으니 폴백은 필연이었다. +- **수정 2곳**(`ver_8_yaml_candidates/stage_1_part_2_v.8.yml`): ① 764행 모델 `flash-lite` → `pro-preview`(reasoning high 유지, 다른 task 모델 불변 — R1 은 flash-lite 그대로) ② worker 프롬프트 골격에 5필드 추가 + `extensions.domain_payload{action_summary, action_type}` 명시 + "투영 1순위 출처" 지시문(각 필드의 JSON 형태와 투영 대상 표기, 근거 없으면 비우고 review 남기라는 단서 포함). +- **검증**: YAML 파싱 OK · 골격을 JSON 으로 파싱해 스키마 대조 → **후보 필드 18종, 스키마 밖 0, 누락 0** · 투영 1순위 출처 8종 **전건 포함** · `code:` 블록 4개 AST PASS · 프롬프트 7,167 B/132행. +- **배치**: 제자리 overwrite, 수정 전 원본 → `outdated/stage_1_part_2_v.8_Aug_22_12am.yml`(sha `1f0b9a52…`). +- **미검증**: 실주행 전. pro-preview 는 flash-lite 대비 비용·지연이 크므로 `max_concurrency: 8` 에서의 실행 시간과 rate limit 을 다음 회차에 관측할 것. + +### 교훈 +1. **"어긋났다"를 코드에서 찾기 전에 그 계약을 선언한 자산이 있는지부터 확인한다.** F0 코드의 대체 체인(`seed.Action → action_summary → Action_proposal`)만 보고 스키마 불일치라 단정했으나, 그것은 정책이 명시한 **방어선**이었고 정본 매핑은 별도 JSON 자산에 있었다. 정책 파일을 먼저 읽었으면 오진을 피했다. +2. **폴백이 조용하지 않게 설계돼 있으면 그 로그가 곧 진단서다.** `schema_field_fallback` 22건은 "투영기가 돌았고, 출처가 비었음을 알고 있다"를 동시에 말해 준다. 결과물만 보고 원인을 추정하기 전에 **파이프라인이 스스로 남긴 검토 기록을 먼저 세어야 한다.** +3. **스키마가 필드를 정의해도 프롬프트가 보여주지 않으면 LLM 은 채우지 않는다.** 스키마·정책·프롬프트 세 곳이 같은 필드 집합을 말하는지 기계로 대조할 수 있다(이번에 골격 JSON ↔ 스키마 properties ↔ 정책 sources 3자 대조로 0/0/0 확인). 이 대조를 개정 절차에 넣으면 같은 종류의 누락이 재발하지 않는다. +4. **모델 등급 강등은 스키마·게이트를 전부 통과하면서 품질만 떨어뜨린다.** 파이프라인은 "성공"으로 끝났고 무결성 검사도 전부 통과했다. 산출물 크기를 구버전과 대조하지 않았으면 발견되지 않았을 종류의 회귀다 — **개정 시 모델 배정표를 구버전과 나란히 비교하는 항목을 두어야 한다.** + +## 2026-08-22 ㉞ — slice event 식별자를 정본으로 교체 (R0 오차단 해소) + +한 줄 요약: R0 가 `BLOCK: E-01.E-01:001: source_refs outside Stage A universe: ['event:unidentified:0003','event:unidentified:0010']` 로 실패. 워커 잘못이 아니라 **A0 가 같은 자료를 두 산출물에 다른 식별자로 적어 놓은 것** — slice 는 `event:unidentified:NNNN`, manifest 는 `EVT-001-01`, **교집합 0**. + +- **실측 진단**: 문제의 두 ref 는 워커가 받은 slice 의 `source_universe` 안에 **실재**했다(event_candidate 30건 전부 그 형식). 반면 R0 가 대조하는 `source_universe_manifest.event_candidate_ids` 는 84건 전부 `EVT-...-NN`. 30 vs 84 라는 숫자가 결정적 단서였다 — **30 은 증거 문서 수, 84 는 실제 event 후보 수**. 즉 slice 의 "event 후보"는 후보가 아니라 **문서 레코드가 후보로 오인된 것**이었다. +- **기전**: A0 가 `compile_domain_slices(..., events_document)` 로 원시 `evidence_event_candidates.json` 을 넘겼고, `domain_slice_compiler._records` 가 문서-단위 `items` 30건을 후보로 집었다. 그 문서 객체에는 `candidate_id` 가 없으므로 `_record_id`(42행)의 폴백 `f"{kind}:unidentified:{index:04d}"` 가 30번 발화했다. +- **왜 이제야 터졌나**: flash-lite 시절 워커는 event 후보를 거의 인용하지 못했다. pro-preview 로 바꿔 산출이 실해지자 곧바로 드러났다 — **신규 결함이 아니라 가려져 있던 결함의 노출**이다. +- **수정 3곳**(`ver_8_yaml_candidates/stage_1_part_2_v.8.yml`, A0): ① `stage_a`·`source_manifest` 계산을 slice 컴파일 **앞으로** 이동(P-2a 주석) — slice 가 인용할 식별자의 정본이 `event_candidate_map` 이기 때문. 원래 자리에는 `write_doc` 2건만 남겼다(중복 계산 0, 호출 각 1회 확인) ② 후보 레코드 평탄화 `event_candidate_records = [... by_candidate_id.values() ...]` + 공집합 시 `PART2_EVENT_CANDIDATE_MAP_EMPTY` 경성 검사 ③ 컴파일러 인자를 `events_document` → `event_candidate_records`. +- **맵을 통째로 넘기면 안 되는 이유(실측)**: `_records` 는 dict 를 받으면 `("by_evidence_index","by_event_candidate_id","by_id")` 순으로 보는데, 맵의 실제 키는 **`by_candidate_id`** 이고 `by_evidence_index` 는 값이 id 리스트라 먼저 걸려 **빈 목록**을 돌려준다(첫 시도에서 0건 재현). 평탄화 목록으로 넘겨야 한다. +- **검증(서버 실행)**: 정본 84 · 평탄화 84 · `_records` 84 · `_record_id` → `EVT-001-01…` · **정본과 교집합 84/84** · `unidentified` 잔존 0 · **R0 검사 재현 `set(ids) <= canon` = True**. YAML 파싱 OK, `code:` 블록 4개 AST PASS, 정의/사용 순서 633·641 → 645 → 707·708. +- **부수 이득**: 후보 레코드는 문서 레코드보다 훨씬 풍부하고 **84/84 가 `action_summary` 를 보유**한다 — ㉝에서 프롬프트에 요구를 추가한 투영 `Action` 의 1순위 출처가 이제 slice 안에 실제로 존재한다. +- **배치**: 제자리 overwrite, 수정 전 원본 → `outdated/stage_1_part_2_v.8_Aug_22_1am.yml`(sha `eb713b19…`). + +### 교훈 +1. **"universe 밖" 오류를 만나면 워커를 의심하기 전에 두 universe 가 같은 것인지부터 본다.** 검사자와 피검사자가 서로 다른 목록을 보고 있으면, 규율을 완벽히 지킨 산출물도 100% 차단된다. 이번엔 워커가 받은 slice 안의 값을 인용했는데도 차단됐다. +2. **개수 불일치는 형식 불일치보다 먼저 눈에 띄는 단서다.** 30 vs 84 를 보고 "형식이 다르다"가 아니라 "**차원이 다르다**(문서 vs 후보)"를 읽어야 진짜 기전에 닿는다. 형식만 맞추려 했으면 문서 30건에 EVT 번호를 붙이는 잘못된 수정을 했을 것이다. +3. **식별자 폴백은 침묵하는 오염원이다.** `f"{kind}:unidentified:{index:04d}"` 는 예외를 던지지 않고 그럴듯한 문자열을 만들어 하류로 흘린다. 정본이 없으면 만들어 내지 말고 멈추는 편이 낫다 — 이번 수정에 공집합 경성 검사를 함께 넣은 이유다. +4. **모델을 강화하면 숨어 있던 계약 위반이 드러난다.** 능력이 낮은 모델은 계약을 적게 건드리므로 결함도 적게 노출한다. **품질 개선 직후의 새 실패는 회귀가 아니라 발굴일 가능성을 먼저 검토해야 한다.** + +## 2026-08-22 ㉟ — worker 프롬프트에 스키마 어휘·객체 형태 명시 (S0 enum 게이트 실패 해소) + +한 줄 요약: S0 가 `S0_SIGNAL_GATE_FAILED` 로 실패(E-13 4건·X3 2건, `evidence_slot_status[].status` outside enum). 원인은 ㉝와 같은 계열 — **스키마가 값을 정의해도 프롬프트가 보여 주지 않으면 모델이 지어낸다.** 전수 대조로 enum 8곳·엄격 객체 6종을 한 번에 안내했다. + +- **실측**: 정본 enum 은 `filled|partial|missing|conflicted`. 워커 산출은 E-13 이 `supported`·`missing_required`(소문자), X3 가 `SUPPORTED`(대문자) — **도메인마다 제각각**이고 6건 전부 enum 밖. 프롬프트 본문에서 `filled`·`partial`·`conflicted` 각 **0회**. +- **R0 를 통과한 이유**: R0 검증기가 `schema_subset_validator`, 곧 스키마 **부분집합만** 본다. enum·additionalProperties 위반은 마지막 S0 게이트에서야 걸린다. +- **전수 대조로 드러난 더 큰 구멍**: 시드 스키마 enum 선언 **8곳 전부** 안내 누락이었다. 나아가 `element_fact_candidates`·`opposing_fact_candidates`·`defense_candidates` 는 `additionalProperties: false` 에 필수 키가 `source_id`·`source_kind` 인데, 워커는 `{slot_id, fact, source_refs}` 를 내고 있었다 — **키 3개 전부 스키마 밖, 필수 2개 전부 누락**. enum 만 고쳤으면 다음 회차에 확실히 다시 실패했을 자리다. +- **수정 1곳**(`ver_8_yaml_candidates/stage_1_part_2_v.8.yml`, worker 프롬프트 — ㉝의 "투영 1순위 출처" 블록 뒤): ① 스키마 고정 어휘 5종(seed `status` 5값 · `evidence_slot_status.status` 4값 + 판단 기준 · `calculation_requests.completeness` 4값 · `severity` 4값 · `source_kind` 10값) ② `additionalProperties: false` 인 객체 6종의 정확한 형태와 필수 키 — 특히 세 후보 배열에 "`slot_id`·`fact` 같은 키는 없다, 슬롯 판정은 `evidence_slot_status[]` 가 맡는다"를 명시 ③ "R0 검증기는 부분집합만 보므로 여기서 틀려도 그 단계에서는 안 걸린다"를 프롬프트에 적어 모델이 R0 통과를 안전신호로 오해하지 않게 했다. +- **검증(기계 대조)**: 스키마에서 enum 선언을 전수 추출해 프롬프트 포함 여부 대조 → **8/8 안내, 누락 0**. 엄격 객체 6종의 `required` 키 전건 포함(1차 수정 후 `calculation_domain`·`reason` 2개가 남아 2차로 보완). YAML 파싱 OK, `code:` 블록 4개 AST PASS. 프롬프트 7,167 → **9,198 B / 157행**. +- **배치**: 제자리 overwrite, 수정 전 원본 → `outdated/stage_1_part_2_v.8_Aug_22_9_45am.yml`(sha `5e2166ee…`). +- **이월**: R0 검증을 부분집합이 아니라 전체 스키마 검증으로 올리면 이 계열 결함이 워커 단계에서 즉시 잡힌다. 다만 R0 의 실패 정책·재시도 설계를 함께 바꿔야 하므로 별건이다. + +### 교훈 +1. **같은 결함이 세 번 반복됐다(㉜ 키 이름 · ㉝ 필드 누락 · ㉟ 어휘·형태 누락).** 공통 뿌리는 "계약을 선언한 자산과 그 계약을 이행할 프롬프트가 따로 관리된다"는 것이다. 개별 수정이 아니라 **스키마에서 프롬프트 안내를 기계로 대조하는 절차**가 필요하다 — 이번에 쓴 대조(enum 전수 추출 → 프롬프트 포함 여부, `required` 키 → 포함 여부)를 개정 체크리스트로 고정할 것. +2. **한 건이 보고되면 같은 종류를 전수로 훑고 나서 고친다.** 보고된 것은 `evidence_slot_status.status` 하나였지만 실제로는 enum 8곳 + 객체 형태 6종이 뚫려 있었다. 보고된 것만 고쳤으면 최소 두 회차를 더 돌았다. +3. **느슨한 검증기는 결함을 없애지 않고 뒤로 미룬다.** `schema_subset_validator` 가 R0 에서 통과시킨 값이 S0 에서 경성으로 막혔다. 검증을 어디서 얼마나 하느냐가 곧 "결함이 어느 task 에서 터지느냐"를 정한다 — 원인 지점과 발현 지점이 멀어질수록 진단 비용이 커진다. + +## 2026-08-22 ㊱ — R0 시드 수용(admission) 도입: 전체 스키마 검증에 등급을 주다 + +한 줄 요약: "R0 검증을 부분집합에서 전체로 올린다"는 이월을 실행했는데 **전제가 틀렸다 — 전체 검증은 이미 돌고 있었다.** `worker_output_validator` 가 `schema_subset_validator.validate` 로 전 계약을 검사하고, R0 가 그 결과를 `guard_warnings` 로 강등하고 있었다(14도메인 실측 **1,151건**). 그래서 한 일은 검증을 켜는 것이 아니라 **이미 나오던 결과에 처분을 주는 것**이었다. 적대적 sub-agent **3라운드**(FAIL 결함 8 → FAIL 결함 4 → PASS 결함 0). + +- **신설 자산** `Default_Agent/stage1_runtime/seed_admission_policy.v1.json`(매니페스트 433→434, A0 `PART2_REQUIRED_ASSETS` 등재로 게이트 19자산): 처분표 38코드 · `enum_synonyms` 8경로 · `required_derivation` 29 · `surplus_sink` · `quarantine_budget` · `enforcement: shadow|enforce`. 정책 선언·코드 실행 분리는 `bo_surface_projection_policy` 관용을 따랐다. +- **R0 수용 기계**(약 300행): 검증 → 처분(block·quarantine·review·repair) → 복구 → 재검증을 수렴까지(최대 12패스, 실측 12=40 동일 수렴). 복구 4종 — `normalize`(동의어+대소문자 접기) · `derive`(constant/from_context/universe 조회/패턴/**rename_sibling**) · `harvest`(자유 공간 이관 + **별칭 승격**) · `evict`. +- **최종 실측(14도메인 실데이터)**: 위반 1,151 → **잔여 24건, 전부 `review` 로 선언된 것**. 후보 48 전건 수용 · 격리 0 · 중단 0 · **선언 48 = 수용 48 + 격리 0** · 정보 소실 **3건**(전부 enum 정규화 원값으로 `received` 보존) · 결정적 · 전 도메인 0.08s. +- **파괴 시험 6종 전부 의도대로**: `bo_type=dict` → 그 후보만 격리하고 47건 투영 완주 / `seed_id` 위반 → 삭제 아닌 격리 / 뿌리 `schema_version` 훼손 → block / 표에 없는 enum → 회차 계속 / 격리율 0.9375 → `BUDGET_EXCEEDED` / shadow → 원본 14/14 불변. +- **하류 실질 보존(어댑터 실행 대조)**: `s3_domain_seed_adapter` SG-05 status `{inferred:133}` → `{inferred:104, supported:13, active:7}`, `slot_id=unknown_slot` 8 → **0**. 개정 전에는 어댑터가 아무것도 못 읽었다. +- **1차가 잡은 치명 2건**(둘 다 개정의 존재 이유를 무너뜨렸다): ① `admit_seed` 가 `worker_validator` 를 전역으로 읽는데 그 이름은 `main()` 지역 → 정상 배포에서 첫 도메인 `NameError` 즉사(인자 주입으로 해소) ② `sink[norm]=moved` 가 인덱스를 접은 경로를 키로 써 같은 경로 두 번째 값부터 조용히 덮음 → **정보 74건 소실**(오국한·김선웅·흑석동 상가·포천시 임야…). 후보 보관을 `{path, source_path, value}` **덧붙이기 전용 목록**으로 바꾸고 보관처가 없으면 지우지 않도록 해 **74 → 3건**. +- **2차가 잡은 4건**: `VALIDATOR_CRASHED` 의 path 가 뿌리라 격리가 집행되지 않고 투영에서 다시 죽던 것(후보별 재검증으로 범인을 좁히고, 못 좁히면 `VALIDATOR_CRASHED_ROOT`→block) / 회계 검사가 수용 **후** 배열을 "선언 수"로 세어 **항등식**이 된 죽은 검사(후보 36개가 사라져도 무반응 → 기준을 수용 전으로) / 보관 항목에 원본 경로 부재로 역할 결합(채권자↔채무자) 복원 불가 / `registered` 상수 파생이 registry 로 판정 가능한 사실을 단언. +- **3차가 잡은 1건**: 회계 누적이 `if worker_validator is not None:` 안에 있어 검증기 부재 시 `lost:-48` 헛발동. 분기 밖으로 이동. +- **검토 등급 분리**: 기계적 키 이동 1,207건(SOFT)과 **객체째 밀려난 실질 서술 13건(HARD, `seed_admission_content_evicted`)** 을 나눴다. 판정을 `isinstance(moved, dict)` 로 좁힌 것이 핵심 — 스칼라까지 포함하면 467건(38%)이 걸려 등급이 신호를 잃는다. 스칼라 329건은 `source_path` 100% 보유라 기계 복원이 되므로 사람 검토가 불필요하다. +- **자진 신고**: 투영부 방어를 넣을 때 `replace(..., 1)` 로 첫 출현을 쳐서 **F0 에 들어갔다**. 실제 사망 지점은 R0 `project_to_bo_surface` 였고, 스스로 발견해 R0 에도 넣었다(F0 것도 같은 잠재 결함이 있어 존치, 정상 문자열 경로 48/48 불변 확인). +- **배치**: 제자리 overwrite, 수정 전 원본 → `outdated/stage_1_part_2_v.8_Aug_22_15_35am.yml`(sha `39e5e151…`). 변경 A0 1헝크 · F0 1헝크(+7행) · R0 7헝크, **worker·R1·S0 추가삭제 0행**. +- **이월**: ① **서버 배포 필요** — 새 정책과 434 매니페스트를 올려야 A0 게이트를 지난다(서버 433, 자산 미배포) ② **첫 실전은 `shadow`** 로 기록만 받고 확인 후 `enforce` ③ **스키마 개정**: `party_refs` 패턴 `^[A-Za-z]…$` 가 한글을 배제해 `party_roles` 66/66 이 빈 배열로 남고 하류 `projections.py:82` 가 "당사자 없음"을 읽는다 — 복구기가 아니라 스키마 문제 ④ SOFT 1,207건을 도메인·경로별 집계로 접는 것은 다음 회차 의제. + +### 교훈 +1. **"켜야 한다"고 말하기 전에 이미 켜져 있는지 본다.** 나는 두 번이나 "부분집합 검증을 전체로 올리자"고 답했지만 전체 검증은 처음부터 돌고 있었고 결과가 버려지고 있었다. 검증기 이름(`schema_subset_validator`)이 실제 능력(enum·additionalProperties 지원)과 달라 오해를 굳혔다 — **이름이 아니라 호출부와 반환값 처리를 읽어야 한다.** +2. **엄격하게 만드는 개정은 "그냥 켜면" 100% 실패한다.** 켜기 전에 위반을 전수로 세어야 규모를 안다(1,151건). 그 수를 몰랐으면 복구 계층 없이 켰을 것이고 전 도메인이 즉사했다. +3. **정보 보존은 "옮겼다"로 끝나지 않는다 — 옮긴 자리를 하류가 읽어야 보존이다.** 첫 판은 바이트를 옮기면서도 어댑터가 못 읽는 키를 썼고, 같은 키로 덮어써 74건을 실제로 잃었다. **덧붙이기 전용 자료구조는 이 계열 사고를 규율이 아니라 형태로 막는다.** +4. **지표가 0이면 좋아할 것이 아니라 그 경로가 도달 가능한지부터 확인한다.** 1차 판에서 "격리 0"은 안전이 아니라 격리 경로가 죽어 있고 `evict` 가 후보를 조용히 지우고 있다는 신호였다. **회계 항등식(선언 = 수용 + 격리)** 을 두면 이런 누수가 드러난다 — 단, 그 항등식이 양변에 같은 값을 놓지 않는지도 확인해야 한다(2차 결함이 정확히 그것이었다). +5. **등급으로 덮을 문제와 스키마로 고칠 문제를 섞지 않는다.** 당사자 이름 85건을 HARD 로 올려도 `party_refs: []` 레코드는 고쳐지지 않는다. 복구기가 감당할 수 있는 것과 계약 자체를 고쳐야 하는 것의 경계를 그어야 검토 채널이 소음이 되지 않는다. +6. **`replace(..., 1)` 은 같은 패턴이 여러 task 에 있는 파일에서 위험하다.** 투영부 방어가 F0 로 갔고, 스스로 잡지 않았으면 "적용 완료"로 보고했을 것이다. **치환 후에는 들어간 줄이 어느 task 범위인지 기계로 대조**한다(헝크 행번호 ↔ task 경계). + +## 2026-08-22 ㊲ — 시드 신선도를 워커 자기신고에서 실물 근거로 (echo 4등급 분리) + +한 줄 요약: R0 가 `ValueError: E-00: stale seed output: slice_sha256 mismatch` 로 회차 전체를 죽였다. **시드는 낡지 않았다** — 모델을 거치지 않는 실물 두 값(계획서 sha ↔ R0 가 직접 읽은 문서 sha)이 `54ae4e6b…` 로 일치했다. 워커 한 명의 전사 실수가 회차를 죽인 것. 신선도 판정을 실물로 옮기고 echo 불일치를 4등급으로 갈랐다. 적대적 sub-agent **4라운드**(FAIL BLOCKER → FAIL 4건 → FAIL 1건 → PASS 결함 0). + +- **원인 확정**: E-00 이 낸 `de582c93…b15d` 는 슬라이스의 `.stage_b_domain_slice.activation.activation_manifest_sha256`(전체 64자 경로 탐색). 프롬프트는 `"slice_sha256": "{{item.slice_sha256}}"` 로 **정답을 직접 주고 13/14 가 그대로 복사**했다. 결정적 사실 — **그 값은 14개 슬라이스 전부에 같은 값**이라 어느 도메인에서든 재발 가능했다(내 첫 진단은 "E-00 슬라이스에 마침 있었다"로 위험을 과소평가했다). +- **수정 3곳**(`ver_8_yaml_candidates/stage_1_part_2_v.8.yml`, R0 3헝크 + worker 프롬프트 1헝크): ① `validate_seed_object` 의 `raise ValueError` 제거 → `echo_mismatches` 수집 ② 호출부에 **R0-7a 실물 대조**(`plan.slice_sha256` ↔ `hashlib.sha256(read_raw(slice))`) → 어긋나면 `PART2_SLICE_STALE` 경성 ③ **R0-7b echo 4등급**. +- **4등급(분기 순서가 곧 설계다)**: `FOREIGN_SLICE`(남의 slice·compiled sha → **block**, 실물 대조로는 원리상 못 잡는 "엉뚱한 자료로 작업") → `UNVERIFIABLE`(원문 못 읽음 → **quarantine**) → `SHA_MISMATCH`(그 도메인 `activation_manifest_sha256` = 값으로 식별되는 알려진 오집합 → **review**) → `UNEXPLAINED`(설명 안 됨 → **quarantine**). 정책 `disposition_by_code` 등재 + 코드 내 동일 안전 기본값. +- **실측(14도메인, 주입 0·배포본 그대로)**: 기준선 완주 14/14 · 중단 0 · E-00 만 review. 파괴 10종 전수 — 슬라이스 변조/계획서 변조 → `PART2_SLICE_STALE`, 남의 slice·compiled sha → block(읽기 상태 3종 모두 유지), 부재 → quarantine, 정책 승격·강등 양방향 작동. sha 28개 전부 고유(오탐 0). +- **정본 기준선(회귀 감시용 고정값)**: `input 40 · ledger 40 · declared 40 = admitted 40 + quarantined 0 · 소실 0 · review_items 624 · exception 1 · admission_records 498(review 252/derive 130/harvest 116) · echo 1`. 매니페스트 434 · 157/0/277. A0·R1·F0·S0 IDENTICAL. +- **4라운드가 잡은 것**: D1 **BLOCKER — `sha_text` 가 A0 전용인데 R0 에서 호출**(정상 배포에서 첫 도메인 즉사) / D2 슬라이스 부재 시 신선도 무방비 / D3 봉투 변경 시 `allowed_bo_types` 조용한 소실 / D4·D6 시그니처 기본값 보고 불일치 / D5 프롬프트 값 자리 자기모순 / D7 sha 중복 오탐 / D8 등급 순서 역전 / D9 분기 순서 충돌. 전부 닫혔다. +- **배치**: 제자리 overwrite, 수정 전 원본 → `outdated/stage_1_part_2_v.8_Aug_22_21h_30m.yml`(sha `457213002af84759`). +- **이월**: ① **서버 배포**(policy + manifest 434) — 코드는 구 policy 로도 안전 기본값으로 완주하나, 정책으로 등급을 조정하려면 선결 ② 실주행 첫 회차에 `SEED_ECHO_*` 4종 분포 확인 — 프롬프트 개정이 먹혔으면 echo 0 이어야 하고, 여러 도메인에 `UNEXPLAINED` 가 뜨면 골격 해석이 흔들린 것 ③ 집계 시 `UNVERIFIABLE` 과 `UNEXPLAINED` 는 합산해 읽을 것(원문을 못 읽으면 원인을 단정하지 않으므로). + +### 교훈 +1. **내 검증 하네스가 결함을 가렸다 — 이번 회차 최대 실패.** `sha_text` 가 배포본에 없는데 하네스가 그 이름을 주입해 놓고 "동작 확인"이라고 보고했다. `ast.parse` 는 이 계열을 **원리적으로** 못 잡는다. 두 가지를 규약으로 못박았다 — **주입 없이 배포본을 그대로 태울 것**, **미정의 전역명 검사(symtable)를 AST 와 나란히 돌릴 것**. 뒤이은 라운드에서 이 검사가 즉시 0/1 을 판정했다. +2. **모델의 자기 신고를 게이트의 유일한 근거로 쓰면 전사 실수가 곧 회차 사망이다.** 같은 사실을 확인할 **물리적 경로가 있는데도** 신고에 의존하고 있었다. 게이트를 세울 때 "이 값을 모델을 거치지 않고 확인할 수 있는가"를 먼저 물어야 한다. +3. **그렇다고 신고를 버리면 안 된다 — 강등이지 폐기가 아니다.** 실물 대조는 "무엇이 거기 있는가"만 답하고, echo 는 "워커가 무엇을 읽었는가"에 대한 **유일한 증거**다. 워커가 남의 슬라이스를 읽고 정직하게 echo 하면 실물 대조로는 원리상 못 잡는다. 그래서 echo 를 경성 게이트에서 검토 신호로 내리되 **남의 자료 sha 는 block 으로 남겼다.** +4. **판정 근거의 정보량을 따져야 등급이 선다.** `activation_manifest_sha256` 은 전 도메인이 공유해 "어느 슬라이스를 읽었는지"에 **0비트도 기여하지 않는다.** 그래서 실물 대조가 불가한 상태에서는 그것을 근거로 강등할 수 없다(D9). 등급 체계에서 분기 **순서**가 곧 "무엇을 더 신뢰하는가"의 선언이다. +5. **프롬프트에 규칙을 값 자리에 복제하지 않는다.** 정답 리터럴 옆에 "그대로 옮겨라"를 붙인 것은 자기모순이었다 — 지시를 충실히 따르는 워커일수록 문자열 전체를 값으로 낼 유인이 생긴다. **골격은 모양, 제약 목록은 규칙** — 이 분업이 이 파일의 관례이고 어긴 것이 위험의 원천이었다(D5). +6. **`replace(..., 1)` 과 assert 뒤 write 는 이 파일에서 위험하다.** 이번에도 D1~D3 수정이 뒤이은 assert 실패로 **기록되기 전에 통째로 날아갔는데** 성공 로그만 보고 넘어갈 뻔했다. 치환 후에는 **파일을 다시 읽어 확인**하고, 다중 편집은 앵커가 아니라 확인된 행범위로 처리한다. + +## 2026-08-23 ㊳ — Part 4 yaml 신작 (사실원장 3-task, 검증 2회 왕복 PASS) +- **산출**: `ver_8_yaml_candidates/stage_1_part_4_v.8.yml`(sha fc60011042f1159a·69,004B, 구 v3와 무관 신작) — F0 code 리듀서(배포게이트 13·봉인 3종·컴파일러 13인자 호출) → F1 flash-lite 예외 재정판정(amount 한정) → F2 code 최종 게이트/작성자(등식 5종 재계수·닫힌 29키·handoff FINALIZED), 완전 직렬 DAG. +- **신설 자산 6종** 배포 위치 저장 + 조립본 동기화(빌더 등록 없음): fact_ledger_compiler.py(+.txt 미러, f6c4b476)·fact_ledger_policy.v1.json·스키마 3종. 게이트 사이드카 25종 조립본 바이트 동일 복사. 매니페스트 434→465(+31), 전수 match 188·MISMATCH 0·absent 277. 인계면 45→55행(신설 10·갱신 12, 구 signal 3종 Part_4 소비 제거 = C-7 폐쇄). +- **검증 1차 FAIL의 MAJOR**: 검사기 출력에서 존재하지 않는 `findings` 키를 읽어 falsy 0을 "통과"로 오독 + 소비 3건이 `try_read_json` 래퍼라 U4 비가시. 교훈 두 겹 — ① 검사기류 출력은 키 존재 단언 후 소비(`assert "finding_count" in r`), ② 소비 선언은 `read_json(경로상수)` 직접 호출이어야 실측 가능(부재 의미론은 try/except로 보존). +- 재검증 PASS(DEFECTS 0): 4-yaml finding 0·골든 11필드×59행 불일치 0·26-A 121/25/90/6/6·F2 파괴 8종·부재 의미론 실측. D3는 전략서 §7.4 사실 정정으로 처리 — 골든-S2 SG-01 사본의 registry_index_sha256(a47a76aa…)≠현 배포(9f177ebf…), 재봉인은 그 1필드만 치환(요약서 §5 레시피). +- 이월: 실주행(서버에 신설 31자산+매니페스트 배포 필요)·F1 실 LLM 품질·(나) 기준선 G-r2/26-B·G-w2·SUPP-calc6·F2 사문 try_read_json 정리(v.9). 요약서: `extension_research/part_4_yaml_construction_summary.md`. + +## 2026-08-23 ㊴ — Stage 1 part 1~4 작업분석서 4종 작성 (Stage 2 명세 설계 입력용, 검증 전건 PASS) +- **산출**: `extension_research/Analysis_Doc_stage_1_part_{1,2,3,4}_investrigation_final.md` (74.9/80.5/50.5/55.9 KB). 각 문서: DAG ASCII art(task_procedure 실물 일치) · task별 IO 전수 표(포맷·생산자/소비자) · 자산 표(배포 경로 + .py/.txt 미러 병기) · sub-task 명칭/내역/목적(code task 내부 단계·실패 코드 전수, LLM task 모델/산출 계약) · upstream-downstream 연결 쌍별 이유/방식/필드 소비. +- **방법**: Workflow 16 agents — part별 분석 4 병렬 → 독립 검증자(적대적, YAML에서 task/IO/자산 직접 추출해 양방향 전수 대조) → FAIL 시 수정→재검증 루프. 통과 라운드: p1=2, p2=3, p3=1, p4=2. 최종 결함 0. +- **분석 대상 실측 기준**: p1 sha 195b43ed·467,668B·task 13종(fan-out B1/B2 포함, 자산 45파일) / p2 sha f0a22394·249,809B·task 6종(worker ×14, 자산 152파일) / p3 sha 9a163a03·52,878B·task 2종(L0/L2, 자산 92파일) / p4 sha fc600110·69,004B·task 3종(F0/F1/F2). 인계면 55행·stage_chain 4구간과 전건 대조 일치. +- 검증자 미확인 잔여(문서에 명시됨, 결함 아님): 실주행 기록(서버측)·R1 v3 바이트 대조·part4 "17축" 명칭은 계약 문언 승계(정책 실물은 축 그룹 10+노트 1). diff --git a/Case_02_Comparison_Research/YAML_Prompts/1. Stage_1/v.7/extension_research/Analysis_Doc_stage_1_part_1_investrigation_final.md b/Case_02_Comparison_Research/YAML_Prompts/1. Stage_1/v.7/extension_research/Analysis_Doc_stage_1_part_1_investrigation_final.md new file mode 100644 index 00000000..a3ed76bc --- /dev/null +++ b/Case_02_Comparison_Research/YAML_Prompts/1. Stage_1/v.7/extension_research/Analysis_Doc_stage_1_part_1_investrigation_final.md @@ -0,0 +1,393 @@ +# Stage 1 Part 1 (선별/Screening) 작업분석서 — 최종 실측본 + +> **분석 대상 정본**: `extension_research/ver_8_yaml_candidates/stage_1_part_1_v.8.yml` +> **sha256 (첫 16자)**: `195b43edfaf1df14` +> **바이트 크기**: 467,668 bytes (6,667행) +> **분석 기준일**: 2026-08-23 +> **용도**: Stage 2 작업명세서 설계의 입력 문서. 전 항목 실물 정독 기반이며 추정 서술이 없다. + +--- + +## 0. 개요 (실측 요약) + +- Agent 선언: `Liti-agent_Civil_Suit_Plaintiff_Stage_1_Part_1` (version `v.2`), Stage 1개: `stage1_사건개요파악_기초작업`. +- Stage 기본 LLM: `llm_provider: openai` / `llm_model: gpt-4o-2024-08-06` (단, 모든 LLM task가 task 수준에서 `google` / `gemini-3.1-flash-lite`로 재정의한다). +- mcpServers 2종: `localdocs` (`http://mcp-localdocs:8012/mcp`, streamable-http), `code-executor` (`https://code-executor.mcp.eroomai.com/mcp`, streamable-http, Bearer 헤더). +- task 총 **13종** (LLM 5종 + code-executor/run_code 8종). 이 중 `Task_B1_map_doc_*`, `Task_B2_map_events_*` 2종은 planner의 `dynamic_fanout`으로 N개 인스턴스로 확장되는 템플릿 task다(`max_concurrency: 12`). +- 두 사슬 구조: **스크리너 사슬**(T0-01→T0-02→T0-03→T1)과 **증거 사슬**(T2→B1/B2 fan-out→게이트 3종→감사)이 `IN`에서 병렬 분기하고, `Task_D0_domain_activation_gate`에서 합류한 뒤 `Task_B2_SHA256_soft_gate_handoff_writer`가 봉인하여 `OUT`으로 나간다. +- 헤더 주석의 구성 선언(실물과 일치 확인): 신설 4(`Task_A0_domain_screener_01/02/03`, `Task_D0_domain_activation_gate`), 개정 3(`Task_A_client_goal`, `Task_B1_map_doc_*`, `Task_B2_SHA256_soft_gate_handoff_writer`), 무변경 6(나머지). + +--- + +## 1. 전체 작업 DAG flow (ASCII — `task_procedure` 실물과 1:1 일치) + +`task_procedure`(YAML 6589~6667행)의 노드 13개(IN/OUT 포함 15개)와 `nexts`/`wait_until` 전량을 그대로 옮긴 것이다. + +``` + ┌────┐ + │ IN │ + └─┬──┘ + nexts ┌──────────────┴───────────────────┐ nexts + ▼ ▼ + ┌────────────────────────────┐ ┌───────────────────────────────┐ + │ Task_A0_domain_screener_01 │ │ Task_Evidence_shard_planner │ + │ (LLM: 스크리닝 초안) │ │ (code: shard 분리+fan-out 계획)│ + └─────────────┬──────────────┘ └──────────────┬────────────────┘ + ▼ ▼ (dynamic_fanout ×N) + ┌────────────────────────────┐ ┌───────────────────────────────┐ + │ Task_A0_domain_screener_02 │ │ Task_B1_map_doc_* (×N, LLM) │ + │ (code: 초안 봉인+검증) │ │ wait: Task_Evidence_shard_ │ + └─────────────┬──────────────┘ │ planner │ + │ └───┬──────────────────┬────────┘ + ▼ │ {same_ordinal} │ all + ┌────────────────────────────┐ ▼ ▼ + │ Task_A0_domain_screener_03 │ ┌────────────────────┐ ┌──────────────────────────────┐ + │ (code: 후보 어휘 발췌) │ │ Task_B2_map_events │ │ Task_B1_quality_gate_ │ + └─────────────┬──────────────┘ │ _* (×N, LLM) │ │ evidence_indexed (code) │ + ▼ │ wait: B1_map_doc_ │ │ wait: all Task_B1_map_doc_* │ + ┌────────────────────────────┐ │ {same_ordinal} │ └───────────────┬──────────────┘ + │ Task_A_client_goal (LLM) │ └─────────┬──────────┘ │ + │ nexts: [] (사슬의 끝. │ │ all │ + │ D0의 선행이 아님) │ ▼ ▼ + └────────────────────────────┘ ┌──────────────────────────────────────────────┐ + │ │ Task_B2_quality_gate_event_candidates (code) │ + │ │ wait: all Task_B2_map_events_*, │ + │ │ Task_B1_quality_gate_evidence_indexed │ + │ └───────────────────────┬──────────────────────┘ + │ ▼ + │ ┌──────────────────────────────────────────────┐ + │ │ Task_B12_gate_llm_adjudicator (LLM) │ + │ └───────────────────────┬──────────────────────┘ + │ ▼ + │ ┌──────────────────────────────────────────────┐ + │ │ Task_B12_gate_audit_finalizer (code) │ + │ └───────────────────────┬──────────────────────┘ + │ (02 완료 대기) │ + └──────────────────────┐ │ + ▼ ▼ + ┌──────────────────────────────────────────────────────────┐ + │ Task_D0_domain_activation_gate (code) — 두 사슬의 합류점 │ + │ wait_until: [Task_B12_gate_audit_finalizer, │ + │ Task_A0_domain_screener_02] │ + │ ※ 03이 아니라 02를 기다린다 — D0는 │ + │ candidate_profile_vocabulary.md 를 쓰지 않는다 │ + └───────────────────────────┬──────────────────────────────┘ + ▼ + ┌──────────────────────────────────────────────────────────┐ + │ Task_B2_SHA256_soft_gate_handoff_writer (code) │ + └───────────────────────────┬──────────────────────────────┘ + ▼ + ┌─────┐ + │ OUT │ + └─────┘ +``` + +`task_procedure` 원문 엣지 전수(문자 단위 일치): + +| 노드 | nexts | wait_until | +|---|---|---| +| `IN` | `["Task_A0_domain_screener_01", "Task_Evidence_shard_planner"]` | `[]` | +| `Task_A0_domain_screener_01` | `["Task_A0_domain_screener_02"]` | `["IN"]` | +| `Task_A0_domain_screener_02` | `["Task_A0_domain_screener_03"]` | `["Task_A0_domain_screener_01"]` | +| `Task_A0_domain_screener_03` | `["Task_A_client_goal"]` | `["Task_A0_domain_screener_02"]` | +| `Task_A_client_goal` | `[]` | `["Task_A0_domain_screener_03"]` | +| `Task_Evidence_shard_planner` | `["Task_B1_map_doc_*"]` | `["IN"]` | +| `Task_B1_map_doc_*` | `["Task_B2_map_events_{same_ordinal}", "Task_B1_quality_gate_evidence_indexed"]` | `["Task_Evidence_shard_planner"]` | +| `Task_B2_map_events_*` | `["Task_B2_quality_gate_event_candidates"]` | `["Task_B1_map_doc_{same_ordinal}"]` | +| `Task_B1_quality_gate_evidence_indexed` | `["Task_B2_quality_gate_event_candidates"]` | `["all Task_B1_map_doc_*"]` | +| `Task_B2_quality_gate_event_candidates` | `["Task_B12_gate_llm_adjudicator"]` | `["all Task_B2_map_events_*", "Task_B1_quality_gate_evidence_indexed"]` | +| `Task_B12_gate_llm_adjudicator` | `["Task_B12_gate_audit_finalizer"]` | `["Task_B2_quality_gate_event_candidates"]` | +| `Task_B12_gate_audit_finalizer` | `["Task_D0_domain_activation_gate"]` | `["Task_B12_gate_llm_adjudicator"]` | +| `Task_D0_domain_activation_gate` | `["Task_B2_SHA256_soft_gate_handoff_writer"]` | `["Task_B12_gate_audit_finalizer", "Task_A0_domain_screener_02"]` | +| `Task_B2_SHA256_soft_gate_handoff_writer` | `["OUT"]` | `["Task_D0_domain_activation_gate"]` | +| `OUT` | `[]` | `["Task_B2_SHA256_soft_gate_handoff_writer"]` | + +fan-out 토큰 3종(무변경 실물): `Task_B1_map_doc_*` / `Task_B2_map_events_{same_ordinal}` / `all Task_B1_map_doc_*` 계열. `Task_B2_map_events_*`의 실행 컨텍스트는 planner stdout의 `dynamic_fanout` 리스트(각 항목 `{{item.*}}`)이며, B1→B2 same-ordinal 관계는 plan의 `streaming_edges`(`condition: {file_exists: b1_output}`)로도 이중 선언된다. + +--- + +## 2. IO — task별 입력·출력 파일 전수 표 + +포맷 표기: json / md / py·txt(코드 미러) / yml. `※정적`은 `Default_Agent/` 아래 배포 정적 자산(생산자는 빌드/배포 파이프라인, Part 1 런타임은 읽기 전용). + +### 2.1 외부 사건 입력 (Part 1 어느 task도 생산하지 않음) + +| 경로 | 포맷 | 소비자 (Part 1 내) | 비고 (인계면 정본 기준) | +|---|---|---|---| +| `client_meeting.md` | md | `Task_A0_domain_screener_01`, `Task_A0_domain_screener_02`, `Task_A_client_goal`, `Task_B1_map_doc_*`, `Task_B2_map_events_*` | 유일한 사실 원천. Part 2의 `Task_C_BO_A0_context_and_domain_slice_compiler`도 직접 읽는다(C-8) | +| `evidence_all.json` | json | `Task_Evidence_shard_planner` | 인계면 required_keys: `documents`, `source_files`, `total_count`. planner 코드는 `items/documents/evidences/evidence/docs` 키 또는 순수 배열을 수용 (`source_selector: "$.items || $"`) | + +### 2.2 Part 1 내부 생산/소비 파일 전수 + +| # | 경로 | 포맷 | 생산자 | Part 1 내 소비자 | Part 1 밖 소비자 (인계면 실측) | +|---|---|---|---|---|---| +| 1 | `routing/domain_screening_draft.json` | json | `Task_A0_domain_screener_01` | `Task_A0_domain_screener_02` | (없음) | +| 2 | `routing/domain_screening.json` | json | `Task_A0_domain_screener_02` | `Task_A0_domain_screener_03`, `Task_A_client_goal`, `Task_D0_domain_activation_gate`, `Task_B2_SHA256_soft_gate_handoff_writer` | `Task_C_BO_A0_context_and_domain_slice_compiler` (Part 2). `screening_sha256`의 원문 — 재직렬화 금지 | +| 3 | `routing/candidate_profile_vocabulary.md` | md | `Task_A0_domain_screener_03` | `Task_A_client_goal` | (없음) | +| 4 | `client_goal.json` | json | `Task_A_client_goal` | (Part 1 내 없음) | Part 3 이후 스테이지 (인계면 note: "task 이름은 그 착수 시 기입") | +| 5 | `evidence_shards/E-{ordinal:03d}.json` ×N | json | `Task_Evidence_shard_planner` | `Task_B1_map_doc_*`(자기 ordinal), `Task_B2_map_events_*`(자기 ordinal), `Task_B1_quality_gate_evidence_indexed`(drift spot-check 발췌용) | (없음) | +| 6 | `evidence_shard_plan.json` | json | `Task_Evidence_shard_planner` | `Task_B1_quality_gate_evidence_indexed`, `Task_B2_quality_gate_event_candidates` (B1/B2 mapper는 파일이 아니라 `{{item.*}}` runtime parameter로 소비) | (없음) | +| 7 | `evidence_indexed_parts/E-{ordinal:03d}.json` ×N | json | `Task_B1_map_doc_*` (인스턴스별 1개) | `Task_B2_map_events_*`(same ordinal, authority metadata), `Task_B1_quality_gate_evidence_indexed`(전수) | (없음) | +| 8 | `evidence_event_candidate_parts/E-{ordinal:03d}.json` ×N | json | `Task_B2_map_events_*` (인스턴스별 1개) | `Task_B2_quality_gate_event_candidates`(전수) | (없음) | +| 9 | `evidence_indexed.json` | json | `Task_B1_quality_gate_evidence_indexed` | `Task_D0_domain_activation_gate`, `Task_B2_SHA256_soft_gate_handoff_writer` | `Task_C_BO_A0_context_and_domain_slice_compiler`, `Task_C_FL_F0_fact_ledger_reducer` (Part 2). `registry_component_ids`가 여기를 통과(C-1) | +| 10 | `stage1_tmp/quality_gate/finalized_evidence_id_manifest.json` | json | `Task_B1_quality_gate_evidence_indexed` | `Task_B2_quality_gate_event_candidates` | (없음) | +| 11 | `stage1_tmp/quality_gate/B1_precheck.json` | json | `Task_B1_quality_gate_evidence_indexed` | `Task_B2_quality_gate_event_candidates`, `Task_B12_gate_audit_finalizer` | (없음) | +| 12 | `evidence_event_candidates.json` | json | `Task_B2_quality_gate_event_candidates` | `Task_D0_domain_activation_gate`, `Task_B2_SHA256_soft_gate_handoff_writer` | `Task_C_BO_A0_context_and_domain_slice_compiler` (Part 2) | +| 13 | `stage1_tmp/quality_gate/B2_precheck.json` | json | `Task_B2_quality_gate_event_candidates` | `Task_B12_gate_audit_finalizer` | (없음) | +| 14 | `stage1_tmp/quality_gate/B12_exception_pack.json` | json | `Task_B2_quality_gate_event_candidates` | `Task_B12_gate_llm_adjudicator`, `Task_B12_gate_audit_finalizer` | (없음) | +| 15 | `stage1_tmp/quality_gate/B12_adjudication_decisions.json` | json | `Task_B12_gate_llm_adjudicator` | `Task_B12_gate_audit_finalizer` | (없음) | +| 16 | `quality_gates/B1_evidence_indexed_gate.json` | json | `Task_B12_gate_audit_finalizer` | `Task_D0_domain_activation_gate`, `Task_B2_SHA256_soft_gate_handoff_writer` | (없음) | +| 17 | `quality_gates/B2_event_candidates_gate.json` | json | `Task_B12_gate_audit_finalizer` | `Task_D0_domain_activation_gate`, `Task_B2_SHA256_soft_gate_handoff_writer` | (없음) | +| 18 | `routing/domain_activation_manifest.json` | json | `Task_D0_domain_activation_gate` (실제 문서 조립은 반입된 `activation_gate.py` 모듈; task는 바이트 그대로 업로드) | `Task_B2_SHA256_soft_gate_handoff_writer` | Part 2/3의 `Task_C_BO_A0…`, `Task_C_BO_S0_signal_bundle_writer`, `Task_C_LE_L0…`, `Task_C_LE_L2…`, `Task_C_FL_F0…`, `Task_C_FL_F2…` (인계면 17 required_keys) | +| 19 | `quality_gates/stage1_part1_soft_gate_handoff.json` | json | `Task_B2_SHA256_soft_gate_handoff_writer` | (Part 1 내 없음) | `Task_C_BO_A0_context_and_domain_slice_compiler` (Part 2). required_keys: `digest_guard`, `handoff_status`, `review_items`, `schema_version`, `source_stage` | + +### 2.3 task별 IO 매트릭스 (읽기 R / 쓰기 W, 실행 코드·프롬프트 실측) + +| task | R (전수) | W (전수) | +|---|---|---| +| Task_A0_domain_screener_01 | `client_meeting.md`, `Default_Agent/routing/activation_cue_digest.md` (둘 다 preflight inline) | `routing/domain_screening_draft.json` (write_file 1회) | +| Task_A0_domain_screener_02 | `routing/domain_screening_draft.json`, `client_meeting.md`, `Default_Agent/domains/_registry_index.json`(read_raw), `Default_Agent/routing/activation_cue_digest.md`(read_raw) | `routing/domain_screening.json` | +| Task_A0_domain_screener_03 | `routing/domain_screening.json`(read_raw), `Default_Agent/domains/_registry_index.json`(read_raw), `Default_Agent/platform/reference_catalogs/domain_ids.json`, `Default_Agent/platform/reference_catalogs/calculation_ids.json`, `Default_Agent/domains/<후보 domain_id>/domain_config.json`(read_raw, 후보 수만큼) | `routing/candidate_profile_vocabulary.md` | +| Task_A_client_goal | `client_meeting.md`, `routing/domain_screening.json`, `Default_Agent/platform/schemas/client_goal_domain_profiles.schema.json`, `routing/candidate_profile_vocabulary.md` | `client_goal.json` (write_file 1회, final writer) | +| Task_Evidence_shard_planner | `evidence_all.json`; list_docs: `evidence_indexed_parts/ E-*.json`, `evidence_event_candidate_parts/ E-*.json` (stale 검사) | `evidence_shards/E-{nnn}.json` ×N, `evidence_shard_plan.json`; stdout `dynamic_fanout` | +| Task_B1_map_doc_* (인스턴스) | `{{item.shard_doc_path}}`, `client_meeting.md`, `Default_Agent/routing/evidence_component_union.md`(preflight inline); runtime param `{{item.assigned_ordinal/evidence_index_candidate/source_document_ref/b1_output_path}}` | `evidence_indexed_parts/E-{ordinal:03d}.json` 1개 | +| Task_B2_map_events_* (인스턴스) | `{{item.b1_output_path}}`, `{{item.shard_doc_path}}`, `client_meeting.md`; runtime param `{{item.*}}` | `evidence_event_candidate_parts/E-{ordinal:03d}.json` 1개 | +| Task_B1_quality_gate_evidence_indexed | `evidence_shard_plan.json`, `evidence_indexed_parts/E-{o}.json` 전수(expected_ordinals 순회), drift 의심 시 해당 `evidence_shards/…` 발췌(EXCERPT_LIMIT 1500); list_docs로 초과 part 탐지 | `evidence_indexed.json`, `stage1_tmp/quality_gate/finalized_evidence_id_manifest.json`, `stage1_tmp/quality_gate/B1_precheck.json` | +| Task_B2_quality_gate_event_candidates | `evidence_shard_plan.json`, `stage1_tmp/quality_gate/finalized_evidence_id_manifest.json`, `stage1_tmp/quality_gate/B1_precheck.json`, `evidence_event_candidate_parts/E-{o}.json` 전수 | `evidence_event_candidates.json`, `stage1_tmp/quality_gate/B2_precheck.json`, `stage1_tmp/quality_gate/B12_exception_pack.json` | +| Task_B12_gate_llm_adjudicator | `stage1_tmp/quality_gate/B12_exception_pack.json` (유일 입력, preflight) | `stage1_tmp/quality_gate/B12_adjudication_decisions.json` | +| Task_B12_gate_audit_finalizer | `stage1_tmp/quality_gate/B1_precheck.json`, `…/B2_precheck.json`, `…/B12_adjudication_decisions.json`, `…/B12_exception_pack.json` | `quality_gates/B1_evidence_indexed_gate.json`, `quality_gates/B2_event_candidates_gate.json` | +| Task_D0_domain_activation_gate | 모듈 미러 5종(.txt) + `Default_Agent/runtime_manifest.json`(해시 대조) + CASE_INPUTS 5종(`routing/domain_screening.json`, `evidence_indexed.json`, `evidence_event_candidates.json`, `quality_gates/B1_evidence_indexed_gate.json`, `quality_gates/B2_event_candidates_gate.json`) + `Default_Agent/domains/_registry_index.json` + 26개 `Default_Agent/domains/` + `Default_Agent/signals/schemas/domain_activation_manifest.schema.json`(반입만; 실패 시 warn) | `routing/domain_activation_manifest.json` (모듈 산출 바이트 그대로; `/tmp/s1` 실행 뿌리에 스테이징 파일 다수) | +| Task_B2_SHA256_soft_gate_handoff_writer | INPUT_PATHS 7종(read_raw): `evidence_indexed.json`, `evidence_event_candidates.json`, `quality_gates/B1_evidence_indexed_gate.json`, `quality_gates/B2_event_candidates_gate.json`, `routing/domain_screening.json`, `routing/domain_activation_manifest.json`, `Default_Agent/domains/_registry_index.json` | `quality_gates/stage1_part1_soft_gate_handoff.json` | + +### 2.4 Part 경계 인계 (stage1_stage_chain.v1.json 의 Part 1 구간, 실측) + +- **requires**: `client_meeting.md`, `evidence_all.json` +- **produces** (Part 2가 requires로 전수 소비): `routing/domain_activation_manifest.json`, `quality_gates/stage1_part1_soft_gate_handoff.json`, `routing/domain_screening.json`, `evidence_indexed.json`, `evidence_event_candidates.json` +- **yaml**: `stage_1_part_1_v.8.yml`, order 1 +- enforcement 선언: "Task_C_BO_A0 가 봉인 세 해시를 대조하므로 Part 1 미수행 시 SEAL_FAILED 로 멈춘다" (순서 강제는 파일 부재 실패 + 해시 봉인으로 이루어진다). + +--- + +## 3. 작업용 assets — 배포 위치 포함 전수 표 + +배포 루트: `BASE/extension_research/Default_Agent/` (BASE = `…/YAML_Prompts/1. Stage_1/v.7`). 크기는 실측 바이트. **python 자산은 `.py` 정본과 바이트 동일 `.txt` 미러가 같은 디렉터리에 병존**한다(빌더 규칙, 예외는 `build_default_agent_stage1.py` 하나 — YAML 헤더 명문). + +### 3.1 라우팅/어휘 사전 (md) + +| 자산 | 배포 위치 | 크기 | 소비 task | 성격 | +|---|---|---|---|---| +| `activation_cue_digest.md` | `Default_Agent/routing/` | 35,110 B | T0-01(preflight inline), T0-02(read_raw) | 26개 도메인 라우팅 단서 사전. `## LEGEND`/`## GLOBAL_DEFAULTS`/`## USAGE`/`## DOMAINS`(`### ` 절 26개). 단서 줄 `P|N|C|OVR` 4형식. **실물 머리말에 `registry_version:`/`registry_index:` 줄 없음** — T0-02는 `DIGEST_HEADER_ABSENT` 경고로 낡음 대조를 건너뛰고 정본(`_registry_index.json`)에서 값을 취한다 | +| `evidence_component_union.md` | `Default_Agent/routing/` | 17,593 B | B1 mapper(preflight inline) | 증거 구성요소 합집합. 실측 헤더: `component_count=112`, `domain_count=26`, `proof_role_count=77`, `registry_version=Stage1.Assembly.2026-08-10.v1`, `registry_index_sha256=9f177ebf…`. 줄 형식 `||