<a id="rule-index"></a>
# 규칙과 알고리즘 색인

상위 경로: [Norma 시스템 지도](README.md#system-map) → 규칙과 알고리즘

| 결정 단위 | 입력 | 결과 | 실제 소유 구현 |
|---|---|---|---|
| [permission 판정](#permission-rule) | tool name/input metadata, mode, rules, callback | allow/deny와 optional rewrite | `permission.Evaluate` |
| [scheduler](#scheduler-rule) | arrival order, concurrency-safe flag, semaphore | 실행 순서와 result order | `harness.streamExec` |
| [loop 종료](#termination-rule) | tool presence, budgets, hooks, tasks, context | continuation 또는 terminal | `harness.loop` |
| [provider retry](#retry-rule) | establishment error/status, attempts, context | retry/backoff 또는 error | `llm.doStream` |
| [context 선택](#context-rule) | history, boundary, compactor/view | provider messages | `harness` + `llm.MessagesForAPI` |
| [Noa compression](#noa-rule) | projected tokens, refs, blocks, protected ranges | nudge/truncate/block state | `noa` pipeline |

<a id="permission-rule"></a>
## Permission 판정

```text
if name matches Disallowed OR tool self-decision is deny: DENY
else if mode == bypass: ALLOW
else if mode == plan: ALLOW only read-only
else if name matches Allowed: ALLOW
else if mode == acceptEdits and name is Write/Edit/NotebookEdit: ALLOW
else if read-only OR tool self-decision is allow: ALLOW
else if mode in {dontAsk, auto}: DENY
else if callback exists: callback decision
else: DENY
```

explicit deny와 tool hard-deny가 bypass보다 앞선다. name rule의 괄호 안 argument는 파싱하지 않는다. permission rewrite 뒤 hook rewrite가 가능하지만 후자를 재판정하지 않는다. [permission 근거](evidence:permission) [tool pipeline](runtime/tools-and-permissions.md#permission-pipeline)

<a id="scheduler-rule"></a>
## Tool scheduler

scheduler는 response에서 완성된 call의 arrival order를 queue에 둔다. queue 앞쪽 safe calls를 semaphore 한도까지 시작하고, exclusive call은 이전 실행이 모두 끝난 뒤 단독 실행한다. exclusive가 queue head에 있는 동안 뒤 safe call은 앞질러 가지 않는다. progress/result event는 ready 상태를 관찰하되 final result 배열은 arrival order다. context cancellation 시 unfinished call에 synthetic error result를 만든다. [scheduler 근거](evidence:harness-streaming)

| 경계 | 보장 | 보장하지 않음 |
|---|---|---|
| semaphore | 이 Prompt의 safe call 동시수 | 여러 Session/host의 global rate |
| exclusive flag | 같은 stream executor에서 겹치지 않음 | external shared state의 process-wide lock |
| ordered results | model tool-use와 result pairing | external side effect의 commit order |
| synthetic abort | protocol pair 완성 | context를 무시한 tool의 실제 중단 |

<a id="termination-rule"></a>
## Loop branch, continue, stop

tool-use가 있으면 main turn count를 증가시키고 다음 request로 이어진다. tool-use가 없으면 recovery/stop hook/token budget/task notification을 순서대로 평가한 뒤 terminal로 간다. budget settlement는 별도 phase이고 원래 budget reason을 유지한다. provider max-output 또는 context overflow는 제한된 recovery 분기를 사용한다. [loop 근거](evidence:harness-query)

| continue 이유 | main turn count와 관계 |
|---|---|
| normal tool next turn | tool-use main turn에 포함 |
| reactive compact retry | 추가 request지만 같은 의미로 단순 합산되지 않음 |
| max-output escalate/recovery | recovery request |
| stop-hook blocking | injected context 후 추가 request |
| token-budget continuation | budget nudge 후 추가 request |
| task notification | background 결과 전달 후 추가 request |

종료 조건의 숫자를 model call 총량이나 외부 action 총량으로 사용하면 안 된다. 비용·action budget은 host가 event/action ledger에서 별도로 계산한다.

<a id="retry-rule"></a>
## Provider retry

```text
logical request rate-limit acquisition
for attempt = 0..retries:
  create request with same body
  if 200: return response before body consumption
  if context canceled: stop
  if network error or {408,429,500,502,503,504}: wait and retry
  otherwise: return status/body error
```

기본 retries는 3회이고 delay는 0.5/1/2초 후 8초 cap이다. retry count가 음수면 비활성화된다. stream consumption이 시작된 뒤 drop은 재시도하지 않아 partial assistant/tool event duplicate를 피한다. OpenAI empty-response retry는 entire request 재전송이므로 별도 설정과 duplicate-risk 판단이 필요하다. [retry 근거](evidence:llm-retry)

<a id="context-rule"></a>
## Provider-visible context 선택

custom compactor가 있으면 built-in compaction은 무시된다. request마다 `Pre`가 history를 바꿀 수 있고, `ContextView`를 구현하면 해당 `View`가 messages를 완전히 결정한다. 그렇지 않으면 `llm.MessagesForAPI`가 last boundary 뒤 messages와 paired tool blocks를 만든다. overflow에서 `Reactive`는 한 번 retry view를 줄 수 있다. [compaction 근거](evidence:compaction) [boundary 근거](evidence:llm-boundary)

stored history, transcript와 request view는 byte-identical하거나 같은 durability를 갖는 상태가 아니다. [상태 소유권](runtime/context-and-resume.md#state-ownership)을 따른다.

<a id="noa-rule"></a>
## Noa projection과 compression 판정

Noa의 per-turn node 순서는 assign refs → sync blocks → prune → hide consumed calls → emergency truncate → nudge다. 순서를 바꾸면 ref/boundary/liveness와 projected-size invariant가 깨진다. compression batch는 range를 전부 먼저 검증하고 archives를 전부 쓴 뒤 state를 commit한다. [Noa pipeline 근거](evidence:noa-pipeline) [Compress 근거](evidence:noa-compress)

- refs와 first-render token snapshot은 session 안에서 안정적이어야 한다.
- hard-protected tool message와 recent soft-protected tail은 compression range에서 제외된다.
- minimum range floor는 batch에 atomic하게 적용되며 lower-tier block boundary range는 예외가 있다.
- overlap/order/boundary/summary length failure는 state mutation 전 거부한다.
- mixed tier range는 lowest tier만 흡수하고 higher tier는 생존한다.
- archive write가 하나라도 실패하면 state를 바꾸지 않고 best-effort rollback한다.
- state sidecar write는 archive/state commit과 원자적이지 않다.

기본 threshold 숫자와 현재 failing tests는 [Noa 판정](modules/noa.md#decision-policy)과 [한계](modules/noa.md#limits)에 있다.
