# v3.6 PreToolUse Allow Contract Candidates (Track C · Phase 0/1)

- task_id: task-2664
- chair_authorization_id: `CHAIR-AUTH-V3-6-PRETOOLUSE-PACKET-20260525-JJONGS-PHASE-0-1-001`
- 단일소스: ANU v3 master spec section 8B.5 (line 1489-1512) + section 8B.6 (line 1514-1547) + section 8B.9 (line 1595-1609)
- 범위: **Phase 0/1 only** · allow contract schema 후보 정리 · live application 0
- live_enforcement: 0
- commit_push_pr_merge: 0
- base_commit: origin/main `2752182a`

## 1. 핵심 원칙 (spec 8B.5 verbatim 1:1)

```text
background polling 자체 금지 아님.
ANU 본체 session-bound direct polling 금지.
delegated watcher contract-bound polling 허용.
```

→ deny rule (별도 packet)이 잡은 행위라도 **valid watcher contract** 가 첨부되면 ALLOW.

## 2. Allow 조건 9 (spec 8B.5 verbatim 1:1)

| AC | 조건 | 필수 |
| --- | --- | --- |
| AC-1 | `owner` ∈ {dev bot (dev1~dev7), ci_watch_handoff_runner} | YES |
| AC-2 | `collector_role == "watcher"` | YES |
| AC-3 | `task_id` 존재 (non-empty string) | YES |
| AC-4 | `pr_number` 존재 (integer 또는 null 허용) | YES |
| AC-5 | `head_sha` lock 존재 (★ 40-char hex 또는 null 명시) | YES |
| AC-6 | `terminal_states` 존재 (non-empty array) | YES |
| AC-7 | `ttl` 존재 (ISO 8601 duration 또는 absolute timestamp) | YES |
| AC-8 | `callback_target` 존재 (`"ANU_CALLBACK_ENVELOPE"` 권장) | YES |
| AC-9 | `duplicate_policy` 존재 (`"DUPLICATE_CALLBACK_IGNORED"` 권장) | YES |
| AC-10 | `callback_only_reporting == true` (★ ★ Phase 1 hard requirement) | YES |

★ AC-1 ~ AC-10 모두 PASS 시에만 ALLOW. 하나라도 fail 시 verdict는 deny rule 분기에 위임 (`DENY` 또는 `REQUIRE_WATCHER_CONTRACT`).

## 3. Watcher contract schema (spec 8B.6 verbatim 1:1)

```json
{
  "schema": "anu.v3_6.watcher_contract.v1",
  "task_id": "task-2642",
  "pr_number": 145,
  "head_sha": "335d69e1...",
  "terminal_states": [
    "CI_SUCCESS",
    "GEMINI_PASS",
    "PHASE3_PASS",
    "MERGEABLE_OR_CLEAN"
  ],
  "ttl": "PT3H",
  "callback_target": "ANU_CALLBACK_ENVELOPE",
  "duplicate_policy": "DUPLICATE_CALLBACK_IGNORED",
  "owner": "dev6-perun",
  "collector_role": "watcher",
  "callback_only_reporting": true
}
```

## 4. Fail 조건 (spec 8B.6 verbatim 1:1)

| fail_id | 조건 | 분기 |
| --- | --- | --- |
| F-1 | `owner == ANU 본체` (★ ANU session 또는 chair-facing collector) | DENY |
| F-2 | `collector_role == ANU 본체` | DENY |
| F-3 | `callback_only_reporting == false` | DENY |
| F-4 | `ttl` 없음 | DENY |
| F-5 | `head_sha` 없음 | DENY |
| F-6 | `terminal_states` 없음 | DENY |
| F-7 | `callback_target` 없음 | DENY |

## 5. ALLOW 분기 결정 schema (spec 8B.9 verbatim 1:1)

```text
ALLOW only if:
- watcher_contract_path 존재
- schema valid
- owner != ANU 본체
- collector_role=watcher
- callback_only_reporting=true
- ttl/head_sha/terminal_states/callback_target 존재
```

→ `gh pr view` 자체는 금지가 아니다. **ANU 본체가 contract 없이 terminal state를 기다리는 행위가 금지**.

## 6. 후보 ALLOW pattern 예시

### AL-1. dev bot watcher polling (gh pr view)

| 필드 | 값 |
| --- | --- |
| pattern | `gh pr view <pr_number> --json statusCheckRollup` |
| owner | `dev1-anubis` ~ `dev7-icarna` 중 1 |
| collector_role | `watcher` |
| callback_only_reporting | `true` |
| watcher_contract_path | `memory/.watcher_contracts/<task_id>-<pr_number>.json` (★ 후보 경로) |
| verdict | `ALLOW` |

### AL-2. ci_watch_handoff_runner background loop

| 필드 | 값 |
| --- | --- |
| pattern | runner script invoking `gh pr checks` in polling loop |
| owner | `ci_watch_handoff_runner` |
| collector_role | `watcher` |
| ttl | `PT3H` 또는 `PT6H` (운영 정책) |
| terminal_states | `["CI_SUCCESS", "GEMINI_PASS", "PHASE3_PASS", "MERGEABLE_OR_CLEAN"]` |
| verdict | `ALLOW` |

### AL-3. dev bot watcher invokes gh run watch

| 필드 | 값 |
| --- | --- |
| pattern | `gh run watch <run_id>` |
| owner | dev_bot |
| collector_role | `watcher` |
| callback_only_reporting | `true` |
| ttl | `PT3H` |
| verdict | `ALLOW` |

### AL-4. read-only gh pr view 단일 호출 (no loop, no background)

| 필드 | 값 |
| --- | --- |
| pattern | `gh pr view <pr_number>` (★ background=false, sleep loop 없음) |
| owner | any |
| 추가 조건 | non-background, non-loop, terminal_state_wait_intent 부재 |
| verdict | `ALLOW` (★ Phase 1 default · deny rule 비매치) |
| 비고 | 단일 read-only 명령은 deny rule pattern 자체에 미매치. allow contract 불필요. |

## 7. Phase 0/1 watcher_contract_path 후보 경로

- `memory/.watcher_contracts/<task_id>-<pr_number>.json` (★ 신규 디렉터리 · 본 packet phase 0 작성 0)
- 또는 inline JSON envelope as Bash tool input parameter (★ hook이 parse 가능한 형식)

★ 디렉터리 생성은 Phase 2+ 별도 chair signature 후 진행. 본 packet에서는 path 후보만 명시.

## 8. Schema validation 규칙 (Phase 1 적용 권장)

```text
1. JSON parseable
2. "schema" 필드 == "anu.v3_6.watcher_contract.v1"
3. 모든 9 필수 키 존재 (task_id, pr_number, head_sha, terminal_states, ttl, callback_target, duplicate_policy, owner, collector_role)
4. callback_only_reporting=true 확인
5. owner ∈ allowed_owners list (dev1~dev7 또는 ci_watch_handoff_runner)
6. ttl current_time 초과 시 EXPIRED → DENY
```

## 9. Allow contract 사용 시 차단 우회 불가 항목 (★ chair-only)

다음 deny rule은 valid watcher contract 가 있어도 ALLOW로 변환되지 않는다 (★ chair verbatim signature 별도 필수):

- DR-10 (admin override 시도)
- DR-11 (BOT App token 직접 사용)
- DR-12 (chair_authorization 자기발급)
- DR-13 (real auto-merge activation)
- DR-14 (PR #141 pilot 임의 실행)
- DR-15 (runner infra 직접 수정)
- DR-17 (destructive command — force push / reset --hard / rm -rf)

★ 즉 watcher contract는 **terminal-state-wait polling 허용 한정**.

## 10. Phase 0/1 적용 범위 (★ ANCHOR 준수)

- **Phase 0**: contract schema 후보 + allow 조건 정리 + path 후보 (★ 본 문서)
- **Phase 1**: shadow-mode validation — 실 polling 명령에 대해 contract 첨부 여부만 marker 기록, 실제 ALLOW/DENY 분기 0
- **Phase 2+ 금지**: hook가 contract 검증 후 실제 ALLOW/DENY 분기 적용 — 별도 chair signature 후 진행

## 11. forbidden actions (본 packet 작성 중 0)

- live settings.json 변경 = 0
- hooks/** 변경 = 0
- dispatch.py 변경 = 0
- Axis 1/2 runtime 변경 = 0
- commit/push/PR/merge = 0
- task-2662 / task-2663 파일 touch = 0
- watcher_contracts 디렉터리 실제 생성 = 0
- HARNESS_ENFORCED 전체 선언 = 0

forbidden_action_count = **0**

끝
