# os-status behavioral invariants

Reversion protection for the SessionStart status hook. Any change that breaks one of these
is a regression, not a refactor. Enforced by `tests/hook_test.py` (model-free;
`python3 tests/hook_test.py`).

1. **Single banner.** At most one `systemMessage` warning banner per session, containing one
   short line per surviving `warn`. Multiple warns never produce multiple banners.
2. **Notes are never suppressed.** `note` results bypass snooze and suppress entirely and are
   emitted as `additionalContext`, not inside the banner. This exists so os-adr's
   Eval-B-tuned usage note survives conversion unchanged (design D2).
3. **Exit 0, always.** The hook exits 0 on every path — checks warning, checks raising, the
   checks module failing to import, malformed settings.json, no git project. A session is
   never blocked by os-status.
4. **Failure isolation.** A check that raises or blows its time budget becomes a generic
   `warn` naming the check; it never hides other checks' results and never leaks exception
   text into the banner.
5. **State only in `.cc-os/`.** The hook writes nothing outside the project's `.cc-os/`
   directory, and writes nothing at all outside a git project. `.cc-os/` holds state,
   never code: the shared `config` file at `.cc-os/config` (ADR-026), and os-status's own
   `snooze-<check>` / `suppress-<check>` markers under `.cc-os/status/` (ADR-027). Reads
   fall back to the pre-ADR-027 top-level location; the first write migrates and removes
   the legacy files. Add `.cc-os/` to the project's `.gitignore` (one entry covers every
   cc-os plugin's state).
6. **PRESENT_NOTE / ABSENT_NOTE byte-identical to os-adr.** `checks.py` carries verbatim
   copies of the wording in `plugins/os-adr/hooks/session_start.py` (the wording source of
   record, tuned by the Eval B campaign and validated by Eval C). Never paraphrase; if the
   wording changes there, copy it here byte-for-byte (the test compares the two files).
7. **Legacy suppress honored.** The `adr-system-present` check returns `ok` when
   `.os-adr/suppress` exists, so projects silenced under the old os-adr hook stay silent.
8. **Convention-shaped seam.** Every check keeps the uniform signature
   `check(ctx) -> CheckResult(status, message)` with registration in `checks.REGISTRY`
   only — so later extraction to per-plugin subprocess checks stays mechanical (design D1).
