# task-2661 보고서 — CALLBACK_NORMAL_FIRE_DELAY_REMEDIATION_PHASE_2B_ABSOLUTE_AT

## 최종 상태 (회장 verbatim)

**`CALLBACK_NORMAL_FIRE_DELAY_REMEDIATION_P2B_HOLD_FOR_CHAIR`**

(★ 코드 정정 / regression 8/8 PASS / cokacdir 호환성 dry-run PASS 완료, 그러나 **ANCHOR-8** 에 따라 `ghs_` (BOT App token) 부재 → 봇 자율 push/PR 금지 → 회장 결정 대기)

- chair_authorization_id (verbatim): `CHAIR-AUTH-CALLBACK-DELAY-P2B-20260525-JJONGS-ABSOLUTE-AT-001`
- branch: `task/task-2661-dev5`
- commit: `2d6dbee0`
- base: `d4098b04` (origin/main · `task-2642 — CI_WATCH_HANDOFF_RUNNER`)
- worktree: `/home/jay/workspace/.worktrees/task-2661-dev5`

## SCQA

### Situation
task-2659 PR #147 PASS_WITH_2_OBSERVATIONS 이후 회장 callback scheduling policy audit 요청. `finish-task.sh:1369` 의 `--at "10m"` 하드코딩이 done→fire idle gap 14분의 직접 원인이라는 감사 결과 (`memory/events/callback-fire-at-delay-audit-packet-260525.json`). task-2660 (dev1 헤르메스) 가 `--at "10s"` 로 정정 시도 (commit `c6425611`) 했으나 cokacdir live runtime 이 `Error: invalid --at value: 10s` 로 reject. HOLD_FOR_CHAIR + cokacdir runtime format mismatch incident 박제.

### Complication
- cokacdir live runtime 은 `--at "Ns"` (초 단위 suffix) 미지원 — 최소 단위 `1m`.
- 회장 정책 (normal callback ≤ 30s) 은 절대시각 사용 시에만 달성 가능.
- 회장 verbatim 비채택: `--in 10s` / `--delay 10s` / `--at` 절대시각 only 라는 주장.
- 회장 verbatim 채택: API Overloaded 부하 분리 logging.
- ANCHOR-8: `ghs_` 부재 / OWNER PAT 만 시 push/PR 금지.

### Question
30s 이내 fire 를 cokacdir live runtime 호환 형식으로 어떻게 달성하면서, fallback / Axis runtime / live cokacdir 등 금지 영역은 0 변경으로 유지할까?

### Answer (Phase 2b 실행 결과)
3 파일 정정 + 1 신규 regression suite. cokacdir 절대시각 (`YYYY-MM-DD HH:MM:SS`) 형식으로 (`date -d '+30 seconds' '+%Y-%m-%d %H:%M:%S'`) 생성한 값을 `--at` 으로 전달. dispatch helper 는 `build_absolute_at_for_normal_delay(now+30s)` 로 동일 결과 산출. fallback 은 `DEFAULT_AT_FALLBACK="10m"` 유지. registrar docstring 정확화. regression **8/8 카테고리 26 fixture ALL PASS**. cokacdir 실 runtime dry-run 검증 PASS (`schedule_type=absolute`, `status=ok`).

## 회장 보고 필수 8

### 1) PR 번호
**없음** (★ `ghs_` 부재 → ANCHOR-8 발동 → push/PR 차단 → HOLD_FOR_CHAIR)

### 2) changed_files (6 = expected_files 6 + lock 1 별도)
- `scripts/finish-task.sh` (line 1356–1380 · 17 lines diff)
- `dispatch/normal_fallback_callback_helper.py` (+150 / -3 · 177 lines diff)
- `utils/anu_callback_registrar.py` (+17 / -5 · docstring only · 22 lines diff)
- `tests/callback_fire_delay_remediation_p2b/__init__.py` (신규)
- `tests/callback_fire_delay_remediation_p2b/conftest.py` (신규)
- `tests/callback_fire_delay_remediation_p2b/test_regression_8.py` (신규 · 26 fixture)
- `.tasks/locks/task-2661.lock` (★ gitignored · start_task_guard 호환 disk-only)

### 3) normal delay before / after (★ absolute timestamp 형식 명시)

| 단계 | literal | format | register→fire |
| --- | --- | --- | --- |
| **base d4098b04** | `--at "10m"` | relative-suffix minutes | 600s |
| **task-2660 c6425611 (rejected)** | `--at "10s"` | relative-suffix seconds (★ cokacdir reject) | N/A — `Error: invalid --at value: 10s` |
| **Phase 2b after (2d6dbee0)** | `--at "$T2661_NORMAL_ABSOLUTE_AT"` where `T2661_NORMAL_ABSOLUTE_AT="$(date -d '+30 seconds' '+%Y-%m-%d %H:%M:%S')"` | absolute timestamp `YYYY-MM-DD HH:MM:SS` (★ cokacdir live runtime compatible) | ≤ 30s |

task-2659 chronology replay:
- pre-remediation idle gap = .done(14:10:51) → fire(14:25:00) ≈ **14m**
- post-remediation idle gap = .done(14:10:51) → register(14:15:00) → fire(register+30s, 14:15:30) ≈ **4m 39s** (★ pre/2 이하)

### 4) fallback 영향 — **변경 0**
- `DEFAULT_AT_FALLBACK == "10m"` (★ 600s · 변경 0)
- helper_main `--kind fallback` → `--at "10m"` 정확 일치 (R3 fixture)
- `utils/anu_callback_fallback.py` · `utils/completion_callback_fallback_cancel.py` · `utils/axis_3_canary_scale_aware_guard/*` · `utils/anu_spawn_visibility_guard/*` 미수정

### 5) regression 결과 (★ 8/8 개별)

| ID | 카테고리 | fixture 수 | 결과 |
| --- | --- | --- | --- |
| R1 | normal callback uses absolute `--at` ≤ 30s | 3 | **PASS** |
| R2 | `--at "10s"` / `"10m"` (normal) / `--in` / `--delay` not used | 4 | **PASS** |
| R3 | fallback `"10m"` unchanged | 3 | **PASS** |
| R4 | normal delay > 60s 시 reason 없으면 lint warning | 5 | **PASS** |
| R5 | task-2659 14:10→14:25 14m gap 재발 금지 fixture | 2 | **PASS** |
| R6 | callback envelope UTF-8 ≤ 3900 bytes | 3 | **PASS** |
| R7 | registrar docstring: second UNSUPPORTED · absolute supported | 3 | **PASS** |
| R8 | API overloaded / scheduler format failure 별도 channel/marker | 3 | **PASS** |
| 합계 | 8 카테고리 | **26** | **26 / 26 ALL PASS** |

실행 결과:
```
============================== 26 passed in 0.14s ==============================
```

### 6) forbidden_action_count = **0**

금지 12 일괄 검증:
- fallback/dead-man delay 변경 0
- fallback launcher 구조 변경 0
- Phase 4 threshold enforce 0
- `dispatch.py` / `live settings.json` / Axis 1/2/3 runtime / `hooks/*` / `/usr/local/bin/cokacdir` 변경 0
- PR #147 merge 0
- auto-merge 0
- policy 승격 0
- HARNESS_ENFORCED 전체 선언 0
- `--at "10s"` 재사용 0 (regression R2)
- `--in` / `--delay` 옵션 사용 0 (regression R2)

### 7) auto-merge 0 확인
- 본 작업은 push 자체를 안 함 (ghs_ 부재 → ANCHOR-8 발동).
- `gh pr create` 호출 0 · `auto-merge label` 부착 0.
- PR #147 (task-2659) 도 unmerged 유지 anchor.

### 8) cokacdir `--at` absolute timestamp 호환성 동작 검증 (실 runtime)

| 시도 | 형식 | 결과 | schedule_id | 정리 |
| --- | --- | --- | --- | --- |
| pre-test (minimal envelope) | `--at "2026-05-25 15:55:04"` | `status:ok` `schedule_type:absolute` | `9F4662B3` | ✅ removed |
| 본 test (full envelope w/ new labels) | `--at "2026-05-25 16:03:36"` (envelope 에 `callback_kind=normal` / `source_attribution=FINISH_TASK_SH_BOT_COMPLETION_NORMAL` 포함) | `status:ok` `schedule_type:absolute` | `A52CA89C` | ✅ removed |

봇 dispatch 영향: **ZERO** (★ Axis 1/2/3 observer chain traffic 미생성 · 즉시 cancel).

## smoking gun 3 정정 상세

### A. `scripts/finish-task.sh` (line 1354–1380)

```diff
-# 5.5. [task-2626] callback runtime enforcement gate (ADDITIVE · 완전 방어 · 비차단)
-# 봇 completion callback 은 반드시 ANU-key 단일 launcher 경유 (self-key fail-closed).
-# canonical root=/home/jay/workspace 명시. 실 cron 발사는 extract_followup 가 ANU key 로 수행.
+# 5.5. [task-2626 + task-2661 Phase 2b] callback runtime enforcement gate
+#   ADDITIVE · 완전 방어 · 비차단. 봇 completion callback 은 반드시 ANU-key 단일
+#   launcher 경유 (self-key fail-closed). canonical root=/home/jay/workspace 명시.
+# task-2661 Phase 2b: normal callback fire delay 30s 이내 (회장 verbatim).
+#   cokacdir live runtime 은 `--at "Ns"` (초 단위 suffix) reject — 절대시각만 사용.
+#   `date -d '+30 seconds'` 로 (현재시각 + 30초) 의 absolute timestamp 생성.
+#   fallback default 변경 0 — 본 라인은 normal kind 전용.
 T2626_ANU_KEY="c119085addb0f8b7"
 export COKACDIR_KEY_ANU="${COKACDIR_KEY_ANU:-$T2626_ANU_KEY}"
+T2661_NORMAL_ABSOLUTE_AT="$(date -d '+30 seconds' '+%Y-%m-%d %H:%M:%S')"
 T2626_ENVELOPE="task_id=${TASK_ID}
 result_path=memory/events/${TASK_ID}.result.json
 report_path=memory/reports/${TASK_ID}.md
 collector_role=ANU
+callback_kind=normal
+source_attribution=FINISH_TASK_SH_BOT_COMPLETION_NORMAL
 owner_key=${T2626_ANU_KEY}
 canonical_root=${WORKSPACE}"
 ( cd "$WORKSPACE" && PYTHONPATH="$WORKSPACE" python3 -m dispatch.normal_fallback_callback_helper launch \
     --kind normal --task-id "$TASK_ID" \
     --executor-key "${COKACDIR_KEY_SELF:-executor-self-unknown}" \
     --owner-key "$T2626_ANU_KEY" --chat-id "6937032012" \
-    --prompt "$T2626_ENVELOPE" --at "10m" \
+    --prompt "$T2626_ENVELOPE" --at "$T2661_NORMAL_ABSOLUTE_AT" \
     --canonical-root "$WORKSPACE" ) \
     > "$WORKSPACE/memory/events/${TASK_ID}.callback-launch.json" 2>/dev/null \
-    && echo "[task-2626] callback runtime gate: ANU-owned launcher PASS" \
+    && echo "[task-2626] callback runtime gate: ANU-owned launcher PASS (at=$T2661_NORMAL_ABSOLUTE_AT)" \
     || echo "[task-2626] callback runtime gate: fail-closed/non-blocking (callback-launch.json 참조)"
```

### B. `dispatch/normal_fallback_callback_helper.py`

추가된 상수/함수:
- `DEFAULT_AT_NORMAL_DELAY_SECONDS = 30`
- `DEFAULT_AT_FALLBACK = "10m"` (★ 변경 0 anchor 라벨링)
- `NORMAL_DELAY_REASON_THRESHOLD_SECONDS = 60`
- `build_absolute_at_for_normal_delay(delay_seconds=30, *, now=None) -> str` — now+delta → `YYYY-MM-DD HH:MM:SS`
- `is_absolute_at(at) -> bool`
- `absolute_at_delay_seconds(at, *, now=None) -> Optional[int]`
- `parse_at_seconds(at) -> Optional[int]` (relative-suffix only · absolute returns None)
- `lint_normal_callback_delay(kind, at, *, reason, threshold_seconds=60, now=None) -> dict`

`main()` kind-aware default 분기:
```python
at_value = a.at
if at_value is None:
    if a.kind == CALLBACK_KIND_NORMAL:
        at_value = build_absolute_at_for_normal_delay(DEFAULT_AT_NORMAL_DELAY_SECONDS)
    else:
        at_value = DEFAULT_AT_FALLBACK  # "10m" 변경 0
```

`ENVELOPE_ALLOWED_KEYS` 에 `source_attribution` 추가.

### C. `utils/anu_callback_registrar.py:_delay_to_at_value` docstring (★ 함수 시그니처/본문 변경 0)

거짓 라인 제거 + live runtime 정확화. 핵심 변화:
- "second granularity (10s)" 거짓 주장 삭제
- "second suffix UNSUPPORTED in live runtime" 명기 (★ cokacdir reject evidence 2026-05-25 15:33 인용)
- "absolute timestamp (`YYYY-MM-DD HH:MM:SS`)" supported form 으로 명기
- "1m smallest legal relative-suffix form" 유지
- "Production callers wanting a sub-minute fire MUST pass an absolute timestamp via `at_value` (see `dispatch.normal_fallback_callback_helper.build_absolute_at_for_normal_delay`)" 명기

## 안전 anchor 일괄

- 회장 본 세션 적용 0
- ANU collector 적용 0
- Axis 3 canary observer chain 격리 유지
- Axis 3 RUNNING 자동선언 0
- `dispatch.py` / `live settings.json` / `hooks/*` / `/usr/local/bin/cokacdir` 미접근
- fallback module 변경 0 (`utils/anu_callback_fallback.py` · `utils/completion_callback_fallback_cancel.py`)
- canary guard 변경 0 (`utils/axis_3_canary_scale_aware_guard/*` · `utils/anu_spawn_visibility_guard/*`)
- task-2660 commit `c6425611` 보존 (worktree `.worktrees/task-2660-dev1`)

## 다음 단계 (회장 결정 대기 3)

1. 본 commit `2d6dbee0` push + PR 생성 결정
   - 옵션 A: 회장 본인 push (OWNER PAT 직접)
   - 옵션 B: `ghs_` (BOT App token) 발급 후 dev5 봇 재가동
   - 옵션 C: commit 보존만 + 다른 경로 (cherry-pick to main 등)
2. task-2660 commit `c6425611` 보존/폐기 결정 (★ Phase 2b 가 spec 차원에서 c6425611 의 모든 변경 사항 흡수 — `c6425611` 자체는 cokacdir reject 라 미사용)
3. Phase 3 fallback launcher 분리 / Phase 4 threshold enforce 진행 여부 (★ 별도 회장 verbatim 강제)

## 모델 사용 기록

- 본 작업: Claude Opus 4.7 (1M context) — 본 마르둑(팀장) 직접 수행
- 사유: 회장 verbatim 강제 spec / fail-closed 보안 영역 / 단일 critical task / 팀원 위임 시 spec drift 위험 > 토큰 절감 ROI. 회장 verbatim DIRECT-WORKFLOW.md L9-14 의 "Sonnet 3회 실패 시 팀장 개입" 보다 본건은 위험도 기반 직접 개입 케이스.
- 사용 turn: 1 (start) ~ 본 report 완료까지 ~30 turn 추정 (★ 봇 평균 대비 효율적 — `glm-mcp` / `codex-rescue` 미사용)
