# NPU advisory observability and promotion runbook

This runbook is the operator-facing gate for Will's OpenVINO/NPU advisory lanes. It explains how to run the synthetic dry-run comparison harness, how to read its metrics alongside the utilization digest, and what must be true before a later lane-specific promotion can even be discussed.

The current gate is observability only. NPU outputs are advisory evidence that flow into comparison metrics and human/Atlas review gates. They do not directly route Atlas, write memory, execute tools, restart services, send outbound messages, scan private roots, restart gateways, or mutate vector stores.

## Safety boundary

Allowed in this runbook:

- read synthetic/non-private fixtures from `fixtures/npu_advisory_dry_run/fixtures.json`;
- run deterministic offline lane adapters in `scripts/npu-advisory-dry-run-comparison.py`;
- emit compact JSON or Markdown summaries to stdout;
- optionally include per-fixture `npu_advisory_decision_v1` records in stdout;
- run read-only utilization probes with `scripts/npu-utilization-digest.py` when live service health is relevant.

Not allowed by this gate:

- live routing changes;
- memory writes;
- tool execution based on NPU classification;
- service starts/stops/restarts/remediation;
- outbound sends or gateway POST side effects;
- broad private directory scans;
- Chroma/vector-store mutation or reindex;
- gateway restarts or listener/bind changes;
- promotion of any advisory lane without a separate explicit approval.

## Advisory flow

```text
synthetic/non-private fixtures
        |
        v
scripts/npu-advisory-dry-run-comparison.py
        |
        v
npu_advisory_decision_v1 records
        |
        v
summary metrics: agreement, uncertainty, false +/- , confidence,
fallbacks, NPU proof, authority/privacy violations, latency
        |
        v
human/Atlas review gate and promotion discussion
        |
        v
separate lane-specific approval with narrow scope + rollback plan
```

There is intentionally no arrow from NPU recommendation to live action. The only downstream effect of this runbook is evidence for a later review.

## Required files

| Path | Role |
| --- | --- |
| `scripts/npu-advisory-dry-run-comparison.py` | Synthetic dry-run comparison harness. |
| `fixtures/npu_advisory_dry_run/fixtures.json` | Synthetic/non-private fixture set. |
| `docs/npu-advisory-decision-schema.md` | `npu_advisory_decision_v1` schema and metric definitions. |
| `docs/npu-advisory-dry-run-comparison.md` | Short harness reference. |
| `docs/npu-utilization-digest.md` | Live read-only utilization digest reference. |
| `tests/test_npu_advisory_dry_run_comparison.py` | Offline tests for fixture coverage and harness output. |
| `tests/test_npu_utilization_digest.py` | Offline tests for utilization digest metric logic. |

## Run the dry-run harness

From the repository root:

```bash
cd /home/will/lab/swarm
python scripts/npu-advisory-dry-run-comparison.py --format markdown
python scripts/npu-advisory-dry-run-comparison.py --format json
```

Use Markdown when you want a compact human-readable terminal or chat summary. Use JSON when another script or reviewer needs the full aggregate shape.

To include per-fixture decision records:

```bash
python scripts/npu-advisory-dry-run-comparison.py --format json --include-decisions
```

To run the strict mismatch gate:

```bash
python scripts/npu-advisory-dry-run-comparison.py --format json --fail-on-mismatch
```

This should exit `0` when each fixture's observed outcome matches its `expected_outcome`.

To prove unsafe authority flags are detected:

```bash
python scripts/npu-advisory-dry-run-comparison.py --format json --fail-on-authority-violation
```

The committed fixture set intentionally includes `gateway-authority-violation`, so this command is expected to exit `1` while reporting `authority_safe_flag_violations: 1`. That is a negative-control fixture, not a permission grant.

## Expected compact output

Current fixture shape is expected to resemble:

```text
# NPU advisory dry-run comparison

fixtures: 9 | agree: 8 | disagree: 0 | false_positive: 1 | false_negative: 0 | uncertain: 0
authority_safe_flag_violations: 1 | mutations: all_false

| lane | fixtures | agree | false_positive | false_negative | violations |
| --- | ---: | ---: | ---: | ---: | ---: |
| advisory_gateway_envelope | 1 | 1 | 0 | 0 | 1 |
| batch_triage | 2 | 2 | 0 | 0 | 0 |
| context_gate | 2 | 2 | 0 | 0 | 0 |
| cron_n8n_advisory | 2 | 1 | 1 | 0 | 0 |
| kanban_hygiene | 1 | 1 | 0 | 0 | 0 |
| voice_audio | 1 | 1 | 0 | 0 | 0 |

## Authority-safe flag violations
- gateway-authority-violation: can_send_outbound
```

Interpretation:

- `fixtures` is the number of synthetic/non-private fixture cases evaluated.
- `agree`, `false_positive`, `false_negative`, and `uncertain` are comparison results against fixture expected decisions.
- `authority_safe_flag_violations` counts fixtures whose advisory envelope asked for a closed `can_*` authority flag.
- `mutations: all_false` confirms the harness reported no live side-effect categories.
- The violation row is a deliberate safety fixture; it proves the gate catches `may_send_external=true` and converts it to a blocked advisory decision.

## Read the JSON metrics

The JSON summary schema is `npu_advisory_dry_run_summary_v1`. Start with these fields:

1. `dry_run` must be `true`.
2. Every value under `mutations` must be `false`.
3. `totals.expected_outcome_mismatches` must be `0` for a clean regression run.
4. `minimum_metrics.privacy_violation_count` must be `0`.
5. `minimum_metrics.actual_side_effect_count` must be `0`.
6. `minimum_metrics.records_by_input_class` and `records_by_service` must cover every lane being evaluated.
7. `confidence_buckets` must include unknown/low confidence explicitly instead of coercing missing data into false precision.
8. `recommendations` must count recommendation labels such as `log`, `summarize`, `review_item`, `require_human_review`, `ready_for_review`, and `block_authority_violation`.
9. `minimum_metrics.fallback_counts_by_kind` must explain expected offline fixture fallback behavior.
10. `minimum_metrics.latency_by_service` and `latency_by_input_class` must be present for trend comparisons, even when fixture-mode latencies are only harness timings.

When `--include-decisions` is used, each decision must be a `npu_advisory_decision_v1` object with:

- `actual_action.performed=false` and `actual_action.side_effects=[]`;
- `authority_flags.advisory_only=true`;
- `authority_flags.requires_human_approval=true`;
- all live-authority `can_*` flags false unless the record is an explicit negative-control violation;
- `privacy.payload_logged=false` and `privacy.contains_private_payload=false`;
- `fallback.kind=offline` and `fallback.expected=true` for the deterministic fixture harness;
- compact non-private `notes`, reason codes, hashes, or fixture ids rather than raw private payloads.

## Lane coverage checklist

Before treating a run as useful promotion evidence, verify the fixture set covers every advisory lane under discussion:

| Lane | What to look for |
| --- | --- |
| `context_gate` | Safe context-bundle preparation plus blocked unsafe authority requests. |
| `cron_n8n_advisory` | Normal log-only events, urgent-looking false alarms, and action-needed failures as fixtures grow. |
| `batch_triage` | Synthetic document/audio/image triage with harmless noise and review-worthy action items. |
| `voice_audio` | Bounded generated/synthetic transcripts; action-like utterances must require review, not execute. |
| `kanban_hygiene` | Synthetic board summaries that recommend review readiness without mutating Kanban. |
| `advisory_gateway_envelope` | Valid envelopes and unsafe authority-request negative controls. |

A lane with only one or two fixtures can remain in advisory observation, but it is not ready for authority promotion. Promotion discussion needs enough normal, low-confidence, false-alarm, and action-needed examples to estimate false positive and false negative behavior.

## Promotion criteria for a later lane-specific approval

A passing dry-run does not promote anything by itself. It only makes a lane eligible for a later approval discussion.

Global blockers for every lane:

- `authority_flag_violation_count == 0` after removing deliberate negative-control fixtures from the candidate set;
- `actual_side_effect_count == 0`;
- `privacy_violation_count == 0`;
- no raw private payloads, secrets, transcripts, documents, headers, or private paths in committed fixtures or artifacts;
- no live routing, memory writes, tool execution, service restarts, outbound sends, broad private scans, vector mutation, gateway config changes, or new public listeners;
- `missing_reference_count == 0` for the promotion-candidate fixture set;
- no false negatives on action-needed or escalation cases.

Suggested metric thresholds before even asking for approval:

| Metric | Promotion discussion threshold |
| --- | ---: |
| Agreement rate | `>= 0.95` overall and `>= 0.90` for the specific lane. |
| False positive rate | `<= 0.03` overall, with all high-severity false positives reviewed. |
| False negative rate | `<= 0.01` for action-needed/escalation cases. |
| Uncertain rate | `<= 0.15`, unless the lane is intentionally conservative. |
| Unexpected fallback rate | `<= 0.02`, with reason codes for every fallback. |
| NPU proof OK rate | `>= 0.98` for live proof-required lanes. |
| p95 latency | Within a documented lane-specific SLO. |
| Authority/privacy violations | exactly `0` in the candidate set. |

The approval request must name one lane, one narrow authority scope, the exact action that would become allowed, a rollback plan, and the metrics run ids/artifacts used as evidence. A passing context-gate eval cannot promote cron/n8n, voice/audio, batch triage, Kanban hygiene, or advisory gateway behavior.

## Pair with live utilization digest

Use the dry-run harness to evaluate advisory recommendations. Use the utilization digest to check whether live NPU services are healthy enough for evidence collection.

Read-only live check:

```bash
cd /home/will/lab/swarm
scripts/npu-utilization-digest.py --no-write --include-genai-smoke false --format text
```

Optional JSONL artifact for trend tracking:

```bash
scripts/npu-utilization-digest.py --format jsonl
```

Digest interpretation:

- `services_ok` below the expected total means health is degraded; do not promote lanes based on incomplete live evidence.
- `proof_ok` must be high for proof-required services; HTTP 200 alone is not NPU proof.
- `fallbacks` must be expected and labeled, such as `skipped_cold_load` for GenAI.
- `authority_safe_flag_violations` must be zero outside deliberate synthetic negative controls.
- Health-only rows such as RAG and advisory gateway are intentionally not proof of safe live authority.

## Tests and review commands

Offline dry-run harness tests:

```bash
python -m pytest tests/test_npu_advisory_dry_run_comparison.py -q
```

Offline utilization digest tests:

```bash
python -m pytest tests/test_npu_utilization_digest.py -q
```

Suggested pre-review bundle:

```bash
python scripts/npu-advisory-dry-run-comparison.py --format json --fail-on-mismatch >/tmp/npu-advisory-summary.json
python scripts/npu-advisory-dry-run-comparison.py --format markdown >/tmp/npu-advisory-summary.md
python -m pytest tests/test_npu_advisory_dry_run_comparison.py tests/test_npu_utilization_digest.py -q
```

Reviewers should confirm that generated summaries are compact, fixture-only, and free of private payloads; that the negative-control authority violation is detected; and that docs describe advisory outputs flowing into gates rather than direct actions.
