# task-3056 — 시스템 명세 자동갱신: 유령 변경 제거 + cron 철회 + 수집시각 표기

- **팀**: dev4 (카르티케야, 백엔드) · **레벨**: Lv.2 · **일자**: 2026-08-29
- **대상**: `scripts/update-system-spec.py`, `memory/specs/anu-system-spec.md`,
  `memory/specs/.spec-state-cache.json`, `scripts/tests/test_update_system_spec.py`(신규)
- **결과**: **success** — exit 0 / 유령 [cron] 0건 / 조기종료 실증 완료 / 5개 섹션 전부 KST 표기

---

## 1. 구현 요약

| # | 항목 | 조치 |
|---|------|------|
| ① | 유령 변경 제거 (P1) | `detect_changes()` 에 `if section not in new_state: continue` 가드 추가 |
| ② | cron 섹션 철회 (P2) | `SECTIONS` 에서 `"cron"` 제거 · 캐시 `cron` 키 삭제 · 문서 블록을 부재 선언으로 교체 (마커·`collect_cron()`·`render_cron()`·`COLLECTORS`/`RENDERERS` 엔트리 전부 생존) |
| ③ | 수집시각 표기 (P2) | `_collected_at_kst()`/`_collected_header()` 신설(UTC+9 명시 변환, 서버 TZ 비의존) → 5개 render 함수 첫 줄 |

### ★ 후속 수정 (팀장 판단으로 해소) — `main()` 렌더 루프 1줄

```diff
-    # 4. 변경된 섹션만 업데이트
-    for section in changes:
+    # 4. 섹션 렌더 갱신
+    # task-3056: DoD "5개 섹션에 수집시각(KST) 표기" 충족을 위해 changes 가 아닌
+    # SECTIONS 전체를 순회한다. 변경 없는 회차는 위 `if not changes: return 0`
+    # 조기종료가 먼저 막으므로 여기 도달하지 않는다 (파일 재작성 없음).
+    # 수집 실패 섹션은 new_state 에 없으므로 가드로 건너뛴다 (KeyError 방지).
+    for section in SECTIONS:
         if section not in new_state:
             continue
```

**근거 (팀장 판단, 원문 보존)**
- DoD 가 "5개 섹션에 수집시각(KST)이 표기된다" 를 명시적으로 요구한다. 변경 전 상태는 DoD 미충족(2/5)이었다.
- 명세의 금지사항 "5섹션 수집 로직 변경 금지" 는 `collect_*` 를 가리킨다. `main()` 의 렌더 루프는 수집 로직이 아니다.
- "매번 재작성" 부작용은 실재하지 않는다: `content` 는 어차피 통짜로 한 번 write 된다.
  섹션 body 를 2개 만들든 5개 만들든 파일 I/O 는 동일하다.
- **조기종료 경로는 그대로 살아있다** — `if not changes: return 0` 이 `main()` 앞단에서 먼저 막는다.
  변경 없는 회차에는 이 루프에 아예 도달하지 않는다. (아래 3절에서 sha256+mtime 으로 재확인)
- 라벨이 `_수집:_` 이므로 "변경 없이 오늘 수집·검증됐다" 를 표기하는 것이 의미상 정확하다.
  변경 전에는 `skills`/`services`/`projects` 가 영원히 시각 없음으로 남아 ③의 목적 자체가 무산됐다.

★ `if section not in new_state: continue` 가드는 해당 루프에 **이미 존재**했다 (수집 실패 섹션 KeyError 방지). 유지했고 변이로 봉인했다.

---

## 2. 5개 섹션 KST 헤더 — 실행 후 실제 문서 파싱 결과

`memory/specs/anu-system-spec.md` 의 `AUTO:*:START ~ END` 사이를 파싱한 실측:

**수정 전 (2/5)**
```
skills       first_line='**총 103개 스킬**'
services     first_line='**실행 중: 17개**'
projects     first_line='**총 9개 프로젝트**'
scripts      first_line='_수집: 2026-08-29 14:58 KST_'
task_stats   first_line='_수집: 2026-08-29 14:58 KST_'
```

**수정 후 (5/5)**
```
skills       KST=YES  first_line='_수집: 2026-08-29 15:06 KST_'
services     KST=YES  first_line='_수집: 2026-08-29 15:06 KST_'
projects     KST=YES  first_line='_수집: 2026-08-29 15:06 KST_'
scripts      KST=YES  first_line='_수집: 2026-08-29 15:06 KST_'
task_stats   KST=YES  first_line='_수집: 2026-08-29 15:06 KST_'

cron 마커 생존: True True
```
★ `scripts`/`task_stats` 는 이 회차의 `changes` 에 없었는데도 15:06 으로 갱신됐다 —
`for section in SECTIONS:` 순회가 실제로 동작한다는 직접 증거다.

### ★ 백필 1회 필요했음 (보고 사항)
수정 직후 첫 실행은 **`변경 없음 - 파일 업데이트 생략` / exit 0** 으로 조기종료했다.
직전 14:58 회차(수정 전)가 캐시를 이미 최신화해 둔 상태였기 때문이다.
그런데 헤더가 없던 3개 섹션(`skills`/`services`/`projects`)은 **가장 느리게 변하는 섹션**이라
자연 변경을 기다리면 수개월간 시각 없음으로 남는다(=③ 목적 무산).

→ **allowed_resources 내 `.spec-state-cache.json` 에서 그 3개 키만 제거**해 1회 백필 실행했다.
   코드 변경 아님. 렌더 포맷이 바뀐 데 따른 1회성 마이그레이션이다.
   changelog 에는 `섹션 신규 생성` 3줄이 남았다(스크립트 정상 동작 결과. 소급 수정 안 함).

---

## 3. 조기종료 무손상 재확인 (sha256 + mtime + exit code)

백필 이후 **연속 2회 실행**. 대상 3개 파일 전부 불변:

```
=== BEFORE ===
99cb741538bff23b080980b0bae7fd43  mtime=2026-08-29 15:06:09.551176197 +0900  anu-system-spec.md
4bb1511e63e16289ee3b9a2cc4e01636  mtime=2026-08-29 15:06:09.592175497 +0900  anu-system-spec-changelog.md
096e07d2d5de537a4e22b6e715403a7b  mtime=2026-08-29 15:06:09.564175974 +0900  .spec-state-cache.json

=== 1회차 ===
[update-system-spec] 데이터 수집 시작...
  [OK] skills / [OK] services / [OK] projects / [OK] scripts / [OK] task_stats
[update-system-spec] 변경 없음 - 파일 업데이트 생략.
RUN1_EXIT=0

=== 2회차 ===
[update-system-spec] 데이터 수집 시작...
  [OK] skills / [OK] services / [OK] projects / [OK] scripts / [OK] task_stats
[update-system-spec] 변경 없음 - 파일 업데이트 생략.
RUN2_EXIT=0

=== AFTER ===
99cb741538bff23b080980b0bae7fd43  mtime=2026-08-29 15:06:09.551176197 +0900  anu-system-spec.md
4bb1511e63e16289ee3b9a2cc4e01636  mtime=2026-08-29 15:06:09.592175497 +0900  anu-system-spec-changelog.md
096e07d2d5de537a4e22b6e715403a7b  mtime=2026-08-29 15:06:09.564175974 +0900  .spec-state-cache.json
```
**sha256 3/3 동일 · mtime(ns) 3/3 동일 · exit 0 / exit 0.**

### changelog 최신 블록 — `[cron]` 0건
```
## [2026-08-29 15:06] 변경 내역
- [skills] `skills` 섹션 신규 생성
- [services] `services` 섹션 신규 생성
- [projects] `projects` 섹션 신규 생성
```
`grep -c "\[cron\]"` → **0**

---

## 4. 테스트

`scripts/tests/test_update_system_spec.py` (신규, 426줄 / 31 테스트).
모든 파일 쓰기는 pytest `tmp_path` 샌드박스 안에서만 수행하고, 실제 워크스페이스 파일은 읽기 전용 접근.

```
============================== 31 passed in 0.55s ==============================
```

### 이번에 보강한 것
- `test_all_five_sections_get_kst_header_after_real_run` — 샌드박스에서 **실제 실행 후**
  문서의 `AUTO:*` 마커 사이를 파싱해 5개 섹션 전부 첫 줄 KST 확인
- `test_cron_block_untouched_by_full_section_sweep` — SECTIONS 전체 순회로 바뀌어도 cron 부재 선언 블록 불변
- `test_failed_section_does_not_raise_keyerror_in_sweep` — `projects` 수집을 실패시켜
  KeyError 미발생 + exit 1 + 나머지 4섹션 KST 유지 (가드 봉인)
- `test_second_run_short_circuits_without_rewriting_files` — **`.spec-state-cache.json` 의
  sha256 + mtime 불변 검사 추가** (기존엔 spec/changelog 2개만 검사)

### Pyright
```
0 errors, 0 warnings, 0 informations
```
301행·341행 미사용 `mod` 픽스처 인자 2건 제거(테스트 본문 무변경).
해당 테스트들은 각각 `_build_sandbox`/`SCRIPT_PATH` 만 사용하므로 의미 변화 없음.

---

## 5. 봉인 — 변이 4종 (전부 FAIL 유도 · 복원 sha 일치)

원본 sha256 = `044926e690aad2d0fbc40cef9de7ad347aedb63423fbb19859762f1822a5baf6`

| # | 변이 | no-op 아님 증명 (mut_sha≠orig) | 결과 | FAIL 테스트 | 복원 sha 일치 |
|---|------|------|------|------|------|
| M1 | 렌더루프 되돌리기 `SECTIONS`→`changes` | `a2fa138de062` ≠ `044926e690aa` OK | 2 failed, 29 passed | `test_all_five_sections_get_kst_header_after_real_run`, `test_failed_section_does_not_raise_keyerror_in_sweep` | OK |
| M2 | `detect_changes` 가드 제거 (유령 부활) | `b7f3f2f685de` ≠ `044926e690aa` OK | 1 failed, 30 passed | `test_missing_arbitrary_section_produces_no_ghost_change` | OK |
| M3 | `SECTIONS` 에 `"cron"` 복원 | `848545a926e4` ≠ `044926e690aa` OK | 6 failed, 25 passed | `test_second_run_short_circuits_without_rewriting_files`, `test_second_run_changelog_has_no_cron_entry`, `test_cron_removed_from_sections`, `test_cron_collector_renderer_entries_preserved`, `test_all_five_sections_get_kst_header_after_real_run`, `test_cron_block_untouched_by_full_section_sweep` | OK |
| M4 | 수집시각 KST 헤더 제거 | `82f6b6e2f391` ≠ `044926e690aa` OK | 10 failed, 21 passed | `test_render_first_line_is_kst_timestamp[skills/services/projects/scripts/task_stats]`, `test_render_empty_branch_also_has_kst_header[services/projects/scripts]`, `test_all_five_sections_get_kst_header_after_real_run`, `test_failed_section_does_not_raise_keyerror_in_sweep` | OK |

★ 4종 모두 (1) 변이 후 sha 가 원본과 다름을 `assert` 로 선증명 → (2) FAIL 유도 확인 → (3) 원본 바이트 복원 후 sha 재대조까지 통과.

---

## 6. 변경 규모

| 파일 | 이번 세션 delta | task-3056 전체 delta |
|------|------|------|
| `scripts/update-system-spec.py` | **-2 / +6** (렌더 루프 1줄 + 주석 4줄) | +34 / -10 |
| `scripts/tests/test_update_system_spec.py` | **+76 / -2** (신규 테스트 3종 71줄, 캐시 assert 5줄, `mod` 인자 정리 2줄) | 신규 426줄 |
| `memory/specs/anu-system-spec.md` | 스크립트 자동 갱신 | +65 / -64 |
| `memory/specs/.spec-state-cache.json` | 백필 1회 (3키 제거 → 스크립트가 재생성) | `cron` 키 삭제 |

---

## 7. 금지사항 준수 확인

- `ANU_KEY`/`ANU_CHAT` 정의 안 함 (`os.environ.get(..., "")` 원형 유지) — 테스트로 봉인
- `collect_cron()`/`render_cron()`/`COLLECTORS['cron']`/`RENDERERS['cron']` 전부 생존 — 테스트로 봉인
- `AUTO:cron:START/END` 마커 생존 — 테스트로 봉인
- changelog 소급 수정 없음 (263건 유령 항목 그대로 보존)
- `backup-spec.sh` · `memory/backups/**` 미접촉
- `.bak-260829` 백업 3개 미접촉 (`update-system-spec.py.bak-260829`, `anu-system-spec.md.bak-260829`, `.spec-state-cache.json.bak-260829`)
- `collect_*` 로직 변경 없음 · 그 외 리팩터링 없음
- 자격증명 값 로그/문서 미출력 (`test_cron_block_leaks_no_credential` 로 봉인)

## 8. ANU 판단 대기 / 잔여

1. **06:15 크론 첫 실행** — 오늘 15:06 회차가 캐시를 최신화했으므로 내일 06:15 은
   실변경이 없으면 `변경 없음` + exit 0 으로 조기종료한다(정상).
2. **느린 섹션의 시각 갱신 주기** — 조기종료를 보존한 결과, `skills`/`services`/`projects` 의
   `_수집:_` 시각은 **어느 한 섹션이라도 변경될 때만** 갱신된다.
   "변경 없는 회차에도 시각을 갱신"하려면 조기종료를 포기해야 하므로 상충한다. 현 설계는 조기종료 우선.

---

## ★ 팀장(비슈누) 독립 검증 — 2026-08-29 15:1x KST

팀원 보고를 신뢰하지 않고 팀장이 직접 재실행·재측정한 결과.

| # | 검증 항목 | 결과 |
|---|---|---|
| 1 | 전체 1회 실행 exit code | **EXIT=0** (수정 전 baseline=1) |
| 2 | 조기종료(무변경 회차 재작성 안 함) | spec·changelog·cache **md5 3/3 전부 불변**, EXIT2=0 |
| 3 | 5개 섹션 KST 헤더 (문서 직접 파싱) | skills·services·projects·scripts·task_stats **5/5 YES** (`_수집: 2026-08-29 15:06 KST_`) |
| 4 | `AUTO:cron` 마커 생존 | START/END **양쪽 생존**, 내용은 부재 선언 |
| 5 | 4월 JSON 잔재 (ID 6종 grep) | **0건** |
| 6 | `SECTIONS` | `["skills","services","projects","scripts","task_stats"]` — cron 없음 |
| 7 | `collect_cron`/`render_cron` 생존 | `:62`, `:173` **양쪽 존재** |
| 8 | 캐시 키 | `scripts, task_stats, skills, services, projects` — **cron 키 없음** |
| 9 | changelog 최신 블록 `[cron]` | **0건** |
| 10 | pytest | **31 passed in 0.54s** |
| 11 | 백업 3종 무손상 | 16404B / 34582B / 11207B 그대로 |
| 12 | 자격증명 노출 | 3개 파일 전부 **0건** |

## ★ 잔여 / ANU 판단 필요

1. **백업 파일이 문서 스크립트 목록에 유입됨** — `scripts/update-system-spec.py.bak-260829` 이
   `scripts/` 최상위라 `collect_scripts()` 가 수집한다(174→175). 명세가 백업 위치를
   `<파일>.bak-260829` 로 지정했으므로 지시대로 두었다. **백업 3개를 나중에 지우면
   changelog 에 `scripts 삭제:` 항목이 한 번 더 남는다.** 정리 시점은 ANU 판단.
2. **`_수집:_` 시각은 "어느 한 섹션이라도 변경된 회차"에만 갱신된다.** 조기종료 보존과
   상충하므로 조기종료를 우선했다. 즉 시각은 "마지막으로 무언가 바뀐 시점"의 수집시각이며,
   그 이후 회차에서 값이 동일함은 검증됐으나 문서에 반영되지 않는다.
   (매 회차 갱신을 원하면 조기종료를 포기해야 한다 — 이번 태스크의 P1 과 정면 충돌)
3. **유령 가드의 실효 범위 변경** — cron 이 `SECTIONS` 에서 빠지면서 detect_changes 루프가
   cron 을 순회하지 않는다. 따라서 가드는 이제 **"남은 5개 중 수집이 실패한 섹션"** 을 막는다.
   그 케이스로 테스트·변이 봉인을 걸었다(M2).
4. **`.spec-state-cache.json` 은 git `skip-worktree`** 라 cron 키 삭제가 `git status` 에 안 뜬다.
   디스크 변경은 실재. 커밋 누락으로 오해하지 말 것.
5. **명세 오차 1건 (반증)** — 명세는 "`/home/jay/workspace` 는 git 저장소가 아님" 이라 했으나
   `git rev-parse --is-inside-work-tree` → **true**. 작업 방식(PR 없이 직접 수정)은
   `merge_policy: direct` 와 일치하므로 영향 없음.
6. **changelog 263건 유령은 소급 삭제하지 않았다** (명세 지시: 과거 기록). 앞으로 안 생긴다.

---

## 요약 (SCQA)

**S**: 시스템 명세 자동갱신 스크립트가 exit 1 로 끝나고, 실제 변경이 없는데도 changelog 에 유령 `[cron]` 항목을 계속 쌓고 있었다. 5개 섹션 중 수집시각 표기는 2개뿐이었다.
**C**: 유령 변경 제거·cron 철회·5섹션 KST 표기 세 가지를 동시에 해결하되, 무변경 회차에 파일을 재작성하지 않는 조기종료(P1)를 깨서는 안 됐다.
**Q**: 조기종료를 보존한 채로 세 결함을 전부 해소할 수 있는가?
**A**: 가능했다. 가드 1줄 + `SECTIONS` 에서 cron 제거 + 렌더 루프를 SECTIONS 전체 순회로 바꿔 달성했고, 실제 워크스페이스 연속 실행에서 md5 3/3 불변 + exit 0 으로 조기종료 무손상을 실증했다.

## S — 상황

`scripts/update-system-spec.py` 는 `memory/specs/anu-system-spec.md` 를 06:15 cron 으로 자동 갱신한다.
task-3056 착수 시점의 실측 baseline 은 **exit 1**, changelog 에는 실제 변경이 없는데도
`[cron]` 유령 항목이 누적(263건)되고 있었고, 5개 섹션 중 수집시각(KST) 표기가 있는 것은
`scripts`/`task_stats` **2개뿐**이었다.

## C — 문제

세 결함이 서로 얽혀 있었다.
(1) `detect_changes()` 가 수집 실패 섹션을 `new_state` 부재인 채로 비교해 **유령 변경**을 만들어냈다.
(2) `cron` 섹션은 수집 대상이 아닌데 `SECTIONS` 에 남아 매 회차 `[cron]` 변경을 기록했다.
(3) 렌더 루프가 `for section in changes:` 였기 때문에, 느리게 변하는
`skills`/`services`/`projects` 는 자연 변경이 생길 때까지 수개월간 수집시각이 표기되지 않았다.
동시에 P1(무변경 회차 파일 재작성 금지 = 조기종료)을 깨뜨려서는 안 됐다.

## Q — 질문

조기종료(무변경 회차에 파일을 건드리지 않음)를 **보존한 채로**,
유령 변경을 없애고 5개 섹션 전부에 KST 수집시각을 표기할 수 있는가?
그리고 `collect_cron()`/`render_cron()`/`AUTO:cron` 마커 등 금지된 자산을 손대지 않고 가능한가?

## A — 답변

**가능했고, 실제로 검증됐다.**
`detect_changes()` 에 `if section not in new_state: continue` 가드를 두어 유령 변경을 차단하고,
`SECTIONS` 에서 `"cron"` 만 제거(함수·마커·`COLLECTORS`/`RENDERERS` 엔트리는 전부 생존),
렌더 루프를 `for section in SECTIONS:` 로 바꿔 5개 섹션 전부에 `_수집: … KST_` 헤더를 붙였다.
조기종료는 `if not changes: return 0` 이 렌더 루프보다 앞단에 있어 그대로 살아 있으며,
아래 L1 스모크테스트에서 연속 실행 시 3개 파일 md5 불변으로 실증했다.
회귀 봉인은 신규 테스트 31건 + 변이 4종(전부 FAIL 유도 후 원본 sha 복원)으로 걸었다.

---

## L1 스모크테스트

★ **목(mock) 아님.** pytest 샌드박스가 아니라 **실제 워크스페이스**(`/home/jay/workspace`)에서
운영 파일(`memory/specs/anu-system-spec.md` 등)을 대상으로 스크립트를 그대로 실행한 결과다.
아래 출력은 QC 해소 시점(2026-08-29 15:18 KST)에 **재실행한 실제 stdout** 이다.

### L1-1. 실행 전 md5 baseline

```
$ cd /home/jay/workspace && md5sum memory/specs/anu-system-spec.md memory/specs/anu-system-spec-changelog.md memory/specs/.spec-state-cache.json
0407b5b70da610830c7f185774fdddb4  memory/specs/anu-system-spec.md
caeb8c1e75b394d91726b8f72b01f6eb  memory/specs/anu-system-spec-changelog.md
7f8da77a83990b3d35a4893cf1c283f5  memory/specs/.spec-state-cache.json
```

### L1-2. 1회차 실행 — exit 0 (정상 갱신 경로)

```
$ cd /home/jay/workspace && python3 scripts/update-system-spec.py; echo "EXIT=$?"
[update-system-spec] 데이터 수집 시작...
  [OK] skills
  [OK] services
  [OK] projects
  [OK] scripts
  [OK] task_stats
[update-system-spec] 변경 감지: ['task_stats']
[update-system-spec] /home/jay/workspace/memory/specs/anu-system-spec.md 업데이트 완료.
[update-system-spec] 변경 로그 기록 완료: /home/jay/workspace/memory/specs/anu-system-spec-changelog.md
EXIT=0
```

**EXIT=0 확인.** (task-3056 착수 전 baseline 은 exit 1 이었다.)
이 회차는 15:06 회차 이후 실제로 `task_stats` 가 변한 상태였으므로 갱신 경로를 탔다.
변경 감지 목록은 `['task_stats']` 단 1건 — **유령 `[cron]` 항목 0건**이다.

### L1-3. 연속 2회차·3회차 — 조기종료 + exit 0

```
$ md5sum ... (1회차 직후)
0c3590af72e16bba7cf2b58c81ec1799  memory/specs/anu-system-spec.md
0ef64b32edecfd0a146c61a82af7571f  memory/specs/anu-system-spec-changelog.md
b4bef1504d4b9b6395160dfc83be0536  memory/specs/.spec-state-cache.json

$ python3 scripts/update-system-spec.py; echo "EXIT=$?"     # 2회차
[update-system-spec] 데이터 수집 시작...
  [OK] skills
  [OK] services
  [OK] projects
  [OK] scripts
  [OK] task_stats
[update-system-spec] 변경 없음 - 파일 업데이트 생략.
EXIT=0

$ python3 scripts/update-system-spec.py; echo "EXIT=$?"     # 3회차
[update-system-spec] 데이터 수집 시작...
  [OK] skills
  [OK] services
  [OK] projects
  [OK] scripts
  [OK] task_stats
[update-system-spec] 변경 없음 - 파일 업데이트 생략.
EXIT=0

$ md5sum ... (3회차 직후)
0c3590af72e16bba7cf2b58c81ec1799  memory/specs/anu-system-spec.md
0ef64b32edecfd0a146c61a82af7571f  memory/specs/anu-system-spec-changelog.md
b4bef1504d4b9b6395160dfc83be0536  memory/specs/.spec-state-cache.json
```

**`변경 없음 - 파일 업데이트 생략` + EXIT=0 (2회 연속) · md5 3/3 완전 일치 = 조기종료 무손상 확인.**
P1(무변경 회차 파일 재작성 금지)이 렌더 루프 변경 이후에도 살아 있다는 실행 증거다.

### L1-4. 실행 후 문서 상태 (실제 파일 파싱)

```
skills       KST=YES first_line='_수집: 2026-08-29 15:18 KST_'
services     KST=YES first_line='_수집: 2026-08-29 15:18 KST_'
projects     KST=YES first_line='_수집: 2026-08-29 15:18 KST_'
scripts      KST=YES first_line='_수집: 2026-08-29 15:18 KST_'
task_stats   KST=YES first_line='_수집: 2026-08-29 15:18 KST_'
cron markers: True True
```

5/5 KST 표기 · `AUTO:cron:START/END` 마커 **양쪽 생존**.

### L1-5. 테스트 스위트

```
$ cd /home/jay/workspace && python3 -m pytest scripts/tests/test_update_system_spec.py -q | tail -2
scripts/tests/test_update_system_spec.py ............................... [100%]
============================== 31 passed in 0.54s ==============================
```

**31 passed. 실패 0.**

### L1 판정

| # | 항목 | 기대 | 실측 | 결과 |
|---|------|------|------|------|
| 1 | 실제 워크스페이스 1회 실행 | exit 0 | EXIT=0 | 성공 |
| 2 | 연속 2회차 조기종료 | `변경 없음` + exit 0 | 동일 | 성공 |
| 3 | 연속 3회차 조기종료 | `변경 없음` + exit 0 | 동일 | 성공 |
| 4 | 조기종료 회차 파일 불변 | md5 3/3 동일 | 3/3 동일 | 성공 |
| 5 | 5섹션 KST 헤더 | 5/5 | 5/5 YES | 성공 |
| 6 | `AUTO:cron` 마커 생존 | START/END 존재 | True True | 성공 |
| 7 | changelog 유령 `[cron]` | 0건 | 0건 | 성공 |
| 8 | pytest | 전량 통과 | 31 passed | PASS |

### L1-6. changelog 최신 블록 (참고 — 아래 인용문에 리터럴 `##` 줄이 포함됨) — `[cron]` 0건

```
## [2026-08-29 15:18] 변경 내역
- [task_stats] `task_stats` 데이터 변경

$ grep -c '\[cron\]'  (최신 블록 대상)
0
```

---

## 수정 파일

task-3056 이 실제로 손댄 파일은 아래 4개뿐이다.

- /home/jay/workspace/scripts/update-system-spec.py
- /home/jay/workspace/memory/specs/anu-system-spec.md
- /home/jay/workspace/memory/specs/.spec-state-cache.json
- /home/jay/workspace/scripts/tests/test_update_system_spec.py

### 수정 파일별 검증 상태

| 파일 | 변경 내용 | 검증 방법 | 상태 |
|------|-----------|-----------|------|
| scripts/update-system-spec.py | detect_changes 유령 가드 · SECTIONS 에서 cron 제거 · _collected_at_kst/_collected_header 신설 · 렌더 루프 SECTIONS 전체 순회 | 실제 워크스페이스 3회 실행(L1-2/L1-3) + pytest 31건 + 변이 4종 FAIL 유도 | verified |
| memory/specs/anu-system-spec.md | 5개 섹션에 KST 수집시각 헤더 · cron 블록 부재 선언(마커 보존) | 문서 직접 파싱 5/5 YES · cron 마커 True True(L1-4) | verified |
| memory/specs/.spec-state-cache.json | cron 키 삭제 · 렌더 포맷 변경에 따른 3키 1회 백필 | 조기종료 회차 md5 불변(L1-3) · 캐시 키 목록에 cron 없음 | verified |
| scripts/tests/test_update_system_spec.py | 신규 426줄 / 31 테스트 (조기종료·유령·cron 생존·KST 헤더·자격증명 봉인) | pytest 31 passed(L1-6) · 변이 4종에서 전부 FAIL 유도 확인 | verified |

★ `memory/specs/.spec-state-cache.json` 은 git `skip-worktree`(`git ls-files -v` → `S`) 상태라
`git add` 대상이 되지 않는다. 디스크 변경은 실재하며, 커밋 누락이 아니라 **의도된 추적 제외**다.
(무리하게 skip-worktree 를 풀지 않았다 — 다른 팀 공유 파일이다.)
