# Repository contract

The invariants a change must preserve. They exist because each one was broken at least once during
the first field run, and every break produced a wrong answer rather than an error.

## Exit-code contract

| Code | Meaning | Producer |
|---:|---|---|
| 0 | `SAFE` — no remediation required | `detect.sh` |
| 1 | `VULNERABLE` — remediation required | `detect.sh` |
| 3 | `NOT_INSTALLED` — `libbrotli1` absent | `detect.sh` |
| 10 | `ERROR` — precondition or invariant violated | any script |
| 1 | `FAIL` — remediation did not take effect | `verify.sh` |

Code `1` means two different things depending on the script, and that is deliberate: in both cases
the answer is "the host is not in the state you want". Callers must look at which script produced
the code, never at the number alone.

## Invariants

1. **Read-only by default.** `detect.sh` and `verify.sh` change nothing. A change to either that
   touches package state, services or configuration is a defect, not a feature.
2. **One mutation point.** `remediate.sh` is the only script allowed to install, restart or write
   outside its state directory.
3. **Backup before change.** The backup directory is created and populated before `apt-get install`
   runs, and its path is printed.
4. **Authenticity gate.** Installation requires the candidate's packaged changelog to reference
   `CVE-2026-111497`. A newer version is not evidence.
5. **Dry run before install.** The expected package set is exactly the Brotli packages; anything
   else aborts the run.
6. **No vulnerable consumer left behind.** After remediation no server process may map a pre-patch
   inode. Desktop and infrastructure processes are reported as `deferred`, never as fixed.
7. **Before-state is mandatory.** `remediate.sh` and `verify.sh` refuse to run without
   `state.before`; `verify.sh` also requires `state.after`.
8. **Idempotence.** A second `detect.sh` on a remediated host answers `SAFE`; a second
   `remediate.sh` stops with code 10 because the candidate equals the installed version.
9. **No secrets, no identifiers.** No credentials, tokens, real addresses or hostnames in the
   repository or in the reports; hosts are passed as SSH aliases.
10. **File hygiene.** UTF-8, LF, no BOM, final newline; `.sh` files committed as `100755`.

## Classification matrix

The installed version is reduced to its upstream number before classification — epoch and
distribution revision are stripped:

| Installed string | Upstream | Classified |
|---|---|---|
| `1.2.0-3build1` | 1.2.0 | vulnerable |
| `1.2.0-3ubuntu0.1` | 1.2.0 | vulnerable upstream, `backported=1` → `SAFE` |
| `1.0.9-2+b6` | 1.0.9 | vulnerable |
| `1.0.9-2+deb12u1` | 1.0.9 | vulnerable upstream, `backported=1` → `SAFE` |
| `1.1.0-2build2` | 1.1.0 | vulnerable |
| `2:1.1.0-1` | 1.1.0 | epoch stripped, vulnerable |

Vulnerable range: `1.0.3`–`1.0.9`, `1.1.0`, `1.2.0`. Fixed: `1.0.10`, `1.1.1`, `1.2.1`, `>= 1.3`.

## State files

`state.before` and `state.after` are `key=value` line files, parsed by key — never sourced. A value
such as `Debian GNU/Linux 12 (bookworm)` contains parentheses and spaces, and sourcing turns it
into a syntax error.

| File | Keys |
|---|---|
| `state.before` | `host`, `os`, `installed`, `upstream`, `candidate`, `dec_inode`, `com_inode`, `enc_inode`, `consumers` |
| `state.after` | `installed`, `candidate`, `backup_dir` |

## File layout

```text
src/common.sh      side-effect free: logging, version parsing, matrix, state_get, patterns
src/detect.sh      read-only audit -> verdict + report.json + state.before
src/remediate.sh   backup -> gate -> dry-run -> install -> restart -> inode check -> state.after
src/verify.sh      version, CVE reference, stale mappings, failed units, round-trip
```

The library is side-effect free on purpose: a reviewer can read `common.sh` and know that no
decision inside it can alter a host.

## Documentation contract

| Rule | Check |
|---|---|
| English is canonical; translations carry a language suffix | `ghagent doc --root .` |
| No absolute machine paths, no credentials | `ghagent privacy --root .` |
| Every number names its source | review |
| One `H1` per document, no skipped heading levels, fences carry a language | `ghagent doc` |

Next: [Field cases](04-Field-Cases) — what these invariants cost in real life.
