# task-2778_a+3 완료 보고 — env_targets dangling 로직 microfix

상태명(완료): `TASK2778A_ENV_TARGETS_DANGLING_LOGIC_MICROFIX_DONE_ACTIVE_FALSE`
담당: dev2-team (오딘) | 대상 PR: #258 (브랜치 `task/task-2778_a-dev2`, head ff 갱신) | ACTIVE=false

## SCQA 요약

**S (Situation)**: `dispatch/terminal_artifact_index.py`의 `build_manifest`는 worktree terminal artifact(report/.done/callback envelope)를 canonical로 surface하며, envelope의 target 존재 여부로 7종 상태(DANGLING/ENVELOPE_ONLY/PICKUP_REQUIRED 등)를 판정한다.

**C (Complication)**: fresh Gemini(@b5cc101b, logic axis HIGH)가 지적한 대로, envelope 파일은 있으나 target 필드가 전혀 없어 `env_targets`가 빈 리스트일 때 `not env_targets_in_canonical and not env_targets_in_worktree`가 항상 참이 되어 **dangling이 무조건 참**으로 오판되었다. target이 없는데도 "dangling target"으로 분류되어 envelope presence/missing/dangling 의미가 흐려졌다.

**Q (Question)**: env_targets가 비어 있을 때 dangling을 거짓으로 만들고, dangling을 "target이 명시적으로 존재하지만 어디에도 없을 때만" 참이 되게 할 수 있는가?

**A (Answer)**: envelope 상태 분기 맨 앞에 `has_env_targets = bool(env_targets)` 가드를 추가하여 빈 targets는 `CALLBACK_ENVELOPE_ONLY`(target 부재)로 분리하고, dangling은 `has_env_targets`가 참인 경우에만 평가하도록 수정. 회귀 테스트 2건 추가, 전체 검증 통과(2778: 31, 2777: 30, 2776: 6, 2775: 63 = 130건 PASS), py_compile OK. PR #258 head를 b5cc101b→4498d50b로 fast-forward 갱신(force 0, merge 0).

## 수정 내용 (logic axis, path containment 무손상)

`dispatch/terminal_artifact_index.py` `build_manifest` envelope 상태 블록:
- 추가: `has_env_targets = bool(env_targets)` — raw parsed target 리스트의 명시적 존재 여부
- `not has_env_targets` → `STATUS_CALLBACK_ENVELOPE_ONLY` (dangling 아님, target 부재 분리; 요구 1·2·4)
- `has_env_targets and not in_canonical and not in_worktree` → `STATUS_CALLBACK_ENVELOPE_DANGLING` (요구 3)
- worktree-only / canonical-present 분기 보존 (요구 5·6)
- best-effort MEDIUM: `_rel_or_abs` except를 `(ValueError, OSError)`로 robustness 강화

path containment helper(`_safe_artifact_path`/`_safe_contained_target`/`_is_under`/`_resolve_target_presence`)는 **일절 미변경**(요구 7·8·9). malicious traversal target은 raw 리스트엔 존재하므로 `has_env_targets=True`, 보안 helper가 거부 → 여전히 dangling 처리(보안 무회귀).

## 수정/생성 파일 (allowed_resources 내 3개)
- `dispatch/terminal_artifact_index.py` — +13 −6 (logic fix)
- `tests/regression/test_terminal_artifact_visibility_2778.py` — 회귀 테스트 2건 추가
- `memory/reports/task-2778a-r3.md` / 본 보고서 — 보고서

## 테스트 결과 (요구 테스트 8 충족)
- 신규: `test_envelope_present_but_no_target_field_not_dangling`, `test_envelope_empty_target_values_not_dangling` — PASS
- 기존 dangling 무회귀: `test_envelope_truly_dangling_in_both`, `test_malicious_envelope_marked_dangling_not_present` — PASS
- 파일 전체: **31 passed** (기존 29 + 신규 2)
- 인접 회귀: 2777 **30 passed**, 2776 **6 passed**, 2775 **63 passed**

## L1 스모크테스트 결과 (실 build_manifest 호출)
- 서버 재시작: 해당없음 (순수 read-only 로직 모듈, 서버/API 없음)
- API 응답 확인: 해당없음 — 대신 실제 함수 직접 호출로 동작 검증
- 실행 검증 (`L1_SMOKE_RESULT: PASS`):
  - L1-A: target 필드 없는 envelope → status=`CALLBACK_ENVELOPE_ONLY`, DANGLING 부재 (버그 수정 실동작 확인)
  - L1-B: target 명시 + 미존재 → `CALLBACK_ENVELOPE_DANGLING_TARGETS` 유지 (회귀 0)
  - L1-C: canonical에 target 존재 → DANGLING 부재
- 스크린샷: 해당없음 (백엔드 로직)

## 검증 요구 15 결과
1 py_compile PASS / 2 2778 PASS / 3 2777 PASS / 4 2776 PASS / 5 2775 PASS / 6 raw key 0(NO_RAWKEY) / 7 ANU key literal 0(NO_ANUKEY) / 8 expected_files 밖 수정 0 / 9 forbidden path 0(NO_FORBIDDEN_TOUCHED) / 10 callback·fire·systemd·cron·subprocess 실호출 0(docstring 언급만, invariant 테스트 통과) / 11 ACTIVE=false / 12 PR #258 head ff only(b5cc101b→4498d50b) / 13 force push 0 / 14 새 PR 0 / 15 merge 0.

## 발견 이슈 및 해결
- 브랜치 `task/task-2778_a-dev2`의 underscore(`_a`)가 pre-commit/pre-push 훅 정규식(`^task/(task-[0-9]+([.+][0-9]+)?)-`)과 불일치 → +1/+2 커밋과 동일하게 sanctioned `TASKCTL_BYPASS`(감사 증거 자동 기록) 경로로 커밋/푸시. 훅 파일 미수정.
- best-effort MEDIUM 중 `main`/`args` 미초기화 항목은 해당 코드가 `scripts/anu_terminal_artifact_surface.py`(forbidden_paths)에 있어 **범위 외 → 미수정**.
- L1-C에서 canonical-present 단독 케이스가 `TERMINAL_CALLBACK_MISSING` fallback으로 떨어지는 것은 빈-statuses 방어(line 398-399)의 **기존 설계 동작**이며 본 logic axis와 무관·무회귀(원본 else 분기와 동일 구조).

## 머지 판단
- 머지 필요: No (merge_policy none — head 갱신까지만, 봇 merge 금지)
- 브랜치: task/task-2778_a-dev2 / 워크트리: /home/jay/workspace/.worktrees/task-2778_a-dev2
- 머지 의견: HIGH(env_targets dangling) 실코드 해소, 130건 회귀 PASS, 보안 무회귀. 이후 ANU 독립검증 → fresh Gemini 재리뷰 → CI → 회장 승인 시 별도 머지.

## 반복 한도 준수
본 microfix는 path containment(+2로 닫힘) **재손대기 0**, 별도 env_targets logic axis 1회 수정. 이후 fresh Gemini에서 또 active HIGH 발생 시 추가 microfix 금지 → `TASK2778A_LOOP_BOUNDARY_REVIEW_REQUIRED_ACTIVE_FALSE` 보고 예정.

## 모델 사용 기록
- 토르(백엔드, sonnet): 소스 로직 수정
- 헤임달(테스터, sonnet): 회귀 테스트 추가/실행
- 오딘(팀장, opus): 설계/검토/통합/검증/PR head 갱신
- haiku 미사용
