diff --git a/docs/chad/done-guard-window/done-guard-window-artifact.md b/docs/chad/done-guard-window/done-guard-window-artifact.md new file mode 100644 index 00000000..f0b8a5ec --- /dev/null +++ b/docs/chad/done-guard-window/done-guard-window-artifact.md @@ -0,0 +1,87 @@ +# done-guard-window — Artifact + +*최종 결과물과 학습 내용을 기록한다.* + +## 결과 + +**조사·설계까지 완료 · 구현 미착수.** (2026-08-25) + +| 항목 | 상태 | +|---|---| +| 오탐 실사례 확보 | ✅ `boundary-perf-invariant` 종결 시 가드 2건 | +| 원인 확정 | ✅ `switchedAt`이 "마지막 활성화"라 재활성화가 판정 창을 초기화 | +| 영향 범위 특정 | ✅ `collectDoneIssues()` 의존 가드 3종 (`:442`·`:474`·`:486`) | +| 설계 긴장 확인 | ✅ `tests/done-guard.test.mjs:461`이 "옛 마커 차단"을 의도로 고정 | +| 창 기준 결정 | ⏳ (A) `firstActivatedAt` 유력, 미확정 | +| 구현·테스트 | ⏳ 미착수 | + +한 줄 요약: `done` 가드가 증거를 찾는 창이 `[switchedAt, now]`인데, `switchedAt`은 +**마지막 활성화 시각**이라 재활성화하면 이미 만족된 증거가 창 밖으로 밀려난다. +있어야 할 것은 `[task 작업 시작, now]`다. + +**주의:** 이 task의 핵심 난점은 "오탐을 없애는 것"이 아니라 **"오탐만 없애는 것"** 이다. +창을 넓히면 오탐은 사라지지만 가드가 막던 망각도 함께 통과한다 — spec 요구사항 2가 금지한다. + +## 조사 기록 (2026-08-25) + +### 어떻게 드러났나 + +`boundary-perf-invariant`(plan 14/14, PR #44 머지 완료)를 종결하려 하자 가드 2건이 걸렸다: + +``` +cause: spec이 `review: required`인데 이 task 기간의 리뷰 마커가 artifact에 없음 +cause: task 활성화 이후 커밋이 0개임 +``` + +둘 다 오탐이었다. `done`은 활성 task만 대상이므로 끝난 task를 종결하려면 **재활성화**해야 하는데, +그것이 `switchedAt`을 `04:25:13Z` → `09:19:47Z`로 갱신했다. + +| 가드 | 실제 증거 | 새 창 기준 판정 | +|---|---|---| +| 리뷰 마커 | `at=05:35:00Z` (codex 리뷰) | 창 밖 → "없음" | +| 커밋 | 6건 `9414faf`…`f704672` (PR #44로 머지) | 창 밖 → "0개" | + +`--force`로 우회했고, **그 우회 자체가 이 task를 만든 이유다.** + +### 왜 구조적인가 + +`runTask`는 생성(`:204`)과 **재활성화(`:231`) 모두** `switchedAt`을 현재 시각으로 덮어쓴다. +따라서: +- 끝난 task를 닫을 때마다 **반드시** 이 오탐이 난다 — 종결에는 재활성화가 선행되므로. +- 종결만의 문제도 아니다. **task를 전환했다가 되돌아오는 것만으로도** 창이 초기화되어 + 그 전 작업 증거가 통째로 사라진다. + +즉 "가끔 나는 버그"가 아니라 **정상 경로에서 항상 재현되는 구조**다. +이대로면 `--force`가 종결의 표준 절차가 되고, 가드는 이름만 남는다. + +### 설계 긴장 (이 task의 진짜 어려움) + +기존 테스트가 반대 방향을 못박아 두고 있다 — +`tests/done-guard.test.mjs:461` *"이전 task 기간의 마커(at < switchedAt)는 무효 → 차단"*. +**옛 리뷰 마커를 재사용해 통과하는 것을 막는 것이 의도**다. + +그러므로 수정은 "옛 마커를 허용"하는 방향이면 안 된다. 되살려야 하는 것은 +**같은 task의 이전 활성 구간**뿐이고, 다른 task 기간의 마커는 계속 막아야 한다. +이 구분이 되지 않으면 오탐은 사라져도 가드는 무력해진다. + +### 후보 요약 + +(A) `firstActivatedAt` 신규 필드 — **유력**. 의미가 정확하고 재활성화가 건드리지 않는다. +(B) `meta.created` 재사용 — **부적합**. `slice(0,10)`이라 날짜만이라 창이 과도하게 넓다. +(C) 활성 구간 누적 — 가장 정확하나 상태·복잡도 증가. +(D) task 디렉터리 첫 커밋에서 유도 — 새 상태 불필요, (A)와 조합 검토 가치. +(E) 종결 시에만 완화 — 증상만 가림, 전환 케이스 미해결. + +상세·평가는 spec의 "후보" 표가 정본이다. + +## Reviews + +## Learnings + +- **오탐은 가드를 죽인다.** 이 건은 "가드가 틀렸다"보다 **"가드를 우회하는 것이 정상 절차가 된다"** + 가 진짜 위험이다. 실제로 이번에 `--force`를 썼고, 같은 구조가 남아 있는 한 다음에도 쓰게 된다. + 오탐률은 정확도 문제가 아니라 **신뢰 문제**다. +- **"마지막 X 시각"을 "X 구간의 시작"으로 쓰면 안 된다.** 두 개념이 대부분의 경우 우연히 + 일치해서 오래 버그가 드러나지 않는다 — 재활성화처럼 갈라지는 순간에만 터진다. +- **가드를 고칠 때의 판정 기준은 "통과하는가"가 아니라 "여전히 막는가"** 다. + 이 task의 성공 조건에 기존 차단 테스트(`:461`) 통과를 명시적으로 넣은 이유다. diff --git a/docs/chad/done-guard-window/done-guard-window-context.md b/docs/chad/done-guard-window/done-guard-window-context.md new file mode 100644 index 00000000..650b9139 --- /dev/null +++ b/docs/chad/done-guard-window/done-guard-window-context.md @@ -0,0 +1,35 @@ +# done-guard-window — Context Card + + +## Now +- Goal: `done` 가드의 판정 창을 "마지막 활성화" → "task 작업 구간"으로 바꿔 오탐 제거. + **단 가드가 막던 망각은 계속 잡아야 한다.** +- Current atomic step: **[결정] 창 기준 선택** — (A) `firstActivatedAt` 유력. + 결정 후 **재현 테스트 먼저**, 그다음 구현. +- Stop / human-decision condition: 없음(기술 결정). 다만 착수 지시를 받고 시작한다 — + 현재는 조사·설계까지만 된 상태다. + +## Constraints and settled decisions +- 원인 확정: `switchedAt`은 **마지막 활성화 시각**이지 작업 구간이 아니다. + `runTask`가 생성(`:204`)·재활성화(`:231`) 모두 현재 시각으로 덮어쓴다. +- 의존 가드 3종: 리뷰 마커(`:442`) · 커밋 0개(`:474`) · 테스트 미작성(`:486`). +- **문제는 종결 케이스보다 넓다** — task 전환 후 복귀만으로도 창이 초기화된다. +- **가드를 넓혀서 통과시키면 안 된다.** `tests/done-guard.test.mjs:461`이 + "옛 마커 재사용 차단"을 의도로 고정해 뒀다 — 이 테스트가 계속 통과해야 한다. +- `meta.created`는 **날짜만**(`slice(0,10)`)이라 창 시작으로 부적합(과도하게 넓어짐). +- 하위 호환: 새 필드가 없으면 **degrade**(차단 아님). `switchedAt` 부재 시 기존 처리(`:444`)와 동일 원칙. + +## JIT retrieval map +- Narrow globs: `src/commands/task.mjs`, `tests/done-guard.test.mjs` +- Identifiers: `collectDoneIssues`, `switchedAt`, `writeActive`, `parseReviewMarkers`, `readTaskMeta` +- Read next: `collectDoneIssues()` `:402-500` → `runTask()` `:200-235` +- Verification command: `node --test tests/done-guard.test.mjs` (27 test) + +## Failure capsules (max 3 unresolved) +- (none unresolved — 원인 확정, 남은 것은 설계 선택과 구현이다) + +## Resume checklist +- 실사례는 `docs/chad/boundary-perf-invariant/…-artifact.md` `## 종결 기록`에 있다 — + 재현이 필요하면 거기 타임스탬프(활성화 04:25:13Z / 마커 05:35:00Z / 재활성화 09:19:47Z)를 쓴다. +- **테스트를 먼저 쓴다.** 가드 자신을 고치는 task라 "고쳤다"의 근거가 테스트여야 한다. +- 판정 기준은 "오탐이 사라졌는가"가 아니라 **"오탐은 사라지고 `:461`은 계속 막는가"** 다. diff --git a/docs/chad/done-guard-window/done-guard-window-handoff.md b/docs/chad/done-guard-window/done-guard-window-handoff.md new file mode 100644 index 00000000..5232a9a6 --- /dev/null +++ b/docs/chad/done-guard-window/done-guard-window-handoff.md @@ -0,0 +1,3 @@ +# done-guard-window — Handoff + +(세션 종료 시 post-commit hook이 자동 갱신합니다) diff --git a/docs/chad/done-guard-window/done-guard-window-meta.json b/docs/chad/done-guard-window/done-guard-window-meta.json new file mode 100644 index 00000000..c635baf5 --- /dev/null +++ b/docs/chad/done-guard-window/done-guard-window-meta.json @@ -0,0 +1,7 @@ +{ + "user": "chad", + "task": "done-guard-window", + "created": "2026-08-25", + "status": "open", + "closedAt": null +} diff --git a/docs/chad/done-guard-window/done-guard-window-plan.md b/docs/chad/done-guard-window/done-guard-window-plan.md new file mode 100644 index 00000000..72bdd7e2 --- /dev/null +++ b/docs/chad/done-guard-window/done-guard-window-plan.md @@ -0,0 +1,28 @@ +# done-guard-window — Plan + +## 목표 +`done` 가드의 판정 창을 "마지막 활성화"가 아니라 "task 작업 구간"으로 바꿔 오탐을 없애되, +가드가 막던 **망각**은 계속 잡게 한다. + +## 단계 +- [x] 오탐 실사례 확보 — `boundary-perf-invariant` 종결 시 가드 2건, 원인은 재활성화 +- [x] 영향 범위 특정 — `collectDoneIssues()`의 `switchedAt` 의존 가드 3종(`:442`·`:474`·`:486`) +- [x] 문제가 종결 케이스보다 넓음을 확인 — **task 전환 후 복귀만으로도** 창이 초기화된다 +- [x] 설계 긴장 확인 — `tests/done-guard.test.mjs:461`이 "옛 마커 차단"을 의도로 고정 +- [ ] **[결정] 창 기준 선택** — (A) `firstActivatedAt` 유력 · (B) `meta.created`는 날짜만이라 부적합 + · (D) task 디렉터리 첫 커밋 조합 검토 · 마이그레이션/하위 호환 설계 포함 +- [ ] **재현 테스트 먼저 작성** — 재활성화 후 원래 창의 마커·커밋이 인정되는지 (지금은 실패해야 함) +- [ ] 구현 — 창 기준 교체 + 재활성화가 창 시작을 덮어쓰지 않게 +- [ ] **회귀 확인** — `:461` 옛 마커 재사용 차단 테스트가 **계속 통과**하는지가 핵심 판정 +- [ ] 하위 호환 — 새 필드 없는 기존 task는 degrade(차단하지 않음) 확인 +- [ ] spec Ambiguity 게이트 닫고 artifact 기록 +- [ ] 외부 리뷰(`/harness-review`) 후 `## Reviews` 기록 (spec이 `review: required`) + +## Ontology 변경 로그 +- **판정 창(evidence window)** 개념을 `switchedAt`과 분리해 신설. 둘을 같은 것으로 다룬 것이 + 버그의 원인이며, 이 분리가 수정의 축이다. +- **가드 약화** 를 명시적 실패 양식으로 등재 — 오탐을 없애려다 진짜 누락까지 통과시키는 것. + +## 참고 +- 이 task는 **하네스 자신의 가드**를 고친다. 스스로를 검증하는 코드이므로 테스트 우선으로 간다. +- 우회 이력: 2026-08-25 `done --force` 1회 (사유는 boundary-perf-invariant artifact에 기록). diff --git a/docs/chad/done-guard-window/done-guard-window-spec.md b/docs/chad/done-guard-window/done-guard-window-spec.md new file mode 100644 index 00000000..bd138fe6 --- /dev/null +++ b/docs/chad/done-guard-window/done-guard-window-spec.md @@ -0,0 +1,115 @@ +# done-guard-window — Spec + +## 목적 / 요구사항 + +`harness-team done`의 증거 기반 가드가 **구조적으로 오탐을 낸다.** 판정 창의 기준이 +`active.json`의 `switchedAt`인데, 이 값은 "**마지막 활성화 시각**"이지 "**이 task의 작업 구간**"이 +아니다. task를 다시 활성화하면 창이 초기화되어, 이미 만족된 증거가 사라진 것처럼 보인다. + +**2026-08-25 실제 발생.** `boundary-perf-invariant`(plan 14/14, PR #44 머지 완료)를 종결하려는데 +가드 2건이 걸렸다: + +``` +cause: spec이 `review: required`인데 이 task 기간의 리뷰 마커가 artifact에 없음 +cause: task 활성화 이후 커밋이 0개임 +``` + +둘 다 **오탐**이었다. `done`은 활성 task만 대상이라 끝난 task를 종결하려면 재활성화해야 하는데, +그 재활성화가 `switchedAt`을 `04:25:13Z` → `09:19:47Z`로 밀어 버렸다. 실제 증거는 원래 창 안에 있었다: + +| 가드 | 실제 증거 | 새 창(09:19) 기준 | +|---|---|---| +| 리뷰 마커 | `at=05:35:00Z` (codex 리뷰 기록) | 창 **밖** → 없다고 판정 | +| 커밋 | 6건 (`9414faf`…`f704672`, 전부 PR #44로 머지) | 창 **밖** → 0개로 판정 | + +결국 `--force`로 우회했다. **이것이 이 task가 막으려는 결과다** — +가드를 우회하는 것이 정상 절차가 되면 가드는 더 이상 아무것도 막지 못한다. + +요구사항: +1. 재활성화가 증거를 무효화하지 않게 한다. +2. **가드의 원래 힘을 약화시키지 않는다.** 창을 넓히는 것만으로 "green"을 사면 안 된다 — + 이 가드가 막으려던 "망각"을 계속 잡아야 한다. +3. 기존 의도(옛 리뷰 마커 재사용 방지)를 보존한다. + +## 설계 / 접근 + +### 영향 범위 — `switchedAt`에 의존하는 가드 3종 + +`src/commands/task.mjs`의 `collectDoneIssues()`: + +| 위치 | 가드 | 사용 방식 | +|---|---|---| +| `:442-446` | 리뷰 마커 신선도 | `m.at >= Date.parse(switchedAt)` | +| `:474-480` | 활성화 이후 커밋 0개 | `git log --since=` | +| `:486-497` | 테스트 미작성 | `git log --since= --name-only` | + +`switchedAt`은 `runTask`에서 **생성 시(`:204`)와 재활성화 시(`:231`) 모두 현재 시각으로 덮어쓴다.** + +### 문제는 종결보다 넓다 + +"끝난 task를 닫을 때"만의 문제가 아니다. **task를 전환했다가 되돌아오는 것만으로도** 창이 +초기화되어 이전 작업 증거가 통째로 사라진다. 여러 task를 오가는 작업 방식에서는 +항상 발생한다. + +### 설계 긴장 — 그냥 넓히면 안 된다 + +기존 테스트가 의도를 명시적으로 고정해 두고 있다: +`tests/done-guard.test.mjs:461` — *"이전 task 기간의 마커(at < switchedAt)는 무효 → 차단"*. +즉 **옛 리뷰 마커를 재사용해 통과하는 것을 막는 것이 설계 의도**다. +수정은 이 의도를 유지하면서 "**같은 task의 이전 활성 구간**"만 되살려야 한다. +"옛 마커 전부 허용"으로 가면 가드가 무력해진다 — 요구사항 2 위반. + +### 후보 + +| 후보 | 내용 | 평가 | +|---|---|---| +| **(A) `firstActivatedAt` 도입** | 최초 활성화 시각을 meta에 기록하고 창의 시작으로 쓴다. 재활성화는 이 값을 **건드리지 않는다** | **유력.** 의미가 정확("task 작업 구간의 시작"). 새 필드 1개 | +| (B) `meta.created` 사용 | 이미 있는 필드 | ⚠️ **날짜만**(`slice(0,10)`)이라 그날 00:00로 뭉개진다. 오래된 task는 창이 과도하게 넓어져 가드가 약해짐 | +| (C) 활성 구간 누적 | `[{from,to}...]`를 기록해 합집합으로 판정 | 가장 정확하지만 상태·복잡도가 크게 증가 | +| (D) task 디렉터리 이력에서 유도 | `docs///`를 처음 건드린 커밋을 창 시작으로 | 새 상태 불필요. 단 커밋 전 task는 창이 없음 | +| (E) 종결 시에만 창 완화 | `done` 경로에서만 넓게 본다 | 증상만 가림 · 전환 케이스는 그대로 | + +**(A)가 유력**하나 확정하지 않았다 — 마이그레이션(기존 `active.json`·`meta.json`에 필드 없음)과 +하위 호환 처리를 함께 설계해야 하고, (D)와의 조합도 검토 가치가 있다. + +### 하위 호환 + +`switchedAt`이 없는 구 `active.json`은 이미 "시각 비교 포기, 마커 존재만 확인"으로 +degrade한다(`:444` 주석). 새 필드도 **없으면 degrade**하는 같은 원칙을 따라야 한다 — +없다고 차단하면 기존 task가 전부 막힌다. + +## Ontology + +- **`switchedAt`**: 마지막 활성화 시각. **task 작업 구간의 시작이 아니다** — 이 혼동이 버그의 원인. +- **판정 창(evidence window)**: 가드가 "이 task 기간의 증거"를 찾는 시간 범위. + 현재 `[switchedAt, now]`이며, 있어야 할 것은 `[task 작업 시작, now]`. +- **오탐(false positive)**: 증거가 실제로 존재하는데 창 밖이라 없다고 판정되는 것. + **가드 무력화의 전형적 경로** — 오탐이 반복되면 `--force`가 습관이 된다. +- **가드 약화**: 창을 넓혀 오탐을 없앴지만 진짜 누락도 함께 통과시키는 것. 요구사항 2가 금지한다. + +## Ambiguity 자가진단 + +- [x] **Goal 명확도** (40%) — "재활성화가 증거를 무효화하지 않게 하되 가드 힘은 유지한다". +- [x] **Constraint 명확도** (30%) — 기존 의도(옛 마커 재사용 차단) 보존 · 하위 호환 degrade · + 가드 완화 금지. `tests/done-guard.test.mjs`(27 test)가 회귀 그물이다. +- [x] **Success 기준** (30%) — 재활성화 후에도 원래 창의 마커·커밋이 인정되고, + **옛 마커 재사용 차단 테스트는 계속 통과**한다. 재현 테스트를 먼저 추가한다. +- [x] **Context 명확도** — `src/commands/task.mjs`(`collectDoneIssues` · `runTask`) · + `tests/done-guard.test.mjs`. 영향 범위가 좁고 특정됐다. +- [ ] **Ambiguity ≤ 0.2** — **가중합 1.0이나 구현 설계(A~D 중 선택 + 마이그레이션)가 + 미확정**이라 게이트를 열어 둔다. 정책이 아니라 **기술 결정**이므로 구현 단계에서 + 후보를 좁혀 닫는다. 착수 지시가 오면 (A) 기준으로 진행하되 (D) 조합을 검토한다. + +## Done evidence + +```json +{ "version": 1, "review": "required", "tests": "required" } +``` + + + +## 참고 +- 발생 기록: `docs/chad/boundary-perf-invariant/boundary-perf-invariant-artifact.md` `## 종결 기록` +- 관련 구현: `src/commands/task.mjs` `collectDoneIssues()` · `runTask()` +- 회귀 그물: `tests/done-guard.test.mjs` (특히 `:461` 옛 마커 차단 테스트)