Docs · Cocode ADE · 분석 · 설계 문서

Cocode ADE · 12

Decision Log — 결정 기록

명세를 다듬으며 내린 결정을 시간 순서로 쌓은 기록

목차

Records all key decisions made during the spec clarification process. Uses an append-only markdown log instead of Ouroboros Event Sourcing.

Decision List

D-001: MVP 대상 순서 — 개발자 우선, 드리머 모드는 이후

  • Date: 2026-08-25
  • Context: Discovery 단계에서 "1차 사용자가 개발자인지 비개발자(도메인 전문가/시니어/드리머)인지"가 미확정으로 남았고, Socratic 인터뷰 Level 1~2에서도 "개발자를 타겟으로 적었으나 목적은 누구나"라는 모호한 답이 나와 Level 3에서 정면으로 충돌(C-01)함
  • Options:
    1. 개발자·드리머 두 인터페이스를 출시일부터 동시에 1급으로 제공 — 시장 커버리지는 넓지만 UX 설계 표면이 두 배로 늘어나 MVP 지연 위험
    2. 개발자 인터페이스를 먼저 출시하고 드리머 모드는 후속 업데이트로 순연 — 원 설계 노트의 에디터 코어 중심 로드맵과 정합적
  • Decision: 옵션 2 — MVP는 개발자용 인터페이스로 진행, 드리머 모드는 이후 업데이트
  • Rationale: 프로젝트 오너 명시적 확답("초기 버전은 개발자 인터페이스로 진행되어야해"). Discovery 로드맵 1~2단계(에디터 코어 성능/한글 IME 검증, 모노레포 골격)가 형식 절차가 아니라 MVP의 실질적 우선순위로 확정됨
  • Impact: Seed spec C-01/AC-03/AC-04. Planning 단계 PRD의 스코프를 개발자용 기능으로 한정
  • Source: Socratic Interview (Level 3, C-01)

D-002: 경쟁 포지셔닝 — Orca 대비 Dart/Flutter 특화 수직 딥 IDE

  • Date: 2026-08-25
  • Context: Discovery는 Zed/Cursor를 "정면 승부 금지" 대상으로 지목했으나, Socratic 인터뷰 Level 4(B-02)에서 프로젝트 오너가 실제로 인지하는 경쟁자는 Orca(Stably AI)임이 드러남. 보강 조사 결과 Orca는 MIT 오픈소스, ★2만+, 27개+ CLI 에이전트를 격리 worktree에서 병렬 실행하는 범용 ADE로 확인됨
  • Options:
    1. Orca와 동일한 축(언어 무관 멀티 에이전트 오케스트레이션)에서 정면 경쟁
    2. Orca와 다른 축(Dart/Flutter 특화 수직 딥 IDE — 결정론적 스캐폴딩, 런타임 상태 인지)으로 포지셔닝
  • Decision: 옵션 2. 단, 장기적으로는 Orca급 기능 폭(멀티 에이전트 오케스트레이션 등)까지 커버하되, 구현·UX는 끝까지 Dart/Flutter 전용을 유지
  • Rationale: Orca에는 결정론적 스캐폴딩(bricks)도 Flutter 런타임 상태 인지(위젯 트리·핫 리로드)도 없음 — 언어 특화가 방어 가능한 차별화 축. 프로젝트 오너 확인: "Dart/Flutter 특화 딥 IDE 포지셔닝이 맞아"
  • Impact: Seed spec C-03/AC-05. Discovery의 "Zed·Cursor 정면 승부 금지" 경고는 유지하되, 실제 경쟁 분석의 1순위 대상을 Orca로 교체 필요(Planning 단계에서 경쟁 분석 갱신)
  • Source: Socratic Interview (Level 4, B-02) + 보강 조사(WebSearch, 2026-08-25)

D-003: 롤백 + diff 리뷰를 MVP 필수 안전망으로 확정

  • Date: 2026-08-25
  • Context: 비동기 위임(맡기고 떠나기) 모델(A-04)이 확정되면서, 에이전트가 사람 없이 잘못된 방향으로 진행할 위험을 어떻게 처리할지가 Level 4 경계 질문(B-01)으로 제기됨
  • Options:
    1. MVP는 최소 기능으로 출시하고 안전망(롤백/diff)은 이후 추가
    2. 롤백 + diff 리뷰를 MVP 필수 요구사항으로 포함
  • Decision: 옵션 2
  • Rationale: 프로젝트 오너 강한 확언("ADE면 당연해 롤백도 가능하고 diff도 제공해야지!"). Lumide의 local_history(파일 버전 스냅샷, 최대 50버전/5MB) 설계와 같은 축으로, 원 설계 노트에서도 "에이전트가 파일을 갈아엎는 시대의 안전망"으로 이미 지목된 항목
  • Impact: Seed spec C-02/AC-01/AC-02, 도메인 엔티티 EditSnapshot 신설. Design 단계 아키텍처에서 로컬 히스토리 저장 방식 구체화 필요
  • Source: Socratic Interview (Level 4, B-01)

D-004: 배제 카테고리 없음 — 단 MVP 범위는 별도로 한정

  • Date: 2026-08-25
  • Context: "Dart로 못 만드는 소프트웨어는 없다"(A-05)는 프로젝트 오너의 강한 입장과, Discovery가 이미 확인한 cocode ADE 자체 구현 리스크(에디터 코어 성능, 한글 IME) 사이의 층위 구분이 필요했음(C-02 모순 검증)
  • Options:
    1. "Dart로 못 만드는 게 없다"를 cocode ADE 자체 구현 범위에도 그대로 적용해, 리스크 스파이크를 형식 절차로 취급
    2. 이 믿음을 "사용자가 만드는 결과물"의 역량 범위에 대한 것으로 한정하고, cocode ADE 자기 자신의 구현 리스크(에디터 코어)는 별도로 계속 게이팅 리스크로 관리
  • Decision: 옵션 2. 배제 카테고리는 두지 않되(게임·임베디드 포함, B-03), MVP 범위 자체는 D-001로 별도 한정됨
  • Rationale: 두 주장이 서로 다른 층위(제품이 만들 수 있는 것 vs 제품 자신을 만드는 리스크)임을 명확히 분리해야 Discovery의 리스크 관리가 무효화되지 않음. 프로젝트 오너도 자신의 7년 경력을 근거로 별도 확언
  • Impact: Seed spec C-05/AC-07, A-05/A-06/A-07(별개 항목으로 유지)
  • Source: Socratic Interview (Level 3, C-02 / Level 4, B-03)

D-005: 1차 고객을 외부 Dart/Flutter 개발자 시장으로 확정

  • Date: 2026-08-25
  • Context: Discovery §Recommendations 1번이 "P1(코코드 내부 도그푸딩) vs P2(외부 판매) vs 순차"를 미해결로 남겼고, 독립 Contrarian 검토가 이를 Critical 반론 CH-001/CH-027로 지목했다 — C-01(개발자 우선)과 C-03(포지셔닝)이 모두 이 미해결 입력 위에 세워진 불변 제약이라는 지적
  • Options:
    1. 코코드 내부 도그푸딩 우선 — MVP 범위가 좁아지고 리스크가 줄지만 출시가 늦어짐
    2. 처음부터 외부 개발자 시장 대상 — 넓은 시장을 겨냥하되 검증되지 않은 외부 수요에 노출
    3. 구분하지 않음 — CH-001이 지적한 모호함이 그대로 남음
  • Decision: 옵션 2 — 처음부터 외부 Dart/Flutter 개발자 시장
  • Rationale: 프로젝트 오너 확답(2026-08-25). Socratic 인터뷰 A-01("주 고객은 개발자")과도 일관되며, Discovery가 분석한 "가격의 빈 구간($5~9/월)"과 "Flutter 전용 환경 부재" 시장 갭 논리가 모두 외부 시장을 전제로 한 것이었음
  • Impact: Seed spec v1.1.0 C-01에 1차 고객 정의 포함, A-01 확정 상태로 전환. Contrarian CH-001/CH-027 해소
  • Source: Contrarian Review (CH-001, CH-027) + 사용자 확답

D-006: MVP는 agent와 acp를 모두 포함한다

  • Date: 2026-08-25
  • Context: AC-06이 "자체 런타임과 외부 ACP 호스팅 둘 다 MVP 필수"로 규정했으나, Discovery의 6단계 로드맵은 ACP를 3단계·자체 런타임을 5단계에 배치해 MVP 시점에 agent가 없다는 모순이 Contrarian CH-025(Critical)로 지적됨
  • Options:
    1. acp만으로 먼저 출시 — Discovery 로드맵과 일치, 모델 계약 없이 빠른 출시, 다만 "멀티 LLM 선택"이라는 핵심 가치제안이 MVP에 부재
    2. 둘 다 MVP부터 필수 — 개발 기간이 늘지만 첫 출시부터 완전한 가치제안 제공
  • Decision: 옵션 2 — 둘 다 MVP 필수. Seed spec이 지배하며, Discovery의 단계 순서는 구현 순서 제안일 뿐 MVP 범위 정의가 아님을 명확히 함
  • Rationale: 프로젝트 오너 확답(2026-08-25). Discovery가 이미 분석했듯 ACP 프로토콜만으로는 "OpenAI/Claude/Grok을 한 드롭다운에서" 고르는 것이 구조적으로 불가능하므로(에이전트가 노출한 목록 안에서만 선택 가능), agent 없이는 멀티 LLM 가치제안 자체가 성립하지 않음
  • Impact: Seed spec v1.1.0 AC-06에 검증 가능한 최소 요건 명시(agent 프로바이더 2개 이상, acp 에이전트 1개 이상). Planning 단계에서 로드맵 순서 재조정 필요
  • Source: Contrarian Review (CH-025) + 사용자 확답

D-007: "완료" 전이에 사람의 diff 승인을 요구하지 않는다

  • Date: 2026-08-25
  • Context: Contrarian CH-004(Critical)가 "무인 작업이 사람 검토 없이 완료에 도달할 수 있는지" 스펙이 침묵한다고 지적. 이는 C-02(diff/롤백)와 C-04(비동기 위임)의 관계를 정의하는 문제
  • Options:
    1. 자가 검증만으로 완료 가능 — C-04의 "자리를 비울 수 있다"와 정합, diff는 사후 안전망
    2. 사람의 diff 승인이 완료의 선행 조건 — 더 안전하나 비동기 위임의 핵심 가치가 무력화됨("사람이 자리를 비우면 작업이 멈춤")
  • Decision: 옵션 1 — 자가 검증만으로 완료 가능. diff 리뷰는 사후 안전망이며 선행 게이트가 아님
  • Rationale: 프로젝트 오너 확답(2026-08-25). 옵션 2는 C-04가 규정한 비동기 위임 모델과 직접 충돌하며, 그렇게 하면 제품의 핵심 상호작용이 동기 페어프로그래밍으로 되돌아감. 대신 안전성은 다른 경로로 확보한다 — 재시도 상한(D-008), 롤백 원자성·충돌 감지, 자가 검증 신뢰도 실측(A-02 스파이크)
  • Impact: Seed spec v1.1.0 DelegatedTask 불변식 2번에 명시. Contrarian CH-004 해소, CH-012(동기 우선 대안)는 이 결정으로 반려
  • Source: Contrarian Review (CH-004, CH-012) + 사용자 확답

D-008: 자가 검증 재시도 상한을 3회로 확정

  • Date: 2026-08-25
  • Context: Contrarian CH-010(Critical)이 "재시도 횟수·타임아웃·비용 한도가 어디에도 없어, 무인 상태에서 무한정 자원을 소모할 수 있다"고 지적. D-007로 사람 게이트를 제거했기에 이 상한이 유일한 안전장치가 됨
  • Options:
    1. 최대 3회 재시도 후 "실패" 전이 + 에스컬레이션
    2. 시간 기준 제한(예: 30분)
  • Decision: 옵션 1 — 최대 3회
  • Rationale: 프로젝트 오너 확답(2026-08-25). cc-product BMAD 설정(references/bmad-config.md의 feedback.maxRetries: 3)과 일관되며, 3회를 넘으면 문구 수정이 아니라 문제 정의를 다시 잡을 신호로 보는 동일한 논리가 적용됨
    • 정정(2026-08-26): 최초 기재는 이 선례를 "cc-spec 자체의 정책(bmad.json)"으로 귀속했으나, 4차 감사 확인 결과 bmad.json 파일은 존재하지 않으며 해당 설정은 cc-product의 참조 문서에 정의되어 있다. 선례 자체는 실재하므로 근거는 유지하되 출처를 정정한다
  • Impact: Seed spec v1.1.0 C-04 후단 + DelegatedTask에 "실패" 상태 신설 + AC-07. Contrarian CH-010/CH-021 해소
  • Source: Contrarian Review (CH-010, CH-021) + 사용자 확답

D-009: Simplifier 제안 3건 전량 수용

  • Date: 2026-08-25
  • Context: 독립 Simplifier 검토 결과 v1.0.0 복잡도 29.5(Caution, Excessive 임계 31 근접). 세 건의 단순화 제안이 제시됨
  • Options: 각 제안별 Accept / Reject / Defer
  • Decision: S-001(배제 카테고리 없음 클러스터 제거 — C-05/AC-07/A-05), S-002(드리머 모드 운영 모델 가정 A-02 제거), S-003(AgentBackend 엔티티를 DelegatedTask로 통합) 전량 Accept
  • Rationale: 세 제안 모두 Contrarian Critical/Major 반론과 정확히 같은 항목을 지목했다 — S-001은 CH-013(Critical), S-002는 CH-028(Critical), S-003은 CH-026(Major)의 직접 해소책. 즉 단순화와 안전성 개선이 같은 방향이었음. 세 제안 모두 핵심 가치 클러스터(비동기 위임 + diff/롤백 안전망)를 건드리지 않음
  • Impact: 엔티티 4→3, 불변 제약 5→4, 미검증 가정 5→4. 단 Critical 반론 해소를 위해 불변식·AC·NFR을 추가한 결과 최종 복잡도는 34.5로 오히려 상승 — 이는 기능 추가가 아니라 기존 요구의 검증 가능한 분해이므로 의도된 증가로 판단(docs/simplifier-review-cocode.md 말미 참고)
  • Source: Simplifier Suggestion (S-001, S-002, S-003)

D-010: 미검증 믿음을 불변 제약에서 분리해 가정으로 격하

  • Date: 2026-08-25
  • Context: Contrarian CH-011/CH-030(둘 다 Critical)이 "A-04가 스스로 '기술 검증 미완료'라 인정하는데도 C-04라는 불변 제약으로 승격됐고, 에이전트가 자기 검증을 형식적으로 통과시키는 실패 모드를 아무도 다루지 않는다"고 지적
  • Options:
    1. C-04를 그대로 두고 리스크를 인수
    2. 제약과 믿음을 두 층위로 분리 — 제약은 검증 가능한 행위만 규정하고, 검증되지 않은 믿음은 가정으로 격하해 실측 계획을 붙임
  • Decision: 옵션 2
  • Rationale: 불변 제약은 잠금 후 수정 불가하므로, 미검증 믿음이 그 안에 섞이면 나중에 반증돼도 고칠 수 없다. C-04는 "자가 검증을 한다 + 방법은 4종 폐쇄 집합 + 재시도 3회"라는 검증 가능한 행위만 규정하고, "자가 검증이 사람 판단을 대체할 만큼 신뢰할 수 있는가"는 가정 A-02로 분리해 Design 단계 이전 스파이크(의도적 결함 N건 위임 → 검출률 실측)를 배정했다
  • Impact: Seed spec v1.1.0 C-04(행위 규정) / A-02(믿음 + 검증 방법) 분리. Contrarian CH-011/CH-030 해소. 이 원칙은 A-03(re_editor 성능), A-04(한글 IME), A-05(ACP v1/v2)에도 동일하게 적용됨
  • Source: Contrarian Review (CH-011, CH-030)

D-011: C-03 포지셔닝의 고객 미검증 사실을 인정하고 위험 인수

  • Date: 2026-08-26
  • Context: 적대적 감사가 Contrarian 보고서의 CH-009 "해소" 주장을 기각했다. 감사 판정: v1.1.0이 C-03에 추가한 "비-Dart 파일 편집 허용" 조항은 CH-007(파일 수준 경계)을 고친 것이며, CH-009가 지적한 진짜 문제 — 수직 깊이 포지셔닝이 고객 리서치가 아니라 오너 개인 판단에 근거한다는 인식론적 공백 — 는 손대지 않은 채 "오너가 결정했다"로 재천명했을 뿐이라는 것. 같은 보고서가 구조적으로 동일한 CH-008을 "위험 인수"로 정직하게 분류한 것과 대비된다는 지적도 함께 받았다
  • Options:
    1. Resolved 주장을 유지 — 감사 판정을 무시하게 되며, 근거 없는 해소 주장이 게이트 산출물에 남음
    2. Accepted(위험 인수)로 재분류하고, 미검증 사실을 C-03 본문과 가정 목록에 명시
    3. C-03 잠금을 보류하고 고객 검증 후 재결정 — MVP 착수가 리서치 완료까지 지연됨
  • Decision: 옵션 2 — CH-009를 Accepted로 재분류. C-03 본문에 "이 포지셔닝은 고객 리서치가 아니라 프로젝트 오너의 판단에 근거한다"를 경고 표시와 함께 명시하고, 별도 가정 A-07("Dart/Flutter 개발자가 수평적 넓이보다 수직적 깊이를 선호한다")을 신설해 MVP 출시 후 검증 대상으로 추적
  • Rationale: 옵션 1은 정직하지 않다 — 감사의 판정이 정확했다. 옵션 3은 과잉 대응이다: 이 프로젝트는 오너의 확신이 곧 착수 근거인 단계이며, 고객 검증을 선행 조건으로 걸면 아무것도 시작할 수 없다. 위험을 인수하되 그 위험이 무엇인지 문서에 남기는 것이 올바른 처리다. 이는 CH-008(경쟁사가 Flutter 깊이를 따라올 위험)을 이미 같은 방식으로 처리한 것과 일관된다
  • Impact: Seed spec v1.2.0 C-03 경고 문구 + A-07 신설. Contrarian 보고서의 CH-009 판정을 Resolved → Accepted로 정정
  • Source: 적대적 감사 (2026-08-26) — Contrarian 보고서 CH-009 재검토

D-012: A-02 실패 시 사람 승인 모드로 자동 전환하는 조항을 잠금 전에 명시

  • Date: 2026-08-26
  • Context: 적대적 감사가 CH-011/CH-030의 "해소" 주장도 기각했다. 감사 판정: v1.1.0은 미검증 믿음을 가정 A-02로 분리했다고 주장했지만, 정작 그 믿음이 뒷받침하는 행동(비동기·자가검증만으로 완료·사람 게이트 없음)은 C-04(불변 제약)와 DelegatedTask 불변식(추가만 허용) 양쪽에 그대로 잠긴 상태다. A-02 스파이크가 실패해도 그 설계를 되돌릴 장치가 없으므로, 반론이 경고한 잠금 실패 모드를 해소한 게 아니라 재현했다는 것
  • Options:
    1. 대비책 없이 감수 — 자가 검증이 된다는 쪽에 걸고, 실패 시 불변 제약을 위반하며 스펙을 고침
    2. C-04에 전환 조항을 미리 써넣음 — 스파이크 미달 시 "사람 diff 승인 후 완료" 모드로 자동 전환한다고 잠금 전에 규정
    3. 작업 유형별 차등 — 테스트 커버리지가 있는 작업만 자가 검증으로 완료
  • Decision: 옵션 2 — 프로젝트 오너 확답(2026-08-26)
  • Rationale: 옵션 1은 감사가 지적한 바로 그 실패 모드다. 옵션 2의 핵심은 전환이 제약 위반이 아니라 제약이 미리 규정한 동작이 된다는 점이다 — 불변 제약의 취지(함부로 바꾸지 못하게 함)를 지키면서도 미검증 가정 위에 되돌릴 수 없는 설계를 얹지 않는다. 옵션 3은 더 정교하나 스펙 복잡도를 높이고, 전환 조항으로도 같은 안전성을 확보할 수 있다
  • Impact: Seed spec v1.2.0 C-04 전환 조항 + DelegatedTask에 completion_mode 속성 및 "사람승인대기" 상태 신설 + AC-10(검출률 30건/80% 기준). Contrarian 보고서 CH-011/CH-030 해소
  • Source: 적대적 감사 (2026-08-26) + 프로젝트 오너 확답

D-013: 근거 없는 성능 목표치(NFR-01)를 가정으로 강등

  • Date: 2026-08-26
  • Context: 적대적 감사가 v1.1.0의 신규 결함으로 지적 — NFR-01의 수치(설치 150MB / 콜드 스타트 2초 / 유휴 메모리 400MB)는 문서 전체 어디에도 근거·기준선·검증 방법이 없이 곧바로 Must 요구사항으로 잠겼다. 형제 항목인 NFR-02/NFR-03이 각각 추적된 가정(A-03/A-04)과 스파이크 계획에 연결된 것과 대조적이며, 감사는 이를 "다른 곳에서는 Critical로 취급하는 바로 그 패턴(검증되지 않은 수치를 잠긴 게이트로 승격)"이라고 판정했다. 이 수치들은 작성자(Claude)가 추정으로 만들어낸 것이며 사용자가 제시한 값이 아니다
  • Options:
    1. 가정으로 강등 후 스파이크로 확정 — NFR-01은 "A-06 스파이크에서 확정한 목표치를 충족한다"로 바꾸고, 실측 후 수치를 채움
    2. 제안된 수치 유지 — 150MB/2초/400MB를 경쟁 약속으로 받아들임
    3. 오너가 직접 수치 지정
  • Decision: 옵션 1 — 프로젝트 오너 확답(2026-08-26)
  • Rationale: 검증되지 않은 숫자를 잠그는 것은 이 스펙이 다른 곳(CH-013, CH-033, CH-034)에서 일관되게 거부해 온 패턴이다. Lumide 실측 55.8MB가 하한 참조점으로 존재하지만, cocode ADE는 자체 에이전트 런타임을 포함하므로 그 값을 그대로 목표로 삼을 근거가 없다. 로드맵 1단계에서 최소 셸을 조립한 뒤 실측하고, 그 결과를 근거로 목표를 확정하는 것이 이 문서의 다른 NFR과 일관된 처리다
  • Impact: Seed spec v1.2.0 NFR-01을 A-06 참조로 변경 + A-06 신설(용량·성능 목표 미확정, 로드맵 1단계 스파이크로 확정)
  • Source: 적대적 감사 (2026-08-26) + 프로젝트 오너 확답

D-014: 에이전트의 기존 테스트 수정을 금지한다

  • Date: 2026-08-26
  • Context: 적대적 감사가 CH-030을 3차에서도 미해소로 판정했다. 판정 요지: AC-10(당시)은 제3자가 주입한 결함의 검출률을 재는데, CH-030이 지적한 실패 모드는 내생적이다 — 에이전트가 변경과 그 검증을 모두 작성하면서 검증이 통과하도록 모양을 맞추는 것(단언 약화, 자기 버그를 그대로 인코딩한 테스트 작성, 케이스 누락). 주입 결함 검출률이 100%여도 자기 산출물에 대해서는 여전히 무력할 수 있다는 실험 설계상의 지적이며, 타당하다
  • Options:
    1. 기존 테스트 수정 금지 — 에이전트는 테스트를 추가할 수 있으나 약화·비활성화·삭제할 수 없고, 기존 테스트 변경은 사람 승인 필요
    2. 테스트 변경을 diff에서 강조 표시만 — 제한하지 않고 사후 리뷰 시 눈에 띄게 함
    3. Planning으로 미룸
  • Decision: 옵션 1 — 프로젝트 오너 확답(2026-08-26)
  • Rationale: 옵션 2는 비동기 위임의 전제(사람이 자리를 비운다)와 충돌한다 — 강조 표시는 보는 사람이 있을 때만 작동한다. 옵션 1은 단순하고 기계적으로 검증 가능하며(테스트 파일 변경을 감지해 차단), C-04 ②(테스트 통과)를 자가 검증 방법으로 유지하는 데 필요한 최소한의 보호다. 테스트 추가는 허용하므로 에이전트의 정상적 작업을 막지 않는다
  • Impact: Seed spec v2.0.0 C-05 신설 + AC-10. A-02의 검증 방법에 "에이전트 자신이 만든 결함에 대한 검출률"을 별도 측정 항목으로 추가
  • Source: 적대적 감사 3차 (2026-08-26) + 프로젝트 오너 확답

D-015: Seed Spec을 최소 핵심으로 삭감 재작성(v2.0.0)

  • Date: 2026-08-26
  • Context: v1.0.0 → v1.1.0 → v1.2.0 두 차례 개정이 모든 지표를 악화시켰다 — 모호성 0.625 → 0.4125 → 0.47, 복잡도 29.5 → 34.5 → 42.5(Excessive 임계 31), 내부 모순 11건, 미해소 Critical 6건. (v1.1.0 복잡도는 34.5다 — docs/simplifier-review-cocode.md 64행. 최초 기재의 36.5는 오기이며 4차 감사 지적으로 정정) 3차 적대적 감사가 원인을 정확히 지목했다: "v1.2.0은 NFR-01에서 근거 없는 숫자 3개를 제거하면서 같은 개정에서 새 숫자 6개를 도입했다. D-013 자신이 '검증되지 않은 숫자를 잠그는 것은 이 스펙이 일관되게 거부해 온 패턴'이라고 썼는데, 바로 그 문장을 쓴 개정이 새 미검증 숫자를 도입했다." 또한 cc-spec 프로토콜의 수정·재평가 루프 상한(bound: 2)을 이미 소진한 상태였고, 프로토콜은 이 경우 "문구가 아니라 문제 정의를 다시 잡을 신호"로 보고 에스컬레이션하라고 규정한다
  • Options:
    1. 최소 핵심으로 삭감 재작성 — 정말 안 바뀌는 것만 남기고 구현 세부를 Planning으로 이관
    2. v1.3.0으로 계속 패치 — 지적 항목을 하나씩 수정
    3. 현재 상태로 잠금 후 진행 — 미해소 항목을 공개 미결 과제로 기록
  • Decision: 옵션 1 — 프로젝트 오너 확답(2026-08-26)
  • Rationale: 옵션 2는 지난 두 라운드가 이미 실패를 증명한 방식이다 — 요구사항을 "검증 가능하게" 만들려고 항목을 추가할 때마다 근거 없는 임계값이 필요했고, 그것이 다시 불변 층위로 밀려 올라갔다. 근본 원인은 Seed Spec에 Planning/Design 층위의 구현 세부(재시도 타임아웃, 벤치마크 규모, 검출률 기준)를 담으려 한 것이다. 옵션 3은 Specification Gate를 미통과 상태로 남겨 후속 단계가 흔들리는 기반 위에 서게 된다. v2.0.0의 원칙은 세 가지다: ⓐ 출처 없는 숫자를 쓰지 않는다 ⓑ 정말 안 바뀌는 것만 §2에 둔다 ⓒ 식별자 체계를 하나로 통일한다
  • Impact: Seed spec v2.0.0 — 불변 제약 5개, 엔티티 3개, 인수 기준 15개(전량 AC-NN), 가정 7개. 지어낸 수치 6개 제거(30건/80%/10분/3만~5만 줄/200~400개/5개), 내부 모순 11건 해소, 깨진 인용 2건 정정. 살아남은 수치는 전부 출처를 명시(오너 결정·외부 실측·업계 표준)
  • Source: 적대적 감사 3차 (2026-08-26) + cc-spec 프로토콜 L-spec.evaluate-outer-fix-reevaluate exhaust 조항 + 프로젝트 오너 확답

D-016: MVP 대상 플랫폼 3종, 단 한글 IME 게이팅 검증은 macOS 우선

  • Date: 2026-08-26
  • Context: Seed spec v2.0.1이 Planning 필수 입력 9건을 남겼고, 그중 대상 플랫폼이 가장 상위 결정이었다(AC-14 측정 환경·AC-15 입력기 조합·A-06 용량 목표가 모두 여기서 파생). 프로젝트 오너가 macOS·Windows·Linux 3종을 선택했으나, 이는 A-04(한글 IME)에 그대로 곱해진다 — 세 플랫폼의 한글 입력 경로가 완전히 다르고(macOS 2-set / Windows MS-IME / Linux ibus·fcitx), Lumide는 한 플랫폼에서도 이 버그(이슈 #64)를 아직 못 고쳤다
  • Options:
    1. 배포 3종 유지 + 게이팅 스파이크는 macOS 먼저, 나머지 순차
    2. 세 플랫폼 동시 검증 — 리스크를 앞당기지만 1단계 스파이크 기간이 크게 늘어남
    3. MVP 배포 자체를 macOS로 축소
  • Decision: 옵션 1 — 프로젝트 오너 확답(2026-08-26)
  • Rationale: macOS에서 안 되면 나머지를 볼 이유가 없다. 게이팅 리스크의 목적은 "되는지 아닌지를 가장 싸게 판별하는 것"이므로, 한 플랫폼으로 판별하고 나머지는 통과 후 순차로 가는 것이 옳다. 배포 대상을 축소하지 않은 것은 아키텍처 결정(Flutter 데스크톱 3종 지원)이 스파이크 결과와 독립적이기 때문
  • Impact: Seed spec v2.1.0 AC-15에 "1단계 게이팅 스파이크는 macOS만" 명시, A-04 검증 방법에 순차 계획 반영
  • Source: 프로젝트 오너 확답 + Seed spec §4 Planning 입력 1·2번

D-017: agent는 Anthropic + OpenAI 호환 게이트웨이 2경로

  • Date: 2026-08-26
  • Context: AC-06이 "LLM 프로바이더 2개 이상"을 Must로 규정하는데, 프로젝트 오너의 최초 선택은 Anthropic 단독이었다. 스펙 위반이므로 확인이 필요했다
  • Options:
    1. Anthropic + OpenAI 호환 게이트웨이 — SDK 하나 + 호환 계층 하나
    2. Anthropic 단독으로 가고 AC-06을 "1개 이상"으로 낮춤 (멀티 LLM은 acp가 담당하는 것으로 재정의)
    3. anthropic_sdk_dart + openai_dart 두 SDK 모두 직접 구현
  • Decision: 옵션 1 — 프로젝트 오너 확답(2026-08-26)
  • Rationale: OpenAI 호환 엔드포인트 계층 하나로 Grok·Mistral·Ollama·OpenRouter가 함께 들어오므로 구현 부담 대비 커버리지가 가장 크다. 옵션 2는 Discovery가 분석한 핵심 가치제안(자체 런타임에서의 모델 선택)을 포기하게 되고, 옵션 3은 벤더 고유 기능까지 쓸 수 있으나 유지 부담이 크다
  • Impact: Seed spec v2.1.0 AC-06을 확정값으로 갱신. AC-06 위반 해소
  • Source: 프로젝트 오너 확답 + Seed spec §4 Planning 입력 3번

D-018: AC-13 허용 호스트를 실측 기반으로 확정 (원 제목: "11종으로 확정" — 개수는 정본이 아니다, D-051)

  • Date: 2026-08-26
  • Context: v2.0.1이 AC-13의 허용 호스트를 "카테고리 서술"로만 두어, 4차 감사가 "닫힌 집합이 아니라 기계적으로 검증 불가"라고 지적했다. 또한 v1.2.0 시점에는 pub.dev만 예외로 두어 Android/iOS 빌드가 원천 불가능한 모순도 있었다
  • Options: 조사로 실제 호스트 집합을 확정 / 카테고리 서술 유지
  • Decision: 실측으로 확정 — 필수 호스트 + 차단 가능 호스트 + 캐시 디렉터리 예외의 목록. ⚠️ 원문은 각각 "최소 11종 · 4종 · 6종"으로 개수를 적었으나 그 수는 정본이 아니다(D-051) — 정본은 docs/planning-inputs-cocode.md §2.1·§2.3·§2.4 의 표와 그것을 그대로 옮긴 SandboxDefaults 다. 이 항목이 세운 것은 「카테고리 서술이 아니라 실측된 목록」이라는 형태이며, 그 형태는 유효하다
  • Rationale: Flutter 3.47.0 SDK 소스(http_host_validator.dart, cache.dart, FlutterPluginConstants.kt, Android 템플릿), 이 머신의 Gradle origin 인덱스(resource-at-url.bin — 과거 빌드가 실제로 받아온 URL 기록), 라이브 리다이렉트 probe로 교차 검증했다. 특히 순진한 허용 목록을 깨는 3가지를 실측으로 확인: ①services.gradle.org는 zip을 서빙하지 않고 release-assets.githubusercontent.com까지 307 체인 ②plugins.gradle.org도 303 리다이렉트 ③Gradle google()은 maven.google.com이 아니라 dl.google.com/dl/android/maven2/(origin 인덱스 3,060건 vs 0건)
  • Impact: Seed spec v2.1.0에 허용 호스트 표·캐시 디렉터리 표·함정 3가지 명시. 당시 AC-13(샌드박스)을 기계적으로 검증 가능한 형태로 전환
    • 정정(2026-09-13): 이 항목의 개수 "11종"은 정본이 아니다(D-051 — 오너 결정 #109). #38 의 3-OS 실측이 §2.1 표에 없던 api.github.com 을 관측해 11 도 12 도 측정과 어긋났고, 그래서 개수가 아니라 목록이 단일 SSOT 로 확정됐다. 아래 "추출 알고리즘은 여전히 Planning 미결" 도 해소됐다 — D-050 ①(lock 우선 · 전이 포함 · 게이트 시점 재계산 · 세션 고정)
    • 정정(2026-08-26): v3.0.0에서 §4가 재작성되며 샌드박스 기준은 AC-14로, 허용 호스트 표는 docs/planning-inputs-cocode.md §2로 이동했다. 현재 AC-13은 brick 스캐폴딩 원자성이다. 또한 v3.0.0 감사가 지적했듯 이 항목이 주장한 "기계적으로 검증 가능"은 과장이었다 — 허용 집합에 사용자 추가분과 의존성 파생분이 포함되므로 완전한 닫힌 집합이 아니며, 추출 알고리즘은 여전히 Planning 미결이다(§2.5)
  • 한계: 프로젝트별 git: 의존성과 podspec 외부 소스는 도구 기본값에서 도출 불가 — 구현이 워크스페이스의 선언된 의존성에서 호스트를 추출해 세션 허용 목록에 추가하는 방식이어야 한다고 AC-13에 규정
  • Source: 조사(Flutter 3.47.0 실측) + Seed spec §4 Planning 입력 4번

D-019: A-03 참조 워크스페이스로 coco-de/unibook 채택, ACP 검증 대상 4종 선정, v1 전용 출시

  • Date: 2026-08-26
  • Context: Planning 입력 5·6·7번. 참조 워크스페이스와 ACP 검증 대상을 근거 없이 고르면 이전에 반복한 "지어낸 값" 패턴이 되므로 실제 조사로 후보를 평가했다
  • Decision:
    • 참조 워크스페이스: coco-de/unibook을 불변 태그 또는 커밋 SHA로 고정
    • ACP 검증 대상: Claude Agent · Codex · Gemini CLI · Copilot CLI 4종
    • ACP 버전: v1 전용으로 출시, v2는 라우팅 seam만 두고 플래그 뒤로
  • Rationale:
    • unibook은 org 내 유일하게 규모·형태를 동시에 만족한다 — Dart 파일 9,931개(~55.8MB), 생성 파일 712개 git 추적, workspace: 멤버 141개. 차순위 good-teacher는 규모가 1/3.5 수준
    • ACP 4종은 능력 스펙트럼을 덮는다: Claude Agent(fork+resume 최대 집합) / Codex(resume 있고 fork 없음 — fork가 resume을 함의한다고 가정한 코드를 잡음) / Gemini CLI(loadSession만, 파일 IO를 클라이언트로 되돌려 fs/* 구현을 강제) / Copilot CLI(네이티브 서버)
    • v2 배제 근거: 2026-08-25 레지스트리 라이브 probe 32종 중 31종이 v1 negotiate했고, flagship 어댑터 2종을 디컴파일해 protocolVersion: 1 하드코딩·v2 참조 0건을 확인했다. v2는 2026-07-20 공개된 draft이며 스스로 wire format 불안정을 선언한다
  • Impact: Seed spec v2.1.0 AC-14·A-03·A-05 갱신
    • 정정(2026-08-26): 당시 A-05 상태를 "부분 검증됨"으로 적었으나 이는 Seed Spec 스키마의 허용 값(Verified/Unverified/Disproved)이 아니다. v3.0.0에서 Unverified로 교정했다 — 근거 자료는 확보됐으나 실제 구현으로 검증된 바 없으므로 Unverified가 맞다
  • 한계(스펙에 명시): unibook은 비공개 저장소이며 최근 30일 100+ 커밋으로 활발하다 — 불변 고정이 필수이고, 벤치마크를 외부 공개할 계획이라면 대체 워크스페이스가 필요하다. org 전체에 .freezed.dart가 하나도 없어 freezed 생성 파일을 상정한다면 별도 픽스처가 필요하다
  • Source: 조사(GitHub API·ACP 레지스트리 probe·어댑터 디컴파일) + Seed spec §4 Planning 입력 5·6·7번

D-020: 시간 상한 기본값과 A-02 합격 기준은 스파이크 후로 유지

  • Date: 2026-08-26
  • Context: Planning 입력 8·9번. 두 값 모두 "지금 정하라"는 선택지가 있었으나 프로젝트 오너가 보류를 택했다
  • Decision: 두 값 모두 스파이크 실측 후 결정. 스펙에는 값 대신 "상한이 존재하고 초과 시 실패 처리된다"(AC-07)와 안전 기본값(C-04 전환 조항)만 규정
  • Rationale: 이 프로젝트가 세 차례 반복한 실패 패턴이 정확히 "근거 없는 임계값을 지어내 불변 층위에 잠그기"였다(D-013, D-015). 실측 분포를 보기 전에 숫자를 정하면 같은 패턴이다.
  • Impact: Seed spec v2.1.0 Planning 확정표에 "스파이크 후" 상태로 기록
  • ⚠️ 정정 (2026-08-26, 5차 감사 지적): 이 항목의 최초 근거 서술은 사실이 아니었다. "AC-07은 값 없이도 판정 가능한 형태"라고 적었으나, v2.0.1이 AC-07에서 시간 상한 조건절을 제거했으므로(Evolution Log v2.0.1 ⑦) AC-07은 재시도 횟수만 규정한다. 따라서 자가 검증이 실패도 성공도 하지 않고 매달린 경우를 판정할 인수 기준이 현재 존재하지 않는다. 두 보류 항목 중 A-02만이 안전 기본값(C-04 전환 조항)으로 보호되며, 시간 상한은 보호되지 않는다. 값 확정과 별개로 AC 복원이 필요하다 — 이는 v2.2.0 이후의 미결 과제로 남긴다
  • Source: 프로젝트 오너 확답 + Seed spec §4 Planning 입력 8·9번

D-021: 인수 경계를 범위 경계로 환원하고 측정 자료를 Planning 입력으로 분리 (v3.0.0)

  • Date: 2026-08-26
  • Context: 다섯 번째 독립 평가에서 모호성이 0.4075 → 0.4975로 다시 악화했다. 전체 궤적은 0.625 → 0.4125 → 0.47 → 0.4075 → 0.4975 — 개선이 아니라 진동이다. 5차 감사가 내부 모순 17건, 판정 불가 AC 5건, 그리고 문서가 자기 안전 속성을 실제보다 낫게 기술한 허위 서술 1건을 확인했다(v2.0.1이 AC-07에서 시간 상한 조건절을 제거했는데 v2.1.0이 그것이 남아 있다고 서술 — 결과적으로 자가 검증이 매달린 경우를 판정할 AC가 부재). 감사는 결함의 재발 양상도 명시했다: "v2.0.1이 고쳤다고 주장한 바로 그 인용 오류 유형의 재발", "v2.1.0 재작성이 도입한 기존 결함 유형의 새 사례"
  • 진단: 점수가 문서 품질이 아니라 문서 표면적과 상관한다. 근본 원인은 계층 착오 — §4 인수 경계가 임계값·측정 환경·픽스처까지 갖춘 테스트 명세가 되려 했고, 그 결과 아직 만들지 않은 물건의 측정 조건을 미리 적어야 했다. 채울 근거가 없으니 값을 지어냈고, 지어낸 값이 다시 불변 층위에 잠겼다. 파이프라인 설계상 BDD 인수 조건은 Planning 단계의 산출물이므로, Specification 단계가 그 일을 미리 한 것이 문제였다
  • Options:
    1. §4를 범위 경계로 환원하고 측정 자료를 Planning 입력 문서로 분리
    2. v2.2.0으로 계속 패치 — 지난 네 라운드가 실패를 증명한 방식
    3. 프로젝트 오너가 직접 검토·정리
    4. 현 상태로 Planning 진행
  • Decision: 옵션 1 — 프로젝트 오너 확답(2026-08-26)
  • Rationale: 다른 라운드와 성격이 다르다. 이전 개정들은 같은 계층 안에서 문구를 고쳤고 매번 새 표면을 만들었다. 이번은 잘못된 계층에 있던 내용을 옳은 계층으로 옮기는 것이므로, §4의 검증 표면 자체가 줄어든다. 조사한 실측 자료(허용 호스트 목록, 참조 워크스페이스 분석, ACP 에이전트 선정 근거)는 근거가 확인된 것이므로 폐기하지 않고 docs/planning-inputs-cocode.md로 이관했다 — ⚠️ 원문은 "허용 호스트 12종"으로 개수를 적었으나 그 수는 정본이 아니다(D-051, 2026-09-13 정정). 이관된 것은 §2.1 표이고 정본은 그 행 집합이다
  • Impact:
    • Seed spec v3.0.0 — §4가 16개 범위 경계로 재작성됨. 임계값·측정 환경·픽스처 전량 이관
    • docs/planning-inputs-cocode.md 신설 — 확정 결정 6건, 허용 호스트·캐시 경로, 참조 워크스페이스 분석과 주의사항, ACP 에이전트 선정 근거, Planning 확정 대기 항목 5건
    • 5차 감사가 지적한 모순 17건을 함께 처리: 시간 상한 AC 복원(AC-08), 테스트 승인을 completion_mode와 무관하게 적용, AC-06에서 프로토콜 버전 제거, EditSnapshot 작업 시작 상태 정의 수정, Workspace allowed_hosts 신설, MVP 플랫폼을 C-01에 명시, 1급 프로젝트 타입 정의, 식별자 재사용 대응표, 출처표 일치, Evolution Log 정렬, A-05 상태 enum 교정
  • 한계: 이 접근이 수렴한다는 보장은 없다. 작성자는 이전 네 라운드에서도 "이번은 다르다"고 판단했고 네 번 다 틀렸다. 판단 근거는 재평가 결과로만 확인 가능하다
  • Source: 5차 적대적 감사 (2026-08-26) + 프로젝트 오너 확답

D-022: Flutter master 채널 채택 — 네이티브 윈도잉 API 사용

  • Date: 2026-08-26
  • Context: 프로젝트 오너가 Flutter 3.47.0 의 데스크톱 창 분리 기능을 IDE 에 적용하자고 제안했다. 소스를 직접 확인한 결과 API 는 실재하나 채널 게이팅이 있었다:
    • windowingFeature(flutter_tools/lib/src/features.dart:257)가 master: FeatureChannelSetting(available: true) 만 선언하고 stable: 항목이 없다
    • FeatureChannelSetting 기본값이 available = false 이며 주석이 "아래의 어떤 설정으로도 켤 수 없다" 고 명시
    • flutter_features.dart:99 의 isEnabled 가 if (!featureSetting.available) return false; 로 config 를 읽기도 전에 종료
    • 실험: stable 3.47.0 에서 flutter config --enable-windowing 은 성공한 것처럼 응답하지만 위 로직상 값이 읽히지 않는다 — "지원되는 것 같다"고 보이는 원인
    • API 자체는 @internal 이며 _window.dart(비공개)에 있고, 원문이 "패치 버전에서도 breaking change 를 만들 것" 이라 예고
  • 추가 확인 (오너 제보): 플랫폼별 컨트롤러가 windowHandle 을 노출한다 — ffi.Pointer<ffi.Void> 로 네이티브 핸들(Linux GtkWindow, macOS NSWindow, Win32 HWND)에 직접 접근할 수 있다(_window_linux.dart:208, _window_macos.dart:200). WindowControllerLinux/WindowControllerMacOS 인터페이스로 제공되며, Windows 네이티브 도킹 같은 고급 통합이 가능해진다.
  • Options:
    1. 추상화 뒤에 두고 window_manager 로 구현 — stable 유지, 네이티브 윈도잉이 stable 에 오면 구현체 교체
    2. master 채널로 가고 네이티브 API 사용
    3. 구현체 2개를 플래그로 병행
    4. MVP 에서 창 분리 제외
  • Decision: 옵션 2 — master 채널 + 네이티브 윈도잉 API. 프로젝트 오너 확답(2026-08-26, 두 차례 확인 후)
  • Rationale: windowHandle 로 열리는 네이티브 도킹은 window_manager 로 대체할 수 없는 능력이며, C-03 이 잠근 "Dart/Flutter 전용 수직 깊이" 포지셔닝에 직접 기여한다. Orca(수평적 오케스트레이터)가 갖지 못하는 종류의 차별화다.
  • 인수한 위험 (명시):
    • master 채널은 윈도잉만이 아니라 제품 빌드 기반 전체에 적용된다. stable 이 아닌 트리 위에서 제품을 빌드하게 된다
    • Flutter 가 이 API 에 패치 버전에서도 breaking change 를 예고했다 — 업스트림 변경이 곧바로 빌드를 깨뜨릴 수 있다
    • @internal API 이므로 공개 API 안정성 보장 대상이 아니다
    • CI·개발 환경 전체가 master 채널로 고정돼야 하며, cob doctor 등 도구 체인도 이를 전제해야 한다
  • 완화: 창 관리 호출을 얇은 자체 인터페이스 뒤에 두어, 업스트림 변경 시 수정 지점을 한 곳으로 모은다. 이는 옵션 1 의 추상화와 같은 구조이나 구현체는 네이티브 API 를 직접 쓴다.
  • Impact: 아키텍처 문서의 플랫폼 계층, cob doctor 채널 점검, CI 워크플로 Flutter 설치 단계. A-03(에디터 성능) 스파이크도 master 채널에서 측정해야 한다
  • Source: 프로젝트 오너 확답 + Flutter 3.47.0 소스 직접 검증

D-023: 프로젝트 초기 세팅은 cob 로 수행

  • Date: 2026-08-26
  • Context: 프로젝트 오너가 초기 세팅에 cob(co-bricks CLI)를 쓰도록 지시했다. cob doctor 실행 결과 dart·flutter·melos·gcloud·firebase·gh·bricks 전 항목 통과를 확인했다(/Users/dongwoo/.pub-cache/bin/cob, 저장소 /Users/dongwoo/orca/co-bricks).
  • Decision: cocode ADE 모노레포의 초기 생성과 이후 기능 추가를 cob 로 수행한다.
  • 확인된 cob 능력 (cob --help 실행 결과):
    • cob create / cob create-from-config — Mason brick 기반 신규 프로젝트 생성
    • cob generate — project.yaml 사양으로 모노레포 + 기능 일괄 생성
    • cob compose / cob add / cob feature — 기능 골격 생성 및 증분 추가(usecase·bloc·widget·endpoint)
    • cob apply — 기존 모노레포에 기능 brick 적용
    • cob diff / cob catalog / cob list-features — 차이 검출·brick 목록
    • cob doctor / cob preflight — 도구 체인 점검
    • cob plan — PRD 의 FR/AC 커버리지를 capability 제안과 대조(고아 FR 은 hard-fail)
  • 주목할 점: cob plan 이 PRD 의 FR/AC 를 게이팅한다. 이 프로젝트는 docs/prd-cocode.md 와 docs/bdd-cocode.md 를 이미 갖고 있으므로, Breakdown 단계에서 이 명령이 커버리지 검증에 쓰일 수 있다.
  • Impact: 아키텍처 문서의 프로젝트 부트스트랩 절, Scaffold 단계(파이프라인 4.5단계)의 실행 수단
  • 미결: cocode ADE 는 앱이 아니라 데스크톱 IDE 이고 Serverpod 백엔드가 없다. 기존 brick 이 이 형태를 지원하는지, 새 brick 이 필요한지는 Scaffold 단계에서 cob list-features 로 확인해야 한다
  • Source: 프로젝트 오너 지시 + cob --help·cob doctor 실행 확인

D-024: 브랜치 토폴로지 — development 신설, PR 은 development 로

  • Date: 2026-08-27
  • Context: /cc-product:develop 착수 시도에서 세 가지가 막고 있음을 확인했다. main 이 0 파일이라 Story 브랜치를 딸 기준이 없었고, development 브랜치가 아예 없었으며, PR #1 이 base=main + head=feature 브랜치라 릴리스 컷오프 가드(.github/scripts/check_release_branch.py)에 정확히 막혀 하위 CI 잡 7개가 전부 skipped 였다. CI 요약은 그 skip 을 "검증 대상 없음이 아니라 미검증"으로 실패 처리한다.
  • Decision: main 에서 development 를 신설하고 PR #1 의 base 를 development 로 변경한다. 계층은 CLAUDE.md 규약대로 main ← release/YYYY-MM-DD ← development ← project/* → epic/* → story/*.
  • 결과: 컷오프 가드 통과(🚦 릴리스 컷오프 가드: success). PR #1 을 squash 로 머지(7c886a3) — development 10,214 파일.
  • 부수 효과: squash 로 b8289d0(58MB main.dill blob)·ff5d7f9 가 development 의 조상이 아니게 됐다. 히스토리 재작성 없이 해소되어 #13 의 "재작성 여부 결정" AC 가 불필요해졌다.
  • Impact: 이후 모든 Story 브랜치의 기준. project/2-cocode-ade-mvp·epic/3-scaffold-baseline 생성 완료
  • Source: CI 실행 로그(run 33021528438) + .github/scripts/check_release_branch.py 직접 확인

D-025: Flutter 3.47.0 stable 유지 — D-022 를 뒤집는다

📌 패치 상향 (2026-09-10) — .fvmrc 를 3.47.0 → 3.47.3 으로 올렸다(프로젝트 오너 지시). 이 결정을 바꾸지 않는다 — 채널은 여전히 stable 이고 D-022(master 채널 요구)는 뒤집힌 채다. 같은 minor 안의 패치 이동이라 ^3.47.0 캐럿 제약(root·app/cocode·app/cocode_console·app/cocode_widgetbook·shared/dependencies)은 그대로 유효해 pubspec 5곳은 손대지 않았다. 검증: dart pub get 성공 · core 테스트 125건 통과 · check_flutter_stable_pin.py 통과. 함께 맞춘 하드코딩: build.desktop.yaml · warm-flutter-sdk.yml · README · CLAUDE.md · SCAFFOLD.md. ⚠️ docs/*.md 의 «Flutter 3.47.0 기준 실측» 기록은 그대로 둔다 — 그 시점의 사실이다.

📌 패치 상향 (2026-10-02, #483) — .fvmrc 를 3.47.3 → 3.47.6 으로 올렸다(프로젝트 오너 지시 — 처음엔 CI flutter-setup 기본값(#478)인 3.47.5 로 맞추려 했으나, 2026-10-01 에 나온 최신 stable 3.47.6 으로 정했다). CI flutter-setup 기본값도 같은 3.47.6 으로 올려 로컬(fvm · lefthook with-fvm.sh)과 CI 의 패치 버전을 다시 하나로 맞췄다. 이 결정을 바꾸지 않는다 — 채널은 여전히 stable 이다. ^3.47.0 캐럿 제약은 그대로 유효해 pubspec 은 손대지 않았다. 검증: flutter pub get 성공(lock 변화 없음) · core 테스트 485건 통과 · check_flutter_stable_pin.py 통과. 함께 맞춘 하드코딩: flutter-setup/action.yaml · build.desktop.yaml · warm-flutter-sdk.yml · README · CLAUDE.md · SCAFFOLD.md · run_all_scenarios.sh 주석. ⚠️ docs/*.md 의 «3.47.3 에서 측정» 기록(AC-15·A-06·AC-16 등)은 그대로 둔다 — 위 선례와 같은 이유다.

  • Date: 2026-08-27
  • Context: D-022 는 windowHandle 네이티브 도킹을 위해 master 채널 전환을 결정하고 그 위험(제품 빌드 기반 전체가 master 위에 서고 @internal API 가 패치 버전에서도 breaking change)을 인수했다. 그러나 전환은 실행되지 않았고 .fvmrc 는 3.47.0 stable 이었다. 결정 이슈 #100 으로 오너에게 확인했다.
  • Decision: master 채널로 전환하지 않는다. 3.47.0 stable 을 유지한다.
  • 확인된 귀결 — 네이티브 윈도잉은 MVP 밖: ~/fvm/versions/3.47.0/packages/flutter_tools/lib/src/features.dart:257 실측 —
    const windowingFeature = Feature(
                          name: 'support for windowing on macOS, Linux, and Windows',
                          configSetting: 'enable-windowing',
                          environmentOverride: 'FLUTTER_WINDOWING',
                          runtimeId: 'windowing',
                          master: FeatureChannelSetting(available: true),   // ← master 만 선언
                        );
                        
    stable: 항목이 없고 FeatureChannelSetting 기본값이 available: false 이며, flutter_features.dart 가 if (!featureSetting.available) return false; 로 config 를 읽기 전에 차단한다. flutter config --enable-windowing 도 FLUTTER_WINDOWING 환경변수도 stable 에서는 효과가 없다.
  • Impact: #9(master 전환) 범위 밖 → 종료. #10(회귀 가드) 방향 전환 — "master 이탈 감지". #54 는 인터페이스 seam 만 유지하고 네이티브 도킹 구현은 MVP 밖. ux-spec §2.3 의 단일 창 전제가 확정된다.
  • ⚠️ 주의: 이 결정은 프로젝트 착수 시의 동기("데스크톱 창분리 기능이 새롭게 지원되어서 IDE에 적용하면 좋을것 같아")와 어긋난다. 창 분리를 MVP 에 넣으려면 D-022 로 되돌아가야 한다.
  • Source: 프로젝트 오너 결정(#100) + Flutter 3.47.0 SDK 소스 직접 검증

D-026: 도너(unibook) 유래 자산 존치

  • Date: 2026-08-27
  • Status: 대체됨 — D-087 (2026-09-28, #458). 백엔드 존치 부분만 유지된다.
  • Context: SCAFFOLD.md 가 "표준 구조를 먼저 세우고 개선은 이후"로 판단을 미뤄 둔 항목. 결정 이슈 #102 로 오너에게 확인했다.
  • Decision: 존치한다. 오너 원문 — "존치 해줘 추후 백엔드가 필요할수도 있어서 남겨두었어".
  • 존치 대상 실측 (7c886a3): feature/console/ 38 · feature/common/ 9 · feature/application/ 6 · backend/{cocode_client,cocode_server} · app/{cocode_console,cocode_widgetbook} · app/cocode/{android,ios,web}
  • ⚠️ 이 결정이 풀지 못하는 것: CI 를 막는 도너 가드는 실재하지 않는 패키지(book_content_viewer·my_library)를 참조한다 — cob create 가 애초에 생성하지 않았다. 존치할 대상 자체가 없으므로 #12(도너 CI 가드 정리)는 그대로 필요하다.
  • Impact: #8 범위 전환(제거 → 존치 확인). #6 존치 확정. #4 배치표는 cocode_* 12종을 기존 자산과 공존하도록 배치해야 한다. #99(SSPL-1.0 재배포 검토)가 가정이 아니라 실제 쟁점이 됐다.
  • 미결: 백엔드를 "존치하되 MVP 미사용"인지 "MVP 사용"인지 — #4 배치표가 기록한다
  • Source: 프로젝트 오너 결정(#102) + 트리 실측

D-027: architecture §6.1 DD-21 / 21a / 21b 채택

  • Date: 2026-08-27
  • Context: Design 리뷰가 C-04 ⑤를 도달 불가능한 사문으로 판정했다 — ⑤는 ①~④가 모두 "대상 없음"일 때만 열리는데 아키텍처가 ③을 항상 "미실행"으로 처리해 그 조건이 성립할 수 없었다. 아키텍처 §6.1 이 해소안을 냈으나 채택 확정이 사람 결정이었다. 결정 이슈 #101 로 확인했다.
  • Decision: 채택한다.
    • DD-21 — ①~④는 "적용 불가"를 판정하지 않고 실행한다. {통과, 실패, 대상없음} 을 반환하고 대상없음 은 폐쇄 사유 코드 4종(no_target:build_no_configured_target · no_target:test_zero_collected · no_target:no_debug_session · no_target:file_not_analyzed)을 동반한다.
    • DD-21a — 결과 우선순위 실패 > 통과. 초판은 중재가 없어 ④가 새 오류를 뱉어도 ①이 성공했다는 이유로 완료 전이가 가능했다.
    • DD-21b — ⑤는 3항 술어(파일 읽힘 · 파서 무오류 · 정적 분석 새 오류 없음). 실행자는 toolchain.
    • 실행 순서 ④ → ② → ① → ③, 조기 종료는 실패 확인 후에만. 초판의 "통과가 나오면 나머지 미실행" 규칙은 철회한다.
  • Impact: AC-09 가 기계적으로 판정 가능해진다. #63 착수 가능. #73 은 대상없음(no_target)과 "환경 실패"가 다른 축임을 명시해야 한다.
  • 막지 못하는 것(정직하게): 통과한 방법이 편집 대상을 실제로 검사했는지는 보장하지 않는다. CI yaml 한 줄만 고친 작업도 ①이나 ②가 통과해 완료될 수 있다. Seed Spec C-04/AC-09 문면에서 오는 것이라 Design 이 경계를 넓히지 않고는 고칠 수 없다 — A-02 스파이크가 측정할 실패 모드로 남는다.
  • Source: 프로젝트 오너 결정(#101) + docs/architecture-cocode.md §6.1

D-028: 대용량 파일 CI 가드 임계 50 MiB

  • Date: 2026-08-28
  • Context: #13 이 index 내 대용량 파일 가드를 신설하며 임계 바이트 값을 「판정 주체: 프로젝트 오너, 값 확정 전 AC 미완료」로 남겼다. 사례는 main.dill 55.64 MiB(커밋 b8289d0), GitHub 는 50 MiB 경고 · 100 MiB 차단.
  • Options:
    1. 50 MiB — GitHub 경고선 그대로. main.dill 급을 잡고 현행 추적 파일 최대가 훨씬 작아 오탐 없음
    2. 10 MiB — 작은 산출물도 조기 차단하나 정당한 대용량 자산이 생기면 허용목록 관리 필요
    3. 25 MiB — 절충
  • Decision: 옵션 1 — 50 MiB (52,428,800 B)
  • Rationale: 프로젝트 오너 확정(2026-08-28). 실측 사례를 잡는 최소 개입이며 값 변경은 이 로그에 근거를 남기고 check_large_files.py 의 MAX_BYTES 를 수정한다(짝 테스트가 상수 회귀를 고정).
  • Impact: .github/scripts/check_large_files.py + ci.yml changes 잡 배선. #13 AC 4 완료 가능.
  • Source: 프로젝트 오너 결정 (#13 사이클 중 확인)

D-029: 히스토리 잔존 main.dill blob 처분 — 결정 대기

  • Date: 2026-08-28 (기록만 — 결정 아님)
  • Context: #13 AC 8 이 「히스토리 재작성 여부는 범위 밖 · 결정 대기로 기록」을 요구했다. 단 전제가 이슈 작성 시점과 달라졌다: 이슈는 「결정 시한: PR #1 이 development 로 머지되기 전」이라 했으나, PR #1 은 squash 로 머지되어(7c886a3) b8289d0(blob 58,339,928 B 보유 커밋)은 development·epic 계보의 조상이 아니게 됐다(git merge-base --is-ancestor b8289d0 origin/development → exit 1, 실측 2026-08-28). blob 은 현재 product-artifact-200c74e2 브랜치 계보에만 잔존한다.
  • Options:
    1. product-artifact-200c74e2 원격 브랜치 삭제 — blob 이 GC 대상이 되어 신규 clone 이 가벼워짐. 히스토리 재작성 불필요(브랜치 삭제만으로 충분해짐 — 전제 변경의 이득)
    2. 보존 — 파이프라인 산출 이력 보관. 그 브랜치를 fetch 하는 clone 만 55.64 MiB 를 추가 수신
  • Decision: 미정 — 결정 필요(판정 주체: 프로젝트 오너). 기본 브랜치 계보가 이미 깨끗해 시급성은 소멸 — 시한 없음.
  • Impact: 결정 전까지 없음. 재유입은 D-028 가드 + .gitignore *.dill 이 막는다.
  • Source: #13 AC 8 (전제 변경 실측 포함)

D-030: 재시도 시 직전 시도의 편집 처분 — 누적(되돌리지 않는다)

  • Date: 2026-08-29
  • Context: #20 이 EditSnapshot 에 시도 경계 속성(attempt_no)을 신설하며, 그 경계가 무엇을 뜻하는지 정하려면 선행 결정이 필요했다 — 자가 검증 실패 후 재시도가 시작될 때 직전 시도가 남긴 파일 편집을 자동으로 되돌리는가. flow-permutation-cocode.md FP-103 이 이 자리를 「없음(미규정)」으로 기록했고 I-5 로 등급했다. AC-07(재시도 3회 상한)이 최대 4세트(최초 1 + 재시도 3)의 편집을 만들 수 있게 하는데, 그 4세트가 사람에게 어떤 상태로 넘어가는지가 정해지지 않았다.
  • Options:
    1. 누적 — 재시도는 직전 편집 위에 계속 쌓는다. 실패 후 사람이 보는 diff 에 모든 시도의 흔적이 남고, 시도 구분은 attempt_no 가 한다
    2. 되감기 — 각 재시도는 직전 시도를 자동 롤백한 깨끗한 상태에서 시작한다. 최종 diff 는 마지막 시도만 담아 깔끔하다
    3. 누적 + 화면에서 실패한 시도를 기본 접힘 처리 (저장은 1과 동일, UX 요구 1건 추가)
  • Decision: 옵션 1 — 누적. 재시도는 직전 시도의 편집을 자동으로 되돌리지 않는다.
  • Rationale: 프로젝트 오너 확정(2026-08-29). 근거 3가지 — ⓐ flow-permutation-cocode.md:325 B-15 가 「retry_count 가 3인 시점의 편집과 3을 초과한 시점의 편집이 모두 diff 에 나타난다」(근거 AC-19 + C-02)를 요구한다. 누적이어야 그 문면이 성립하고, 되감기를 택하면 B-15 와 그 근거 AC 를 함께 개정해야 한다. ⓑ C-02·EditSnapshot 불변식 1 이 「에이전트의 모든 파일 편집은 대응하는 EditSnapshot 없이 존재할 수 없다」이므로 되감기의 복원 쓰기 자체가 스냅샷을 낳는다 — 4세트가 8세트가 되고 sequence_no 계열이 편집과 복원으로 뒤엉킨다. ⓒ 되감기는 사용자가 요청하지 않은 파일 쓰기를 시스템이 자동 수행하는 것이라, 「롤백됨으로의 전이는 사용자의 명시적 롤백 요청으로만 발생한다」(DelegatedTask 불변식 5)와 긴장 관계다. 대가: 실패 후 사람이 보는 diff 에 실패한 시도의 흔적이 남는다 — 그것을 구분하는 것이 바로 이 Story 가 추가하는 attempt_no 이며, 되감기가 「지워서」 푸는 문제를 누적은 「표시해서」 푼다.
  • Impact: docs/seed-spec-cocode.md §3 EditSnapshot — attempt_no 속성 신설 + 시도 경계 롤백 규칙(ⓐⓑⓒ 3종 질의). DelegatedTask 불변식 4(:85 — retry_count > 3 이면 실패)와의 관계: 누적이므로 실패 시점에 최대 4세트가 남아 있고, 그 전부가 AC-19 의 「실패한 작업의 편집은 조용히 폐기되지도 확정되지도 않는다」 대상이 된다. docs/architecture-cocode.md DD-10 스냅샷 레코드에 attempt 필드, DD-11 1단계 대상 산출 함수에 롤백 질의 인자. 구현은 #23(저장소)·#24(순수 함수) 소유.
  • Source: 프로젝트 오너 결정 (#20 사이클 중 확인, 2026-08-29)

D-031: 도너 표면을 분석·빌드 범위에서 제외 — 「존치」를 파일 보존과 대상 포함으로 분리

  • Date: 2026-08-29
  • Status: 대체됨 — D-087 (2026-09-28, #458). 도너 표면이 삭제돼 exclude·ignore 는 backend/cocode_server 한 줄만 남았다.
  • Context: D-026 은 도너(unibook) 자산의 존치를 정했으나 그것이 빌드·codegen·분석 대상에 포함된다는 뜻인지는 정하지 않았다. 그 공백 때문에 「존치」와 「CI green」이 동시에 성립하지 않았고, #8 이 이 구분의 필요를 제기했으나 그 이슈가 닫히며 소유가 유실됐다. 실측으로 규모가 확인됐다 — 워크스페이스 dart analyze --no-fatal-warnings → error 13,872건 / 76개 패키지. 최대 원인은 codegen 결손이 아니다: feature/application/dashboard 등 10개 패키지가 src/di/injector.module.dart 를 export 하는데 injectable 은 이 리포에서 제거된 패키지(pubspec 선언 0 · import 0)라 melos run build 로도 사라지지 않는 영구 결손이며, 브릭이 도너와 다른 세대의 스캐폴드를 찍은 결과다.
  • Options:
    1. 분석·빌드 범위에서 제외 — analysis_options.yaml 의 exclude 와 melos: ignore: 에 도너 경로를 넣는다. 파일은 한 줄도 지우지 않는다. 이미 이 리포의 관용구(analysis_options.yaml 이 cocode_client·deploy/aws·pdfrx·postal_ko 를 패키지 통째로 배제 중)
    2. 도너 코드 수리 — injectable 의존 10패키지를 GetIt 수동 등록으로 전환 + 누락 백엔드 모델 305개 복원 + dio/Failure 세대차 정리. #8 의 수렴 실험(308→61→40)이 멈추지 않았고, 끝까지 가면 「Serverpod 백엔드가 없다」고 못박은 제품에 전자책 백엔드를 통째로 복원하게 된다
    3. 도너 자산 제거 — D-026 번복
  • Decision: 옵션 1 — 분석·빌드 범위에서 제외. 프로젝트 오너 확정(2026-08-29).
  • Rationale: D-026 의 「추후 백엔드가 필요할 수도 있어서」라는 의도를 그대로 지키면서(파일 0건 수정) CI 를 되살리는 유일한 경로다. 옵션 2 는 수주 이상이고 cocode ADE 제품 가치에 기여하지 않으며, 옵션 3 은 오너 결정을 번복한다. 완전 가역 — 두 블록을 삭제하면 원상 복구된다.
  • Impact: analysis_options.yaml analyzer.exclude + dart_code_metrics.exclude (도너 표면 11경로 + 루트 build/** + 고아 로케일 테스트 1건) · pubspec.yaml melos: ignore: 9패키지. 실측 결과 dart analyze error 13,872 → 0, melos run backend:pod:generate 는 빈 집합 exec 으로 rc=0 no-op(그 실패의 근원은 상류 bricks#268 이고 이 리포에서 닫히지 않는다). ⚠️ 부작용 — CI 의 check_generated_tracked.py 는 「생성 트리 없음 — 건너뜀」으로 통과한다. 로컬(생성물 보유)에서는 계속 588건으로 실패한다. 이 비대칭은 가드가 명시적으로 출력하므로 무음은 아니지만, CI green 을 「생성 코드가 추적된다」로 읽으면 안 된다(정책 자기모순 자체는 상류 bricks#270). ⚠️ app/cocode/** 는 cocode_app 착지점이다 — 그 Story 착수 시 exclude 를 lib/ 하위로 좁혀야 한다.
  • Source: 프로젝트 오너 결정 (2026-08-29) · 실측 근거는 #141

📌 온디스크·표기 개명 — .coco/ → .cocode/, 「coco ADE」 → 「cocode ADE」 (#296, 2026-09-16) — #294 가 「일부러 두고 온 것」 ②③ 으로 남긴 두 축을 닫는다. 이로써 cocode 네임스페이스 정렬은 #293(패키지) → #294(심볼) → #296(온디스크·표기) 세 단계로 끝난다.

① .coco/ → .cocode/ (DD-08·DD-09 개정). 워크스페이스 메타데이터 디렉터리와 그 네임스페이스를 공유하던 런타임 접두사 4종을 함께 옮겼다:

옮긴 것 자리
.coco/ → .cocode/ CocodeLayout.cocodeDirName · SandboxWritePathPolicy.deniedDirName
.coco-staging-<id> → .cocode-staging-<id> scaffold_engine.dart (DD-13a 원자성)
.<name>.coco-tmp-… → .cocode-tmp-… atomic_replace.dart
.<name>.coco-rollback-… → .cocode-rollback-… rollback_engine.dart
coco.<ns>.<key> → cocode.<ns>.<key> ui 위젯 Key 16 네임스페이스 · 커맨드 id · 타임라인 id

왜 지금인가 — 마이그레이션 비용이 0인 마지막 구간이다. 제품이 아직 워크스페이스를 출하하지 않아 세상에 .coco/ 디렉터리가 0개다. 출하 후에는 같은 변경이 데이터 포맷 마이그레이션이 된다.

⚠️ 함께 옮겨야 하는 것이 둘 있었다. ⓐ 대소문자 폴딩 볼륨 거부 테스트의 .Coco/history — 거부 대상과 같은 철자여야 시험이 성립한다. ⓑ CocodeCommandAction.commandId 의 'coco.$name' — 여기만 . 뒤가 $ 라 네임스페이스 일괄 치환이 비껴갔고, 놓쳤다면 레지스트리 키와 위젯 Key 가 갈려 팔레트가 조용히 아무 커맨드도 못 찾게 된다.

② 「coco ADE」 → 「cocode ADE」. 제품명 표기 195건. 실행에 닿는 것은 둘뿐이었다 — app/cocode/lib/main.dart 의 창 제목과 .pipeline/cocode.yaml 의 project_name.

그대로 두는 것 3종 (#294 의 판단을 유지한다): ① coco-de/* — GitHub 조직 슬러그. 실측으로 불가능을 확인했다: coco-de 는 이 저장소를 소유한 Organization(repo 16)이고, cocode 는 제3자 User 계정 (repo 17)이며 cocode/cocode 는 404 다. remote·git 의존성을 cocode/* 로 바꾸면 남의 계정을 가리켜 pub get 이 즉시 깨지고, 그 계정이 같은 이름의 저장소를 만들면 공급망 위험이 된다. 저장소 안에서 고를 수 있는 값이 아니다. com.coco-de.cocode (OS 자격증명 네임스페이스)도 같은 이유다. ② CocoFixtureModel — AC-15 픽스처의 산출물이 SHA-256·바이트·줄 수로 고정돼 있고 그 3값은 #92 실측의 재현 계약이다 ([[cocode-byte-pinned-fixtures]] 참조 — #294 에서 한 번 휩쓸려 CI 가 잡았다). ③ #292·#293·#294 의 과거 기록 인용 — 당시 이름이 coco_*·Coco* 였다는 문장과 거기 인용된 정규식(coco[A-Z] 등)은 사실이다. 고치면 기록이 거짓이 된다.

📌 심볼 개명 — Coco*·coco* → Cocode*·cocode* (#294, 2026-09-16) — #292 가 「이번 범위가 아니다」로 남겨 둔 클래스·상수·변수·테스트 이름을 마저 옮겼다. 패키지명이 cocode_* 인데 그 안의 타입이 Coco* 면 같은 것을 두 이름으로 부르게 된다.

옮긴 것 — Coco[A-Z] 2,426건 · coco_[a-z0-9] 320건 · 파일명 138개(git mv). 뒤이어 접두사에 가려 경계 정규식에 걸리지 않던 것들도 찾아 옮겼다: lowerCamel coco[A-Z] 224건(cocoTestApp 등 36종) · 비공개 _Coco* 13종 · 상수 kCoco* 5종 · CI 자산 stage_coco_pure_packages.py → stage_cocode_pure_packages.py · verify.coco-pure-tests.yaml → verify.cocode-pure-tests.yaml(required check 가 아니라 파일명 변경이 안전하다).

일부러 두고 온 것 3종 — 개명하면 다른 것을 가리키게 되는 이름들이다: ① GitHub 조직 슬러그 coco-de/*(remote·git 의존성 URL — 저장소 밖의 사실) ② 온디스크 워크스페이스 디렉터리 .coco/(DD-08 이 정한 경로 계약이자 데이터 포맷이다. 상수 이름은 cocodeDirName 으로 옮겼지만 값 .coco 는 그대로 둔다 — 바꾸려면 DD-08·DD-09 를 고치는 별도 결정이 필요하다) → #296 이 닫았다 ③ 서술 표기 「coco ADE」와 #292·D-026 의 과거 기록 인용(coco_* 로 적힌 그 문장들은 당시 사실이라 고치면 기록이 거짓이 된다) → 표기는 #296 이 닫았고, 과거 기록 인용은 그대로 둔다

⚠️ Cocoa·CocoaPods(iOS/macOS 빌드 산출물 98건)는 건드리지 않는다 — 치환식은 Coco 다음 글자가 대문자 또는 _ 일 때만 매치하므로 Cocoa(소문자 a)와 Cocode(소문자 d)는 구조적으로 걸리지 않는다. 실측으로도 0건 변경.

⚠️ 개명이 식별자를 2자씩 늘려 lib/ 9줄이 80자를 넘겼다 — dart analyze --fatal-infos lib 가 게이트라 info 하나로 CI 가 막힌다. dart format 으로 접었다(실측: 58 → 18 issues, lib/ 0건).

📌 네임스페이스 정렬 + 도너 위젯북 제거 (#292, 2026-09-16) — 오너 결정 2건.

① coco_* → cocode_*. D-026 이 「coco_* 12종을 기존 자산과 공존하도록 배치」로 적은 그 네임스페이스를 프로젝트 이름(cocode)에 맞춘다. 패키지 11종 (acp·agent·bricks·core·editor·lsp·platform·terminal·toolchain· ui·workspace) 디렉토리·pubspec name:·import 509줄·설정/CI 참조를 옮겼다. 클래스명(Coco* 121종)과 「coco ADE」 제품 표기는 이번 범위가 아니다.

⚠️ CI 스크립트 8곳이 coco_ 를 정규식·글롭(package/coco_[A-Za-z0-9_]+/, package/coco_*/lib/**)으로 쓰고 있어 단순 문자열 치환에 걸리지 않았다 — 놓쳤다면 그 가드들이 cocode ADE 코드를 조용히 보지 않게 된다. 별도로 찾아 고쳤다.

② app/cocode_widgetbook_donor 제거 (687파일 · 8.3MB). D-031 이 옵션 3 「도너 자산 제거 — D-026 번복」으로 기각했던 것이며, 이 결정은 그 기각을 뒤집는다. 근거: 그 앱은 존재하지 않는 도너 feature 패키지에 묶여 이 저장소에서 빌드된 적이 없고(#289 실측), 의존하는 패키지가 0건이며, 제거 후 유일한 위젯북은 app/cocode_widgetbook 이다.

따라 정리한 참조: analysis_options.yaml exclude 3곳 · detect_console_web_impact.sh NOT_CONSOLE_REGEX · plan_codegen_targets.py 제외 목록 · verify_widgetbook_coverage.sh(인정 위치가 feature/*/*/widgetbook/ 만 남음) · SCAFFOLD.md · package-placement-cocode.md.

📌 경로 이동 (#287, 2026-09-15) — 도너 위젯북이 app/cocode_widgetbook → app/cocode_widgetbook_donor 로 옮겨졌다. 프로젝트 이름(cocode)에 맞춰 발행되는 위젯북이 app/cocode_widgetbook 을 차지해야 한다는 오너 결정에 따른 것이다.

D-026·D-031 은 그대로다. 도너 트리는 바이트 단위 동일하게 이동했고(트리 해시 대조), 도너 파일 수정은 pubspec.yaml 의 resolution: workspace 1줄 제거가 전부다 — 패키지명(cocode_widgetbook)을 바꾸면 내부 import 15곳을 고쳐야 해서, 대신 pub workspace(= melos 스코프) 밖으로 빼 이름 충돌을 없앴다. check_workspace_membership.py 는 resolution: 미선언 패키지를 검사 대상에서 제외하므로(#6 AC 5) 통과한다.

따라 옮긴 참조: analysis_options.yaml exclude 3곳 · 루트 workspace: · melos ignore: (cocode_widgetbook 항목 제거 — 그 이름은 이제 분석·테스트 대상인 cocode ADE 위젯북이다) · melos:scripts:analyze 의 --ignore="*cocode_widgetbook*" 제거(새 앱을 삼킨다) · scripts/verify_widgetbook_coverage.sh 5곳 · Pages 워크플로(pages.cocode-widgetbook.yaml).

⚠️ 아래 #281 표의 verify_widgetbook_coverage.sh 행에 적힌 app/cocode_widgetbook/ 은 그 시점의 경로다 — 지금은 _donor 다.

📌 파급 반영 완료 (#277 · #278 · #279 · #280 · #281, 2026-09-14) — D-031 이 남긴 「검증 단계 미반영」을 닫았다.

D-031 은 analysis_options.yaml 과 pubspec.yaml 두 블록만 바꾸면 되는 결정으로 적혔으나, CI 의 검증 지점 아홉 곳이 그 경계를 모른 채 남아 Project #2 최종 PR(#273)을 막았다. 앞의 여섯은 부트스트랩을 막아 하위 5잡을 통째로 skip 시켰고, 그것을 풀자 나머지가 드러났다. 증상은 codegen 실패로 보였지만 뿌리는 하나다 — backend:pod:generate 가 rc=0 no-op 이라 serverpod 프로토콜이 결코 생성되지 않는데, 그 산출물을 필수로 요구하는 검사들이 그대로였다.

자리 무엇이 성립하지 않았나 처분
ci.yml 〈빌드 아티팩트 검증〉 핵심 파일 generated/endpoints.dart·protocol.dart 는 gitignore 대상이고 생성도 안 된다 목록에서 제거
ci.yml mocks 하한 검사 누락 2건이 PodService·EndpointEmail·EndpointSignupValidation 즉 생성 불가 타입을 mock SCAN_PATHS 를 composite 과 동일하게(feature/ 제외)
check_endpoint_auth_gate.py 등록 목록(SSOT)이 생성되지 않아 전수 대조 불가 전수 대조만 강등. 게이트 선언 판정 본체는 그대로 돌고 발견분은 매 실행 전량 출력
check_shared_steps.py feature/*/* 3축 244건 전량이 도너 도너 3축 보류(+ 배럴의 개명 잔재 2건은 실제 결함이라 삭제)
detect_backend_impact.sh test/unit 81건 중 76건이 생성 불가 프로토콜을 import 빌드 범위 밖이면 스킵
detect_console_web_impact.sh 콘솔이 의존하는 도너 feature 10개가 제거된 injectable 의 injector.module.dart 를 export 해 영구 결손 같음
verify_widgetbook_coverage.sh 열거 대상(feature/)도 use-case 위치(app/cocode_widgetbook/)도 전부 도너 같음

부트스트랩이 풀리자 하위 5잡이 처음 실행됐고, 둘이 같은 이유로 죽었다 (#278).

자리 무엇이 성립하지 않았나 처분
analyze_changed_packages.py → dcm analyze 대상 128개 중 109개가 도너. dcm 이 41/128 컨텍스트(feature/console/console_book_registration)에서 내부 크래시 — type 'Null' is not a subtype of type 'Element' of 'key' 정본에서 유도해 128 → 19
test 잡의 ALL_TEST_PKGS melos list 는 melos: ignore: 만 봐서, 생성되지 않는 cocode_client 에 전이 의존하는 55개가 남았다. package/core·pod_service 가 Client·NotAuthenticatedException 참조로 컴파일 불가 같은 유도로 78 → 17
🎭 E2E 소스 정적 분석 (#280) app/cocode/integration_test 를 dart analyze 에 직접 인자로 넘겨 제외를 우회 — 도너 심볼(AppRouterKeys.storeTab 등) 34건. continue-on-error 는 유예일 뿐이고 뒤의 결과 확인 스텝이 집행한다 같은 판정 통과

여덟 곳 중 마지막 둘은 목록을 베끼지 않고 정본에서 유도한다. 잡마다 손으로 적으면 반드시 갈라지고(ci.yml 주석 자신이 「양쪽 제외 목록은 항상 함께 고칠 것」이라고 경고하는 그 드리프트다 — #9499 때 shared/i10n·shared/config 9건이 게이트 밖으로 샜다), donor_excluded_packages.py 가 analysis_options.yaml 에서 제외 접두를 읽어 두 잡에 공급한다. 남는 대상은 전부 cocode ADE 자신의 코드이고, shared/i10n·shared/config 회귀 가드가 살아 있음을 확인했다.

그 판정은 두 방향의 함정을 다 막는다 — 과소 제외는 빌드 안 되는 도너를 검증하려다 CI 를 죽이고, 과잉 제외는 이 제품의 검증을 조용히 증발시킨다. 그래서 파일 글롭을 접두로 읽지 않고, DCM 개별 규칙의 exclude: 를 전역 패키지 제외로 승격하지 않으며(승격하면 규칙에 - "package/core/**" 한 줄 적는 것만으로 그 패키지가 검증에서 사라진다), 읽기 실패 시에는 아무것도 제외하지 않는다.

스코프를 바로잡자 드러난 것 — 이 반영의 부수 소득

dcm analyze 가 도너 109개를 빼고 완주하자(종전에는 41/128 컨텍스트에서 내부 크래시로 죽어 아무 신호도 없었다) cocode ADE 자신의 코드에 대한 실제 지적이 나왔다 — error 4 · warning 144 · style 2276(게이트는 error 만 차단). error 4건은 전부 이 프로젝트가 새로 쓴 테스트 코드였고, 워크스페이스 단일 분석(#9681)이 test/ 까지 보게 되면서 처음 보인 것이다. 규칙을 되돌리지 않고 코드를 고쳤다 — 두 규칙 모두 「잔여 0건」을 근거로 error 로 승격된 것이라(#9203 · #9193) 되돌리면 그 결정을 무른다.

파일 규칙 처분
core/test/domain/task_transition_test.dart (2) avoid-shadowing where 람다 파라미터가 바깥 for 변수를 가림 → 개명
editor/test/theme/cocode_semantic_token_sink_test.dart (2) avoid-unremovable-callbacks-in-listeners 익명 콜백 → 이름 있는 함수 + addTearDown 해제

즉 도너를 범위에서 빼는 것은 검증을 줄이는 일이 아니라, 가려져 있던 이 제품의 검증을 처음으로 드러내는 일이었다. 크래시로 죽던 잡은 무신호였지 통과가 아니었다.

완전 가역이라는 D-031 의 성질을 검증 단계까지 연장했다. 여덟 완화의 스위치는 셋뿐이다 — melos: ignore: 의 cocode_server·cocode_console, analysis_options.yaml 의 feature/**. 그중 하나라도 빠지면 해당 검사는 그 즉시 원래 차단으로 복귀하고, 각 파일의 회귀 테스트가 그 복귀를 고정한다. melos: ignore: 판독은 깊이를 고정한다 — melos: scripts: 의 개별 ignore:(format:select 등)를 최상위로 오인하면 스크립트 하나가 cocode_server 를 적어 두는 것만으로 완화가 영구화되어, 도너가 정리된 뒤에도 검사가 돌아오지 않는다.

마지막 차단은 D-031 이 아니었다 (#281)

위 아홉 곳을 닫자 🗄️ 백엔드 통합 테스트 하나가 남았는데, 원인은 코드가 아니라 존재하지 않는 러너 라벨이었다. runs-on: [self-hosted, docker, cocode, ARM64] 인데 등록 러너 16대(전부 online) 중 cocode 라벨 보유가 0대다. 매칭이 0이면 잡은 실패하지 않고 영원히 queued 로 남아, 그 잡을 needs 로 거는 📊 CI 요약 까지 멈춘다. 부트스트랩이 상시 실패해 이 잡이 늘 skip 되던 탓에 그때까지 드러나지 않았다. 이 잡의 cocode 만 제거했다(매칭 0 → 16대). 잡 수준 skip 은 택하지 않았다 — 그러려면 📊 CI 요약 의 「전 잡 success」 판정을 완화해야 하는데 그 자리는 #11121·#10060 두 사고의 흉터이고, 인프라 버그를 우회하려고 검증 판정을 무르지 않는다.

⚠️ 남은 것 — 이 반영이 덮지 않는다. 아래는 전부 development 와 바이트 동일한 도너 부채이고 Project #2 가 만든 것이 아니다. 백엔드·도너가 범위로 돌아오는 날 함께 처리 대상이 된다.

항목 현재 처분
인증 게이트 미선언 3건 — AppRouterEndpoint·AppleIdpEndpoint·HomeFeedEndpoint 차단하지 않고 매 실행 전량 출력. 등재는 보안 선언이라 오너 판단 영역
공유 BDD step 위반 244건 (feature/console·feature/application) 보류
console_member_list_page.dart.dart (이중 확장자 잔재, 참조 0건) 남김 — 지워도 위젯북 커버리지 누락은 그대로라 원인이 아니고, 도너 파일 삭제는 D-026 에 걸린다

실측(2026-09-14): analyze 잡 29스텝 · format 잡 67스텝 전수 스윕 — 반영 후 둘 다 실패 0 (development 는 각각 11 · 19). 가드 자체 테스트 5파일 전부 통과, 26건 추가. PR #273 전체 파이프라인: 반영 전 하위 5잡 전부 skip → 반영 후 8잡 실행.

D-032: 실행 중 작업의 중지 입력과 승인 거부 경로를 신설 — status 폐쇄 집합 7종 → 9종

  • Date: 2026-09-10
  • Context: 이슈 #105. flow-permutation C-5 가 무한 체류 4지점(FP-105·106·107·405)을 지적했고, UX-D-06("작업 중단/취소 버튼을 두지 않는다")과 정면 충돌했다. UX-D-06 이 안전 근거로 든 AC-08 은 Given 이 "자가 검증 시도가 진행 중일 때"로 한정돼(seed-spec:110) 편집 단계와 잠금 대기를 덮지 못한다.
  • Options: ①중지 입력 + 거부 status 신설 ②UX-D-06 유지(미덮힘 구간을 위험 인수로 기록) ③거부 경로만 신설
  • Decision: 옵션 ① — 중지 입력과 거부 경로를 둘 다 신설. 프로젝트 오너 확정(2026-09-10).
  • Rationale: ②는 편집 단계·잠금 대기에서 자동 종료가 성립하는 이유를 AC/DD 번호로 논증해야 하는데 AC-08 의 Given 이 그 구간을 덮지 않아 논증이 서지 않는다. ③은 승인 흐름만 닫고 C-5 의 무한 체류 4지점을 그대로 남긴다. 막힌 4건(#68 #69 #70 #71 · 18pt)이 모두 ①에서만 풀린다.
  • Impact: seed-spec v3.3.0 — status 값 집합 9종(거부됨 · 중지됨 추가), 종결 상태 정의 확장, AC-22(실행 중 중지) · AC-23(승인 거부) 신설. 후속: docs/state-machine-cocode.md 전이표에 두 종결 전이 추가, AgentSession.cancel 호출부, 승인 큐·카드 UI(#85). UX-D-06 은 이 결정으로 폐기된다 — docs/ux-spec-cocode.md 정정 필요.

D-033: 자가 검증 시도 시간 상한의 출하 기본값을 20분 잠정값으로 확정 — D-020 보류 해제

  • Date: 2026-09-10
  • Context: 이슈 #106. D-020 이 이 값을 "스파이크 실측 후"로 보류했으나, v3.0.0 이 신설한 AC-08 은 설치 직후 양의 유한한 기본값이 존재할 것을 요구한다. 값이 없으면 #50 #66 #67 #73 네 Story 가 구현을 마쳐도 완료 판정 자체가 열리지 않는다(재작업이 아니라 판정 불가).
  • Options: ①지금 잠정 기본값을 정하고 "실측 전 잠정값"으로 표기 ②실측까지 값을 비우고 대기를 위험으로 인수
  • Decision: 옵션 ① — 20분. 프로젝트 오너 확정(2026-09-10).
  • Rationale: 값의 근거를 지어내지 않고 실측에서 도출했다 — job-timeout-budget B-1(실측 최대 × 2) 을 이 저장소 CI 잡 실측 41건에 적용: 🧪 변경 패키지 분석·테스트(= C-04 ②테스트 + ④정적분석) max 8.9m × 2 = 17.8m → 20분 절상(B-3: 상한은 과대추정이 안전측). D-013·D-015·D-020 이 경계한 패턴과 구분되는 지점은 ⓐ출처 있는 표본에서 규칙으로 도출됐고 ⓑ불변 층위(§4)가 아니라 조정 가능한 Planning 층위에 산다는 것이다. AC-08 문면은 값을 담지 않는다.
  • Impact: docs/planning-inputs-cocode.md §1·§6·§7.1 신설. C-04 재시도 3회와 곱해 작업당 자가 검증 상계 60분. AC-20 유휴 상한(#50)이 이 값을 시드로 삼는다. 스파이크(#96)가 실제 분포를 남기면 B-1 로 재산정하고 상한 3종 관계 문장을 함께 갱신한다.

D-034: agent 자체 런타임의 모델 비용은 BYOK

  • Date: 2026-09-10
  • Context: 이슈 #107. #45(LLM 프로바이더 2개 이상 연결, 13pt)가 #52 를 선행으로 걸고 있고, 이 답이 CredentialVault 의 "키 소유자와 주입 지점"을 정한다.
  • Options: ①BYOK ②제품 조달·재판매 ③출시 후로 보류
  • Decision: 옵션 ① — BYOK. 프로젝트 오너 확정(2026-09-10).
  • Rationale: 사용자 키를 OS 자격증명 저장소에 보관하는 현행 DD-23 설계가 그대로 유효해 재배선이 없고 #45 가 즉시 착수 가능하다. ②는 주입 지점·쿼터·요금 계량이 새로 필요하고 Q-03(가격대) 결정이 연쇄로 열린다. ③은 PRD Q-02 행에 보류 표기가 없어 보류 사실 자체를 새로 기록해야 하는데, 그 비용을 치르고도 #45 는 결국 BYOK 를 잠정 전제로 진행하게 된다.
  • Impact: 과금·정산 설계가 MVP 범위에서 빠진다. #52 는 BYOK 로 닫히고 #45 착수 가능. #46(자격증명 저장소)은 원래 이 결정에 막히지 않았다(#52 의 AC 가 명시).

📌 파급 반영 완료 (#52, 2026-09-13) — D-034 가 남긴 반영을 닫았다.

항목 처분
키 소유자·주입 지점 CredentialVault(package/core/.../contracts.dart) doc 에 반영 — 소유자 = 사용자, 주입 지점 = agent 인프로세스 한 곳. 제품 키를 보관하는 자리가 없다. 자식 프로세스로 가는 통로도 없다(DD-23a 가 자격증명성 변수를 자동 거부)
#107 결정 대기 문구 제거 — 결정은 2026-09-10 에 났고 그 뒤로도 계약에 「대기」로 남아 있었다
AC-06 의 선행 조건이었는가 그렇다 — 그리고 이미 해소됐다. #45 가 #52 를 선행 작업으로 걸었고(자격증명 획득 경로 전체의 배선이 여기 달렸다), PRD Q-02 행에는 보류 표기가 없어 출시 후로 미루려면 보류 사실 자체를 새로 기록해야 했다. 오너가 2026-09-10 에 확정해 그 선행 조건이 사라졌다 — 출시 후로 미루는 경로는 선택되지 않았다
#46 은 막히지 않았다 재확인. DD-23 이 확정한 저장 위치·금지 규칙만으로 자격증명 저장소 구현이 성립한다 — BYOK 든 제품 조달이든 「OS 자격증명 저장소에 넣고 워크스페이스·평문·환경변수로는 내보내지 않는다」는 같다
PRD §1.6 Q-02 행 해소 표기 + 결정 이슈 링크. §1.4 의 「모델 마진 없이 도구값만」 논리는 acp 경로에만 적용된다고 범위를 적었다
가격값 인용 금지 $5~9/월·$0·$20/월 은 설계 노트의 시장 분석값이고 이 제품의 가격이 아니다. Q-02 가 BYOK 로 닫혔다는 사실이 그 수치를 가격으로 만들지 않는다 — Q-03(가격대) 행에 그 문장을 명시적으로 추가했다. 실측: 이 수치들은 docs/discovery-cocode.md(원 출처)와 docs/prd-cocode.md(인용 주의 문단)에만 있고, 가격으로 단언하는 문장은 0건이다

D-035: C-02 의 무조건 롤백 보장 범위를 보존 정책 유지분으로 한정 — 값 3종은 #29 의 몫

  • Date: 2026-09-10
  • Context: 이슈 #108. EditSnapshot 보존 정책 값 3종(K·총량 상한·보존 기간)과 C-02 의 "임의 경계 시점 무조건 롤백" 보장이 충돌해, 값을 정하면 C-02 를 깨고 안 정하면 삭제 트리거(P7)가 발동하지 않아 보존 정책이 사실상 비활성으로 출하된다.
  • Options: ①Seed Spec 개정으로 C-02 에 보존 한계 명시 ②값을 정하지 않고 용량 증가 위험 인수 ③값만 정하고 충돌은 기록만
  • Decision: 옵션 ① — C-02 개정. 프로젝트 오너 확정(2026-09-10).
  • Rationale: ②는 #29 의 "출하 기본값" AC 를 충족할 수 없고 히스토리가 무한 증가한다. ③은 불변식과 설계가 어긋난 채 남아 롤백 실패 때마다 판정 근거가 갈린다. ①만이 모순을 실제로 없앤다.
  • Impact: seed-spec v3.3.0 — C-02 에 "보장 범위는 보존 정책이 유지하고 있는 EditSnapshot 에 한한다 + 삭제분 고지 의무" 추가. 이 개정은 값을 정하지 않는다 — 값 3종은 모순이 사라진 뒤 #29 가 실측 근거로 정하는 산출물이다(근거 없는 숫자를 불변 층위에 잠그지 않는다는 D-020 원칙 유지). #32(verification_log 의 P4 삭제 대상 포함 여부)가 이제 판정 가능해진다.

D-036: 롤백 충돌 판정에 작업 범위 한정을 스펙 문면으로 명시

  • Date: 2026-09-10
  • Context: 이슈 #110. #24 는 이미 "전역 마지막 EditSnapshot 이 롤백 대상 작업의 것인지도 함께 본다"를 AC 로 적어 두었으나(근거: flow-permutation §6.1 C-4 / FP-304) Seed Spec 문면에는 그 한정이 없었다 — 구현이 스펙보다 앞서 있는 상태였다.
  • Options: ①문면에 "같은 작업에서" 범위 명시 ②문면 유지 + #24 의 사전검사 제거 ③"전역 마지막이 대상 작업 것이 아니면 항상 충돌" 을 스펙에 명시
  • Decision: 옵션 ① — 문면에 범위 명시(실질적으로 ③의 안전성을 ①의 형태로 담았다). 프로젝트 오너 확정(2026-09-10).
  • Rationale: ②는 두 작업이 같은 파일을 순차 편집한 뒤 앞 작업을 롤백할 때 뒤 작업 편집이 충돌로 잡히지 않고 사라지는 동작을 인수하게 되고, #24 의 AC 한 줄과 그 테스트가 통째로 무효가 된다. 이미 나간 구현과 스펙을 일치시키는 방향이 재작업이 가장 적다.
  • Impact: seed-spec v3.3.0 — EditSnapshot 불변식과 AC-02 를 "롤백 대상 작업이 남긴 마지막 result_content_hash" 로 한정하고, "전역 마지막 스냅샷이 롤백 대상 작업의 것이 아니면 충돌로 제시" 를 규범으로 추가. #24 의 사전검사가 스펙 근거를 얻었고 #89(최대 sequence_no 롤백 표시)의 경계 정의가 판정 가능해진다.

D-037: 기존 워크스페이스에 co-brick 덧붙이기는 MVP 범위 밖

  • Date: 2026-09-10
  • Context: 이슈 #113. §4 인수 경계에 대응 AC 0건인 속성 정의("co-brick")가 남아 Specification Gate 재통과(현재 0.3125 / 기준 0.2)에 계속 걸렸다. 이 답이 푸는 것은 자기 자신(#42, 3pt)뿐이다.
  • Options: ①MVP 에 포함 ②MVP 범위 밖 ③계속 미결
  • Decision: 옵션 ② — MVP 범위 밖. 프로젝트 오너 확정(2026-09-10).
  • Rationale: ①은 seed-spec §4 AC 신설 → bdd Feature 7 시나리오 + §2 대응표 → ux-spec 화면 인벤토리 신설로 연쇄 개정이 발생하고 모호성 재평가 회차가 한 번 더 든다. 다른 Story 가 co-brick 적용을 전제하지 않으므로(#40 은 최초 스캐폴딩 brick 만, ux-spec 진입점 화면 0건) 제외의 부작용이 없다. ③은 게이트를 계속 막는다.
  • Impact: seed-spec v3.3.0 — AC-24(Must-Not) 신설, 용어표와 Workspace.applied_bricks 를 "최초 스캐폴딩 brick" 의미로 정정(MVP 에서 원소 1개). #42 가 닫힌다. 후속: docs/bdd-cocode.md·docs/ux-spec-cocode.md 의 co-brick 문구 잔여분 정정.
  • Source: 프로젝트 오너 확답 — 결정 이슈 #113 (2026-09-10)

📌 파급 반영 완료 (#42, 2026-09-12) — D-037 이 남긴 「후속」을 닫았다.

항목 처분
결정 번호 이슈 #42 본문은 D-024 를 예약했으나 그 번호는 「브랜치 토폴로지」가 선점했다(일지가 그 사이 D-044 까지 자랐다). 이 결정의 정본 번호는 D-037 이며, #42 의 인수 조건에서 D-024 로 적힌 지점은 전부 D-037 로 읽는다. 새 항목을 중복 발급하지 않는다 — 같은 결정에 번호가 둘이면 어느 쪽이 정본인지 알 수 없다
prd:301 범위 미확정 1 경고 블록 제거 → 확정 서술
prd:550 속성표 「+ 이후 적용된 co-brick 목록」 삭제 → 최초 스캐폴딩 brick 만
prd:665 미결 표 행 제거
architecture:459 §4.6 말미 「미결이다」 → 확정 서술(원자성은 최초 스캐폴딩 한 경로에만 적용)
architecture:633 §9 미결 12 행 제거
bdd §3 커버리지의 한계 항목 8 신설 — Must-Not 이라 부재 자체가 요건이므로 시나리오를 쓰지 않는다(AC-04·AC-21 과 같은 처분)
breakdown 150·355·392·491 파생 언급 4곳 해소 표기
ux-spec 화면 인벤토리 해당 없음 — 제외 결정이므로 co-brick 적용 진입점 화면을 신설하지 않는다. §10 은 이 결정으로 바뀌지 않는다(#42 인수 조건 6의 제외 경로)
모호성 재평가 이 Story 는 §4 에 AC 를 신설하지 않는다 — AC-24 는 v3.3.0 이 이미 세웠고 #42 는 파생 문서 정합만 맞춘다. 따라서 재평가를 촉발할 §4 개정이 없으며, Specification Gate 재측정은 별도 게이트 회차의 몫이다(#42 인수 조건 7의 「판정 주체가 근거 문서에 이름으로 지정돼 있지 않음」은 그대로 남는다 — 이 Story 가 그 주체를 지명하지 않는다)

D-038: EditSnapshot 의 생성·삭제·이름변경 표현 — 부재값 + previous_path 채택 (Seed Spec v3.4.0)

  • Date: 2026-09-10 (기록만 — 결정 아님)
  • Context: 이슈 #28 (spike). base_content_hash/result_content_hash 가 "해당 파일 내용의 해시"로만 정의돼 생성(base 미정의)·삭제(result 미정의)·이름변경(경로 2개)을 표현할 수 없었다(flow-permutation §6.2 I-4 / FP-306·307). Design 결정은 docs/architecture-cocode.md §4.7 DD-25 로 섰다 — 두 해시에 부재값(∅) 도입, previous_path 신설(이름변경 = 스냅샷 1건 · 경로 2개 투영), 불변식 2·4 를 경로별 투영 전이에 적용, 게이트 거부 규칙, C-05 를 투영 전이 단위로 판정. Seed Spec §3 문면 반영은 오너 판정 사항이라 개정 요청으로 올린다(§4.7 «개정 요청» ①~④).
  • Options:
    1. 개정 요청대로 §3 에 부재값·previous_path·불변식 6·7 을 신설하고 AC-11 문면을 명확화 (v3.4.0)
    2. 두 해시의 값 집합을 바꾸지 않고 이름변경을 삭제+생성 2건으로 표현하되 둘을 떼어낼 수 없게 묶는 속성을 신설 — 그 사이 경계로 롤백하면 파일이 두 경로 모두에 없는 상태가 생기므로 묶음 규칙이 추가로 필요하다
    3. 생성·삭제·이름변경을 EditSnapshot 대상에서 제외 — AC-11 이 생성을 인정하고 C-02 가 "EditSnapshot 없는 편집 경로는 없다"라 불가
  • Decision: 옵션 1 채택 — 프로젝트 오너 확정(2026-09-10). 개정 요청 ①~④ 를 그대로 docs/seed-spec-cocode.md v3.4.0 에 반영했다(§3 부재값·previous_path·불변식 6·7 · §4 AC-11 문면 · §2 C-05 Rationale · §6 Evolution Log).
  • Rationale: DD-25 가 이미 옵션 1 을 전제로 서 있어 재작업이 없고, 이름변경이 스냅샷 1건이라 중간 경계 롤백에서 파일이 두 경로 모두에 없는 상태가 생기지 않는다. 옵션 2 는 그 위험을 막기 위해 묶음 규칙을 새로 설계해야 하고 DD-25 의 R3·R4·R6·R7 이 전제를 잃는다. 옵션 3 은 AC-11(생성 인정)·C-02(EditSnapshot 없는 편집 경로 없음)와 모순이라 애초에 불가다.
  • Impact: ✅ 판정 완료 — 엔티티 개정이 해제됐다. core 의 EditSnapshot 이 부재값·previous_path·투영 전이를 표현하도록 개정하는 것이 #23 착수의 선행이며 이 PR 이 함께 수행한다. (판정 전 기록) 판정 전까지 core 의 EditSnapshot 엔티티(두 해시의 비어있음 금지 검사)는 바꾸지 않는다. #22(게이트 연산 어휘)·#23(레코드 prev 필드·∅ 인코딩)·#24(투영 전이 함수)·#25(∅ 목표 = unlink)는 DD-25 위에 구현하되, 판정 없이 #23 이 착수하면 D-036 이전의 #24 와 같은 "구현이 스펙보다 앞선" 상태가 다시 생긴다 — #23 착수 전 판정 필요. AC-11 문면 명확화(개정 요청 ③)가 기각되면 R6(테스트 파일 삭제·이름변경의 승인 대상 여부)이 근거를 잃는다.
  • Source: #28 (architecture §4.7 DD-25 «개정 요청»)

D-039: 보존 정책 값의 산정 규칙·측정 규약을 확정 — 값은 규칙·표본·재산정 조건 3요소로만 정하고(#29 의 몫), 보존 기간은 MVP 값 집합에서 제외

  • Date: 2026-09-10
  • Context: 이슈 #30(spike). D-035 가 C-02 를 개정해 모순 자체는 사라졌으나, 값 3종(K·총량 상한·보존 기간)을 "무엇을 재서 어떤 규칙으로 도출하는가"가 어디에도 없었다 — docs/architecture-cocode.md §4.4 는 측정 항목 4종만 열거하고 §9 미결 1 은 "Planning, A-06 과 같은 회차"로 시점만 적었다. 실물 표본(cocode ADE SnapshotStore 가 남긴 스냅샷)은 #22·#23 이 미착수라 존재하지 않는다. 또한 §4.4 P1~P7 어디에도 보존 기간을 소비하는 규칙이 없다(grep -n "기간" docs/architecture-cocode.md 히트 2건 전부 "값 3개" 문장뿐 — P3 은 K, P4·P4′는 총량 상한만 읽는다). 값이 있어도 아무 규칙도 읽지 않는 값은 값이 아니다.
  • Options:
    1. 산정 규칙(V-1~V-4)과 측정 규약을 확정하고, 참조 워크스페이스 git 이력을 프록시 표본(R-1 필드 동반)으로 삼아 규칙을 적용한 후보까지만 내되 값 확정은 #29 에 남긴다
    2. 프록시 없이 실물 표본이 생길 때까지 값 칸을 비워 둔다
    3. 지금 값을 확정한다
  • Decision: 옵션 1 — 규칙·규약·후보까지. 값은 여기서 확정하지 않는다. 보존 기간은 소비 규칙이 없으므로 MVP 값 집합에서 제외(권고안 A). 번복 조건: 오너 또는 Design 이 시간 기반 정리 요구(비공개 코드·개인정보의 장기 잔존 등)를 세우면 규칙 P8 신설이 값에 선행하며, 그때 쓸 규칙 문안과 측정 계획은 docs/retention-values-cocode.md §3 V-3 에 미리 적어 두었다.
  • Rationale: ③은 D-013·D-015·D-020 이 세 번 반복을 경계한 패턴 그대로다(근거 없는 값을 잠그기). ②는 #29 의 "설치 직후 양의 유한한 출하 기본값" AC 가 실물 표본 전에는 영원히 열리지 않는다 — D-033 이 시간 상한에 대해 같은 이유로 잠정값을 택했다. ①은 D-033 선례를 그대로 따른다: ⓐ 출처 있는 표본에서 규칙(job-timeout-budget B-1·B-3 준용)으로 도출하고 ⓑ 값이 사는 자리는 불변 층위(§2·§4)가 아니라 조정 가능한 Planning 층위(planning-inputs §7)이며 ⓒ 실물 표본이 생기면 같은 규칙으로 재산정한다. 프록시(참조 워크스페이스 coco-de/unibook @ 9db9015, first-parent 커밋 = 1 작업)는 우리 저장 설계(content-addressed gzip blob + dedup, DD-10)와 구조가 같아 바이트 단위가 맞고, 그 커밋의 다수가 실제로 에이전트(cc-dev)가 만든 PR 이라 성격도 가깝다. 다만 커밋은 같은 파일의 중간 쓰기를 접고 재시도 누적(D-030)을 담지 않으므로 하한이며, 그래서 후보에는 B-1 의 ×2 를 그대로 건다. 보존 기간을 빼는 이유: 총량 상한이 볼륨을 이미 유계로 만들고, 시간만으로 지우는 규칙은 디스크 이득 없이 롤백 지점만 줄이므로 C-02 쪽에서 안전측이 아니다(B-3 — 보존은 과대 쪽이 안전측). "사용자가 얼마나 오래된 작업을 되돌리는가"의 표본은 실물 사용 외에는 없고, git 이력의 revert 는 성격이 달라 대체가 안 된다(같은 문서 §5.8 — 표본 5건, 판정 불가로 기록).
  • Impact: docs/retention-values-cocode.md 신설(규칙 V-1~V-4 · 측정 규약 M-1~M-6 · R-1 프록시 실측 · 후보 · #29 인계). docs/planning-inputs-cocode.md §6 행 갱신(값은 #29 가 §7.2 로 옮겨 적을 때 확정). docs/architecture-cocode.md §4.4 포인터·§9 미결 1 갱신. #29 착수 조건 ⓐ 충족(#30 은 "값을 정하는 분기"를 택했다 — 정하는 방법을 확정했고 값의 기록은 #29). #29 의 "값 3종" AC 는 "규칙이 소비하는 값 전부(K·총량 상한) + 예약 키" 로 읽는다. #32(P4 삭제 대상 판정)와는 독립. 계수 규칙(무엇을 세는가 — flow-permutation §8 R-3 의 요구)은 history/objects/** 압축 바이트 합으로 제안하고 채택은 #29(Design).
  • Source: #30 spike 세션 · 참조 워크스페이스 실측(scripts/spike/measure_retention_proxy.py, R-1 필드 동반) · D-033 선례 · job-timeout-budget B-1~B-4

D-040: Windows 롤백 치환은 Dart File.rename 그대로 — FFI 미채택, ReplaceFileW 채택 불가

  • Date: 2026-09-10
  • Context: 이슈 #27(spike, Epic #21). architecture §4.3 5단계가 Windows 치환 API 를 "ReplaceFileW/MoveFileExW 계열을 쓰되 Dart File.rename 이 그 의미를 보장하는지·핸들 점유 시 동작은 확인 필요"(§9 미결 3)로 남겨 두어, #25(저널 2단계 커밋)가 치환 원시 연산을 정할 수 없었다. 개발 머신이 전부 macOS 라 로컬 실측이 불가능했다.
  • Options: ①Dart File.rename 그대로 ②MoveFileExW 를 FFI 로 직접 호출 ③ReplaceFileW FFI ④SetFileInformationByHandle(FileRenameInfoEx, REPLACE_IF_EXISTS|POSIX_SEMANTICS) FFI
  • Decision: 옵션 ① — File.rename 그대로, ④는 조건부 옵션으로만 기록(docs/windows-atomic-replace-cocode.md W-6), ②③은 불채택.
  • Rationale: 추측이 아니라 windows-2022 러너 실측 2회(run 34460374634 · 34461196523, Windows Server 2022 build 20348 · NTFS · Dart 3.13.0)다. ⓐ SDK 소스(3.13.0 runtime/bin/file_win.cc 799·813행)가 File.rename = MoveFileExW(REPLACE_EXISTING|WRITE_THROUGH) 임을 보이고, 실측 76셀에서 FFI MoveFileExW 와 결과·코드가 전부 같았다 → ②는 이득 0. ⓑ rename 계열은 두 실행 13,056회 동시 읽기에서 빈/부분/혼합 내용 0건(이름 공간 원자성), ReplaceFileW 는 13,952회 중 1,328회(9.5%) 대상 이름이 없는 창을 노출하고 지연이 4~5배이며 대상 부재 시 실패 → ③은 AC-02 의 "원자적"을 깨뜨린다. ⓒ ④가 File.rename 을 이기는 셀은 홀더가 FILE_SHARE_DELETE 를 준 경우뿐이고, Dart 자신의 File.open 핸들·CRT _wopen 계열·PowerShell 기본 FileShare.Read 앞에서는 똑같이 실패하며 OS 하한(Windows 10 1607 / Server 2016)이 추가된다 → 이득이 우리 코드가 아니라 홀더의 공유 모드에 달려 있으므로 MVP 에 넣지 않는다. D-013·D-015·D-020 이 경계한 패턴과 구분되는 지점: 값(재시도 횟수·간격)은 정하지 않았고 실측 근거가 생길 자리(#25 파라미터 + 텔레메트리)만 지정했다.
  • Impact: architecture §4.3 5단계 문구 교체 + §9 미결 3 해소. #25 는 계약 W-1~W-7(같은 볼륨 임시 파일 · 치환 직전 자기 Dart 핸들 0개 · 5/32 는 일시 점유로 재시도 후 충돌 제시, 단 5 는 읽기 전용·디렉터리를 먼저 걸러낸다 · 읽기 경로 32 재시도 · ReplaceFileW 금지 · 추가 fsync 없음)을 구현한다. 실측 하네스(.github/workflows/probe.windows-atomic-replace.yaml)는 재현용으로 남긴다. 미확정 항목 10건은 그 문서 §5.

D-041: 검증 원문 비영속(DD-23b)의 보안 판단을 유지·번복할 판정 주체 — 결정 대기 (판정 주체의 확정은 프로젝트 오너)

  • Date: 2026-09-10 (기록만 — 결정 아님)
  • Context: 이슈 #32 (spike). DD-23b(architecture §7.1)는 verification_log 에 원문을 담지 않고 사람이 볼 원문은 "UI 세션 버퍼"에만 둔다고 정했으나, 그 버퍼의 정의와 종결된 작업의 원문 사후 조회 가능 여부가 비어 있었다. Design 결정은 architecture §7.1 DD-23c 로 섰다 — ① 세션 버퍼 = toolchain 프로세스 메모리(워크스페이스 단위, 닫힘·프로세스 종료에서만 소멸, 상한 초과 시 "종결 작업 → 오래된 회차 → 스트림 앞부분" 순 정리) ② 종결 작업의 원문 사후 조회 불가 확정(영속 경로 신설 없음 — DD-23b 유출 표면 논거 + ux-spec P1 은 재현으로 충족) ③ verification_log 회차별 누적(attempt_no, DD-21a 는 현재 회차만 본다) ④ P4 삭제 단위에 작업 레코드·verification_log 불포함. 그런데 ② 가 기대는 "원문을 어떤 형태로도 영속하지 않는다"는 보안 판단을 Design 단독으로 유지·번복할 수 있는지, 오너 확답 사항인지가 어디에도 정해져 있지 않다(#32 AC 8). 권한 배분 자체는 오너만 할 수 있으므로 이 항목을 오너에게 올린다.
  • Options:
    1. Design 단독 권한 — DD-23b·23c 를 Design 결정으로 확정하고, 번복도 Design 이 architecture 개정으로 한다
    2. 오너 확답 사항 — 원문 영속 여부는 보안 정책이므로 오너가 일지 항목으로 확정하고, Design 은 그 안에서 표현만 정한다
    3. 판정 주체를 정하지 않고 둔다 — DD-23c ② 가 "미정 시 동작"으로 계속 적용된다
  • Decision: 미정 — 결정 필요(판정 주체: 프로젝트 오너 — "누가 정하는가"를 정하는 것은 오너의 몫이다). 판정 전까지 DD-23b·DD-23c ② 가 그대로 적용된다(차단 방향 — 영속하지 않는 쪽이 유출 표면이 작다).
  • Impact: 판정 전 #63(스키마·실행기)·#86(패널)은 DD-23c 위에 구현한다 — 원문 비영속은 구현 부담이 아니라 부담의 부재이므로 판정이 늦어도 재작업이 생기지 않는다. 옵션 2 로 가서 오너가 "영속"을 택하면 DD-23c ②·④ 와 ux-spec §3.4·§4.4·§5.2 고지 문구, bdd Feature 4 의 비영속 시나리오가 재검토 대상이고 ①·③ 은 영향받지 않는다. 세션 버퍼 총량 상한의 값은 별건(architecture §9 미결 20 — Design, 실측).
  • Source: #32 spike 세션 · architecture §7.1 DD-23c · §9 미결 20·21 · §10-18·19
  • ⚠️ 번호: 같은 날 epic/21 에 #30(D-039)·#27(D-040)이 병렬로 착지해, 이 항목은 그 둘을 확인한 뒤 D-041 로 적었다(architecture §10-11 — "일지의 마지막 번호를 매번 재확인"의 실사례 — #30 의 PR 은 처음 D-038 로 올라와 머지 시 재번호됐다).

D-042: 특정 스냅샷 롤백에서 선택한 스냅샷 자신의 포함 여부 — 결정 대기 (판정 주체: 미정 · owner #89)

  • Date: 2026-09-10 (기록만 — 결정 아님)
  • Context: 이슈 #24(롤백 대상 산출 순수 함수) 구현 중 발견. 「특정 스냅샷으로 롤백」의 대상 집합에 선택한 스냅샷 자신이 들어가는지를 두고 문서가 정면으로 갈린다:
    • 제외 — docs/seed-spec-cocode.md 불변식 3 「그 이후 생성된」 · docs/bdd-cocode.md:151(seq 1 선택 → 2·3 되돌림)
    • 포함 — docs/flow-permutation-cocode.md:129 · docs/ux-spec-cocode.md §4.2 와이어프레임(seq 1 선택 → 4건)
  • Options:
    1. seed-spec 문면대로 제외 — bdd 와 일치. flow-permutation·ux-spec 와이어프레임을 정정해야 한다
    2. 포함 — 와이어프레임과 일치하고 사용자가 「이 스냅샷으로 되돌린다」를 직관적으로 읽는 방식일 수 있다. seed-spec 불변식 3 문면 개정이 따른다
    3. 질의를 둘로 나눈다(「이 스냅샷 이후」/「이 스냅샷 포함」) — 표현력은 늘지만 UI 선택지가 하나 더 생긴다
  • Decision: 미정 — 결정 필요. #24 의 구현은 seed-spec 문면을 문자 그대로 읽어 sequenceNo 가 선택한 것보다 큰 스냅샷만 대상으로 삼는다(옵션 1 잠정).
  • Impact: 두 해석이 「자기 하나 vs 아무것도 아님」으로 정면 충돌하는 유일한 지점은 최대 sequenceNo 질의다 — 거기서는 옵션 1 이면 대상 0건, 옵션 2 면 1건이라 «되돌릴 게 없다»와 «하나 되돌린다»가 갈린다. 그래서 #24 는 그 질의를 거부한다(RollbackRejected, owner: '#89'). 나머지 질의는 두 해석의 차이가 «자기 포함 여부» 한 건뿐이라 구현이 양쪽 모두에서 동작한다. 확정되면 RollbackTargets 의 비교 한 곳과 그 거부 분기만 바뀐다. ⚠️ #89(경계 입력·빈 상태 동작 정의)가 「특정 스냅샷으로 롤백이 선택한 스냅샷 자신을 되돌리는지」를 같은 계열의 미정으로 이미 달고 있고 「seed-spec:104 문면 개정이 필요하면 오너 판단이 걸린다」고 적는다 — 그래서 owner 를 #89 로 적었다.
  • Source: #24 (package/core/lib/src/domain/rollback_targets.dart — SnapshotRollback 문서 주석)

D-043: 쓰기 커밋 프로토콜의 크래시 창 수렴 방향 — 저널로 확정(ⓑ), 되돌림(ⓐ) 없음 · 미해결은 보고 — 오너 확인 대기 (#31)

  • Date: 2026-09-11
  • Context: #31 AC 7 은 「파일은 바뀌었고 레코드는 없는 상태로 복구가 진입했을 때 ⓐ 파일을 base 로 되돌린다 / ⓑ 현재 내용으로 사후 EditSnapshot 을 합성한다」를 미정 — 결정 필요(판정 주체 Design)로 남겼다. C-02·불변식 1 은 결과 상태만 요구하고 수렴 방향을 규정하지 않는다.
  • Options: ⓐ base 로 되돌림 (에이전트 편집 유실, 레코드 없음) · ⓑ 사후 스냅샷 합성 (내용이 에이전트 것인지 불확실) · ⓒ 커밋 의도 저널로 확정 — 저널이 파일 조작에 선행하므로 「파일이 result 상태」는 곧 게이트의 편집이 착지한 것이고, 레코드는 저널에 이미 확정돼 있다(합성이 아니다)
  • Decision: ⓒ. 파일이 result 면 저널의 레코드를 빠진 인덱스에 append(확정), base 면 폐기, 둘 다 아니면(사람이 그 사이 고침) 자동 수렴 없이 미해결 보고 + 저널 유지. 판정 주체는 Design(architecture §4.3a DD-26)이며 오너 확인을 기다린다 — 확인 전까지도 재작업이 없는 쪽이다(어느 쪽으로도 파일을 건드리지 않는다).
  • Rationale: ⓐ 는 실제로 착지한 에이전트 편집을 조용히 지운다(AC-19 의 「조용히 폐기되지 않는다」와 어긋난다). ⓑ 의 「합성」이 위험한 이유는 내용의 출처가 불확실해서인데, 저널이 선행하면 그 불확실성이 없다. 남는 유일한 불확실은 「base 도 result 도 아님」이고 그것은 판정 불가이므로 보고한다.
  • Impact: architecture §4.3a(DD-26) · workspace WriteJournalRecovery·WorkspaceRecovery(시작 훅 순서: 쓰기 저널 → 롤백 저널 → edit_locks) · 게이트의 recoveryPending 충돌(미해결 경로 봉쇄)·abandonWriteJournal · 크래시 주입 테스트(AC 1)가 전 경계에서 ⓐ/ⓑ 만 관측함을 고정

D-044: 보존 정책 값 확정 — K = 14 · 총량 상한 80 MiB(프록시 파생 잠정값) · 보존 기간 없음 · GC 는 게이트 직렬 구간에서 (#29)

  • Date: 2026-09-12
  • Context: #30(D-039)이 산정 규칙(V-1~V-4)·측정 규약(R-1)·후보 세트(H=7일: K=14 · 80 MiB text)까지 확정하고 「값의 기록은 #29 가 planning-inputs §7 로 옮겨 적는 순간」으로 남겼다. architecture §4.4 는 설치 직후에도 양의 유한한 기본값을 요구한다.
  • Options: ① 기본 세트(H=7일, K=14 · 80 MiB) ② 보수 세트(H=30일, K=… · 300 MiB) ③ 바이너리 포함(all, 400 MiB) — 같은 문서 §6.3 표
  • Decision: ①. planning-inputs §7.2 에 규칙·표본·재산정 조건 3요소와 함께 기록했고, workspace RetentionSettings.shipped 가 그 값을 든다(코드에 상수로 흩어지지 않는다 — 한 곳). 설정 키는 history.retention.pinned_tasks_per_path · history.retention.max_bytes. 보존 기간은 MVP 값 집합에서 제외(D-039 V-3 그대로).
  • Rationale: ①은 §10-④ 가 미정인 동안의 기본 전제(텍스트만 스냅샷)와 맞고 C-1(단일 작업 최대 × 2 ≤ 상한)을 만족한다. ②③은 전제가 바뀔 때 갈아 끼우는 값이지 지금 근거가 있는 값이 아니다.
  • Impact: RetentionGc(P1~P7 + P4′ · E-1 바닥 규칙 · E-2 초과 상태 보고) — GC 는 게이트의 직렬 구간에서 돌고, 남은 쓰기·롤백 저널이 참조하는 blob 은 P5 의 회수 대상에서 제외한다(그 blob 은 재기동 복구·재수행이 읽는다). 정리 마커 tasks/<task_id>/retention.json 이 P6 의 표시 근거(UX-D-12 R3 — [롤백] 비활성 + 사유). 실물 표본이 생기면 같은 규칙으로 재산정(§7.2 재산정 조건).

D-045: 편집 리스의 획득·해제 시점 — 획득 = 편집 시도 시점 · 해제 = 게이트 연산의 반환(D-14 후보 ③) (#76)

  • Date: 2026-09-12
  • Context: prd §4 D-14 가 「파일의 편집을 마치는 시점」 판정 기준을 미규정으로 남기고 후보 셋(에이전트 자기 선언 / 마지막 쓰기 완료 / 도구 호출 반환)만 열거했고, architecture §9 미결 14 가 그것을 그대로 옮기며 임시 규칙(「미정 동안 세션 종료·작업 종결 트리거로만 해제」)을 달았다. 획득 시점(편집 시도 시점 vs 작업 시작 시 일괄 예약)도 같은 공백이다(prd §2 흐름 2 3단계). 기준이 없으면 AC-12 를 기계적으로 판정할 수 없다.
  • Options: ① 에이전트의 자기 선언 · ② 마지막 쓰기 완료 · ③ 도구 호출 반환 / 획득은 ⓐ 편집 시도 시점 · ⓑ 작업 시작 시 일괄 예약
  • Decision: ③ + ⓐ. 리스 구간 = [게이트 연산 호출 직전, 그 연산의 반환 직후]. 후보 ③ 의 「반환」의 좌표를 WorkspaceFileGate 연산의 반환으로 고정한다(요청 종결이 아니다). 실패·거부 경로도 같은 경계에서 해제한다.
  • Rationale: ①은 그런 사건이 프로토콜에 없다 — AgentEvent 폐쇄 집합과 ACP v1 클라이언트 능력 어디에도 「이 파일 다 썼다」가 없어 acp 에서 구현 불가다(AC-18 초판이 rev.2 에서 정정당한 것과 같은 결함 유형 — 한 백엔드에서만 성립하는 판정). ②는 「마지막」을 그 시점에 판정할 수 없고(미래를 알아야 한다), 판정 가능해지는 시점으로 늦추면 트리거 2와 같은 사건이 되거나 사람승인대기(완료승인) 경계에서 별도 해제 규칙이 또 필요해져 불변식 2 후반부와 충돌한다. ③은 판정이 없고(사건 발생 여부), 두 백엔드에서 같은 좌표를 가지며(architecture:278), 단일 쓰기 지점(C-02/DD-15) 위에 있어 에이전트 편집의 경계와 일치한다. ⓑ 는 입력이 없다 — 작업 시작 시점에 편집할 파일 집합의 출처가 어느 문서에도 없고(DelegatedTask 속성 10종에 없다), 택하면 FR-205 가 트리거 2의 중복 서술이 되어 별도 규칙으로 존재할 이유를 잃는다.
  • Impact: architecture §4.5(확정 문장으로 교체) · §9 미결 14(해소) · prd FR-205·§2 흐름 2 3·5단계·§4 D-14(해소) · #75 EditLockRegistry 표면(획득·해제가 짝지어진 구간 API 여야 한다 — 자유 put/remove 는 해제를 빠뜨린 호출자를 만든다) · 승인 판정은 리스 구간 밖에 둔다(불변식 2 후반부가 런타임 검사 없이 성립) · 트리거 3종은 축소되지 않는다(리스 구간 안의 사망은 정상 경로가 덮지 못한다) · #77 로 인계: 다중 경로 연산(rename 은 from·to 둘)의 획득 순서가 교착의 실질 표면이고, 대기 중 작업의 status 는 실행중(전이가 아니라 게이트 연산 안의 대기)이라 state-machine-cocode.md §4 의 전이 함의 문장(ⓑ 전제)과 어긋난다 — #16 의 결정(새 status 값 없음)은 그대로 유효
  • Source: #76 (docs/edit-lock-lifecycle-cocode.md)

D-046: 잠금 대기열 규칙 — 경로별 FIFO · 정렬 획득으로 교착 예방 · 대기 상한 없음 (#77)

  • Date: 2026-09-12
  • Context: flow §3 FP-405(교착)·FP-406(대기자 복수 시 선택 규칙 → 기아)·§6.2 I-1·§6.3 N-5(대기열 순서·예상 대기 표시)·§6.1 C-5(잠금 대기에 상한 없음)·§5 B-08 이 전부 미규정으로 열려 있었다. D-045(#76)가 획득·해제 시점을 닫으면서 「획득이 즉시 성공하지 않을 때 무엇이 일어나는가」가 남았다.
  • Options: 순서 — FIFO / 우선순위 / 미규정(임의) · 교착 — 검출기+희생자 선택 / 정렬 획득 예방 / 타임아웃 · 상한 — 값을 정한다 / 두지 않는다
  • Decision: ① 경로별 FIFO 큐(전역 큐가 아니다 — AC-12 는 경로 단위 배타만 요구한다) · ② 한 연산이 요구하는 경로를 전부-아니면-전무로 획득(교착 검출기는 두지 않는다) · ③ 잠금 대기에 시간 상한을 두지 않는다 · ④ 큐는 영속하지 않는다 · ⑤ 화면: S-01b 는 그대로(대기 칸 신설 없음), S-06 실행중 행의 잠금 칸을 3값(잠금 N·잠금 대기·빈칸)으로. 예상 대기·순번은 그리지 않는다.
  • Rationale: ① 우선순위는 DelegatedTask 속성 10종에 근거가 없어 기각(D-045 가 ⓑ 를 기각한 것과 같은 형태 — 없는 입력을 요구하는 규칙은 규칙이 아니다). FIFO 는 앞 순번이 단조 감소하고 리스 구간이 유한하므로(게이트 연산 1회) 기아가 정책이 아니라 자료구조로 닫힌다. ② 잠그는 단위는 FileEditOperation.touchedPaths 이고 리스를 둘 드는 연산은 rename 뿐이다. ⚠️ 초판은 「정렬해 하나씩 획득」이라고 적었고 그것은 틀렸다 — #75 구현 리뷰가 교착 3종을 재현했다(대기자 간선이 키 순서를 따르지 않는 A · 정렬 순서와 포함 관계가 어긋나는 E1 · 한 연산의 두 경로가 중첩되는 자기교착 C). 추월 예외로 A 를 막으면 이번에는 기아가 재현돼 교착과 기아가 맞교환이 됐다. 뿌리는 「리스를 든 채 기다리는 상태」 하나이며, 전부-아니면-전무가 그것을 없앤다: 나가는 간선을 가진 노드는 보유가 0 이라 순환 대기가 성립할 수 없고, 추월 예외가 없어 엄격 FIFO 가 유지된다. 정렬은 남되 역할이 바뀐다(중첩 키 제거 + 결정성) — 검출이 아니라 예방이고 판정이 아니라 계산이다. 검출기는 희생자 선택 규칙의 근거가 없고(희생된 작업의 status 도 #71 소유의 미정 계열) 예방이 성립하면 도달 불가 코드가 된다. 전제(한 작업이 동시에 두 게이트 연산을 진행하지 않는다)는 게이트 runSerially 의 성질이며 #75 의 테스트가 단언한다. ③ 값의 출처가 없다 — AC-08 의 상한은 Given 이 「자가 검증 시도가 진행 중일 때」로 한정돼 잠금 대기를 덮지 못한다(flow FP-405 와 ux-spec UX-D-06 폐기 주석이 같은 말로 적어 둔 사실). 그리고 필요하지 않다: 교착·기아는 ①②로 성립 불가, 승인 대기는 리스 구간 밖이라 대기열을 잡지 않으며(D-045 §2.1), 보유자 사망은 DD-13 트리거 3종이 덮는다. 즉 「상한 없음」의 근거가 무방비가 아니라 구조적 유한성이다. ④ 트리거 3이 재기동 시 리스를 전부 무효화하므로 재기동 후 대기열은 정의상 빈 집합이다 — 영속하면 그 모순을 지우는 코드를 따로 써야 한다. ⑤ S-01b 대기 칸은 §2.4 표에 대응 출처 속성이 없고 사람이 읽기 전에 사라진다. 예상 대기는 계산할 입력이 없고(UX-D-07 과 같은 뿌리), 순번은 계산 가능하나 행동을 바꾸지 않는다.
  • Impact: flow FP-401·402·403·405·406 해소 · I-1 해소 · N-5 해소 · B-08 해소 · C-5 는 잠금 축만 해소(편집 단계 FP-105·작업 총량 FP-106·응답 불가 FP-107 은 #71) · ux-spec §3.2 실행중 행 3값 + UX-D-23 신설 · §10 S-01b·S-06 · §12 UX-D-20 의 보류 사유 소멸 기록(DD-13 의 EditLease.ownerTaskId 가 귀속 속성이다 — 남은 것은 UX 판정 하나) · #75 구현 대상: 경로별 FIFO 큐(비영속) · 전부-아니면-전무 획득(정렬은 중첩 키 제거용) · 경로 포함 관계 충돌 판정(구현이 드러낸 보강 — 디렉터리 연산은 리스 1개를 들면서 하위 파일 N개를 한 커밋에 담으므로 정확 일치만 보면 동시 편집이 통과한다, edit-lock-queue-cocode.md §3.1) · 게이트 직렬성 전제의 테스트 단언 · 대기/보유 조회 seam · #78 은 「재기동 후 대기열 = 빈 집합」을 검증 대상으로 받는다
  • 미정으로 남긴 것: 잠금 대기 상한의 값(두지 않기로 했으므로 불필요하나, 리스 구간이 넓어지면 재검토 — D-045 §6) · 게이트 연산 자체의 시간 상한(잠금 축이 아니라 파일 IO 축이다. 잠금으로 끌어오면 근거 없는 값을 「잠금 대기 상한」이라는 이름으로 정하게 된다) · U-11 의 status 값 재확정(#16 단독 소유 — D-045 로 전제가 바뀌어 state-machine-cocode.md §4 의 전이 함의 문장이 갱신 대상이다)
  • Source: #77 (docs/edit-lock-queue-cocode.md)

D-047: edit_locks 를 「경로 집합」에서 「경로 → 리스」로 — Seed Spec §3 개정 요청 (오너 판정 대기) (#75)

  • Date: 2026-09-12 (기록만 — 결정 아님)
  • Context: seed-spec:70(§3 Workspace Attributes)이 edit_locks 를 「에이전트가 현재 편집 중인 파일 경로 집합」으로 정의한다. architecture §4.5 DD-13 이 이를 경로 → 리스(소유 task_id · 소유 세션 식별자 · 갱신 시각)로 대체했고, #15 가 Workspace.editLocks 를 Map<String, EditLease> 로 이미 세웠으며 #75 가 그 위에 레지스트리와 해제 트리거 3종을 구현했다. §3 머리말이 「잠금 후 기존 엔티티 수정 불가, 추가만 허용」이므로 속성 정의의 변경은 구현 재량이 아니라 문서 결정이다 — #75 인수조건 5 가 「Seed Spec §3 개정 또는 명시적 어긋남 기록이 선행된다」고 요구한 자리이며, 이 항목과 architecture §10-20 이 그 기록이다.
  • Options:
    1. §3 의 edit_locks 정의를 경로 → 리스 사상으로 개정 (DD-25 → D-038 과 같은 경로)
    2. 「경로 집합」 문면을 유지하고 리스를 별도 속성으로 추가 — §3 머리말의 「추가만 허용」에 형식상 맞지만, 집합과 사상이 같은 사실의 두 표현이라 둘이 어긋날 때 어느 쪽이 정본인지가 규정되지 않는다
    3. 집합 유지 + 소유자 추적 포기 — 불가. 해제 트리거 1(세션 종료)과 2(작업 종결)는 「그 세션/그 task_id 가 소유한 리스」를 찾아야 하는데 집합에는 소유자가 없다. AC-18 이 성립하지 않는다
  • Decision: 미정 — 오너 판정 대기. 설계·구현은 옵션 1 을 전제로 서 있다(DD-13 · #15 · #75).
  • Rationale: 옵션 3 은 AC-18 과 모순이라 애초에 불가하고, 옵션 2 는 정본 규정이 없는 이중 표현을 만든다. 옵션 1 이 이미 architecture §4.5 와 코드에 서 있으므로 재작업이 없다.
  • Impact: ⚠️ 판정 전까지 「스펙보다 앞선」 상태다(§10-20). 다만 막히는 후속 작업은 없다 — D-038 판정 전의 DD-25 는 엔티티 개정이 선행해야 #23 이 착수할 수 있었지만, 여기서는 Workspace.editLocks 가 이미 사상으로 서 있고 그 위에서 AC-12·AC-18 이 성립한다. 판정이 「집합 유지」로 뒤집히면 해제 트리거 1·2 의 재설계가 필요하다(그 경우 AC-18 이 성립할 다른 수단을 먼저 찾아야 한다). 개정이 확정되면 ux-spec §12 의 UX-D-20 미결(「작업↔잠금 귀속 속성」)도 함께 닫힌다 — EditLease.ownerTaskId 가 그 귀속이며 D-046 이 그 사실을 기록했다.
  • Source: #75 (architecture §4.5 «개정 요청» · §10-20)

D-048: 트리거 3 스윕과 S-01b 첫 렌더의 순서 — 스윕이 선행하고, 그 전에는 잠금 칸을 렌더하지 않는다 (#78)

  • Date: 2026-09-12
  • Context: #78 인수조건 1 이 측정 시점을 「해제 트리거 3(앱 재기동) 스윕 완료 이후 첫 프레임」으로 고정하려 했으나, 두 사건의 순서를 규정한 문서가 없다 — ux-spec §2.4(198~205행) 상태바 표는 잠금 칸의 값 출처만 적고 갱신 시점을 규정하지 않는다. 순서가 없으면 ⓐ(표시 수 = 레지스트리 실제 크기)·ⓑ(스윕 전 미렌더)를 기계적으로 판정할 수 없다.
  • Options:
    1. 스윕 → 첫 렌더. 스윕 완료 전에는 잠금 칸을 렌더하지 않는다
    2. 스윕 → 첫 렌더이되, 스윕 전에는 0 을 그린다
    3. 첫 렌더 → 스윕. 스윕이 끝나면 값을 갱신한다
  • Decision: 옵션 1. 판정 술어는 EditLockRegistry.startupSweepCompleted 이며, false 인 동안 S-01b 는 잠금 칸을 그리지 않는다.
  • Rationale: 옵션 3 은 유령 잠금을 화면에 띄운다 — 디스크에 남은 리스가 그대로 「잠긴 파일 N개」로 표시되고, 그것은 AC-18 이 없애려는 상태가 사용자에게 도달한 것이다. 이 한 가지로 탈락한다. 옵션 2 도 답이 아니다 — 0 은 「잠긴 파일이 없다」는 사실 주장인데 스윕 전에는 그 값이 확정되지 않았다. ux-spec §3.2 가 실패 사유에 대해 세운 규칙(「문장을 지어내지 않는다 — 모르면 자리표시자」)과 같은 논리이며, 여기서 자리표시자에 해당하는 것이 칸의 부재다. 옵션 1 은 구현 비용도 없다: 스윕은 이미 시작 훅(WorkspaceRecovery.runOnStartup) 3단계이고, 셸에 필요한 것은 판정 술어 하나뿐이다.
  • Impact: core EditLockRegistry.startupSweepCompleted 신설 · workspace FileEditLockRegistry 가 discardPersistedLeases 완료 시 그 값을 세운다(되돌아가지 않는다) · ux-spec §2.4 잠금 칸 행과 §10 S-01b 에 렌더 조건 명시 · flow FP-408·I-7 해소.
  • ⚠️ 이 결정이 닫지 않는 것: 「첫 프레임」은 렌더 계층의 사건이고 이 리포에는 아직 셸이 없다(core·workspace 둘뿐, CocodeStatusBar grep 0건). 그래서 #78 이 단언하는 것은 데이터 계약이다 — 술어가 true 인 뒤 leases.length 가 실제 크기와 같고, false 인 동안 그릴 값이 없다는 것. 렌더 순서 자체의 실기기 검증은 셸 Epic 소관이며, 셸은 이 술어를 지키기만 하면 된다.
  • 함께 남긴 미결: 「재기동 후 기존 DelegatedTask 가 이어서 실행되는지」는 어느 문서도 규정하지 않는다(#78 인수조건 2 가 확인한 grep 결과 그대로 — 재시작|재기동 히트는 arch:349·:394, flow:205·:277 뿐). #78 은 그 전제를 쓰지 않고 「새로 시작된 두 작업」으로만 배타를 판정했다. 작업 재개 여부는 미정 — 결정 필요(판정 주체: Design)로 남는다.
  • Source: #78

D-049: 스캐폴딩 "진행 중 실패" = 첫 스테이징 쓰기 ~ 커밋 완료 직전의 모든 비정상 종료 — 원인이 아니라 위치로 가른다 (#41)

🔢 번호 재배정(2026-09-13) — 이 항목은 epic/33 에서 D-045 로 발급됐으나, 같은 번호를 project/2 의 Epic #74(edit_locks)가 먼저 가져갔다(PR #206). Epic #33 을 project/2 에 합칠 때 D-049 로 재배정했다 — #41 의 PR·코멘트와 커밋 메시지에 남은 옛 번호 D-045 는 이 항목을 가리킨다.

  • Date: 2026-09-12

  • Context: 이슈 #41(spike). FR-102/AC-13/불변식 3 은 "진행 중 실패하면 부분 생성된 파일이 전량 제거된다"는 결과만 규정하고 무엇이 "진행 중 실패"인지는 규정하지 않는다(PRD 303행 「판정 미확정 2」 · 449행 흐름 1 미결 B · 643행 D-16 · architecture §9 미결 15). PRD 는 후보 4종(brick 훅 비정상 종료 / 파일 쓰기 오류 / 변수 검증 실패 / 사용자 취소)만 열거했다. 트리거가 없으면 DD-13a 3번의 "실패 시 스테이징 전체 삭제"를 구현할 수 없다.

  • Options:

    1. 원인 열거로 정의 — 후보 4종 중 어디까지가 실패인지 택일한다. 문제: 같은 원인이 시점에 따라 다른 결과를 낳는다(변수 검증 실패는 생성기 기동 전이면 지울 것이 없고, 훅이 실행 중 검증에 실패하면 스테이징이 이미 있다). 열거는 그 차이를 표현하지 못하고, 새 원인이 생길 때마다 정의를 고쳐야 한다.
    2. 위치(구간)로 정의 — DD-13a 가 이미 스테이징 → 훅 → 커밋의 3구간을 세웠으므로, 그 구간 경계로 가른다. 후보 4종은 열거가 아니라 구간에 매핑된다.
    3. 계속 미결 — architecture §4.6 항목 4 의 임시 규칙("커밋 단계 이전의 모든 비정상 종료")을 그대로 둔다. 하한이 없어 "지울 스테이징이 없는데 실패로 표시"하는 경로가 남는다.
  • Decision: 옵션 2 — 위치로 가른다. "진행 중 실패"는 반열린 구간 [첫 스테이징 쓰기, 커밋 완료) 안에서 일어난 모든 비정상 종료이며, 원인을 묻지 않는다. 구간 밖의 두 경우는 실패가 아니라 각각 다른 판정이다:

    구간 판정 관측 가능한 결과
    첫 스테이징 쓰기 이전 착수 전 거부 스테이징이 없다. 지울 것도 없고 워크스페이스도 없다 — AC-13 불변식이 자명하게 성립
    [첫 쓰기, 커밋 완료) 진행 중 실패 스테이징 디렉터리 전체 삭제 → 최종 위치에 아무것도 없음(DD-13a 3)
    커밋 완료 이후 스캐폴딩 성공 이후의 실패는 스캐폴딩이 아니라 워크스페이스 연산이며 게이트를 경유한다

    PRD 후보 4종은 이 구간에 다음과 같이 매핑된다 — 셋은 위치가 고정이고 둘은 위치에 따라 갈린다:

    후보 위치 판정
    brick 훅 비정상 종료 훅 구간(첫 쓰기 이후) 진행 중 실패
    파일 쓰기 오류 스테이징 쓰기 구간 진행 중 실패
    훅이 스테이징 밖을 쓰려다 런처에 차단됨(§4.6-2) 훅 구간 진행 중 실패
    변수 검증 실패 생성기 기동 전이면 첫 쓰기 이전 · 훅의 실행 중 검증이면 이후 갈린다 — 위치가 정한다
    사용자 취소 취소를 누른 시점 갈린다 — 첫 쓰기 이후면 진행 중 실패

    커밋(단일 디렉터리 이동) 자체의 실패는 구간 안이다 — 이동이 원자적이므로 최종 위치에는 아무것도 없고, 스테이징을 삭제한다.

  • Rationale: 원인 열거(①)는 "같은 원인·다른 시점"을 표현하지 못하고 확장할 때마다 정의를 고쳐야 하는 반면, 위치 판정은 구현이 이미 아는 사실만 쓴다 — 엔진은 자기가 첫 쓰기를 했는지와 커밋을 마쳤는지를 판정 없이 안다(P2: 판정하지 않는다). ③의 임시 규칙은 상한("커밋 이전")만 있고 하한이 없어, 변수 검증이 생성기 기동 전에 실패해도 "실패"로 표시하게 된다 — 사용자가 할 일은 "재시도"가 아니라 "변수 다시 입력"인데 같은 화면을 보게 된다. 이 결정은 그 임시 규칙을 뒤집지 않고 하한을 붙여 확정한 것이다.

  • Impact:

    • docs/architecture-cocode.md §4.6 항목 4 의 임시 규칙이 이 결정으로 대체되고 §9 미결 15 행이 제거된다.
    • docs/prd-cocode.md 「판정 미확정 2 — 실패의 정의」 경고 블록과 흐름 1 미결 B 가 해소되고 §4 D-16 행이 이 결정 참조로 바뀐다.
    • docs/ux-spec-cocode.md UX-D-23 이 제시 형태를 정하고 §11 미결 표의 「스캐폴딩 실패 사유 제시 형태(S-14)」 행이 제거된다(PRD 흐름 1 미결 A 도 함께 닫힌다).
    • #40(brick 스캐폴딩 원자성)의 롤백 트리거가 이 구간 판정을 그대로 구현한다 — BrickEngine 은 첫 스테이징 쓰기 여부를 상태로 들고 있어야 한다.
  • Source: #41 (판정 주체 Design — PRD §4 D-16 행 · architecture §9 미결 15 '누가' 열)


D-050: 샌드박스 정책 미결 — 의존성 호스트 추출·재지정 없는 경로 2종 확정, 허용 호스트 개수는 정본화하지 않는다 (#39)

🔢 번호 재배정(2026-09-13) — 이 항목은 epic/33 에서 D-046 로 발급됐으나, 같은 번호를 project/2 의 Epic #74(edit_locks)가 먼저 가져갔다(PR #206). Epic #33 을 project/2 에 합칠 때 D-050 로 재배정했다 — #39 의 PR·코멘트와 커밋 메시지에 남은 옛 번호 D-046 는 이 항목을 가리킨다.

  • Date: 2026-09-12
  • Context: 이슈 #39(spike). architecture §9 미결 10(의존성 호스트 추출 알고리즘) · 11(허용 호스트 개수 정본) · 16(타겟 플랫폼 판정 입력)과 PRD D-20(재지정 변수 없는 경로 2종)이 미결이라 두 구현이 서로 다른 허용 집합을 만들 수 있었다. 이 결정은 그중 기계적으로 판정 가능한 것을 닫고, 오너 판단이 필요한 것을 그대로 남긴다.
  • Options: 항목마다 다르다 — 아래 각 절에 적는다.

① 의존성 호스트 추출 알고리즘 (§9 미결 10 · PRD D-6) — 확정

  • 판정 주체: Planning (§9 '누가' 열 그대로)
  • Decision: 세 축을 이렇게 정한다.
    • 매니페스트 범위 = lock 우선. pubspec.lock·Podfile.lock 이 있으면 그것을 읽고, 없으면 pubspec.yaml + dependency_overrides 와 Podfile 을 읽는다. lock 은 실제로 해석된 결과를 담으므로 override 와 전이 의존성이 이미 반영돼 있다 — 매니페스트만 보면 그 둘을 놓치고, 놓친 호스트는 fail-closed 로 빌드를 멈춘다(PRD NFR-C).
    • 전이 의존성 = 포함한다. §2.5 가 「닫힌 집합으로 선언할 수 없는 부분」으로 지목한 것이 정확히 전이 git: 의존성(gitlab·bitbucket·사내 호스트)이다. 제외하면 그 호스트가 목록에 영영 들어오지 않는다.
    • 재계산 시점 = 워크스페이스 열기 시 + 매니페스트·lock 파일이 게이트를 지나 바뀐 직후. 매 기동마다 다시 계산하지 않고(비용), 영원히 고정하지도 않는다(낡음). 게이트가 유일한 쓰기 지점이므로(C-02) 그 파일들의 변경 시점을 아는 곳이 이미 있다 — 별도 감시자가 필요 없다.
    • 계산된 집합은 DD-19 의 경로 해석과 같이 세션 값으로 고정한다.
  • Rationale: 세 축 모두 「더 넓게 잡아 fail-open 되는 쪽」과 「좁게 잡아 빌드를 멈추는 쪽」의 선택인데, NFR-C 가 fail-closed 를 요구하므로 놓치지 않는 쪽(lock · 전이 포함)을 택하되 재계산 시점을 게이트에 붙여 낡음을 막는다.

② 재지정 변수가 없는 경로 2종 (PRD D-20) — 확정: DD-19 제안 채택

  • 판정 주체: Design — §9 미결 표에 대응 행이 없어 승계할 '누가' 값이 없었다(이슈 #39 의 실측 그대로). 이 항목은 architecture §5.4 DD-19 가 규칙을 제안한 자리이므로 그 문서의 판정 주체(Design)를 따른다. 이 결정으로 §9 에 새 행을 만들지 않는다 — 닫힌 항목에 미결 행을 추가하는 것이기 때문이다.
  • Decision: DD-19 의 제안을 그대로 채택한다 — $FLUTTER_ROOT/bin/cache 와 Xcode DerivedData 는 실행 시점에 해석해 세션 값으로 고정하고, 해석에 실패하면 그 경로를 예외로 추가하지 않는다(차단 방향).
  • Rationale: 이미 #36 이 그 형태로 구현했고(WriteExceptionResolver — WritePathUnresolved 는 목록에서 빠진다) 테스트가 고정한다. 넓은 기본값으로 대신 채우면 예외가 의도보다 넓어져 AC-14 ⓐ 가 느슨해진다.

③ 미지 리다이렉트 대상 (PRD D-22) — 재결정하지 않음

  • 판정 주체: 해당 없음
  • Decision: DD-17a 가 이미 확정했다(차단 + fail-closed + 사후 사용자 추가). DD-17 의 「체인의 모든 호스트를 같은 규칙으로 판정하고 '중간이라서' 예외를 주지 않는다」도 그대로 승계한다. '체인의 모든 호스트를 허용'은 허용 목록을 무력화하므로 채택하지 않는다.
  • 확인: #35 가 그 규칙을 구현으로 확인했다 — 프록시가 리다이렉트를 따라가지 않아 체인의 각 홉이 새 연결로 오고, 그래서 각각 판정된다(docs/architecture-cocode.md §5.3 구현 주석 · 리다이렉트 체인 테스트 2건).

④ 허용 호스트 개수 정본 (§9 미결 11) · 타겟 플랫폼 판정 입력 (§9 미결 16) — 정본화하지 않는다 / 오너 미결 유지

  • 판정 주체: 개수 = Planning · 타겟 플랫폼 = Design (§9 '누가' 열). 두 항목 모두 오너 결정 이슈 #109 가 열려 있다.

  • Decision (개수): 개수를 정본화하지 않는다. 목록 자체가 SSOT 다. 근거는 선호가 아니라 실측이다 — #38 의 3-OS 프로브가 planning-inputs §2.1 표(12행)에 없는 호스트 api.github.com 을 CocoaPods 경로에서 실제로 관측했다(docs/sandbox-limits-cocode.md §3). 즉 11 도 12 도 측정과 어긋난다; 두 선택지는 선호로 고르는 것이 아니라 증거로 탈락했다. 남는 것은 #109 의 세 번째 선택지 하나다. 따라서 인수 판정은 「비어 있지 않다」 + 대조 검사로만 하고(이미 #36 이 그렇게 구현했다), 코드·테스트·문서 어디에도 개수를 쓰지 않는다.

  • Decision (타겟 플랫폼 판정 입력): 이 결정이 정하지 않는다 — #109 유지. SandboxDefaults.hostsFor(targets) 는 타겟 집합을 인자로 받아 그 판정을 호출부로 밀어냈다(#36). 판정 입력(플랫폼 디렉터리 존재 / 빌드 명령 인자 / 사용자 설정 / 선언)을 고르는 것은 제품 동작을 정하는 일이라 오너 결정이 서야 한다. 미판정 시 기본값인 전 플랫폼 합집합이 AC-14 를 느슨하게 한다는 §9 미결 16 의 경고는 그대로 유효하다.

  • ⚠️ 오너 확인 대기: 위 두 항목은 #109 가 정본이다. 이 일지는 ⓐ 측정이 선택지 둘을 탈락시켰다는 사실과 ⓑ 그동안의 집행 규칙을 적는 것이며, #109 를 대신 닫지 않는다. D-029·D-041·D-042·D-043 과 같은 「결정 대기」 형식이다.

    • 정정(2026-09-13): 대기가 끝났다. 오너가 #109 에서 둘 다 판정했다 — 개수는 D-051(정본화하지 않는다, 목록이 SSOT — 이 ④ 의 제안 그대로 확정), 타겟 플랫폼 판정 입력은 D-052(워크스페이스의 플랫폼 디렉터리 존재 — 이 ④ 가 정하지 않고 남긴 자리). 이 항목의 「미결 유지」는 그 두 번호로 대체됐고, ④ 가 적은 집행 규칙(개수를 쓰지 않는다)은 D-051 이 영구 규칙으로 승격했다.
  • Impact:

    • docs/architecture-cocode.md §9 미결 10 행 제거(①로 확정). 11·16 행은 남는다 — #109 참조를 달아 그 이슈가 정본임을 명시한다.
    • docs/prd-cocode.md §4 D-6(추출 알고리즘) 해소 · D-20(경로 2종) 해소 · D-22 는 재결정하지 않음 확인.
    • docs/planning-inputs-cocode.md §2.5 「Planning 이 확정할 것」이 ①로 채워진다.
    • #36 이 이미 ②④의 구현 형태다 — 이 결정이 그 구현을 정당화하며, 반대로 구현을 바꾸지 않는다.
  • Source: #39 (판정 주체는 architecture §9 '누가' 열 · ④는 오너 #109 대기)


D-051: 허용 호스트 개수를 정본화하지 않는다 — 목록 자체가 단일 SSOT 이고 개수는 파생값이다 (#39 AC1)

🔢 번호 재배정(2026-09-13) — 이 항목은 epic/33 에서 D-047 로 발급됐으나, 같은 번호를 project/2 의 Epic #74(edit_locks)가 먼저 가져갔다(PR #206). Epic #33 을 project/2 에 합칠 때 D-051 로 재배정했다 — #39 의 PR·코멘트와 커밋 메시지에 남은 옛 번호 D-047 는 이 항목을 가리킨다.

  • Date: 2026-09-13
  • Context: 이슈 #39 인수조건 1. 허용 호스트 개수가 문서 3곳에서 갈려 있었다 — D-018 「필수 최소 11종」 · docs/planning-inputs-cocode.md §2.1 표 12행 · D-021 Impact 「허용 호스트 12종」. docs/architecture-cocode.md §9 미결 11 이 이 불일치를 미결로 적었고, 그 미결 때문에 §5.3 기본 목록·bdd·#36 구현이 모두 「개수를 인용하지 않는다」로 우회하고 있었다. D-050 ④ 는 이 항목을 오너 결정 #109 에 남겼다.
  • Options:
    1. 11 로 확정 — D-018 의 실측 기록을 정본으로 삼는다
    2. 12 로 확정 — planning-inputs §2.1 표의 행 수를 정본으로 삼는다
    3. 개수를 정본화하지 않는다 — 허용 호스트 목록을 단일 SSOT 로 두고 개수는 그 목록에서 파생되는 값으로 취급한다
  • Decision: 옵션 3. 허용 호스트 목록이 단일 SSOT 다. 코드의 SandboxDefaults.hosts 와 planning-inputs §2.1 표가 같은 행 집합을 갖고, 개수는 그 목록을 세면 나오는 파생값일 뿐이다. 어떤 문서도 개수를 판정 기준으로 인용하지 않는다.
  • Rationale: 선호가 아니라 측정이 선택지 둘을 탈락시켰다. #38 의 3-OS 프로브가 §2.1 표(12행)에 없는 호스트 api.github.com 을 CocoaPods 경로에서 실제로 관측했고(docs/sandbox-limits-cocode.md §3), 같은 실측이 Gradle/JVM 은 HTTP_PROXY 를 무시하고 직접 나간다는 것도 확인했다 — 프록시에 오지 않는 트래픽은 애초에 세어지지도 않는다. 즉 11 도 12 도 측정과 어긋난다. 개수를 11·12·13 어느 수로 고정해도 다음 실측에서 같은 불일치가 재발하므로, 한 수를 골라 봉합하는 대신 숫자를 정본으로 두는 구조 자체를 바꾼다. 목록은 실측이 늘면 행이 늘어도 여전히 정본이지만, 개수는 늘어나는 순간 틀린 정본이 된다.
  • Impact:
    • docs/architecture-cocode.md §9 미결 11 행이 제거되고 이 결정 번호 참조로 대체된다. §5.3 「기본 목록」 문단의 개수 인용(D-018 · §2.1 표 · D-021 비교)도 함께 사라진다.
    • 개수 인용 3곳 정리 — D-018 의 「필수 최소 11종」·D-021 Impact 의 「허용 호스트 12종」·planning-inputs §2.1 표가 모두 목록 참조로 바뀐다. 세 곳 다 기록은 지우지 않고 그 수가 정본이 아님을 명시하는 형태다(append-only 일지의 기존 정정 관례).
    • planning-inputs §2.1 표가 실측을 흡수한다 — api.github.com 과 trunk.cocoapods.org 가 출처 「#38 3-OS 실측」으로 표에 들어간다. 이 둘은 #36 이 이미 코드 목록에 넣어 둔 행이며(빼면 pod 경로가 fail-closed 로 막힌다), 이로써 코드와 표의 행 집합이 같아진다 — SSOT 가 둘로 갈리지 않는다.
    • #36 의 현행 우회가 영구 규칙으로 승격된다. 「판정은 『비어 있지 않다』 + 대조 검사로만 하고 코드·테스트 어디에도 N종을 쓰지 않는다」는 임시 회피가 아니라 이 결정의 집행 형태다 — 걷어내지 않는다.
    • docs/prd-cocode.md 의 개수 불일치 항목과 docs/bdd-cocode.md 의 「개수를 인용하지 않는 이유」 주석이 이 결정 참조로 바뀐다.
  • Source: #39 AC1 (판정 주체 Planning — architecture §9 '누가' 열) · 프로젝트 오너 결정 #109 (2026-09-13). D-050 ④ 가 남긴 자리를 이 항목이 채운다.

D-052: 「타겟하는 플랫폼」의 판정 입력 = 워크스페이스의 플랫폼 디렉터리 존재 (#39 AC2 · PRD D-21)

🔢 번호 재배정(2026-09-13) — 이 항목은 epic/33 에서 D-048 로 발급됐으나, 같은 번호를 project/2 의 Epic #74(edit_locks)가 먼저 가져갔다(PR #206). Epic #33 을 project/2 에 합칠 때 D-052 로 재배정했다 — #39 의 PR·코멘트와 커밋 메시지에 남은 옛 번호 D-048 는 이 항목을 가리킨다.

  • Date: 2026-09-13
  • Context: 이슈 #39 인수조건 2. SandboxDefaults.hostsFor(targets) 는 기본 호스트 목록을 타겟 플랫폼으로 거르는데(planning-inputs §2.1 「플랫폼」 열 — Linux 데스크톱 전용 빌드에 Android·iOS 호스트는 불필요), 그 타겟 집합을 무엇으로 정하는가가 PRD D-21 · architecture §9 미결 16 으로 비어 있었다. #36 은 판정을 발명하지 않고 타겟 집합을 인자로 받아 호출부로 밀어 뒀다. 미판정 동안의 기본값은 전 플랫폼 합집합이었고, §9 미결 16 자신이 그 기본값이 AC-14 를 느슨하게 한다고 경고했다.
  • Options:
    1. 플랫폼 디렉터리 존재 — 워크스페이스 루트의 android/·ios/·macos/·windows/·linux/·web/
    2. 빌드 명령 인자 — 실제로 실행된 flutter build <platform> 의 인자
    3. 사용자 설정 — S-16 설정 화면에서 사람이 고른다
    4. 선언 — 매니페스트(pubspec.yaml)에 적힌 플랫폼
  • Decision: 옵션 1 — 워크스페이스의 플랫폼 디렉터리 존재로 판정한다. 판정 규칙 3가지는 전부 좁히는 방향이다: ⓐ 이름 정확 일치(소문자 — 대소문자 무시 볼륨에서 MacOS/ 까지 받으면 목록이 넓어진다) ⓑ 워크스페이스 루트 바로 아래만(하위 예제·서브패키지의 android/ 는 이 워크스페이스의 선언이 아니다) ⓒ 하나도 없으면 빈 집합이고 합집합으로 되돌아가지 않는다(순수 Dart 패키지는 BuildTarget.all 행만 받는다).
  • Rationale: 결정적이고, 추가 입력도 물어보는 화면도 필요 없으며, Flutter 프로젝트가 실제로 타겟 플랫폼을 선언하는 방식과 일치한다. 나머지 셋은 각각 다른 이유로 판정 입력이 될 수 없다 — ②는 명령을 실행해야 알 수 있어 워크스페이스를 여는 시점에는 존재하지 않고(허용 목록은 그때 합성돼야 한다), ③은 사람이 채워야 하므로 빈 값의 기본을 다시 정해야 해서 같은 미결이 한 겹 뒤로 밀릴 뿐이고, ④는 Flutter 앱이 pubspec.yaml 에 타겟 플랫폼을 적지 않는다(platforms: 는 플러그인 패키지 전용이라 앱 워크스페이스에서는 비어 있다). ①만 워크스페이스를 여는 순간 이미 디스크에 있는 사실이다.
  • 범위 밖: 사용자 설정에 의한 덮어쓰기는 이 결정에 포함하지 않는다(오너 결정 명시). 필요해지면 후속 이슈로 분리하며, 그때까지 S-16 설정 화면의 편집 대상은 기존대로 사용자 추가분뿐이고 타겟 집합은 편집 대상이 아니다.
  • Impact:
    • docs/architecture-cocode.md §9 미결 16 행이 제거되고 이 결정 번호 참조로 대체된다. docs/prd-cocode.md D-21 이 해소된다.
    • AC-14 가 조인다 — 미판정 기본값이던 전 플랫폼 합집합이 사라진다. Linux 데스크톱 전용 워크스페이스의 허용 목록은 planning-inputs §2.1 주석이 든 그대로 BuildTarget.all 행만 남는다.
    • 구현 2개가 신설된다 — 판정 규칙은 core(L0)의 BuildTargetDetection·PlatformDirectory(순수, 파일시스템 없이 검증된다), 디스크를 보는 쪽은 workspace(L1)의 WorkspaceBuildTargets 다. core 는 dart:io 를 갖지 않고 toolchain 은 같은 L1 이라 workspace 를 의존할 수 없으므로 이 배치가 유일하다. 읽기(Directory.existsSync)만 하므로 DD-15 쓰기 허용 파일 목록 대상이 아니다.
    • SandboxSettings.targets 의 생산자가 정해졌다 — 호출부는 WorkspaceBuildTargets.detect(workspaceRoot) 를 쓴다. SandboxDefaults 자신은 여전히 타겟 집합을 인자로 받는다(판정은 IO 가 필요하고 그 층은 순수하다).
  • Source: #39 AC2 (판정 주체 Design — architecture §9 '누가' 열) · 프로젝트 오너 결정 #109 (2026-09-13). D-050 ④ 가 남긴 자리를 이 항목이 채운다.

D-053: 자가 검증 대상없음 사유 코드 폐쇄 집합을 5종으로 확정 — ⑤ 전용 코드 신설

  • Date: 2026-09-13
  • Context: 이슈 #64 잔여 범위. #101(2026-08-27)이 DD-21/21a/21b 를 비준해 ①~④ 의 대상없음 코드 4종은 확정됐으나, DD-21b 가 정한 ⑤ 자신의 대상없음(등록 파서 없음)을 기록할 자리가 폐쇄 집합에 없었다. DD-21b 는 그 거동(완료 차단)만 정하고 기록 값은 정하지 않았다. 함께, architecture §10 항목 12 가 남긴 「C-04 ⑤ 세 번째 절이 공허하게 참이 된다」의 처분 승인 여부도 열려 있었다.
  • Options:
    1. 신설 — no_target:no_registered_parser 를 더해 폐쇄 집합을 5종으로
    2. 코드 없이 차단만 — ⑤ 대상없음은 no_target_code 없이 result: 대상없음 만 기록
    3. ④ 코드 재사용 — no_target:file_not_analyzed 로 적는다
  • Decision: 옵션 1 — no_target:no_registered_parser 신설, 최종 폐쇄 집합 = 5종. 함께 C-04 ⑤ 세 번째 절의 공허성에 대한 DD-21b·§10-12 의 처분(「Seed Spec 문면의 결과이며 Design 이 좁힌 것이 아니다 · Seed Spec 개정 시 검토」)을 승인한다. 산출물은 docs/verification-no-target-cocode.md.
  • Rationale: 옵션 2 는 DD-23b 의 「result=대상없음 일 때만 폐쇄 코드」 규정을 「단 ⑤ 는 예외」로 바꾸며, 그 예외는 ⓐ 필드를 선택으로 만들어 ①~④ 에서도 코드 없는 대상없음을 합법화하거나 ⓑ method 별 스키마 분기를 도입한다 — 둘 다 DD-21 이 없앤 분기를 되돌린다. 옵션 3 은 ⑤ 가 열리는 상황(④ 가 file_not_analyzed)에서 ④·⑤ 두 레코드가 같은 코드를 갖게 해, 사용자가 「분석 대상에 넣어야 하는가」와 「파서 등록을 요청해야 하는가」를 구별할 수 없게 만든다. 옵션 1 은 값 하나를 더할 뿐 스키마 문장이 닫힌 채로 남는다. 거동은 바뀌지 않는다 — 코드 신설은 기록을 열 뿐 완료를 열지 않으며 DD-21b 의 차단 방향이 그대로다.
  • 범위 밖: ⓐ verification_log JSON 스키마 블록의 문서 편집과 코드 반영은 #63(소유 경계 — 세 spike 는 결정만 산출한다) ⓑ 환경 실패 분류(result enum 확장 또는 failure_class 신설)는 #73 의 별개 축이며, 환경 실패를 no_target_code 에 접는 안은 채택 불가다 ⓒ 어떤 확장자에 파서를 등록하는가는 #65(§9 미결 5) — 이 결정은 「등록이 없을 때」만 정한다.
  • Impact:
    • docs/verification-no-target-cocode.md 신설 — no_target_code 값 집합의 SSOT.
    • prd §4 D-2 해소 · bdd §0.5 U-8 해소 · architecture §10 항목 12 처분 승인(재검토 조건: Seed Spec 이 C-04 ⑤ 를 개정하는 회차).
    • #63 에 넘어가는 반영 요청 4건 — DD-23b 주석의 「폐쇄 4종」→「5종」, DD-21b 에 기록 값 존재 명시, core 폐쇄 enum 5종(값 문자열 바이트 일치), §9 미결 5 의 「⑤ 대상없음 기록 값」 축 닫힘 표시.
  • Source: #64 인수조건 2·3·4 (판정 주체 Design) · 선행 비준 #101

D-054: ⑤ 정본 파서 등록 목록을 3종으로 확정 · ③ 관찰 창은 실측 규약만 확정(값 미정)

  • Date: 2026-09-13
  • Context: 이슈 #65. architecture §9 미결 5 가 「③ 의 관찰 창」과 「⑤ 의 형식별 정본 파서 등록 목록」을 값 없이 남겨, AC-09 가 기계적으로 판정되지 않았다. 함께 ④·⑤ 의 「새로운 오류」 기준선은 §6.1 항목 4 가 이미 고정했으므로 확인 대상이었다.
  • Options:
    1. 두 값을 모두 지금 고른다(③ 에 잠정값 부여)
    2. ⑤ 목록은 확정하고 ③ 은 실측 규약만 확정한다
    3. 둘 다 실측까지 미룬다
  • Decision: 옵션 2. ⓐ ⑤ MVP 등록 목록 = 3종 — .dart(package:analyzer) · .yaml·.yml(package:yaml) · .json·.arb(dart:convert). ⓑ ③ 관찰 창의 길이·관찰 이벤트 집합은 「미정 — 결정 필요」(판정 주체 Design) 로 남기고, 무엇을 어떻게 실측할지를 확정한다. ⓒ ④·⑤ 기준선(그 작업의 첫 편집 직전 dart analyze 결과, 작업 시작 시 1회 수집)을 채택 확인한다. 산출물은 docs/verification-parameters-cocode.md.
  • Rationale: ③ 에는 표본이 없다 — 이 저장소 어디에도 「핫 리로드 후 런타임 오류 도달 지연」 관측치가 0건이다. D-033(AC-08 20분)이 정당했던 이유는 GitHub Actions 41건이라는 실측 표본이 이미 있었기 때문이며, 표본 없이 고른 숫자는 D-013·D-015·D-020 이 경계한 패턴 그대로다. 반면 ⑤ 목록은 실측이 필요 없는 판정이다 — 등록 요건 P-1(정본 문법 존재)·P-2(무효 입력을 실제로 거부)·P-3(SDK 또는 dart-lang 1급 구현)을 만족하는 형식이 열거로 정해진다. 관대한 파서를 등록하면 C-04 ⑤ 의 두 번째 절이 항상 참이 되어 ⑤ 가 고무도장이 되므로(.md·.txt 배제 근거), 목록은 넓힐수록 좋은 것이 아니다. .xml/.plist 는 바이너리 변종이 「읽기 실패」와 「파서 오류」를 섞어 #73 의 환경 실패 축을 오염시켜 배제했고, .lock 은 확장자 폐쇄 매핑(DD-21b)을 파일명 특례로 깨뜨려 배제했다. 세 파서 모두 이미 의존 그래프 안이거나 SDK 내장이라 계층 래칫·phantom_edges 를 건드리지 않는다.
  • 미정 시 동작(③): 디버그 세션이 없으면 대상없음(no_target:no_debug_session)이라 영향이 없고, 있으면 ③ 은 미실행 이라 DD-21a-4 가 완료를 막는다 — 차단 방향(안전측). 따라서 이 미결은 출시 차단이 아니라 기능 축소 항목이다. 함께 확정한 부등식: 관찰 창 < AC-08 시도 상한(20분) — 어기면 ③ 은 영원히 끝나지 않고 시도 상한에 먼저 걸린다.
  • 실측 규약(③): 참조 워크스페이스 unibook @ 9db9015(D-019) · 오류 3종(리로드 즉시 / 다음 프레임 / 상호작용 후) × 3 데스크톱 플랫폼 × 20회 이상 · Δ = t₁(오류 이벤트 도착) − t₀(reloadSources 반환) · 산정은 D-033 과 같은 규칙(B-1 실측 최대 × 2, B-3 과대추정이 안전측). 관찰 스트림은 Debug·Extension 우선이고 Stderr 단독 근거는 판정을 도입하므로 배제 검토 대상이다.
  • Impact: architecture §9 미결 5 부분 해소(⑤ 목록 축 닫힘, ③ 축은 값 미정 + 규약 확정, AgentRequest 타임아웃·프로세스 종료 유예 두 축은 범위 밖). prd D-12·FR-301a 해소. #63 에 넘어가는 반영 요청 4건은 산출 문서 §5 표에 있다.
  • Source: #65 인수조건 1·2·3·4 (판정 주체 Design — architecture §9 '누가' 열)

D-055: 검증 실패 원인 분류 — failure_class 폐쇄 5종 신설, 환경 실패는 retry_count 를 소진하지 않는다

  • Date: 2026-09-13
  • Context: 이슈 #73. verification_log 의 result 는 통과|실패|대상없음 3값이라 환경 실패를 담을 자리가 없고, DD-21a-1 이 모든 실패 를 재시도 경로로 보내므로 툴체인 부재·네트워크 단절·프로세스 기동 실패가 retry_count 를 소진한다 — 그 3회 상한은 D-008 이 「유일한 안전장치」로 적은 것이다. flow-permutation FP-111·I-8 이 이를 「원인 구분이 없다」로 지목했고, allowed_hosts 차단(AC-14 ⓑ)과 네트워크 단절도 구분되지 않았다.
  • Options:
    1. result enum 을 4값으로 확장한다
    2. failure_class 정련 필드를 신설한다(result 는 3값 그대로)
    3. 환경 실패를 result: 대상없음 으로 접는다
  • Decision: 옵션 2. ⓐ failure_class 폐쇄 5종 — failure:code_defect · failure:env_tool_unavailable · failure:env_tool_aborted · failure:env_network_unreachable · failure:policy_host_blocked. ⓑ 계수는 code_defect 만 retry_count 를 증가시킨다. ⓒ 환경 실패에는 retry_count 와 별개인 상한을 두되 값은 미정 — 결정 필요(Planning) 이고 미정 동안은 상한 0(첫 환경 실패에 즉시 실패 전이 — 차단 방향). ⓓ 한 회차에 여러 분류가 섞이면 우선순위 code_defect > policy_host_blocked > env_* 의 최댓값 하나가 그 회차의 분류다. 산출물은 docs/verification-failure-class-cocode.md.
  • Rationale: 옵션 1 은 DD-21a 의 네 규칙 전부가 「새 값은 어느 쪽으로 세는가」를 다시 답하게 만들고(규칙 1 은 재시도, 규칙 3 은 ⑤ 개방이라 성질이 다르다) result 의 모든 소비자에 분기를 더한다. 옵션 2 는 그 재해석을 한 건도 만들지 않으며 — result 3값·DD-21a 문면 불변 — 이미 result=대상없음 을 정련하는 no_target_code 와 대칭이라 스키마에 패턴이 하나만 남는다. 옵션 3 은 채택 불가다(DD-21 확정, #101 — 두 축은 별개 필드). 판정 절차는 순서 있는 전역 함수이고 마지막 갈래가 포괄(code_defect)이라 ⓐ result=실패 인데 분류가 비는 경우가 구조적으로 없고 ⓑ 양성 환경 신호가 있을 때만 계수에서 빠지므로 안전장치를 신호 없이 느슨하게 하지 않는다. 계수 규칙이 seed-spec:85·AC-07 과 모순되지 않는 이유: 두 문면 모두 「상한을 초과하면 어떻게 되는가」만 규정하고 무엇이 카운터를 올리는지는 규정하지 않는다 — 증가 규칙은 N3 공백이었고 이미 state-machine §3 이 Design 층에서 채운 자리다.
  • 층위: Design 만으로 성립한다. verification_log 의 스키마는 PRD 가 「§3 은 담을 내용만 규정하고 형식은 → Design」으로 명시 위임했고, 상한의 존재 요건은 AC-08 과 같은 형식이며, 도착 상태는 기존 실패(49칸 표의 ⑪ 칸)라 새 status 값도 새 전이 칸도 만들지 않는다. Seed Spec 개정 불요 — seed-spec:8 의 개정 중단 상태와 충돌하지 않는다.
  • Impact:
    • docs/verification-failure-class-cocode.md 신설 — failure_class 값 집합의 SSOT.
    • flow-permutation FP-111 해소 · I-8 앞 절반 해소(뒤 절반 = AC-08 「기록 vs 전이」는 #67).
    • ux-spec §3.2 — 실패 진입 경로 3종 → 4종(E-4 환경 실패 상한 초과), 사유는 폐쇄 라벨. UX-D-25 신설(코드 집합은 검증 문서 소유, 이 문서는 라벨만 소유). 「사유 미규정」 자리표시자는 비정상 종료 작업 자신의 status 하나로 좁아진다(§9 미결 8, 범위 밖).
    • bdd Feature 4 에 Scenario: [AC-07] 3건 추가(환경 실패 / 코드 결함 / 같은 실패 횟수의 대조).
    • #63·#66 에 넘어가는 반영 요청 5건은 산출 문서 §8 표에 있다. attempt_no 는 retry_count 와 같은 시점에 오르므로(seed-spec:92) 환경 재실행은 새 회차를 만들지 않는다.
  • Source: #73 인수조건 1~10 (판정 주체 Design) · 선행 확정 #101 · 상한 값은 Planning(§9 미결 2 와 같은 회차)

D-056: 자가 검증 시도 시간 상한 초과의 정본은 「기록」 — 전이는 재시도 예산이 소진될 때만

  • Date: 2026-09-13
  • Context: 이슈 #67. seed-spec 의 두 문면이 어긋난다 — AC-08(§4)은 「그 시도가 중단되고 실패로 기록된다」로 주어가 시도이고, 불변식 4(§3)는 「단일 자가 검증 시도가 설정된 시간 상한을 넘기면 「실패」로 전이하고 사람에게 에스컬레이션한다」로 주어가 작업이다. 문자 그대로 읽으면 한 번의 초과가 곧 작업 종결이며, 결론에 따라 종결 상태 생성 여부가 갈린다(prd §4.1 · flow-permutation I-8 뒤 절반 · FP-104).
  • Options:
    1. 「기록」이 정본 — 시도만 중단·기록하고 일반 실패 경로로 합류
    2. 「전이」가 정본 — 첫 초과에서 즉시 실패 전이
  • Decision: 옵션 1 — 「기록」이 정본. 시간 초과는 그 시도만 중단하고 verification_log 에 result: 실패 를 남기며, 그 뒤는 일반 자가 검증 실패와 완전히 같은 경로(DD-21a-1 → 재시도)다. retry_count 가 3 을 초과할 때 비로소 ⑪(자가검증중 → 실패)로 간다. 함께 시간 초과 시도의 failure_class 를 failure:code_defect 로 귀속시키고 D-055 판정 절차에 갈래 0 을 추가한다. 산출물은 docs/verification-timeout-canon-cocode.md.
  • Rationale: 결정적 근거는 D-033 자신이다. 오너가 비준한 그 결정의 「파급」 첫 줄이 「C-04 재시도 3회와 곱해 작업당 자가 검증 상계 60분」인데, 이 곱셈은 시간 초과 시도가 재시도된다는 것을 전제한다 — 「전이」가 정본이면 상계는 20분이고 곱셈이 성립하지 않는다. 즉 값 결정 자체가 「기록」 해석 위에 서 있었고 이 문서는 그것을 명시적으로 만들 뿐이다. 보조 근거 셋: ⓐ 「전이」를 고르면 시간 초과에 대한 재시도가 0회라 C-04 가 상한과 재시도를 나란히 둔 분업이 사라진다 ⓑ 두 문면의 주어가 다르므로(시도 vs 작업) 「기록」 아래에서는 모순이 아니지만, 「전이」 아래에서는 AC-08 의 「기록된다」가 아무 데도 쓰이지 않는 죽은 말이 된다 ⓒ 20분은 실측(GHA 최대 8.9분)의 2배 남짓이라 정상 작업이 한 번쯤 넘길 수 있는 값이고, 재시도 없이 죽이는 것은 안전이 아니라 거짓 실패다. 어느 쪽을 골라도 작업은 유한 시간(≤ 60분)에 반드시 종결한다.
  • failure_class 귀속: 시간 상한으로 우리가 죽인 프로세스는 D-055 §3.2 의 4번(「시그널 종료 … 진단 0건」)에 먼저 걸려 환경 실패로 오분류되고 계수되지 않아 60분 상계가 깨진다. 그래서 갈래 0(우리가 직접 중단했는가 → code_defect)을 맨 앞에 둔다 — 그 시그널은 우리가 보낸 것이라 도구의 거동 정보를 담지 않는다. code_defect 인 이유: 시간 초과가 가장 특이적으로 잡는 실패 모드는 수렴하지 않는 코드(에이전트가 고칠 수 있다)이고, 계수하는 쪽이 현행 거동이라 안전장치를 느슨하게 하지 않으며, 정말로 느린 환경의 해소 경로는 재시도가 아니라 상한 설정 변경이다(AC-08 이 값을 설정 항목으로 둔 이유). 폐쇄 집합은 그대로 5종 — 순서만 바뀌었다.
  • 전이표 반영: state-machine ⑪ 행에서 T-5 를 직접 트리거에서 제외한다(칸의 판정은 불변 — 도달 경로가 하나 줄 뿐). §6 의 「⑪ 의 단일 시도 시간 상한 값」 행은 해소(값 = 20분 / D-033, 의미 = 이 결정). SelfVerificationTimedOut 은 전이 사건이 아니다 — #66 이 그 거부 경로를 제거하고 기존 SelfVerificationFailed 로 수렴시킨다. 새 status 값도 새 전이 칸도 만들지 않는다.
  • 함께 닫힌 것: FP-104 의 「중단된 시도의 부분 편집 처분」 — 검증 중단은 편집을 만들지도 지우지도 않고, 편집은 EditSnapshot.attempt_no 로 회차에 묶여 보존되며(D-030) 실패해도 AC-19 가 diff·롤백을 보장한다. 새 규칙이 필요 없다.
  • «개정 요청»(오너 판정): seed-spec §3 불변식 4 의 두 번째 이접지를 시도 수준 사건으로 읽히게 고친다. §3 머리말이 기존 엔티티 수정 불가를 규정하고 seed-spec:8 이 개정 중단 상태라 문서 결정이다. 판정 전까지 구현은 이 정본(기록)을 따른다 — 오너가 이미 비준한 D-033 의 60분 상계와 정합하는 유일한 읽기이기 때문이다. 기각되면 ⑪ 행에 T-5 를 되돌리고 D-033 의 60분 문장을 20분으로 정정해야 한다.
  • 상한 값의 선택 주체·시점(인수조건 2): 프로젝트 오너 · 2026-09-10 · #106 → D-033 · 20분(planning-inputs §7.1). 조정은 Planning(#96 실측 후 B-1 재산정). 이 Story 는 값을 산출하지 않았다.
  • Source: #67 인수조건 1·2 (판정 주체 Design) · 선행 값 결정 D-033 · 선행 분류 D-055

D-057: 승인 규칙 5종 확정 — 거부 경로 · 무응답 무제한 · 승인 2종 순차 · forward apply 재검사 (override 는 작업 가설)

  • Date: 2026-09-13
  • Context: 이슈 #70. 승인 계열 미결 5종이 한 표로 묶여 있었다 — ⓐ 승인 거부가 어느 status 로 가는지 ⓑ 승인도 롤백도 없는 무응답의 처분(prd 흐름 3-A 8단계) ⓒ 완료승인·테스트변경승인이 모두 필요할 때의 순서·복귀 상태·pending_approval_kind 리셋(flow C-2 / FP-208) ⓓ completion_mode override 가부(prd D-18) ⓔ 승인 대기 중 잠금이 해제되어 생기는 forward apply 충돌(flow C-3 / FP-210).
  • Decision: 산출물은 docs/approval-rules-cocode.md. 다섯 결론:
    1. 거부 → 거부됨. 값은 D-032(#105, 오너)가 이미 신설했으므로 이 Story 는 전이 규칙을 정한다 — 출발은 사람승인대기 하나이고 kind 두 종류가 같다. 거부는 적용을 만들지 않으며(되돌리는 것은 롤백), 편집은 남고(AC-23) 거부됨 → 롤백됨 은 요구된다.
    2. 무응답에 시간 상한을 두지 않는다. 자동 전이 없음.
    3. 승인 2종은 순차 배치 — 테스트변경승인이 먼저. 복귀는 테스트변경승인 «승인» → 항상 실행중(retry_count 불변), 완료승인 «승인» → 완료, 거부 → 거부됨, 롤백 → 롤백됨. 리셋은 엔티티 생성자가 강제한다.
    4. completion_mode override 불가 — ⚠️ 작업 가설(확정은 #111).
    5. forward apply 는 적용 직전에 재검사하고 다르면 자동 적용하지 않고 충돌 제시. 충돌 시 작업은 사람승인대기 에 머문다.
  • Rationale:
    • 2(무응답): #77 이 잠금 대기에 대해 쓴 것과 같은 형태의 논거 — 상한을 두지 않되 근거가 「무방비」가 아니라 구조적이다. ⓐ 승인 대기는 리스를 해제하고(seed:74) 대기열도 잡지 않아(#77 §4) 자원을 점유하지 않으므로 C-5 가 경계한 무한 체류의 전제가 없다 ⓑ 출구가 거부(AC-23)·롤백(⑬) 으로 항상 둘 열려 있고, AC-23 의 「무한 대기하지 않는다」는 출구의 존재로 충족되는 요건이다(When 이 「사용자가 승인을 거부하면」이라 사용자 입력이 전제) ⓒ 자동 종결은 값을 지어내야 하는데 「사람이 휴가를 가는 기간」은 도구가 잴 수 있는 양이 아니다 ⓓ 자동 종결은 사람만 할 수 있는 판정을 시스템이 대신하는 것이라 AC-10·AC-17 의 취지를 우회한다. 상한 대신 가시성(복귀 보드의 「승인 대기 N」)으로 처분하며, 시각 속성이 없으므로 「N일 경과」는 그리지 않는다.
    • 3(순서): 새 규칙이 아니라 정의의 귀결이다 — 테스트변경승인은 「적용 직전」(C-05), 완료승인은 「편집을 마친 뒤」(불변식 2)이고 적용은 편집 완료보다 앞선다. 반대 순서는 완료승인 화면의 diff 가 최종본이 아니게 되어 AC-10·AC-01 을 깬다. 따라서 두 kind 가 동시에 필요한 시점이 없고 단일 값 enum 으로 충분하다(C-2 가 적었듯 결함은 표현력이 아니라 규칙 부재였다).
    • 3(복귀): 진입 출발 상태를 담는 속성이 없어 세 안 중에서 골랐다. 속성 신설은 Seed Spec 개정 선행(§3 머리말)이라 기각. 항상 실행중 이 의미상으로도 옳다 — 적용된 것이 테스트 파일이라 C-04 ②의 대상 자체가 바뀌었고, 그 이전 자가 검증 결과는 무효다. 자가검증중 으로 되돌아가 이어서 검증하면 옛 테스트 기준의 판정을 쓴다. retry_count 는 증가하지 않는다 — 증가 지점은 ⑧ 하나이고(state-machine §3), 사람의 승인 행위가 재시도 예산을 먹으면 안 된다.
    • 3(리셋): 규칙으로 적고 각 전이가 지키기를 기대하는 대신 엔티티 생성자의 정합성 검사에 맡긴다(status != awaitingApproval 이면 kind 는 none). 리셋을 빠뜨린 전이는 코드가 만들어지지 않는다 — C-2 의 「승인 뒤 사람승인대기 잔류」가 표현 불가능해진다.
    • 4(override): #111 의 선택지 ②(조건 없이 허용)는 A-02 미검증 상태에서 자가검증 완료 경로를 열어 AC-17 전환 조항 자체를 무력화한다 — C-04 가 그 전환을 「제약이 미리 규정한 동작」으로 적은 이상 토글 하나로 꺼질 수 없다. 선택지 ③(전환 조항 미발동 시에만 허용)은 지금 판정 불가다: 발동 여부가 A-02 합격 기준에 종속하는데 그 기준이 미설정이라(bdd U-4) ③ 은 현재 항상 「발동 중」 이고 ①과 거동이 같으며, 다른 점은 #68 ↔ #80 사이에 상태 전달 계약이 하나 느는 것뿐이다. ① → ③ 되돌리기는 토글 잠금 해제 조건 하나라 싸고, ② 를 먼저 열었다가 좁히는 것은 이미 생성된 작업의 모드를 재해석해야 한다.
    • 5(forward apply): DD-25 R4 가 롤백 방향에서 이미 세운 규칙(「사람이 이미 같은 내용으로 고쳐 둔 경우도 충돌 — 결과적으로 같으니 통과를 계산하기 시작하면 불변식이 판정 규칙으로 변한다」)을 forward 방향에 그대로 적용한다. 새 판정 축을 만들지 않는다. 비교 단위는 (경로, 투영 전이) 쌍이라 생성·삭제·이름변경의 대기 변경도 담긴다. 적용은 편집 시도라 리스를 다시 획득하므로(D-045) 검사와 적용 사이에 창이 없다. 「덮어쓰기」 선택지는 남의 편집을 조용히 지우는 경로라 두지 않고, 충돌을 이유로 자동 실패 전이도 하지 않는다(2번 논거 위반).
  • Impact:
    • docs/approval-rules-cocode.md 신설 — 승인 이탈 규칙의 SSOT.
    • flow-permutation C-2·C-3·FP-208·FP-210·B-03·B-05 해소.
    • state-machine §6 의 승인 관련 3행 해소(승인 이탈 전이 전체 · 거부 값 추가 여부 · 무응답 처리 N6). 전이표 81칸 확장은 #71 이 수행한다.
    • prd 흐름 3-A 8단계 미결 · 흐름 3-B 미결 A 해소. D-18 은 작업 가설로만 닫힌다.
    • FP-409 의 선행(C-3)이 서서 #72 가 판정 가능해진다 — 앞 승인이 적용된 순간 뒤 승인의 사전검사가 충돌로 떨어지므로 「나중 승인이 앞 승인을 조용히 덮지 않는다」가 관찰 가능한 조건으로 성립한다. 승인 granularity·부분 승인·PendingChange 는 #72 소관.
    • TaskTransition 의 ApprovalGranted(kind) 거부 경로 제거 — #68·#69 가 §3.2 표대로 구현한다.
  • Source: #70 인수조건 1~5 (판정 주체 Design) · 선행 값 결정 D-032(#105, 오너) · override 는 #111 대기

D-058: 승인·롤백 입력은 멱등(4종 전부 no-op 흡수) · 부분 승인 금지 · [적용하지 않음] = 거부 · PendingChange 필요

  • Date: 2026-09-13
  • Context: 이슈 #72. flow-permutation I-9(승인·롤백 요청의 멱등성·중복 입력 — 근거 열이 문자 그대로 「미규정」)와 I-10(승인 granularity, 같은 테스트 파일에 두 작업이 대기)이 열려 있었고, 두 항목 모두 확정 주체가 어느 문서에도 없었다(§6.2 Important 표에 주체 열 자체가 없고 §7.1 은 Critical 유래만 덮으며 architecture §9 에도 미등재). 또 ux-spec 의 테스트변경승인 카드에는 명시적 [적용하지 않음] 버튼이 있는데 flow :128 의 무효 열은 「미승인 방치」라는 부작위만 적어 문서가 어긋나 있었다.
  • Decision: 산출물은 docs/approval-idempotency-cocode.md. 여섯 결론:
    1. 확정 주체 = Design. 오너 판정 불요.
    2. 입력 4종 전부 ⓐ no-op 흡수 — 완료승인 · 테스트변경승인 · [적용하지 않음] · 롤백 지점 선택. 검사 주체는 TaskTransition(DD-05).
    3. [적용하지 않음] = AC-23 의 거부 입력 → 거부됨(종결). 그 변경은 적용되지 않고 이미 적용된 편집은 남는다.
    4. 부분 승인 금지 — 승인은 대상 변경 전체에 대한 단일 판정이고 적용은 전량이거나 전무다. 파일별 승인 후보는 UX-D-13 이 이미 기각했다.
    5. FP-409: 앞 승인이 적용되면 뒤 승인의 사전검사가 충돌로 떨어진다 — 관찰 조건은 「적용 시점의 현재 해시 ≠ 승인 시점 base 이면 적용되지 않는다」.
    6. PendingChange 는 필요하다. 저장 위치·수명·승격 규칙은 architecture §9 미결 23 으로 등재.
  • Rationale:
    • 주체(0): 정할 대상이 값이 아니라 규칙이고(오너 판정을 받아 온 D-032·D-033·D-037·D-044·D-051 은 전부 불변 층위의 값이거나 범위 결정이었다), 멱등성이 성립해야 하는 자리는 TaskTransition 하나이며(DD-05) granularity 는 UX-D-13 이 이미 절반을 정했다. 어느 결론도 status·pending_approval_kind 폐쇄 집합을 건드리지 않아 개정 중단 상태(seed:8)의 영향을 받지 않는다.
    • ⓒ(UI 도달 불가) 단독 채택 불가: 버튼 비활성화는 도메인 함수 밖의 검사라 DD-05 를 우회한다 — 단축키·복원된 세션·자동화 같은 다른 호출 경로에는 보호가 없다. UI 비활성화는 하되(피드백) 계약은 도메인 함수가 진다.
    • ⓑ(거부+사유)를 고르지 않은 이유: 중복 입력의 압도적 다수가 더블 클릭이다(flow :127 의 「동시·반복」 열). 원한 결과가 이미 이뤄졌는데 오류를 띄우는 것은 사실과 다른 실패 신호다. 반대로 「같은 입력」이 아니라 낡은 입력(그 사이 작업이 옮겨 감)은 이 규칙에 걸리지 않고 기존 전이표가 출발 상태 불일치로 거부한다 — 두 경우를 구별하는 술어가 이미 있다.
    • 롤백(ⓓ)은 새 규칙이 아니다 — DD-25 R4 가 이미 「현재 ∅ 이면 충돌 없이 아무 일도 하지 않는다(멱등 — DD-11 7단계와 같은 규칙)」를 적었다. 이 결정은 그 규칙이 롤백 입력 전체에 적용됨을 명시할 뿐이다.
    • [적용하지 않음](2): 「이 변경만 버리고 작업은 계속」 안은 ⓐ 어느 스펙에도 없고 ⓑ 에이전트가 만든 편집 집합과 실제 적용된 집합이 갈라진 채 작업이 이어지는 상태를 새로 만들며 ⓒ 그 뒤 에이전트가 무엇을 해야 하는지 규칙을 또 요구한다. C-05 의 문면은 「승인을 거쳐야 적용된다」이고 적용하지 않음 = 승인하지 않음이다.
    • 부분 승인 금지(3): ⓐ 표현할 자리가 없다 — pending_approval_kind 는 작업 1건에 값 하나인 폐쇄 집합이고 architecture:176 이 새 승인 종류 도입을 금지한다 ⓑ 부분 적용 상태를 담는 어휘가 없다(EditSnapshot·attempt_no·PendingChange 어느 것도) — 담게 하면 AC-13 급의 새 원자성 규칙이 하나 더 필요하다 ⓒ C-05 는 「판정이 필요 없게 한다」인데 부분 승인은 파일별 판정을 사용자에게 되돌려 준다.
    • PendingChange(5): FP-409 판정의 입력인 「승인 시점 base」와 승인 시 쓸 제안 바이트를 들고 있어야 하는데 EditSnapshot 은 적용된 편집의 기록이라 담지 않는다. 승인 대기에 시간 상한이 없어(D-057 §2) 앱 재시작을 가로지르므로 영속이 필요하다 — 메모리에만 두면 재시작 후 카드는 남는데 적용할 것이 없다.
  • Impact:
    • docs/approval-idempotency-cocode.md 신설.
    • prd §1.4 에 FR-405 ~ FR-409 신설 — 입력 4종 각각에 「멱등」이 명시된다(#72 인수조건 1 의 grep 판정 기준). Seed Spec 이 아니라 PRD 를 고른 이유는 flow §7.3 이 「Seed Spec 또는 PRD 반영」을 허용하고 Seed Spec 이 개정 중단 상태이며 이 규칙들이 §2 제약도 §4 인수 경계도 아닌 기능 요구사항이기 때문이다.
    • prd 흐름 3-A 8단계 미결 · 흐름 3-B 미결 A 해소 표시.
    • flow-permutation I-9·I-10·FP-209·FP-409 해소, §2 입력 순열 3행(:127·:128·롤백) 갱신.
    • ux-spec — UX-D-26 신설. 완료승인 카드에도 거부 입력이 필요함을 지적(AC-23 은 kind 를 가리지 않는다) — 버튼 배치는 #85.
    • architecture §9 미결 23 등재(PendingChange 저장 위치·수명·승격 규칙).
    • ⛔ BDD 시나리오 0건(인수조건 6) — flow §7.3 의 「규칙 확정 없이 시나리오를 쓰지 말 것」 그대로, 시나리오는 별도 후속이다.
  • Source: #72 인수조건 1~7 (판정 주체 Design — 이 결정이 정했다) · 선행 D-057(C-3) · D-032(AC-23)

D-059: C-5 세 축 처분 · CancelReason 폐쇄 5종 · 전이표 81칸 확장 — Seed Spec 추가 개정 불요

  • Date: 2026-09-13
  • Context: 이슈 #71. flow-permutation C-5 가 무한 체류 4지점(FP-105 편집 단계 · FP-106 작업 총량 · FP-107 프로바이더 응답 불가 · FP-405 잠금 대기)을 지적했고, 잠금 축은 #77 이 닫았으나 세 축이 열려 있었다. 동시에 AgentSession.cancel(CancelReason) 은 호출부를 쓸 수 없는 죽은 API 였다 — CancelReason 이 docs/ 전수 grep 에서 선언부 1건뿐이고, 호출 경로도 중지를 표현할 status 도 없었다. 그리고 D-032 가 status 를 9종으로 개정한 뒤 state-machine 전이표가 7상태(49칸) 그대로 남아 드리프트가 있었다.
  • 전제 — 방향은 이미 오너가 정했다: D-032(#105, 2026-09-10)가 C-5 와 UX-D-06 의 정면 충돌을 「중지 수단 신설」 쪽으로 처분했고 「UX-D-06 은 이 결정으로 폐기된다」를 Impact 에 적었다. 따라서 #71 은 「상한을 둘까 말까」를 새로 고르는 Story 가 아니라 그 처분을 세 축에 명시하고 집행 가능하게 만드는 Story 다.
  • Decision: 산출물은 docs/stop-and-limits-cocode.md.
    1. FP-105 — 자동 상한을 두지 않는다. 조용한 매달림은 DD-22 S2(단계를 가리지 않는 유휴 초과)가 이미 덮고, 남는 「이벤트를 내며 진전 없음」(라이브락)은 AC-22 중지 입력이 덮는다.
    2. FP-106 — 총량 상한을 두지 않는다.
    3. FP-107 — DD-22 로 이미 덮인다(S1 ∪ S2). 잔여는 값(#50)과 비준(#112) 둘뿐이며 범위 밖.
    4. FP-405 — #77 의 결론을 인용한다(구조적 유한성).
    5. CancelReason 폐쇄 5종 — userStopped · userRolledBack · approvalRejected · taskFailed · workspaceClosing. 호출 순서는 전이가 먼저, cancel 이 나중.
    6. 전이표 81칸 확장 완료 — 신규 전이 7종(⑰⑱⑲⑳㉑㉒㉓), ⬜ 칸 0개.
    7. Seed Spec 추가 개정 불요 — 판단 주체 Design, 일자 2026-09-13.
  • Rationale:
    • FP-105: 「완전 무방비」는 과장이었다 — S2 가 단계를 가리지 않으므로 조용한 매달림은 덮인다. 실제 공백은 라이브락 하나이고, 자동 상한은 ⓐ AC 신설을 요구하는데 flow §7.1 B-07 이 그 확정 주체를 프로젝트 오너로 지정해 Design 의 권한 밖이고 ⓑ 값의 표본이 0건이며 ⓒ 「진전」을 정의하는 순간 판정이 생긴다(DD-21 이 ①~④ 에서, DD-22 가 「연속 오류 횟수」에서 없앤 바로 그 성질) ⓓ 라이브락은 사람이 보면 즉시 아는 상태라 중지 입력이 그 자리를 정확히 덮는다. ⚠️ 잔여 위험을 숨기지 않는다 — 사람이 돌아오기 전까지는 계속 돌고, D-008 이 재시도 상한을 도입한 근거가 이 축에는 아직 적용되지 않았다. 후속 판정 주체는 오너(B-07).
    • FP-106: 총량 식의 세 항 중 자가검증(≤ 60분 — D-033 × 4, D-056)·환경 재실행(D-055 §5)·잠금 대기(#77)가 이미 유한하고 남은 무한은 편집 축 하나다. 총량 상한은 같은 공백에 두 번째 근거 없는 시계를 거는 것이고, 어느 규칙도 어기지 않은 작업을 시계 하나로 끊으면 사용자가 받는 사유가 「오래 걸렸다」뿐이라 failure_class(D-055) 어느 값에도 대응하지 않는다.
    • UX-D-06 의 근거가 왜 성립하지 않았나: 「AC-07·AC-08 이 자동 종료를 보장」이었는데 AC-08 의 Given 이 「자가 검증 시도가 진행 중일 때」(seed:110)로 한정돼 편집 단계·잠금 대기를 덮지 못한다. 인수조건 2 는 한쪽 정정만 요구했으나 양방향으로 넣었다 — 한쪽만 고치면 반대 방향에서 읽는 사람이 낡은 문장을 만난다.
    • CancelReason(5): 값이 호출 지점 하나에 하나씩 대응하므로 폐쇄가 자연스럽고, 새 값은 새 호출 지점이 생길 때만 는다. 전이가 먼저인 이유 셋: ⓐ 중지됨 은 종결 상태이고 DD-05 가 전이의 단일 지점을 요구한다 ⓑ 전이가 거부되면(이미 종결·대기) 세션을 죽이지 않아야 하는데 cancel 이 먼저면 거부된 요청이 프로세스만 죽인다 ⓒ cancel 실패는 런처의 강제 종료가 덮지만, 반대 순서에는 대체 수단이 없다.
    • ⑮(수동 재개)를 거부됨·중지됨 에 주지 않는 이유: ⑮ 의 근거는 「AC-07 이 금지한 것은 자동 재시도다」 하나이고 그 문장은 실패 에만 걸린다. 두 새 상태는 사람이 그만두기로 한 상태라 재개 경로를 두면 「중지」와 「일시정지」의 구별이 사라진다. 편집은 남고 롤백(⑳·㉑)도 열려 있어 잃는 것이 없다.
    • 불변식 영향(전수): 1·2·3·4 는 영향 없음(거부는 완료가 아니라 거부됨 이라 불변식 2 를 오히려 강화한다). 불변식 5 는 확장 — ⑳·㉑ 이 추가되나 「사용자의 명시적 요청」이라는 필요조건만 규정하므로 출발 상태가 늘어도 위반이 아니고, AC-22·AC-23 이 둘 다 롤백 수단을 요구하므로 선택이 아니다. 종결 상태 정의(seed:30)가 5종으로 확장돼 TaskStatus.isTerminal 이 그것을 답해야 한다.
    • Seed Spec 추가 개정 불요(7): 필요한 개정은 v3.3.0 에서 이미 완료됐다(status 9종 · AC-22 · AC-23). 이 문서의 어느 결론도 그 위에 새 문면을 요구하지 않는다.
  • BDD: B-07 은 쓰지 않는다 — 그 문면(「설정된 상한을 넘기면」)이 전제하는 상한을 두지 않기로 했으므로 쓰면 존재하지 않는 규칙을 단언하게 된다(flow §7.3 원칙). B-08 은 #77 소관이고 그 행이 이미 형태까지 바꿔 적었다. 대신 그 자리를 덮는 AC-22·AC-23 시나리오 3건을 Feature 4 에 추가했다 — 새 식별자 없음.
  • Impact:
    • docs/stop-and-limits-cocode.md 신설 · state-machine §2 81칸 확장(신규 전이 7종 · §2.3 재개 미부여 근거) · flow C-5·FP-105·FP-106·FP-107·B-07 갱신 · ux-spec UX-D-06 폐기 주석에 C-5 문자 그대로 인용 · bdd Feature 4 시나리오 3건 · architecture §3.1 에 CancelReason 결정 주석.
    • architecture §9 미결 22 의 선행이 섰다 — 「누가」 열의 「#71 의 중지 수단이 선행한다」가 해소됐고, 남은 것은 EditLockRegistry.withLeases 의 취소 통로 계약(Design).
    • ⬜ 새로 등재되는 미결: 편집 단계 상한을 AC 로 신설할지(판정 주체 프로젝트 오너 — B-07 지정 그대로).
  • Source: #71 인수조건 1~7 (판정 주체 Design, 단 방향은 D-032(오너)가 이미 정했다) · #77(잠금 축 인용) · D-055·D-056·D-057(유한성 근거)


D-060: AC-20 의 「응답 불가」 = DD-22 의 S1 ∪ S2 (#50 AC1 · PRD D-3 · bdd U-9) — 작업 가설

  • Date: 2026-09-13
  • Context: seed-spec AC-20 은 「그 상태가 감지되면」이라고만 쓰고 무엇을 응답 불가로 볼지 규정하지 않는다(PRD D-3, bdd §0.5 U-9). architecture §6.2 가 DD-22 로 해소안(S1 전송 종료 ∪ S2 유휴 초과)을 냈고 그 비준을 오너 결정 #112 로 올려 두었다. 감지 기준이 없으면 AC-20 은 기계적 판정이 불가능하고, #49(감지·전이 구현)는 착수할 입력이 없다.
  • Options:
    1. S1 ∪ S2 — 이벤트 스트림 하나만 보는 두 신호
    2. S1 ∪ S2 ∪ 연속 오류 횟수 — 오류가 N회 연속이면 응답 불가
    3. 백엔드별 신호 — HTTP 상태 코드·JSON-RPC 오류 코드를 각 백엔드가 해석
  • Decision: 옵션 1. S1(TransportClosed — 자식 프로세스 exit·stdio EOF·스트림 종료·인증 거부)과 S2(마지막 AgentEvent 이후 경과 > 유휴 상한) 두 신호만으로 판정한다. 전이 대상은 실패다. ⚠️ 이 채택은 작업 가설이다 — 비준 주체인 오너 결정 #112 에 결정 코멘트가 0건인 상태에서 #49 가 이 가설 위에 구현한다(실측: 2026-09-13).
  • Rationale: 대안 둘이 판정을 요구해서 탈락한다. ②는 「이 오류가 재시도 가능한가」를 먼저 판정해야 세어지고 그 판정이 곧 분쟁 지점이다 — 게다가 프로바이더 SDK 의 자체 재시도는 어차피 S2 의 시계 안에서 일어나므로 덮이지 않는 공백이 없다. ③은 백엔드마다 해석이 갈려 AC-20 이 백엔드별로 다른 조건이 된다(agent 에서 응답 불가인 상태가 acp 에서는 아닌 상태). ①만 AgentSession.events 라는 §3.1 의 유일한 관찰 지점 위에서 판정 개입 없이 성립한다. 가설로 진행하는 근거는 되돌림 비용의 비대칭이다 — 감지기를 만들지 않으면 AC-20 은 아예 판정 불가로 남고, 만들어 두면 기준이 바뀔 때 갈아 끼우는 것은 감지기 하나다(전이 경로는 기준과 무관하다).
  • Impact:
    • docs/unresponsive-detection-cocode.md §1 이 이 결정의 전문이다. #49 의 구현이 그 문서 §4 를 입력으로 받는다.
    • 가설임이 코드에 남는다 — 감지기 주석과 #49 PR 본문에 #112 미결이 인용된다. 오너가 뒤집을 때 영향 범위는 감지기 · bdd Feature 3 AC-20 Scenario Outline · architecture §6.2 넷뿐이다.
    • 연속 오류 횟수는 감지 기준에서 영구히 빠진다 — 재도입하려면 이 결정을 뒤집어야 한다.
  • Source: #50 AC1 (비준 주체 프로젝트 오너 — 결정 이슈 #112, 미결). architecture §6.2 DD-22 의 해소안을 그대로 채택.

D-061: 시간 상한 3종은 별개 설정 항목이고 유휴 상한은 AC-08 출하 기본값의 시드 재사용이다 (#50 AC2·AC3)

  • Date: 2026-09-13
  • Context: 상한이 셋 있는데(AC-08 자가 검증 시도 시간 상한 · AC-20 유휴 상한 · architecture §3.1 의 AgentRequest 요청 수준 타임아웃) 셋의 관계가 어디에도 없었다. architecture:527 은 유휴 상한을 AC-08 기본값의 시드 재사용으로 정했고 :623(§9 미결 2)은 두 값을 한 항목으로 묶었으나, §3.1 의 요청 수준 타임아웃은 :256 에서 「§9 미결 5 와 같은 취급」으로만 언급돼 나머지 둘과의 관계가 비어 있었다. 관계가 없으면 한 상한이 다른 상한을 가려 AC-20 이나 불변식 3 이 깨진다.
  • Options:
    1. 하나로 합친다 — 단일 「에이전트 타임아웃」 설정
    2. 별개 3종 + 크기 부등식 규칙(요청 < 유휴 < 시도 등)을 규정한다
    3. 별개 3종 · 부등식 없음 — 서로 다른 것의 경과를 재므로 애초에 같은 구간을 재지 않는다
  • Decision: 옵션 3. 셋은 재는 대상이 다르다 — 시도 1회의 경과 / 마지막 AgentEvent 이후 경과 / 한 요청이 미종결인 채 흐른 시간. 유휴 상한의 최초 값만 AC-08 출하 기본값을 시드로 받고, 그 뒤로는 독립 조정된다. 이 스파이크는 새 수치를 고르지 않는다 — 값은 §9 미결 2(오너 또는 Planning)·미결 5(Design)가 그대로 소유한다.
  • Rationale: ①은 불변식 3 을 깬다 — 사람의 승인을 기다리는 PermissionRequested 는 정상 상태(사람승인대기)인데, 합친 단일 상한 아래서는 그 대기가 곧 세션의 응답 불가가 된다. ②는 검사 시점이 없다 — 세 값이 모두 미정이라 부등식을 지금 검사할 수 없고, 부등식을 규칙으로 넣으면 설정 화면이 그것을 검증해야 하며 검증 실패 시 동작(거부? 보정?)이 또 하나의 미정이 된다. 게다가 부등식이 깨져도 틀린 결과가 나오지 않는다: architecture §6.2 의 「호스트가 미완료로 붙잡은 AgentRequest 는 S2 대상이 아니다」가 두 시계를 애초에 분리해 두었기 때문이다. 그 제외 규칙과 요청 수준 타임아웃은 대체재가 아니라 한 쌍이다 — 제외가 없으면 호스트의 지연을 에이전트 탓으로 돌리고, 요청 타임아웃이 없으면 제외된 요청에 아무 상한도 남지 않는다.
  • Impact:
    • docs/unresponsive-detection-cocode.md §3 의 표가 3종 관계의 SSOT 다.
    • 미정 동안의 동작이 차단 방향으로 못 박힌다 — 유휴 상한이 주어지지 않으면 감지기를 구성할 수 없고(기본값 없음), 0 이하·무한 값은 거부된다. 조용히 「무한」으로 도는 경로가 없다. #44 의 AgentRequestTimeouts 가 기본 생성자를 두지 않은 것과 같은 형태다.
    • 시드 원천은 이미 있다 — 20분(D-033, 오너 결정 2026-09-10). 따라서 AC-20 유휴 상한의 출하 기본값도 20분이며, 이것은 이 결정이 고른 수치가 아니라 시드 규칙의 파생값이다. #67 은 AC-08 기본값의 최초 선택을 자기 범위 밖으로 제외했지만, 그 값은 #67 이 아니라 오너가 D-033 으로 이미 만들어 뒀다.

      🔧 정정(2026-09-13) — 이 항목의 초판은 「시드 원천이 아직 비어 있다」고 적었다. architecture §9 미결 2 행이 D-033 이후 갱신되지 않아 낡은 상태였고 그 행만 읽고 판정한 것이 원인이다. 같은 PR 이 그 행도 「✅ 해소」로 갱신했다. 결론이 뒤집힌다 — AC-20 은 값이 없어 미충족인 것이 아니라 값이 있어 판정 가능하다.

    • 크기 부등식은 규칙이 아니라 값을 고를 때의 관찰로만 남는다 — permission 요청 타임아웃이 나머지보다 크다.
  • Source: #50 AC2·AC3. 값의 확정 주체는 architecture §9 미결 2(오너 또는 Planning, 이후 조정은 Planning — D-020)와 미결 5(Design) 문면 그대로이며 이 결정이 바꾸지 않는다.

D-062: ACP v2 라우팅 seam = 협상 직후의 와이어 어댑터 선택 한 지점 · 협상은 v1 고정 + fail-closed (#51 AC1·AC4)

  • Date: 2026-09-13
  • Context: architecture §9 미결 13 = prd D-10 = planning-inputs §5.3 이 「v2 라우팅 seam 의 구체적 설계 지점」을 Design 으로 넘겼다. seam 이 없으면 v2 가 왔을 때 분기가 호출 지점마다 흩어지고, 「v2 를 지원하는가」의 답이 코드 한 곳에 없게 된다.
  • Options:
    1. 전송 계층 아래(JSON-RPC 코덱)에서 가른다
    2. AgentRuntime 구현 전체를 버전별로 둔다
    3. 호출 지점마다 분기한다
    4. 협상 직후 와이어 어댑터를 고른다 — initialize 응답의 protocolVersion 으로 구현체 선택
  • Decision: 옵션 4. acp 안에 AcpWireAdapter 인터페이스를 두고 AcpWireV1 하나를 구현한다. 선택은 협상 직후 한 지점이며 그 위(계약)와 그 아래(전송)는 버전을 모른다. 협상은 v1 을 요청하고 응답이 != 1 이면 연결을 거부한다 — 자동 강등도 자동 승격도 없다.
  • Rationale: ①은 v1/v2 차이가 메서드 이름·페이로드이지 JSON-RPC 자체가 아니라서 코덱 안에서 다시 분기해야 하고 seam 이 둘이 된다. ②는 describeProviders()·연결 관리·자격증명 UI 처럼 버전과 무관한 코드까지 두 벌이 되어 조용히 갈라진다(#44 가 계약에서 직렬화를 배제했으므로 계약 표면에 버전이 새어 나올 자리가 애초에 없다). ③은 v2 추가 시 빠뜨린 자리를 컴파일러가 알려 주지 못한다. ④만 분기가 한 줄이고, 새 버전이 구현 추가로 끝나며, 빠뜨린 메서드가 컴파일 오류로 잡힌다. fail-closed 협상의 근거는 D-063 과 공유하는 범위 전제다 — 「v2 로 답할 수 있는 에이전트가 없다」는 디컴파일된 2종에만 참이고 나머지 30종은 미확인이므로, 미확인 에이전트가 v2 로 답하는데 그것을 v1 인 척 다루면 우리가 보내는 요청과 에이전트가 이해하는 요청이 어긋난 채 세션이 시작된다. 거부는 관찰 가능하고 조용한 오해는 그렇지 않다(#34 EnvironmentNotAllowed 와 같은 논리).
  • Impact:
    • docs/acp-integration-design-cocode.md §1 이 전문이다. #47 이 그 §5 를 입력으로 받는다.
    • 어댑터 구현이 하나뿐인데도 인터페이스를 두는 근거가 기록된다 — 미확인 30종이다. 「어차피 v2 는 없다」를 전제했다면 응답 버전을 확인할 이유조차 없다.
    • #48 이 4종 실기동 시 협상된 버전을 기록한다 — 그 기록이 미확인 범위를 줄인다.
  • Source: #51 AC1·AC4 (판정 주체 Design — architecture §9 미결 13 '누가' 열). 범위 전제는 planning-inputs §5.2 의 두 층위 표 그대로.

D-063: ACP 인증 흐름은 terminal·agent 두 가지를 각각 다루고, 능력 차이는 비활성 + 사유다 (#51 AC2·AC3)

  • Date: 2026-09-13
  • Context: architecture §9 미결 13 의 나머지 두 항목. 4종의 인증 방식이 갈리고(Claude Agent = terminal, Codex = agent — planning-inputs §5.1) 능력 조합도 서로 달라, 규칙이 없으면 한 에이전트에만 맞는 UI 가 나온다.
  • Options (인증): ①terminal 만 지원 ②agent 만 지원 ③둘 다 — 에이전트가 정하는 사실이므로 택일이 아니다
  • Options (능력 차이): ⓐ미지원 능력을 자동 대체(fork → 새 세션) ⓑ비활성 + 사유, 대체는 제시만 ⓒ능력이 부족한 에이전트를 목록에서 제외
  • Decision: 인증 ③ — 둘 다, 자리는 S-15. 완료 관측은 terminal 이면 프로토콜 재질의(initialize 재수행), agent 면 authenticate 의 JSON-RPC 응답이다. 능력 차이 ⓑ — 비활성 + 사유, 규칙 G-1~G-5(선언된 것만 참 · 축 사이 함의 없음 · 자동 대체 없음 · 대체는 제시만 · AC-06 을 좁히지 않음).
  • Rationale: 인증은 택일이 성립하지 않는다 — terminal/agent 는 제품이 고르는 것이 아니라 에이전트가 정하는 사실이고, 4종을 다 붙이려면 둘 다 있어야 한다(AC-06 의 「1개 이상」은 1종만 붙여도 충족되지만 A-05 의 검증 계획이 4종이다). 완료 관측을 터미널 출력 파싱으로 하지 않는 이유는 이 설계가 일관되게 피해 온 「분쟁 가능한 판정」이다 — 로그인 성공 문구는 에이전트마다 다르고 버전·지역화로 또 바뀐다. 프로토콜에 다시 물으면 그 답은 에이전트 자신의 것이라 판정이 없다. 능력 차이에서 ⓐ는 사용자가 fork 됐다고 믿는 상태를 만든다 — 이어진 맥락이 없는데 있다고 믿는 쪽이 기능 부재보다 나쁘다. ⓒ는 AC-06 ⓑ(「1개 이상 연결」)와 어긋난다 — loadSession 만 되는 에이전트도 연결된 에이전트다. ⓑ만 ux-spec §4.5 R3(「비활성화되고 사유가 붙는다」)의 기존 형태와 같고 사용자가 상태를 정확히 안다.
  • Impact:
    • docs/acp-integration-design-cocode.md §2·§3 이 전문이다. 화면 계약은 S-15(ux-spec §10) 이며 위젯은 셸 Epic #53 의 몫이다(S-16 과 같은 분업).
    • G-1 fail-closed 의 귀결: 능력이 미확인인 에이전트(Copilot CLI)는 전부 비활성으로 시작하고, #48 실측이 채우면 자동으로 넓어진다. 반대 방향(미확인을 지원으로 가정)은 없다.
    • ACP 에이전트의 자격증명은 보관하지 않는다 — CredentialVault(D-034·#46)는 agent 의 LLM 키만 담는다. 터미널 흐름에서 사용자가 친 것도, 에이전트 흐름의 토큰도 우리 저장소에 들어오지 않는다.
    • 인증되지 않은 에이전트는 목록에서 숨기지 않고 「인증 필요」로 표시한다 — 숨기면 사용자가 왜 없는지 알 수 없다. 위임 생성(S-05)에서 고르는 것도 막지 않는다(막으려면 S-05 가 인증 상태를 판정해야 하고 그 판정은 고르는 순간과 실행하는 순간 사이에 낡는다). 인증되지 않은 채 시작하면 §6.2 S1(인증 거부는 TransportClosed 에 포함)이 잡아 실패 로 전이한다.
    • 파일 IO·명령 실행은 degradation 대상이 아니다 — 클라이언트 능력은 에이전트의 능력이 아니라 우리의 의무다(§3.3). 단 ACP v1 fs 능력에 삭제·이름변경이 없어 ACP 경로로 들어올 수 있는 FileEditOperation 은 write 뿐이며, 삭제·이름변경의 처분은 #22·#37 소유 미결로 그대로 둔다.
  • Source: #51 AC2·AC3 (판정 주체 Design — architecture §9 미결 13). 능력 값의 출처는 planning-inputs §5.1 이고 실측 확인은 #48 이다.

D-064: 자격증명 저장은 pub 패키지 없이 세 OS 의 공식 도구·API 직접 호출 (#46 AC1 · architecture §9 미결 7)

  • Date: 2026-09-13
  • Context: architecture §9 미결 7(「자격증명 저장 패키지 선정」, Design, 구현 착수 시). DD-23 은 저장 위치를 OS 자격증명 저장소 3종으로 정했지만 무엇으로 부를지는 비워 뒀다. 제약이 둘 있었다 — ⓐ CredentialVault 를 소비하는 agent(L2)와 계약이 사는 core(L0)는 순수 Dart 이고 3-OS CI(verify.cocode-pure-tests.yaml)가 그 성질에 걸려 있다 ⓑ 유일하게 Flutter 를 들 수 있는 자리인 cocode_app(L3)에 손대면 DD-02a 완결성 테스트 축 2·3 이 활성화되어 계약 12종 부트스트랩 등록이 함께 요구된다(셸 Epic #53 산출물).
  • Options:
    1. flutter_secure_storage ^10.0.0 — 이미 워크스페이스에 있고(도너 package/core·app/cocode, win32 호환 override 까지 잡혀 있다) 세 저장소에 1:1 대응
    2. keyring/keyring_native(pub) — 순수 Dart 진입점
    3. 세 OS 의 공식 도구·API 직접 호출 — macOS /usr/bin/security · Linux secret-tool · Windows advapi32.dll(FFI)
  • Decision: 옵션 3. toolchain(L1, 순수 Dart)에 CredentialStoreBackend 포트를 두고 백엔드 3종을 붙인다. macOS·Linux 는 자식 프로세스의 표준 입력으로 비밀을 넘기고(단일 런처 DD-15 경유), Windows 만 FFI(CredWriteW/CredReadW/CredDeleteW)다.
  • Rationale: ①은 Flutter 플러그인이라 둘 자리가 없다 — 위 제약 ⓐⓑ가 동시에 막는다. ②는 다운로드 8건·좋아요 0·publisher 없음에 Rust 툴체인을 요구한다(2026-09-13 pub.dev 실측). 보안 경로에 검증되지 않은 신생 패키지를 넣는 것은 이 문서가 피해 온 방향이다. ③이 성립하는 근거는 실측이다: macOS security add-generic-password … -w(마지막 옵션)가 표준 입력에서 읽고(확인까지 2회), secret-tool store 도 표준 입력을 읽는다 — 즉 비밀이 argv 에 실리지 않는다(프로세스 목록은 같은 머신의 다른 사용자에게 보인다). Windows 만 예외인 이유도 실측이다: cmdkey 는 쓰기에 /pass: 로 값을 인자에 싣고, 무엇보다 저장한 비밀을 되읽는 기능이 없다 — 되읽지 못하면 프로바이더 키 조회 자체가 불가능하다.
  • Impact:
    • 구현은 toolchain 의 OsCredentialVault + 백엔드 3종. agent·core 는 순수 Dart 로 남고 3-OS CI 가 그대로 돈다. app/cocode 는 건드리지 않았다 — #53 의 부트스트랩 작업이 앞당겨지지 않는다.
    • 새 pub 의존은 ffi 하나(Dart 팀 공식, dart:ffi 위의 얇은 유틸 — 플랫폼 바인딩 없음).
    • macOS 는 값을 base64 로 감싸 넣는다. 실측: security find-generic-password -w 는 저장 바이트가 인쇄 가능한 ASCII 면 그대로, 아니면 접두사 없는 16진으로 찍는데 둘을 가르는 표지가 없다. 「짝수 길이 + 전부 16진 문자」로 추측하면 deadbeef 같은 정상 키를 오독한다 — 추측을 없애려고 항상 ASCII 가 되게 만든다.
    • 자격증명 CLI 는 집행 프로파일(agent)로 띄우지 않는다. AC-14 의 Given 은 「에이전트가 작업을 수행할 때」이고 이것은 호스트 자신의 설정 동작이며, 무엇보다 agent 의 경로 집행이 쓰기를 워크스페이스 안으로 가두는데 OS 자격증명 저장소는 워크스페이스 밖에 있어야 한다(DD-23 의 요구 자체). DD-23a 환경변수 화이트리스트는 프로파일과 무관하게 적용되므로 유출 축은 영향받지 않는다. ⚠️ enum 에 「호스트 동작」 행이 없어 집행을 끄는 값이 humanTerminal 하나뿐이다 — 이름이 어긋나지만 SandboxProfileRegistry(Epic #33)의 빠짐없는 switch 를 함께 바꿔야 해 값을 늘리지 않고 후속 관찰로 남긴다.
    • LaunchedProcess 에 stdin 이 추가됐다 — 비밀을 표준 입력으로 넘기려면 필요하고, #47 의 ACP JSON-RPC over stdio 도 같은 통로를 쓴다. #34 의 초판에는 없었다.
    • CREDENTIALW 레이아웃은 어느 OS 에서든 검증된다(sizeOf = 80 + 필드 13개 오프셋을 원시 바이트 센티널 스캔으로). 이 검사가 실제로 결함을 잡았다 — 초판이 Persist·AttributeCount 사이에 패딩이 있다고 가정해 88 을 기대했고, 둘은 연속된 DWORD 라 붙는다.
  • Source: #46 AC1 (판정 주체 Design — architecture §9 미결 7 '누가' 열). macOS·Windows CLI 동작은 이 워크트리 실측(2026-09-13, macOS 25.5), pub 후보 평가는 같은 날 pub.dev 조회.

D-065: AC-15 측정 규칙 — 환경·시나리오·표본 창은 확정, 목표치·백분위는 실측 뒤. 단일 10만 줄 픽스처는 합성한다 (#91)

  • Date: 2026-09-14
  • Context: planning-inputs §3.3 이 Planning 확정 대상으로 열거한 다섯(프레임 타임 목표치·백분위·측정 환경·시나리오·표본 창)이 전부 미정이었고, AC-15 의 픽스처인 「10만 줄 규모의 Dart 파일」을 어디서 얻는지도 정해져 있지 않았다. PRD NFR-A 가 "docs/ 8개 문서 전체 grep 결과 unibook 과 «10만 줄»이 함께 언급된 곳은 0건" 이라고 그 공백 자체를 기록해 둔 상태였다.
  • Options:
    1. 다섯을 한꺼번에 지금 확정한다
    2. 다섯을 전부 실측 뒤로 미룬다
    3. 정의(무엇을 재는가)는 지금, 값(어디서 끊는가)은 실측 뒤로 나눈다
  • Decision: 옵션 3. 측정 환경·시나리오·표본 창을 docs/ac15-measurement-rules-cocode.md §4.2~§4.4 로 확정하고, 프레임 타임 목표치와 백분위는 「미정 — 결정 필요(판정 주체: 프로젝트 오너 / 시점: #92 실측 분포 이후)」 로 남긴다. 단일 대용량 파일 픽스처는 결정론적 생성기로 합성한다(생성기를 커밋, 산출물은 커밋하지 않고 SHA-256 으로 고정). CH-032 「다수 생성 파일 동시 오픈」 시나리오는 측정 규칙 층위에서 복원한다.
  • Rationale:
    • ①은 이 프로젝트가 v1.1.0~v2.1.0 에 네 번 반복한 실패다(D-013·D-015·PRD 「재도입 금지 값」). ②는 D-020 을 과잉 적용한다 — 그 결정이 막은 것은 "실측 분포를 보기 전에 숫자를 정하는 것" 이지 「무엇을 재는가」의 정의가 아니다. 정의가 없으면 분포 자체가 의미를 갖지 못한다.
    • 픽스처 합성은 실측이 강제한 결론이다. 참조 워크스페이스 coco-de/unibook @ 9db9015(D-019)를 실체크아웃해 재보니 10만 줄 이상인 단일 .dart 파일이 0개이고 최대가 27,759 줄(목표의 27.8%)이다. 상위 4개를 이어붙이면 95,323 줄로 산술은 맞지만 중복 import·중복 선언으로 파싱이 깨져 A-03 이 재려는 경로(파서·하이라이터·LSP)를 타지 않는다.
    • 산출물을 커밋하지 않는 이유는 check_large_files.py 의 첫 문단 그대로다 — 2.62 MiB 는 50 MiB 상한에 안 걸리지만 한 번 들어온 blob 은 지워도 히스토리에 남는다. 생성기 + 고정 파라미터 + SHA-256 이면 재현에 필요한 것이 전부다.
    • CH-032 복원은 새 요구가 아니라 원상회복이다. v1.1.0 이 그 반론의 해소책으로 "단일 대용량 파일 + 다수 생성 파일 동시 오픈 두 시나리오 모두" 를 채택했고, v2.0.0/v3.0.0 재작성에서 유실됐다(PRD NFR-A 가 그 유실을 기록). 다만 복원 범위는 측정 규칙 층위까지다 — Seed Spec §4 본문 복원은 불변 층위 개정이라 오너 소관으로 남긴다.
  • Impact:
    • 신규 산출물 3종: docs/ac15-measurement-rules-cocode.md · scripts/spike/measure_dart_fixture_inventory.py · scripts/spike/generate_large_dart_fixture.py. 원본 데이터 scripts/spike/dart_inventory_unibook_9db9015.json.
    • planning-inputs §3.2 의 「줄 수 재검증」 항목이 닫혔다 — 실측 결과 .dart 총 1,578,533 줄 / 9,713 파일 / 55,655,722 B, 바이트/줄 35.26. 기존 ~38 추정은 총 줄 수를 7.2% 적게 본다. 파일 수 차이(9,931 → 9,713)는 오류가 아니라 기준 커밋 차이이고, 고정 SHA 가 있는 이 표를 앞으로 인용한다.
    • A-03 측정의 채널 한계가 명시됐다. D-025 가 D-022(master 채널)를 뒤집어 측정은 stable 3.47.3 에서 이뤄진다. sandbox-limits 와 달리 프레임 타임은 Flutter 엔진의 성질이므로 「채널 무관」이 아니라 명시된 한계다 — master 가 flutter#128575 를 고쳤는지는 이 측정이 답하지 않으며, #92 는 그것을 ➖ unavailable 로 적고 결과가 ❌ 여도 재측정 없이 「엔진 탓」이라 적지 않는다.
    • 합성 픽스처의 한계 셋(심볼 다양성·import 그래프 부재·.freezed.dart 형태 부재)은 전부 과소 추정 방향이다 — 나쁜 수가 나오면 실물은 더 나쁘고, 좋은 수는 실물의 보증이 되지 않는다. #92 결론에 이 비대칭을 명시한다.
    • ⏸️ 미정으로 남긴 것 2건: ⓐ 프레임 타임 목표치·백분위(#92 분포 뒤, 오너) ⓑ Seed Spec §4 AC-15 본문에 CH-032 시나리오를 되살릴지(오너). ⓑ 가 닫히지 않으면 AC-15 의 기계적 판정은 S1 만으로 이뤄지고 S2 는 근거 자료로만 남는다.
  • Source: #91 인수 조건 5종. 실측은 이 워크트리(2026-09-14, macOS 25.5, Dart 3.13.3 / Flutter 3.47.3) — unibook blobless clone → 9db9015 체크아웃 → measure_dart_fixture_inventory.py.

D-066: A-03 — 자체 렌더링 텍스트 레이어로 분기하지 않는다(유보). 프레임 CPU 작업이 문서 크기에 평평함을 실측 (#92)

  • Date: 2026-09-14
  • Context: A-03("re_editor 기반 에디터 코어가 AC-15 의 시나리오를 실사용 가능한 성능으로 지원한다")은 Unverified 이면서 MVP 게이팅 리스크였고, Seed Spec §5 가 실패 시 분기를 "자체 렌더링 텍스트 레이어 검토" 로 미리 지정해 두었다. 원인이 flutter#128575(엔진 이슈)면 자체 노력으로 해결 불가일 수 있다는 것이 그 조항의 배경이다.
  • Options:
    1. 절대 프레임 타임을 못 재므로 판정을 미룬다(데스크톱 러너가 생길 때까지)
    2. flutter test 로 잰 수를 프레임 타임이라 부르고 통과로 적는다
    3. 기울기를 재고(문서 크기 대비 프레임 CPU 작업) 그것으로 분기 조건만 판정한다 — 절대 지연은 ➖ unavailable 로 남긴다
  • Decision: 옵션 3. 자체 렌더링 텍스트 레이어로 분기하지 않는다. 단 이것은 A-03 의 해제가 아니라 유보이며, 재검토 조건 3종을 명시한다(docs/ac15-responsiveness-cocode.md §5).
  • Rationale:
    • ②는 이 문서 전체가 경계해 온 실수다 — docs/sandbox-limits-cocode.md §1.2 가 "재지 못한 것과 재서 통과한 것을 섞으면 그 표는 근거가 아니라 인상이 된다" 로 같은 자리를 못박았다.
    • ①은 과잉이다. 분기 조건 자체가 기울기의 진술이기 때문이다 — A-03 이 두려워한 것은 "프레임마다 문서 전체를 훑는 경로"이고, 그 존재 여부는 절대 지연 없이도 판정된다.
    • 실측(macOS · 3회 × 조작당 30회차): 문서를 1,000 → 100,000줄로 100배 늘려도 스크롤 2.98 → 2.32 ms, 커서(34줄) 4.29 → 4.56 ms, 선택(34줄) 4.38 → 4.93 ms 로 전부 같은 자리에 머문다. re_editor 의 CodeLines 가 줄 목록이 아니라 세그먼트 구조라 비용이 줄 수에 걸리지 않는다.
    • JIT 라서 비관적인 것은 절대값이고 기울기가 아니다 — JIT/AOT 는 상수배이지 복잡도를 바꾸지 않는다. 그래서 이 회차로 기울기를 판정하는 것은 성립하고, 절대값을 판정하는 것은 성립하지 않는다.
    • 자체 렌더링은 조합 입력(AC-16)·접근성·선택/스크롤을 전부 다시 만드는 일이라, 근거 없이 착수하면 MVP 를 통째로 밀어낸다. 착수하지 않을 근거가 실측으로 생겼다.
  • Impact:
    • 신규 산출물: docs/ac15-responsiveness-cocode.md · package/editor/benchmark/ac15_responsiveness_bench.dart · 원본 데이터 scripts/spike/ac15_bench_macos_run{1,2,3}.json.
    • AC-15 는 PENDING 그대로다. D-065 §4.5 대로 목표치·백분위가 미정이라 이 회차가 MET 을 만들 수 없다 — 이 문서는 오너 결정의 입력이다.
    • 크기에 끌려가는 비용이 하나 있다 — 최초 적재. 10만 줄 122 ms(≈1.22 µs/줄, 선형). 프레임 비용이 아니라 파일을 여는 순간 1회이고, 첫 프레임 자체는 크기 무관(17~29 ms)이다.
    • ⭐ CH-032 복원(D-065 §5)이 실제로 값을 냈다. 전 측정에서 꼬리가 두꺼운 값은 S2 탭 전환 하나다(중앙값 7.5 ms / 최대 32 ms, 중앙값의 4배). 단일 대용량 파일만 봤으면 이 수는 보고서에 없었다 — v3.0.0 재작성의 시나리오 유실이 관측 손실이었음이 수로 확인됐다. 반면 「버퍼가 많으면 느려진다」는 관측되지 않았다(버퍼 20개를 살려 둔 상태의 스크롤이 단일 파일과 같다) — 비용은 버퍼 수가 아니라 전환 그 자체에 있다.
    • 🔴 상류 결함 발견: re_editor 0.10.0 의 moveCursorToPageDown()·moveCursorToPageUp() 이 본문 없이 // TODO 다(_code_line.dart:492). PageUp/PageDown 이 동작하지 않는다. AC-15 의 세 조작에 없어 이 판정은 바뀌지 않지만 기능 결함이므로 후속 이슈로 분리한다(상류 수정 vs CocodeEditorView 층 대체 구현은 별개 설계 결정).
    • 측정 함정 3종을 기록했다(같은 문서 §4): ⓐ 코드 표면 위 포인터 드래그는 스크롤이 아니라 텍스트 선택이다(초판에서 오프셋 0.0→0.0) ⓑ edit(TextEditingValue) 는 현재 줄 전체를 교체한다 ⓒ 워밍업을 버리지 않으면 첫 크기가 JIT 를 혼자 물어 거짓 기울기가 나온다(첫 프레임 376 ms vs 이후 17~29 ms). 셋 다 초판이 실제로 밟았고, 증거 필드(scroll_moved·cursor_index_after·selected_lines)가 없었으면 no-op 의 시간을 성능으로 보고했을 것이다.
    • ➖ 못 잰 축(통과가 아니다): GPU 래스터화 · 합성 총 지연 · profile 모드 AOT · Windows·Linux · master 채널(flutter#128575 수정 여부).
  • Source: #92 인수 조건 5종. 실측은 이 워크트리(2026-09-13T21:49Z, macOS 26.5.2 arm64 24-core, Dart 3.13.3 / Flutter 3.47.3). AC-3 의 「master 채널」은 D-025 가 D-022 를 뒤집어 전제가 사라진 항목이며, sandbox-limits 와 달리 프레임 타임은 엔진의 성질이므로 「채널 무관」이 아니라 명시된 한계로 적었다(D-065 §4.2).

D-067: AC-16 측정 규칙 4종 확정 · 코퍼스를 리포에 커밋 · 재사용 컴포넌트 결함은 재현되며, 우리 가드는 아직 배선되지 않았다 (#93)

  • Date: 2026-09-14
  • Context: A-04(한글 IME)는 Unverified 이면서 반증 사례가 있는 MVP 게이팅 리스크다. planning-inputs §4.2 가 순서를 규정했다 — "자체 구현 전에 재사용 컴포넌트에서 결함 재현 여부를 먼저 확인". 그 확인 없이는 macOS 스파이크(#94)의 결과를 해석할 근거가 없다.
  • Decision: ① 측정 규칙 4종(입력기·코퍼스·시행 횟수·관찰 방법)을 docs/ac16-measurement-rules-cocode.md §2 로 확정하고 코퍼스·기대값을 scripts/spike/ime_corpus/ 에 커밋한다. ② 재생 하니스 2종(터미널·에디터)을 benchmark/ 에 둔다. ③ 「플랫폼별 검증 시점」은 미정 — 결정 필요(프로젝트 오너) 로 남기고 제안만 적는다. ④ Windows·Linux 검증이 출시 조건임을 Planning 재량 밖으로 명시한다.
  • Rationale (재현 확인 결과가 핵심이다):
    • 🔴 xterm2 5.2.0 에 Lumide #64 의 원인 경로가 그대로 있다. 소스 실측: CustomTextEdit(IME 입력 연결)에 onKeyEvent: _handleKeyEvent 가 붙는데, 그 핸들러 본문(terminal_view.dart:670–:739)에 _composingText 참조가 0건이다(필드는 :245 선언 · :449 렌더링 · :667 세터 — 세 곳 전부 핸들러 밖). 조합 중에도 _printableTextForKeyEvent(:763 — event.character)가 두벌식 자판의 호환 자모를 꺼내 terminal.keyInput(text:) 로 PTY 에 보내고 handled 를 돌려준다. 이슈가 보고한 코드포인트 집합(U+314E·U+314F·U+3134·U+3131·U+3161·U+3139)이 정확히 그것이다. 5.2.0 은 이슈 등록 18일 전 릴리스이며 이후 판이 없다 — 미수정이다.
    • ✅ 우리는 재사용하지 않았다. terminal pubspec 에 xterm2/flutter_pty2 가 없고, _onKey 첫머리에 xterm2 가 갖지 않은 if (_composing.isComposing) return .ignored; 가 있다. 재생 하니스가 양방향으로 확인했다 — 조합 중 누출 0 바이트, 대조군(조합 아닐 때 같은 키)은 통과. 대조군이 없으면 「막혔다」가 가드 때문인지 하니스가 키를 못 보낸 것인지 구분되지 않는다.
    • 🔴 그러나 그 가드는 아직 한 번도 발동할 수 없다. 리포 전수 조회: CocodeComposingController 를 쓰는 곳이 테스트 3곳뿐이고, package/cocode_*·app/cocode/lib 에 TextInputClient/DeltaTextInputClient 구현이 0건이다. 실기기에서 isComposing 은 항상 false 이므로 _onKey 의 default 가지가 event.character 를 그대로 커밋한다 — xterm2 와 같은 코드 형태이고 같은 결과다.
    • 에디터는 사정이 다르다 — re_editor 0.10.0 이 _code_input.dart 에 자체 TextInputClient 를 갖고 composing 을 직접 다룬다(setComposingRect 포함). 표면별로 위험이 비대칭이라는 사실 자체가 #94 설계의 입력이다.
    • 코퍼스를 7시행으로 짠 이유: t3-jongseong-migration(간 → 가나)이 조합 중 앞 글자가 바뀌는 경우를 잡는다. 단순 조합만 재는 코퍼스는 앞 글자를 이미 커밋한 구현을 통과시킨다. t2 는 이슈 본문의 입력 문자열 그대로다.
    • 관찰에 코드포인트 목록을 넣은 이유: Compatibility Jamo(U+3131–U+318E)와 Hangul Jamo(U+1100–U+11FF)의 구분이 원인을 가른다(전자 = 조합 엔진 미동작, 후자 = preedit 조기 커밋). 텍스트만 비교하면 이 구분이 사라진다. 하니스는 fail:lumide-64-class 와 fail:early-commit 을 다른 판정으로 낸다.
  • Impact:
    • 신규 산출물: docs/ac16-measurement-rules-cocode.md · scripts/spike/ime_corpus/{corpus,expected}.json · 하니스 2종 · 재생 결과 JSON 2종.
    • ⭐ #94 의 첫 과제가 바뀐다. 「macOS 에서 통과하는가」가 아니라 「IME 연결(TextInputClient)을 터미널에 배선하는 것」 이다. 배선 없이 실기기를 돌리면 결과는 ❌ 로 나오며, 그 ❌ 를 「자체 구현도 실패했다」로 읽으면 원인을 잘못 짚는다.
    • 입력기·배포판을 못박았다: macOS 내장 2-Set · Windows MS-IME · Linux ibus-hangul on Ubuntu 24.04 LTS(fcitx5 는 범위 밖). Wayland 기본, 실패 시 X11 을 대조군으로.
    • 시행 횟수: 재생 1회(결정론) · 실기기 3회, 3회 중 한 번이라도 실패하면 불통과(다수결 아님).
    • ⏸️ 미정 1건: Windows·Linux 검증 시점. 제안은 「표면이 셸에 통합돼 3종 릴리스 빌드가 나오는 시점」이며, 근거는 IME 가 임베더와 표면의 배선에 걸린다는 위 관찰이다. 늦추는 결정이 출시 조건을 완화하지는 않는다.
    • 부수: Lumide #64 본문의 별개 보고(Nerd Font Mono 변형이 advance 를 단일 셀로 강제해 한글이 찌그러진다 — D2Coding Nerd Font 3.4.0 에서 일반 2.0 / Mono 1.0)는 이 리포가 이미 check_mono_font_grid.py(#55)로 잠근 축이다. 그 가드가 필요한 이유의 외부 실사례로 기록만 하고 새 작업을 만들지 않는다.
  • Source: #93 인수 조건 5종. xterm2 소스 실측은 pub.dev 아카이브 xterm2-5.2.0.tar.gz(2026-09-14 내려받음), 이슈 본문은 gh issue view 64 --repo SoFluffyOS/lumide(state OPEN).

D-068: 터미널 플랫폼 IME 연결 신설 — 확정 추적은 오프셋이 아니라 내용으로 (#94)

  • Date: 2026-09-14
  • Context: D-067(#93)이 공백을 지목했다 — 터미널의 I2 가드(조합 중 → ignored)는 있는데 그것을 발동시킬 TextInputClient 가 리포에 0건이라, 실기기에서는 isComposing 이 영원히 false 이고 두벌식 자판의 호환 자모가 그대로 PTY 로 간다(Lumide #64 와 같은 결과). #94 의 첫 과제는 측정이 아니라 배선이었다.
  • Decision: CocodeTerminalImeConnection(terminal/lib/src/input/)을 신설하고 CocodeTerminalView 가 포커스 연동으로 열고 닫는다(enableIme 기본 true). 확정 텍스트 추적은 오프셋이 아니라 내용으로 한다. 실기기 대조를 위해 onImePlatformValue 캡처 탭과 scripts/spike/verify_ime_capture.py 를 함께 둔다.
  • Rationale:
    • 왜 에디터처럼 하지 않나: re_editor 는 문서 전체를 TextEditingValue 로 들고 플랫폼과 주고받지만 터미널에는 그럴 「문서」가 없다 — 친 것은 즉시 자식에게 흘러가고 우리 쪽에 남지 않는다. 그래서 조합 구간만 들고 확정 즉시 플랫폼 버퍼를 비운다(비우지 않으면 버퍼가 세션 내내 자라고 후보창 위치가 밀린다).
    • enableIme 기본이 켜짐인 이유: 꺼진 쪽이 위험하다. 꺼져 있으면 I2 가드가 한 번도 발동하지 않는다 — 그것이 D-067 이 기록한 그 상태다. 끄는 자리는 조합 상태를 밖에서 밀어 넣는 계측기뿐이다.
    • 🔴 확정 추적을 내용으로 바꾼 것은 실측이 강제했다. 초판은 오프셋(_committedLength)으로 들었고, 플랫폼 채널 재생이 t6-mixed-latin-hangul 에서 echo echo 한 을 냈다(writes: ['echo ', 'echo ', '한']). 원인: 확정 뒤 버퍼를 비우며 길이를 0 으로 되돌리는데, 비움이 반영되기 전의 값(이전 누적 텍스트를 이어 보내는 값)이 오면 확정 접두사를 다시 보낸다. 내용 기준이면 두 스트림이 같은 식으로 풀린다 — 이미 보낸 것으로 시작하면 그 뒤만, 아니면 전체를(비움 후에는 확정 접두사가 비어 있어 no-op).
    • ⭐ 이 결함은 컨트롤러 모드에서 보이지 않았다. 하니스가 컨트롤러를 직접 밀면 플랫폼 버퍼가 없고 「비움과 지연 반영」이라는 상태 자체가 존재하지 않는다. 제품 경로로 한 번 더 재는 것(모드 B)이 왜 필요한지의 실사례다.
    • 모드 B 판정에 ime_attached 를 넣었다 — 연결이 안 열렸는데 「누출 0」을 통과로 적으면 D-067 이 기록한 배선 부재를 성공으로 오독한다. 미부착은 inconclusive:not-attached 로 따로 낸다.
    • 캡처 탭을 콜백으로 둔 이유: cocode_*/lib 의 dart:io 쓰기는 DD-15 허용 목록 밖이다(check_dart_io_write_allowlist.py). 기록 방법은 부르는 쪽이 정한다.
  • Impact:
    • 신규: CocodeTerminalImeConnection(배럴 export) · CocodeTerminalView.enableIme·onImePlatformValue · 테스트 12건 · scripts/spike/verify_ime_capture.py · docs/ac16-macos-spike-cocode.md.
    • terminal 테스트 63 → 75건. 재생 하니스는 2모드(컨트롤러·플랫폼 채널)로 늘어 14 시행.
    • FakeTerminalBackend 에 writtenText 게터 추가(기존 written 리스트는 그대로 — 조각 경계가 확정 경계의 증거라 리스트를 유지한다).
    • ➖ 못 잰 것: macOS 2-Set 입력기가 정말 이 델타 열을 내는가(사람 1회 캡처 필요) · preedit 의 시각적 구분(I1) · macOS 데스크톱 러너 실행(app/cocode 에 cocode_* 의존을 넣으면 DD-02a 축 2·3 이 활성화된다 — D-064 Context, 셸 통합 스토리 소관).
    • A-04 는 Unverified 유지. 재생 통과는 「우리 코드가 규정된 델타를 옳게 다룬다」이지 「입력기가 그 델타를 낸다」가 아니다. 다만 반증 사례의 경로가 우리 코드에 없음이 제품 경로로 확인됐고 남은 미지수가 하나로 좁혀졌다.
    • AC-16 은 6조합 중 macOS 2조합만 재생 수준에서 닫혔다. D-016 이 정한 것은 순서이지 출시 조건의 축소가 아니다.
  • Source: #94 인수 조건 4종. 실측은 이 워크트리(2026-09-14, macOS 26.5.2 arm64, Dart 3.13.3 / Flutter 3.47.3).

D-069: AC-16 은 6조합 전부 미충족 — 코드 경로는 3-OS 로 닫고, 입력기 축은 unavailable 로 남긴다 (#95)

  • Date: 2026-09-14
  • Context: AC-16 은 3플랫폼 × 2표면 = 6조합을 Must 로 잠갔고(BDD Feature 2 Examples 6행), #94 가 macOS 2조합을 「재생 통과 · 입력기 미실측」으로 닫았다. 이 Story 는 Windows·Linux 4조합을 같은 기준으로 처리해야 했다.
  • Options:
    1. 3-OS 재생 CI 의 초록불을 AC-16 충족으로 선언한다
    2. 실기기 입력기를 못 재므로 아무것도 하지 않고 4조합을 미착수로 둔다
    3. 재는 축과 못 재는 축을 나눠 재는 축(코드 경로)을 3-OS 로 닫고, 못 재는 축(입력기)을 unavailable 로 명시한 뒤 6조합 전부 출시 조건 미충족으로 적는다
  • Decision: 옵션 3. probe.ac16-ime-replay.yaml(3-OS 매트릭스)을 신설해 두 표면의 재생을 ubuntu-24.04·macos-15·windows-2022 에서 돌리고, 6조합 취합표를 docs/ac16-windows-linux-cocode.md 에 둔다. 여섯 조합 전부 ❌ 미충족이다.
  • Rationale:
    • ①은 이 문서 집합이 반복해 경계한 실수다 — docs/sandbox-limits-cocode.md §1.2: "재지 못한 것과 재서 통과한 것을 섞으면 그 표는 근거가 아니라 인상이 된다." AC-16 의 Given 은 "한글을 조합 입력하면" 이고, 조합 입력이 없었으면 Given 자체가 성립하지 않는다.
    • ②는 실제로 닫을 수 있는 것을 남겨 둔다. AC-16 의 실패 조건 중 중복 입력·커서 위치는 우리 코드가 조합 델타를 어떻게 다루는지의 함수이고, 그 코드가 플랫폼마다 갈리지 않는지는 3 OS 에서 같은 답이 나오는 것으로만 확인된다 — verify.cocode-pure-tests.yaml 이 desktop_platform 을 3-OS 로 도는 이유("동일성은 세 OS 에서 같은 답이 나오는 것으로만 확인된다")와 같은 논리다.
    • 이 Story 가 바꾼 것은 미지수의 개수다 — 「코드가 플랫폼마다 다른가」와 「입력기가 무엇을 내는가」 둘이었던 것이 하나로 줄었다.
    • 왜 입력기를 못 재는가(6조합 전부): ⓐ 조합은 물리 키를 입력기가 해석하며 시작되는데 러너에 입력기가 없고, 설치해도 조합을 자동으로 칠 방법이 없다 — 합성 키 이벤트는 입력기를 지나지 않고 앱에 바로 도착한다(그것이 정확히 Lumide #64 의 결함 경로다) ⓑ 표면을 띄우려면 app/cocode 가 cocode_* 를 의존해야 하고 그 순간 DD-02a 완결성 테스트 축 2·3 이 활성화된다(D-064 Context — 셸 통합 스토리 소관) ⓒ 이 세션의 측정 환경은 macOS 1대다.
    • 워크플로에 branches: 필터를 두지 않은 이유: 두면 story→epic PR 에서 돌지 않는다(실측: branches 없는 워크플로 8종이 계층 PR 에서 돈다 — _golden_test·claude·cleanup-pr-caches·golden-autofix-cleanup·probe.sandbox-limits·probe.windows-atomic-replace·verify.cocode-pure-tests·verify.linux-build).
    • codegen 스텝을 두지 않은 이유: cocode_* 패키지는 생성 코드를 쓰지 않는다(실측 part '*.g.dart' 0건 · 디스크 *.g.dart·*.freezed.dart 0건). verify.linux-build.yaml 이 codegen 을 도는 것은 도너 feature/** 때문이다.
  • Impact:
    • 신규: .github/workflows/probe.ac16-ime-replay.yaml(3-OS · 게이트 아님 · required check 아님) · docs/ac16-windows-linux-cocode.md(6조합 표).
    • AC-16 PENDING · A-04 Unverified 유지. 6조합 전부 출시 조건 ❌ 미충족.
    • 남은 절차는 플랫폼당 사람 1회 캡처(D-068 의 onImePlatformValue 탭 + verify_ime_capture.py). diff 가 나오면 고치는 쪽은 코퍼스다.
    • ⛔ 완화 경로를 문서에 닫았다 — 「3-OS CI 초록이니 충분하다」·「D-016 이 macOS 먼저랬다」·「입력기는 OS 몫」·「macOS 가 됐으니 나머지도 될 것」 네 가지가 왜 성립하지 않는지를 §4 표에 적었다. 특히 넷째: Windows IMM32 와 macOS NSTextInputClient 는 조합 모델이 다르다.
  • Source: #95 인수 조건 3종. 3-OS 실행 결과는 워크플로 아티팩트(ac16-ime-replay-{Linux,macOS,Windows})와 job summary 가 정본이며, 문서는 그것을 인용하고 수치를 복제하지 않는다(복제하면 다음 실행에서 조용히 낡는다).

D-070: A-02 실측 설계 — 표본 수는 규칙 유도 96건, ⓑ 귀속은 EditSnapshot, 합격 기준은 실측 뒤. AC-09 구멍과 ⑤ 비율은 미결 구현 결정에 달렸다 (#96)

  • Date: 2026-09-14
  • Context: A-02 는 이 문서 집합에서 가장 부담이 큰 가정이자 C-04 전환 조항의 해제 조건이다. 표본 수·합격 기준·ⓑ 정의가 전부 미정이라 설계부터 시작해야 했고, "내생적 실패 모드는 제3자 주입 결함으로 측정되지 않는다" 는 것이 ⓐ/ⓑ 분리의 이유다.
  • Decision:
    • 표본 수: 클래스당 96건(ⓐ 96 + ⓑ 96). Wilson 정규근사 역산 n ≥ z²p(1−p)/w² 에서 z=1.96·최악 p=0.5·w=0.10 → 96.04. 불가 시 하한 43(w=0.15)이며 그 귀결을 판정과 함께 적는다(반폭 ±15%p 면 구간이 그럴듯한 기준 대부분을 가로질러 「기준을 넘었는가」에 답할 수 없다).
    • ⓑ 「에이전트가 만든 결함」 정의(확정): B1 그 파일이 이 작업의 editedPaths 에 있다 ∧ B2 EditSnapshot 의 pre 상태에 같은 결함이 없다 ∧ B3 ⓐ 주입 대장과 서로소.
    • 합격 기준: ⏸️ 미정 — 결정 필요(오너 / 시점: 실측 분포 이후, D-020). ⓐ·ⓑ 의 결합 규칙도 함께 정해야 하며 그것이 빠지기 쉬운 자리임을 명시.
    • ⓑ 오라클은 에이전트가 돌리지 않은 보류 검사(전량 분석 + 전체 테스트 + 빌드)다.
  • Rationale:
    • 표본 수는 지어내지 않고 역산했다. PRD R-4 가 금지한 결함 30건·검출률 80% 는 이 문서 어디에도 목표치로도 참조점으로도 쓰지 않는다. w 는 이 설계가 고른 값이지만 «검출률을 ±10%p 안에서 말할 수 있어야 기준 비교가 성립한다» 는 요구에서 나왔고 넓히면 무엇을 잃는지가 함께 적혀 있다 — 값이 아니라 규칙과 귀결을 고정한 것이며 D-033·D-044 가 보존 정책 값에서 쓴 방식과 같다.
    • ⓑ 귀속에 EditSnapshot 을 쓰는 이유: C-02 가 "EditSnapshot 을 남기지 않는 에이전트 편집 경로는 존재하지 않는다" 를 보장하므로 모든 편집에 pre 상태가 있다 — 새 추적 장치를 만들지 않고 이미 불변식인 것에 얹는다.
    • 「드러낸 결함」(revealed)을 0 으로 접지 않는다. 에이전트 편집이 잠복 버그를 깨운 경우는 책임 귀속이 논쟁적이라 별도 집계한다. 한쪽으로 접으면 그 선택이 검출률을 조용히 움직인다.
    • 결함 카탈로그에 「어느 방법도 못 잡는 결함」을 반드시 넣는다. 없으면 검출률이 «우리 방법이 잡도록 만든 결함을 우리 방법이 잡는가» 라는 동어반복이 된다.
  • Impact:
    • 🔴 가장 큰 발견 — AC-09 구멍과 C-04 ⑤ 비율이 «아직 안 내린 구현 결정»에 달렸다. 참조 워크스페이스 3,455 작업(first-parent 180일, D-044 와 같은 프록시)에서:

      #63 실행자 범위 ④ 가 편집 검사 구멍(AC-4) ⑤ 개방(AC-5)
      워크스페이스 전체 73.6% 26.4% 0.0%
      편집 범위로 한정 73.6% 3.7% 22.6%

      두 비율이 정반대로 움직이므로 단일 값으로 적을 수 없다 — 범위로 적는 것이 정확한 보고다. 실행자는 아직 없다(실측: implements VerificationMethodExecutor 가 테스트 fake 1건뿐).

    • 구멍의 구조를 사유 코드 표로 특정했다 — ①build_no_configured_target·②test_zero_collected 는 워크스페이스 성질, ③no_debug_session 은 세션 성질이고, ④file_not_analyzed 하나만 편집 경로에 의존한다. 그래서 ④가 대상없음 인데 ①②가 통과 면 판정은 통과이고 그 통과는 편집한 파일을 열지 않았다. 실측 사례: CI yaml 만·규칙 문서만·아이콘 png 만 고친 작업. 편집 범위로 좁혀도 0 이 되지 않는다(3.7% — 빌드 구성만 고친 작업).

    • 그 성질을 a02_loophole_demonstration_test.dart(6건)로 고정했다. ⚠️ 버그로 고치면 안 된다 — 고치는 것은 AC-09 의 개정이며 불변 층위 사항이다. 성질이 바뀌면 이 테스트가 먼저 깨지고 설계 문서도 함께 고쳐야 한다는 신호가 된다. 대조군(④가 실패면 ①②가 통과해도 차단)을 함께 둬 「판정기가 늘 통과를 낸다」와 구분했다.

    • 26.4%/3.7% 는 노출 «면적» 이지 발동률이 아니다 — 혼동하지 않는다. 발동은 ⓐ 카탈로그에 ④가 보지 않는 파일의 결함을 넣어 실제로 잰다.

    • 전환 조항의 귀결: A02VerificationState 네 값 중 met 하나만 자가검증을 열고, 현재는 criteriaUnset 이다. ⚠️ 실측 — met 을 구성하는 코드가 리포에 0건이라 지금은 어떤 경로로도 자가검증 기본값이 열리지 않는다.

    • 이 설계가 못 재는 것: 오라클이 못 찾는 결함은 분모에 없다(검출률 과대 추정) · ⓑ 표본은 만들 수 없어 회차를 나눠야 한다 · ③ 은 디버그 세션이 있어야 잰다 · ⓐ/ⓑ 검출률의 실행 자체는 #63 실행자 구현 이후다.

  • Source: #96 인수 조건 6종. 실측은 이 워크트리(2026-09-14) — unibook @ 9db9015 first-parent 180일 3,455 작업, 분류 규칙은 docs/verification-no-target-cocode.md §5 폐쇄 5종 사유 코드(D-053).

D-071: A-06 「설치 용량」은 두 정의로 잰다 — 두 값이 참조점의 반대편에 떨어진다. 목표치는 세우지 않는다 (#97)

  • Date: 2026-09-14
  • Context: A-06 은 목표치가 미확정이고 참조점은 Lumide 0.20.0 의 설치 55.8MB 하나뿐인데 원 기재에 무엇을 잰 값인지가 없다(planning-inputs §6). 콜드 스타트·메모리에는 참조점이 아예 없다. 그 상태로는 어떤 실측치도 해석되지 않는다.
  • Decision: 「설치 용량」을 A 배포 아카이브 / B 설치 후 디스크 점유 둘로 나눠 둘 다 재고, 비교할 때 어느 정의인지 반드시 밝힌다. 측정 대상은 app/cocode 가 아니라 처음부터 만든 최소 셸 3층(bare / bare+agent / bare+shell)이다. 목표치는 세우지 않는다.
  • Rationale:
    • 🔴 정의를 나눈 것이 실측으로 정당화됐다. 최소 셸은 정의 A 로 25.7 MB(−54%), 정의 B 로 61.1 MB(+9.5%) 다 — 같은 앱에 대해 「절반도 안 된다」와 「이미 넘었다」가 동시에 성립한다. 정의 없이 55.8MB 와 비교하는 문장은 어느 쪽으로도 쓸 수 있어 근거가 되지 못한다. (55.8MB 자체도 MB/MiB 불명이라 바이트를 병기한다.)
    • app/cocode 를 재지 않는 이유: 도너에서 통째로 온 앱이라 cocode ADE 가 쓰지 않는 의존이 붙어 있고(#8 정리 대상), 재면 도너의 비용을 재게 된다.
    • 층을 나눈 이유: 한 수는 해석할 수 없다. 층별 한계비용이 나와야 「무엇을 줄이면 얼마가 주는가」가 보인다.
    • 회차별 값을 평균으로 접지 않는 이유: 첫 회차만 진짜 콜드다(OS 페이지 캐시·dyld 공유 캐시). 실측 905 ms vs 189 ms — 약 4.8배. 평균을 내면 그 차이가 사라지는데 사용자가 겪는 것은 둘 다다.
  • Impact:
    • 실측(macOS 26.5.2 arm64, release AOT, Flutter 3.47.3):

      층 A 아카이브 B 디스크 첫 프레임 콜드/워엄 유휴 RSS
      bare 15,613,322 B (14.89 MiB) 38,047,744 B (36.29 MiB) 905 / 189 ms ~105 MiB
      bare+agent +926 B +0 B 914 / 175 ms ~106 MiB
      bare+shell +9.62 MiB → 24.51 MiB +22.00 MiB → 58.29 MiB 967 / 186 ms ~109 MiB
    • 비용은 Flutter 엔진 바닥에서 온다. 콜드 스타트는 최소 셸이 바닥 대비 +62 ms(콜드)·워엄은 차이 없음, 유휴 메모리는 +3 MiB. 즉 그 두 축은 우리가 줄일 수 있는 것이 아니다 — 줄일 여지는 설치 용량 하나이고 그마저 절반 이상이 엔진이다.

    • ⚠️ +926 B 를 「런타임이 공짜」로 읽지 않는다. AOT 트리 셰이킹은 진입점에서 도달 가능한 코드만 남기므로, 잰 것은 패키지 크기가 아니라 그 진입점이 닿는 부분이다. 같은 이유로 bare+shell 은 심볼 참조가 아니라 CocodeEditorView 를 실제로 마운트해 잰다.

    • 목표치를 세우지 않는다 — 그리고 그것이 출시를 막지 않는다. PRD NFR-D 요약표 D행이 이 지표군에 「대응 AC 없음」 을 명시한다. 폐기값(설치 150MB·콜드 스타트 2초·유휴 메모리 400MB, D-013)은 목표치로도 참조점으로도 적지 않으며, 실측치가 그와 우연히 가깝더라도 비교 문장을 만들지 않는다. Lumide 55.8MB 는 하한 참조점이지 목표가 아니다.

    • ➖ 못 잰 것: Windows·Linux(측정 환경이 macOS 1대 — 두 정의 모두 플랫폼마다 다르므로 승계하지 않는다) · 설치 관리자(.dmg/MSIX/AppImage) 경유의 실제 설치 용량(#11 소관) · 완성된 셸의 값.

    • 스크립트 부산물: 워크스페이스 밖 측정을 위해 루트 dependency_overrides 를 통째로 옮긴다 — coui 항목만 고르면 file_picker ^12.2.0 ↔ syntax_highlight >=0.5.0 충돌로 해석이 깨진다(실측). 반대로 coui 를 쓰지 않는 층에 붙이면 쓰지도 않는 그래프를 해석하다 같은 충돌로 실패한다(실측) — 그래서 필요한 층에만 붙인다.

  • Source: #97 인수 조건 5종. 실측은 이 워크트리(2026-09-14).

D-072: Must-Not 3종의 부재 증명은 식별자 스캔 가드로 한다 · BDD 의 해소된 보류 표기를 걷고 세 곳의 수를 맞춘다 (#98)

  • Date: 2026-09-14
  • Context: Seed Spec §4 의 Must-Not 은 "…는 존재하지 않는다" 형태인데 부재를 어떻게 관찰하는지가 정해져 있지 않았다(BDD U-12 — 그나마 AC-04·AC-05 만 다루고 AC-21 은 U-12 에 들어 있지도 않다. v3.1.0 신설이라 U-12 보다 늦다). 동시에 BDD 는 v3.1.0·architecture 개정으로 이미 해소된 항목을 아직 「판정 보류」로 묶고 있었고, 같은 대상을 세는 수가 세 곳에서 달랐다.
  • Decision:
    • 관찰 방법: .github/scripts/check_mvp_scope_must_not.py — cocode ADE 자신의 소스(package/cocode_*/lib|bin, app/cocode 가 core 를 의존하기 시작하면 app/cocode/lib)에서 세 Must-Not 을 가리키는 식별자의 부재를 확인한다. AC-21 의 관찰 방법은 #98 이 새로 정한 것이다.
    • BDD 정정: U-7·U-8·U-9·U-10·U-12 의 「미정·판정 보류」 표기를 걷고, AC-21 시나리오 2건을 신설하며, §0.4 속성 목록에 result_content_hash 를 더하고, 세 곳의 수가 각각 무엇을 세는지 명시한다.
  • Rationale:
    • 부재는 컴파일로도 런타임으로도 증명되지 않는다 — 없는 것은 참조할 수 없어 타입 시스템이 볼 대상이 없고, 없는 화면은 띄울 수 없다. 남는 관찰은 소스 표면에 그 개념이 등장하지 않음이며 이 리포가 check_window_api_seal.py(C7)·check_flutter_bloc_residue.py 에서 이미 쓰는 방식이다.
    • ⚠️ 한계를 가드 안에 적었다: 「그 이름으로 들어오는 것」만 막는다. 다른 이름의 같은 기능은 잡지 못하므로 유일한 방어가 아니라 재유입 경보이고 범위 판정 자체는 사람의 리뷰가 한다.
    • ⛔ AC-12 를 잡지 않도록 패턴을 좁혔다. AC-21 본문이 "한 워크스페이스 안에서 복수 DelegatedTask 를 동시 진행하고 파일 단위로 잠그는 AC-12 는 이 제한과 무관하다" 로 명시한다. 그래서 edit_locks·잠금 큐·동시 작업 식별자는 패턴에 넣지 않고, 오탐 금지를 회귀 테스트로 고정했다(그 경계가 깨지면 정상 기능이 범위 위반으로 보고된다).
    • ⭐ 부재 가드는 아무것도 없을 때 통과한다 — 패턴이 오타로 깨져도, 스캔 경로가 어긋나도, 규칙이 통째로 빠져도 똑같이 초록불이다. 실행되지 않는 가드가 가드가 아니듯 발동하지 않는 가드도 가드가 아니다. 그래서 위반 픽스처 8종의 발동을 함께 고정했고, 그 테스트가 실제로 구멍을 잡았다 — 초판의 \bdreamer\b 는 DreamerModeController 를 놓쳤다(Dreamer 와 Mode 사이에 단어 경계가 없다). 가장 흔한 형태를 못 잡는 가드였다.
    • 수 세 개가 서로 다른 것을 세고 있었다: §2 대응표의 4건 = AC-02 시나리오 총수(맞다) · §3 안전 임계표의 2건 = 그중 충돌 판정에 걸리는 부분집합(맞다) · U-10 행과 §3-5 의 「3건」 = 무엇을 세는지 근거가 없었다. U-10 이 해소되어 보류 시나리오가 0건이 된 지금 그 수는 폐기한다 — 총수를 승계시키지 않는다(#98 인수조건 5가 그 전제를 두지 말라고 명시한다).
  • Impact:
    • 신규: check_mvp_scope_must_not.py + test_check_mvp_scope_must_not.py(발동 8 · 오탐 6 · 스캔 경로 1 · 리포 전수 1), ci.yml changes 잡 배선.
    • BDD AC 커버리지가 AC-01~AC-20 → AC-01~AC-21 로 넓어졌다. breakdown-cocode.md:425 가 "역방향 고아(시나리오가 없는 AC) 1건 — 이쪽이 더 심각하다" 로 기록한 그 공백이 닫혔다(대응표 서문이 범위를 AC-20 까지로 적어 AC-21 을 애초에 배제하고 있었다).
    • §0.4 속성 목록에 result_content_hash 추가 — v3.1.0 ① 이 신설했는데 목록에서 빠져 있었다. 빠진 채로 두면 시나리오가 base_content_hash 만 보고 「모든 롤백이 충돌」이라는 v3.1.0 이 고친 바로 그 버그로 되돌아간다.
    • 해소 표기를 건 다섯 항목: U-7(v3.1.0 ② 경로 기반) · U-8(D-053 폐쇄 5종) · U-9(architecture §6.2 DD-22 / D-060 — 신호 S1·S2, 판정 무개입) · U-10(v3.1.0 ① result_content_hash) · U-12(이 결정). 그 결과 Feature 5 전체 · AC-09 ⑤ · AC-20 · AC-02 · AC-04·AC-05·AC-21 이 판정 가능해졌다.
    • ⏸️ 남은 미정도 정확히 적었다: U-1(시간 상한 기본값) · U-2(프레임 타임 목표치 — #91 이 규칙 확정, 값은 오너) · U-3(#91 이 9db9015 로 고정 — 사실상 해소) · U-4(#96 이 표본 수·ⓑ 정의 확정, 합격 기준은 오너) · U-5(D-050 해소) · U-6(#93 확정) · U-11.
    • ⚠️ U-9 의 유휴 상한 «값» 은 여전히 미정이다(U-1 과 같은 항목) — 감지 기준이 정해진 것과 혼동하지 않는다.
  • Source: #98 인수 조건 6종. 가드 실측: 판정 대상 142개 파일에 위반 0건.

D-073: Q-08 종결 — ⓐ 재배포 아티팩트에 SSPL-1.0 코어를 포함하지 않는다. 근거는 「제거」가 아니라 패키징 경계 증명 (#99)

  • Date: 2026-09-14
  • Context: PRD Q-08(prd-cocode.md:249) — "Serverpod 4 임베드/재배포 계획 유무 및 SSPL-1.0 법무 검토". Discovery §Recommendations 5번이 "계획이 있다면 사전 법무 검토 필요" 로 조건부 제기했고 계획 유무 자체가 미확정이었다. 원래 이 Story 는 ⓑ(backend/cocode_server 존치 여부)의 답이 「제거」이면 SSPL 노출면이 사라져 법무 검토를 생략할 수 있는 경로를 열어 뒀으나, 2026-08-27 D-026(#102)이 존치를 확정하며 그 경로가 닫혔다. 동시에 리포에는 제3자 OSS 고지 표면이 전무했다(루트 LICENSE·NOTICE·THIRD_PARTY* 부재 · showLicensePage 류 0건 · 워크플로의 'license' 히트는 전부 Xcode/Android SDK 수락).
  • Decision:
    • ⓐ: 포함하지 않는다. 데스크톱 재배포 아티팩트에 SSPL-1.0 인 serverpod 코어를 넣지 않는다. 근거는 패키징 경계 증명이다(아래).
    • ⓑ 는 다시 판정하지 않는다 — D-026 이 「존치한다」로 확정했고 이 결정은 그것을 인용만 한다.
    • 법무 검토는 생략한다. ⓐ 가 「포함하지 않음」이므로. 생략의 근거를 재현 가능한 명령 + CI 상시 검증으로 남긴다.
    • 고지 목록은 pubspec.lock 에서 기계 생성하고 CI 가 커밋본과 대조한다.
  • Rationale:
    • 「리포에 있지만 앱이 안 쓴다」는 불합격이다(#99 인수조건 4). 직접 의존 목록은 한 겹만 보여 주고 전이 의존으로 SSPL 이 딸려 올 수 있다. 그래서 전이 의존 폐포로 증명한다 — cocode(데스크톱 앱) 폐포 436개 패키지에 serverpod 없음(serverpod 계열 14개는 전부 BSD 클라이언트·flutter 계열). 대조군: cocode_server 폐포 212개에는 있다 — 그것이 폐포 계산이 실제로 SSPL 을 찾아낸다는 증거다(대조 없이는 「가드가 늘 초록」과 구분되지 않는다).
    • 폐포가 증명의 상한이다 — Dart AOT 는 패키지 그래프 밖의 코드를 링크할 방법이 없으므로 폐포에 없으면 들어갈 수 없다. 반대로 폐포에 있으면 들어갈 수 있다(트리 셰이킹은 보장이 아니다). ➖ 번들 내용물 스캔은 패키징 파이프라인이 있어야 하므로 못 했다(#11 소관) — 그 한계를 적었다.
    • 법무 판단 자체는 여전히 미정이다(판정 주체: 오너가 지정할 외부 법무 — 리포·docs 에 검토 주체·회신 기한 기록 0건). 다만 ⓐ 가 「포함하지 않음」인 동안 그 판단은 필요하지 않고, 가드가 깨지는 날 항목이 자동으로 다시 열린다. 완료 조건은 판단 내용이 아니라 절차라는 인수조건 4의 규정 그대로다.
    • 고지를 기계 생성하는 이유: 대상이 pub.dev hosted 539개다. 손으로 유지할 수 있는 규모가 아니며 손으로 만든 목록은 틀린 줄 모르는 채 낡는다. 판정은 pub.dev 메타데이터가 아니라 패키지가 동봉한 LICENSE 본문으로 하고, 알아보지 못하면 추측하지 않고 실패로 적는다 — 「모르겠다」를 permissive 로 접으면 고지가 근거가 아니라 인상이 된다.
  • Impact:
    • 실측(2026-09-14 lock): 전체 563 = hosted 539 · git 19 · sdk 5. 식별 성공 539 · 실패 0. 분포 BSD-3-Clause 259 · MIT 186 · Apache-2.0 75 · BSD-2-Clause 12 · ISC 3 · MPL-2.0 3 · SSPL-1.0 1. ⚠️ #99 본문의 550(526·19·5)은 그 시점의 lock 이다 — 고정된 수를 규범으로 삼지 않는다(CI 가 매번 다시 만든다).
    • 네트워크/강한 copyleft = 1행: serverpod 4.0.1 · SSPL-1.0 · backend/cocode_server/pubspec.yaml:49. 같은 릴리스의 나머지 serverpod 계열 22개는 전부 BSD-3-Clause 라 표에 없다.
    • 🔴 실제로 잡은 오분류 — MPL-2.0 이 GPL 로 찍혔다. 초판은 LICENSE 본문 전체에서 이름을 찾았는데, MPL-2.0 은 §1.12 「Secondary License」 정의에서 GPL/LGPL/AGPL 을 인용한다. 실측에서 dbus 0.7.15·gtk 2.2.0·nm 0.5.0 세 개가 GPL 로 오분류됐고, 그대로 두면 강한 copyleft 표에 없는 위험 3건이 실려 인수조건 2의 「serverpod 1행 재현」이 깨진다. 수정: copyleft 계열은 제목 구간(앞 600자)만 본다. 회귀 고정이 그 결함을 재현한다.
    • DD-01 표 불일치를 대조해 기록했다 — §1.3 표 12행은 전부 cocode_* 이고 backend 항목이 없는데 backend/cocode_server 는 의도적으로 존치한다. 「§1.3 범위 밖 존치」로 읽는다(DD-01 갱신 아님): 근거는 실측이다 — backend 는 데스크톱 앱 폐포 밖에 있어 cocode ADE 의 구성물이 아님이 관측으로 확인된다. ⏸️ 다만 「존치하되 MVP 미사용」인지 「MVP 사용」인지는 여전히 미정(D-026 미결 · #4 배치표가 기록처)이고, 「MVP 사용」이 되면 이 읽기도 ⓐ 판정도 다시 열린다.
    • 신규 CI 잡 verify.oss-notices.yaml — 회귀 고정 · 생성 대조 · 번들 경계 증명 3종. branches: 필터를 두지 않았다: 두면 계층 PR 에서 돌지 않고, 이 Epic 의 PR 은 development 가 아니라 project/2 로 가므로 ci.yml 에 넣으면 이 작업이 스스로를 검증하지 못한다. 📌 #99 본문의 실측 정정 — "필터 없는 것은 4종뿐" 이라 적혀 있으나 2026-09-14 재실측 결과 8종이다(_hierarchy_check.yaml 은 그 사이 branches 가 생겼고, probe/verify 계열 4종이 늘었다).
    • ⏸️ 미정 2건: ⓐ 고지 노출 방식·배치(앱 내 화면 / 번들 동봉 텍스트 / 둘 다 — 소유 후보는 cocode_app. 판정 주체 오너. ⚠️ 고지 UI 구현은 그 확정 이후로 분리한다) ⓑ 플랫폼별 고지 경로 3종 전부(판정 주체: 데스크톱 패키징 선행 스토리 #11 — 지금 정하면 패키징이 생길 때 다시 정해야 하고 그 사이 문서가 틀린 채로 남는다).
  • Source: #99 인수 조건 7종. 실측은 이 워크트리(2026-09-14) — pubspec.lock · pub 캐시의 LICENSE 본문 · dart pub deps --json 폐포. D-026(#102) 인용, prd-cocode.md:249 출처.

D-074: 에디터 D-013 승인 — 롤백 사전검사가 「체인 불연속」을 충돌로 제시한다 (#373)

  • Date: 2026-09-28
  • Context: 풀스택 코딩 에디터(Project #305)가 D-013 에서 상위 결정을 요청했다. 같은 작업의 연속 스냅샷 사이에 사람 편집이 끼면, 지금 사전검사(D-036)는 마지막 스냅샷의 result 해시만 대조하므로 통과하고 사람 편집이 고지 없이 사라진다.
  • Decision: 승인. RollbackTargets.compute 가 next.base ≠ prev.result 를 ChainDiscontinuityConflict 로 제시한다. 구현은 #372 다.
  • Rationale: AC-02 의 강화(충돌을 더 보고)이며 완화가 아니다. 사전검사와 적용 엔진이 같은 순수 함수를 쓰므로 한 곳을 바꾸면 양쪽에 걸린다.
  • Impact: AC-02 판정에 체인 불연속 조건이 더해진다. 에디터 decision-log D-078.
  • Source: 오너 위임(2026-09-28) · 에디터 D-013 · #373

D-075: UX-D-30 해소 — 기존 로컬 폴더 열기는 정식 경로이고, .cocode/ 는 첫 위임 때 초기화한다 (#375)

  • Date: 2026-09-28
  • Context: UX-D-30 은 「기존 워크스페이스(로컬 폴더) 열기」를 미정으로 남겼다. #437 이 「폴더 열기…」를 임시 경로로 넣었고, 이것이 사실상 기본 진입점이 됐다.
  • Decision: 에디터 D-018 옵션 (a′).
    • 여는 것은 .cocode/ 를 만들지 않는다. 첫 위임 직전에 확인을 받아 .cocode/ 를 만들고 applied_bricks = [] 로 둔다.
    • 따라서 Workspace applied_bricks 의 「MVP 원소 1」(FR-105 · D-037)을 「생성 워크스페이스 1 · 위임한 기존 폴더 0」으로 개정한다. 개정은 에이전트 루프 앱 배선(#447)의 Seed 에서 한다.
    • S-17 첫 실행 화면의 주 동작은 둘이 된다 — 「폴더 열기…」 · 「새 워크스페이스…」.
  • Rationale: 1차 고객(D-005)의 첫 동선이 기존 저장소 열기다. 여는 것만으로 사용자 저장소를 바꾸지 않는다.
  • Impact: 에디터 decision-log D-079. 스코프 계약 3종의 등록 시점이 「첫 위임」으로 정해졌다.
  • Source: 오너 위임(2026-09-28) · #375

D-076: 에이전트 루프(위임 · 자가 검증 · 되돌리기)의 앱 배선을 새 Project #447 로 세운다 (#446)

  • Date: 2026-09-28
  • Context: MVP(Project #2, 2026-09-14)는 에이전트 루프를 패키지와 계약으로 만들었다. 그러나 앱(app/cocode)은 계약 13종 중 5종만 등록하고 agent · acp 를 의존하지 않는다. 담당 상위 Epic 들은 닫혔다.
  • Decision: 새 Project #447 을 세우고 에디터 확장(#305) 잔여와 병행한다. 첫 목표는 백엔드 1종 · 검증 실행기 1종으로 앱에서 루프를 관통하는 수직 슬라이스다. D-006(두 백엔드 모두 MVP 필수)은 그대로이고, 순서만 슬라이스 단위로 나눈다.
  • Rationale: 차별점(위임 루프)이 앱에 없으면 AC-01 · 02 · 06 · 07 · 08 · 09 를 판정할 수 없다. 에디터 Seed 에 넣으면 그 Seed 가 Level 재분류된다.
  • Impact: 에디터 decision-log D-081. D-075 의 초기화 · applied_bricks 개정이 이 Project 로 모인다.
  • Source: 오너 위임(2026-09-28) · #446

D-077: 모바일 E2E · 모바일 배포 경로는 제품 범위 밖이다 — 예약 실행 중지 · maestro 삭제 · 모바일 레인은 첫 main 릴리스 전에 정리 (#444)

  • Date: 2026-09-28
  • Context:
    • patrol-test.yml nightly 가 첫 실행(2026-09-21)부터 7/7 실패했다.
    • flavor 진입점 4종(main_{development,staging,production,donor}.dart)이 도너 앱을 띄우는데, 도너 앱은 없는 패키지를 import 해 컴파일되지 않는다.
    • 데스크톱 릴리스는 fastforge 가 lib/main.dart 를 빌드하므로 무관하다(app/cocode/distribute_options.yaml).
    • CI 인벤토리는 patrol-test.yml 을 「보류(오너 결정)」, maestro-test.yml 을 「삭제」로 판정해 두었다.
  • Decision:
    • patrol-test.yml 의 예약 실행을 멈춘다. 수동 실행만 남긴다.
    • maestro-test.yml 을 지운다(인벤토리 판정 이행).
    • 첫 main 릴리스 전에 두 가지를 정리한다(#449): build.yaml 의 모바일 레인(push:main 에서 TARGET_OS=all 로 켜지는 call-ios · call-android)과, 인벤토리가 「삭제」로 판정한 모바일 배포 워크플로 5종.
    • 도너 시나리오 · app_initializer.dart · flavor 진입점 4종은 D-026 대로 존치한다. 도너 정리를 결정할 때 함께 걷는다. main.dart 머리 주석이 넷 모두를 도너 진입점으로 적는다.
  • Rationale: C-01(데스크톱 3종)에 모바일이 없다. 신호 없는 red 가 매일 자체 호스트 러너를 ~34분 점유하고, 진짜 red 를 가린다.
  • Impact: 인벤토리의 patrol-test.yml · maestro-test.yml · build.yaml 행을 갱신했다.
  • Source: 오너 위임(2026-09-28) · #444 · docs/ci-inventory-cocode.md

D-078: GitHub Actions 의 PR 생성을 허용한다 — 조직 정책이 막고 있어 조직 관리자 권한으로 적용한다 (#445)

  • Date: 2026-09-28
  • Context:
    • 세 워크플로가 모두 PR 생성 단계에서 「GitHub Actions is not permitted to create or approve pull requests」로 실패한다: upstream-bump.yml(cocode) · 「unibook bricks sync」(bricks) · release-please(co-bricks).
    • co-bricks 는 그 때문에 v0.4.0 이후 릴리스가 없고, cob dev 가 출시되지 못했다(#298).
    • 세 저장소 모두 브랜치 보호 · ruleset 이 없다. 그래서 승인 권한이 리뷰 요건을 우회하는 경로가 되지 않는다.
  • Decision: 허용한다(옵션 ⓐ).
    • 저장소 수준 설정은 조직 정책 때문에 409 로 거부됐다(2026-09-28 실측). 따라서 조직 설정 「Allow GitHub Actions to create and approve pull requests」를 켠 뒤 세 저장소에 적용한다.
    • 적용에는 admin:org 범위가 필요하다.
    • GITHUB_TOKEN 이 연 PR 에는 pull_request CI 가 돌지 않는다. 그래서 갱신 PR 은 사람이 닫았다 다시 열어 CI 를 돌린다.
  • Rationale: 세 워크플로가 같은 원인으로 멈춰 있고, 설정 하나로 풀린다. App 토큰(ⓑ)은 CI 가 돈다는 장점이 있지만 조직에 App 기반이 없어 비용이 크다. 필요해지면 그때 옮긴다.
  • Impact: 적용 전까지 세 워크플로는 계속 실패한다. 적용한 뒤 첫 실행에서 PR 이 열리는지 확인하고 #445 를 닫는다.
  • Source: 오너 위임(2026-09-28) · #445

D-087: 도너(unibook) feature·패키지·콘솔을 삭제한다 — D-026·D-031 대체 (#458)

  • Date: 2026-09-28
  • Context:
    • D-026(#102)은 도너 자산을 「추후 백엔드가 필요할 수도 있어서」 존치했다. D-031(#141)은 그 자산의 파일은 두고 분석·빌드 대상에서만 빼서 CI 를 살렸다.
    • 그 뒤로도 도너 표면은 exclude 목록 · melos ignore · CI 가드 예외 · 계층 래칫 베이스라인 곳곳에 비용을 남겼다. core → core 같은 이름 정리도 막았다 — 도너 package/core 와 이름이 겹친다.
    • 프로젝트 오너 요청(2026-09-28): 「사용하지 않는 패키지나 기능 모듈들을 모두 제거하고 … 패키지 이름에서 cocode_ 를 모두 제거해줘」.
  • Decision: cocode ADE 가 쓰지 않는 워크스페이스 패키지를 삭제한다.
    • 판정 기준은 의존 폐포다. 워크스페이스 132개의 폐포(dependencies + dev_dependencies)를 app/cocode · app/cocode_widgetbook · backend/cocode_{server,client} · package/cocode_* 12종에서 계산하고, 폐포 밖 111개를 지운다 — feature/** 97 · app/cocode_console · 도너 package/* 12 · shared/dependencies.
    • app/cocode 안의 도너 잔재도 함께 지운다 — lib/app · lib/core · bootstrap.dart · flavor 진입점 4종 · test_donor_* · 도너 E2E 73파일(오너 확인).
    • 백엔드(backend/cocode_{server,client})와 그것만 쓰는 shared/{config,flavor,i10n_web} 은 남긴다. cob dev 자기 실행(#303)이 쓰고, D-026 의 백엔드 존치 의도도 그대로다(오너 확인).
  • Rationale: 쓰지 않는 코드를 「존치하되 제외」로 두는 비용이 계속 쌓였다. 지운 파일은 git 이력에 남으므로 필요해지면 되살릴 수 있다.
  • Impact:
    • D-031 의 exclude·ignore 는 대상과 함께 사라지고 backend/cocode_server 한 줄만 남는다.
    • 계층 래칫 S1: maxSCC 1 · SCC 0 · 계층 위반 1 · 유령 엣지 0 — architecture DD-03 목표치를 달성했다.
    • pubspec.lock 에서 hosted 의존 168개가 빠졌다. 남은 패키지의 버전 변화는 0 이다.
    • ci.yml 의 console-web-build 잡과 그 영향 판정 스크립트를 지웠다.
    • 남은 정리(후속): 릴리스 파이프라인 build.yaml 의 console 플랫폼 레인과 모바일 레인(#449) · 백엔드 콘솔 엔드포인트 스텁(#428) · package/cocode_* 개명(#459).
  • Supersedes: D-026(존치) · D-031(분석·빌드 범위 제외). 백엔드 존치 부분은 유지한다.
  • Source: 프로젝트 오너 요청(2026-09-28) · #458

D-088: ADE 패키지 12종에서 cocode_ 접두사를 뗀다 — cocode_platform 만 desktop_platform (#459)

  • Date: 2026-09-28
  • Context: 프로젝트 오너 요청(2026-09-28)의 뒷부분이다 — 「아래 패키지 이름에서 cocode_ 를 모두 제거해줘」. 도너 package/core 와 이름이 겹쳐 D-087(#458)의 삭제가 먼저 필요했다.
  • Decision:
    • package/cocode_{acp,agent,bricks,core,editor,lsp,runner,terminal,toolchain,ui,workspace} → package/{…}. 디렉터리 · pubspec name: · 배럴 파일(lib/<이름>.dart) · import URI · 의존 선언 · 계층 선언 · CI 스크립트 · 문서를 함께 바꾼다.
    • cocode_platform 은 desktop_platform 이다(오너 확인). pub.dev 의 platform 3.1.6 이 melos · process · path_provider_platform_interface 경유로 이미 의존 그래프에 있어, 같은 이름의 워크스페이스 패키지를 두면 version solving 이 실패한다(빈 워크스페이스로 재현).
    • 바꾸지 않는 것: 클래스·심볼(Cocode*)과 그 이름에 맞춘 파일 이름, app/cocode* · backend/cocode_* · shared/config(cocode_config). 도메인 값 agent_backend_kind(cocode_agent | cocode_acp) 도 그대로 둔다 — 패키지 이름이 아니라 AgentBackendKind { cocodeAgent, cocodeAcp } 의 값이다.
    • 이름 접두사로 범위를 잡던 CI 가드 4종(dart:io 쓰기 · 프로세스 기동 · 창 seam · MVP 범위)과 순수 패키지 스테이징 스크립트는 접두사 대신 package/*(테스트 도구 test_driver 제외) 와 발견한 패키지 이름으로 범위를 잡는다. 도너가 사라진 뒤로 package/ 가 곧 ADE 이고, 새 패키지가 생기면 자동으로 대상이 된다.
  • Rationale: 접두사는 도너 패키지와 섞여 있던 시절의 구분자였다. 도너가 삭제된 뒤로는 구분할 대상이 없다.
  • Impact: 파일 527개 · 참조 2,652곳. 가드 판정 파일 수(235 · 262 · 262 · 265)와 계층 래칫 지표는 개명 전후 동일하다. 열린 PR 중 package/cocode_* 를 건드리는 것은 이관이 필요하다(이름 표 + 치환 명령을 PR 에 안내).
  • Source: 프로젝트 오너 요청(2026-09-28) · #459

When adding a new decision, copy the template above and increment the number.