# P0-b user-level systemd path driver 런북 (task-2721)

작성일: 2026-06-01
상태: **구현만 완료 — default DISABLED, active=false**. 본 task 는 코드/유닛/런북 작성에 한정하며,
어떤 활성화(systemctl enable/start/daemon-reload, crontab 등록, activation flag 생성)도 실행하지 않는다.

---

## 1. 개요

dev(executor)가 작성한 `result.json` 을 OS 이벤트 기반으로 감지하여, P0-a 의 `pickup_once`
(ANU-owned wake argv dry-run 빌더)를 호출하는 user-level systemd path 드라이버다.

데이터 흐름:

```
dev result.json (memory/events/task-*.result.json)
        │
        ▼  PathExistsGlob(task-*.result.json) 감지
anu-pickup.path  (systemd .path unit, DISABLED by default)
        │
        ▼  trigger
anu-pickup.service  (Type=oneshot)
        │
        ▼  ExecStart
scripts/anu_pickup_entrypoint.sh
   - activation flag(memory/state/p0b_driver_enabled == "enabled") 확인 → 없거나 비활성 시 exit 0 (no-op)
   - driver 레벨 flock single-flight (중첩 차단)
        │
        ▼  python3 -m
dispatch.anu_pickup_driver  (scan_once)
   - target(task-*.result.json final) 한정
   - 6조건 PASS 시에만 pickup_once 호출
        │
        ▼
pickup_once  (P0-a)  →  ANU-owned wake argv 빌드 (dry-run, 실제 cron 발사 0)
```

핵심 안전 원칙:

- **default DISABLED**: activation flag 부재 시 entrypoint 와 driver(`scan_once`) 모두 전면 no-op.
- **실제 wake 0**: driver 는 `pickup_once` 가 빌드한 argv 를 절대 실행하지 않는다. `WAKE_BUILT` 는 argv 빌드 성공만 기록.
- **ANU key literal 0**: 키는 오직 `.env.keys` 의 `COKACDIR_KEY_ANU` 환경변수로만 런타임 로드. unit/path/service/entrypoint/driver/런북 어디에도 키 문자열을 쓰지 않는다.

---

## 2. 4축 단계 분리표

| 축 | 항목 | 본 task 상태 |
|----|------|--------------|
| implemented | `.path`/`.service` unit, `entrypoint.sh`, `anu_pickup_driver.py`, 런북 작성 | ✅ 완료 |
| wired candidate | 코드가 `pickup_once` / `verify_collector_authoritative` 를 import·호출 (실설치 0) | ✅ 후보 (미배선) |
| wired pilot | unit 을 `~/.config/systemd/user/` 에 설치 + pilot 1회 oneshot | ⛔ 미수행 |
| active=false | `daemon-reload` + `enable --now` + activation flag `enabled` | ⛔ **active=false 유지** |

---

## 3. enable 절차 (★ 회장 승인 후에만. 본 task 에서 실행 금지)

1. unit 파일을 user systemd 디렉토리에 복사:
   ```
   cp deploy/systemd/anu-pickup.path    ~/.config/systemd/user/
   cp deploy/systemd/anu-pickup.service ~/.config/systemd/user/
   ```
2. daemon 재로드:
   ```
   systemctl --user daemon-reload
   ```
3. path watcher enable + 즉시 시작:
   ```
   systemctl --user enable --now anu-pickup.path
   ```
4. activation flag 기록 (이 시점부터 driver no-op 해제):
   ```
   printf 'enabled\n' > memory/state/p0b_driver_enabled
   ```

> 4단계의 flag 가 `enabled` 가 아니면 unit 이 트리거돼도 driver 는 전면 no-op 한다(2중 차단).

---

## 4. disable / rollback 절차

1. **즉시 no-op** (가장 빠른 차단): flag 삭제 또는 `disabled` 기록
   ```
   rm -f memory/state/p0b_driver_enabled
   # 또는: printf 'disabled\n' > memory/state/p0b_driver_enabled
   ```
2. path watcher 중지 + disable:
   ```
   systemctl --user disable --now anu-pickup.path
   ```
3. unit 제거:
   ```
   rm -f ~/.config/systemd/user/anu-pickup.path ~/.config/systemd/user/anu-pickup.service
   ```
4. daemon 재로드:
   ```
   systemctl --user daemon-reload
   ```

---

## 5. pilot (저위험 단일 1회)

- pilot flag: `memory/state/p0b_pilot_oneshot` (1회 소비 self-clear).
- low-risk 단일 task 1건에 대해서만 1회 oneshot 을 수행하고 즉시 flag 를 소거한다.
- pilot 결과는 `memory/p0b_state/driver_runs.jsonl` evidence 로 확인한다.

---

## 6. active 승격 5단계 증거

다음 5개 증거가 모두 확인되고 pilot 1회 성공한 뒤에만 ACTIVE 로 승격한다 (5/5 + pilot 1회).

| # | 증거 | 확인 방법 |
|---|------|-----------|
| ① | dev result.json 만 작성 | `memory/events/task-*.result.json` 생성 확인 |
| ② | OS event 감지 | journald(`journalctl --user -u anu-pickup.path`)에 PathModified 트리거 기록 |
| ③ | pickup_once 호출 | evidence jsonl(`memory/p0b_state/driver_runs.jsonl`)에 `verdict=WAKE_BUILT` 기록 |
| ④ | ANU key wake cron 발사 (sealed) | sealed key 로 wake cron 발사 (별도 ANU 세션 권한) |
| ⑤ | 독립 ANU 세션 spawn | 발사된 wake 로 독립 ANU 세션 기동 확인 |

5/5 충족 + pilot 1회 성공 → ACTIVE.

---

## 7. ANU key 운영

- ANU key 는 **오직** `.env.keys` 의 `COKACDIR_KEY_ANU` 환경변수로만 런타임 로드한다.
- unit / path / service / entrypoint / driver / 본 런북 등 **어떤 파일에도 key literal 을 기재하지 않는다.**
- driver/entrypoint argv·로그·evidence jsonl 에 키 문자열이 노출되어선 안 되며, evidence 에는 `owner_key_class`
  분류 라벨(ANU/SELF/FOREIGN)만 기록한다.

---

## 8. micro-fix 이력 (task-2721+1, 2026-06-02)

PR #167 독립 파서 HIGH 2건 수정:

- **HIGH-1 (lock UID 분리)**: entrypoint 의 driver-level flock lock 경로를 `${XDG_RUNTIME_DIR:-/tmp}/anu-pickup-${UID}.driver.lock` 로 변경. `/tmp/anu-pickup.driver.lock` 고정 경로 사용 중단 → 멀티유저 lock 충돌 제거. pickup_once 내부는 lock-free 유지.
- **HIGH-2 (watcher 피드백 루프 제거)**: driver 출력물(driver_runs.jsonl / quarantine)을 감시 디렉터리 `memory/events` 밖 `memory/p0b_state/` 로 분리. `.path` unit 을 `PathExistsGlob=.../task-*.result.json` 입력 전용 glob 으로 좁혀, driver 출력이 unit 을 재트리거하지 않도록 함. finish-task.sh 가 result.json 을 memory/events 에 쓰는 동작은 불변(입력 전용).
- active=false / installed=false / wired=false 유지 — 본 micro-fix 후에도 활성화 일체 없음.

---

## 9. bounded medium-fix 이력 (task-2721+2, 2026-06-02)

PR #167 독립 파서 MEDIUM 5건 수정 (chair 승인 없는 bounded auto-fix 구간 — expected_files 6개 내부, forbidden 0, ANU key net-new 0, active=false).

- **task_id path traversal 방어 (driver, security-medium)**: `process_one` 의 schema 단계(조건3 통과 후, owner proof 전)에서 `task_id` 가 `os.path.basename(task_id) != task_id` 이거나 `..`/`/`/`\` 를 포함하면 `_quarantine("schema_fail")`. `done_path = os.path.join(result_dir, f"{task_id}.pickup.done")` 에 직접 쓰이는 값을 신뢰경계 통과 전에 차단(defense-in-depth) → pickup_once/verify_fn 미호출.
- **fail-safe 예외 범위 보강 (driver, 3곳)**: 의미/동작 변경 없이 예외 포착 범위만 확대.
  - `read_activation` flag 읽기: `except OSError` → `except (OSError, ValueError)` (UnicodeDecodeError 는 ValueError 계열, flag 읽기 실패 = fail-closed no-op).
  - `_quarantine_move` `shutil.move`: `except OSError` → `except (OSError, shutil.Error)` (shutil.Error 는 OSError 미상속).
  - `_dedupe_hit` ledger 읽기: `except OSError` → `except (OSError, ValueError)` (읽기 실패 = dedupe 미확인 보수 처리, 기존 동작 유지).
- **ExecStart 견고성 (service)**: `ExecStart=%h/workspace/scripts/anu_pickup_entrypoint.sh` → `ExecStart=/bin/bash %h/workspace/scripts/anu_pickup_entrypoint.sh` (executable bit 없는 환경에서도 동작).
- regression 13 → 22 PASS (path traversal 5 parametrize + 정상 경로 회귀 1 + fail-safe 3 추가). 정상 경로 회귀 0.
- active=false / installed=false / wired=false 유지 — 본 medium-fix 후에도 활성화 일체 없음.

---

## 10. CRITICAL+HIGH fix 이력 (task-2721+4, 2026-06-02)

PR #167 독립 파서 fresh CRITICAL 1건 + HIGH 1건 수정 (회장 Option 1 명시 승인 — expected_files 6개 내부 bounded fix, forbidden 0, ANU key net-new 0, active=false 유지). 동반 medium 1건 보강 포함.

- **CRITICAL (무한 재트리거 차단 — terminal 파일 처리, driver)**:
  처리 완료된 `task-*.result.json` 이 `PathExistsGlob` 대상에 계속 남아 systemd path watcher 가 무한 재감지/재실행하던 문제. driver 가 terminal verdict 도달 시 result 파일을 watched 디렉터리(`memory/events`) 밖 `memory/p0b_state/processed/` 로 **atomic 이동**(`os.replace` 우선, 실패 시 `shutil.move` fallback)하여 glob 재매칭 0 으로 만든다.
  - **terminal verdict별 파일 처리 정책**:
    - `WAKE_BUILT`(wake argv 빌드 성공) → `processed/` 로 이동 → glob 비대상.
    - `PICKUP_SKIP`(dedupe/done/acked, 또는 pickup_once 의 SKIP_TERMINAL/SKIP_DEDUPE) → `processed/` 로 이동 → glob 비대상.
    - `QUARANTINE` → 기존대로 `memory/p0b_state/quarantine/` 로 이동 → glob 비대상.
    - `NOOP_NOT_READY`(DEFER) → **그대로 잔류**(다음 트리거에서 재평가 — 의도적, terminal 아님).
    - `NOOP_NOT_TARGET` / `NOOP_DISABLED` / `FIRE_FAILED` → 파일 미이동(원래 대상 아님/비활성/에러 재시도).
  - 이동 실패 시 **fail-safe**: 크래시 0, `DriverRecord.error` 에 이동 실패 사유 기록(verdict 는 유지). result writer atomic 화는 건드리지 않음(소비 측 이동만).
  - 결과: terminal 도달분은 glob 재매칭 0 → path watcher 무한 재트리거 소멸. DEFER 만 일시 잔류(grace 후 수렴).

- **HIGH (lock 경로 안전화 — entrypoint)**:
  driver-level flock lock 파일이 `${XDG_RUNTIME_DIR:-/tmp}` fallback 으로 world-writable `/tmp` 를 쓸 수 있어 symlink/truncation 위험. 이를 제거:
  - `XDG_RUNTIME_DIR`(사용자 전용 0700) 가 있으면 우선 사용, 없으면 **workspace 내부** `memory/p0b_state/locks/` 사용. **`/tmp` 고정 사용 전면 중단**(파일 내 `/tmp` 문자열 0).
  - lock 디렉터리 `mkdir -p` + `chmod 700`(소유자 한정)으로 parent dir 존재/권한 보장.
  - lock 경로가 symlink 면(`[[ -L ]]`) 차단(exit 0) — truncation/redirect 방지.
  - driver-level flock single-flight + UID 분리 유지. pickup_once 내부 lock 재도입 0(lock-free 유지).

- **medium (quarantine/processed overwrite 방지)**: 동일 basename 재이동 시 기존 파일 덮어쓰기 방지 — `_collision_safe_dest` 가 ms 타임스탬프 suffix 를 붙여 충돌 회피. quarantine·processed 이동 양쪽에 적용.

- regression 26 → 34 PASS (terminal 이동/glob 재매칭 0 / DEFER 잔류 / 이동 fail-safe / overwrite 방지 / entrypoint lock 정적검사 8건 추가). 기존 26 회귀 0.
- active=false / installed=false / wired=false 유지 — 본 fix 후에도 활성화(systemctl enable/start/daemon-reload, unit 설치, flag 생성) 일체 없음. finish-task.sh 무수정(result writer atomic = P0-b+1 후속). P0-a main 무수정.
