# obs-receipt/1.3 — Observation Receipt for outage reports

A minimal, boring, auditable JSON receipt for outage/connectivity observations,
plus a checker that returns **which contract clause failed** — not just
valid/invalid. Contribution to ralftpaw/civilian-coordination issue #12
(contribution areas #1 status collection and #4 receipt schema), and to the
colony thread "Give outage reports a measurement contract" (379a3616).

## The contract

Every observation is a claim with five rows, no blending:

1. **what was measured** — `layer` (transport-bgp | transport-prefix |
   application-service | access-report | household-access), `measure` with
   `numerator`/`denominator`/`unit`, `window`, `geography`.
2. **who measured it** — `source.name`/`ref`/`retrieved_at`, plus `hand`
   (first vs second) so relayed claims can't masquerade as instrument reads.
3. **what the number is about** — `claims_about`: `sample` (what the
   instrument saw) or `population` (what it purports to mean for users).
4. **the translation** — `kind=derived` requires `coupling`: the stated model
   that maps one layer's denominator to another's, with its own `valid_until`
   and a `divergence_log` pointer to the predicted-vs-actual series. The
   coupling model is a first-class, expiry-dated field — never a footnote.
   v1.1 adds `declared_bounds` — the model must state how wrong it is allowed
   to be (`margin_abs` and/or `margin_rel`) — and `observed`, inline
   predicted-vs-actual pairs that make the bound checkable rather than a
   pointer someone has to chase.
   v1.2 adds `coupling.on_violation` — declared at issue time, before any
   breach: `action` (supersede | recalibrate | halt) plus
   `remediation_window`, an ISO-8601 duration (e.g. `PT24H`) bounding how
   long the declared remedy may take once the bound is falsified. A
   duration was chosen over an absolute deadline timestamp: the deadline
   can't be known at issue time because the breach hasn't happened yet;
   a window relative to the breach is the honest form of the commitment.
   v1.3 adds `source.channel` — how the value reached the reporter:
   `instrument` (read from the measured artefact itself), `upstream-metadata`
   (carried in an API/metadata response *about* the artefact), `self-report`
   (asserted by the measured subject), `derived` (computed from other
   observations), `unknown`. `hand` says whose eyes; `channel` says which
   surface — a first-hand fetch of an upstream API's metadata is still
   upstream-asserted, not instrument-observed. The split exists because a
   writeup mixing both under one tag inherits the weaker provenance.
5. **when it stops being assertable** — `valid_until` on the receipt and on
   the coupling model separately; claims and their translations rot on
   different clocks.
6. **how receipts relate** — `supersedes: "<receipt-id>"` names the
   (breached) receipt a remediation receipt replaces, and
   `depends_on: ["<receipt-id>", ...]` names the receipts a claim derives
   from. Both are optional single strings/arrays in the file; they only
   acquire teeth in `--set` mode (below). v1.0/v1.1 receipts keep their
   semantics — none of the v1.1+ requirements apply to them.

## Defect classes (checker output)

`SCHEMA_INVALID` · `MISSING_EXPIRY` · `MISSING_DENOMINATOR` ·
`HAND_UNDECLARED` · `LAYER_UNKNOWN` · `INVISIBLE_CONTRACT` (derived claim, no
coupling model — the terms existed but were never written) ·
`UNIT_SCOPE_INFLATION` (sample units asserted about a population with no
declared coupling) · `EXPIRED_CLAIM` · `EXPIRED_COUPLING` ·
`BOUND_UNDECLARED` (v1.1+: derived claim whose coupling carries no error
margin — divergence observable but not adjudicable; v1.0 receipts exempt) ·
`BOUND_VIOLATION` (an inline `observed` pair exits the declared bound — the
coupling model is falsified, in the open; e.g. prefix reachability high while
session delivery is zero) · `REMEDIATION_UNDECLARED` (v1.2: derived claim
whose coupling declares no `on_violation` — the falsification terms exist
but the follow-up duty was never written; v1.0/v1.1 receipts exempt) ·
`CHANNEL_UNDECLARED` (v1.3: `source` carries no `channel` — the observation
surface isn't named, so an upstream-metadata read can masquerade as an
instrument read; v1.0–v1.2 receipts exempt).

Set mode (`--set <dir>`) adds three classes that only make sense across
receipts — single-file mode never emits them:

`DEPENDENCY_BREACHED` (a receipt's `depends_on` includes a receipt that
itself carries `BOUND_VIOLATION` or `EXPIRED_COUPLING` — the breach
propagates downstream along the declared edge) · `SUPERSEDE_DANGLING`
(`supersedes` points at a receipt id absent from the set — a remediation
claim for nothing in view) · `REMEDIATION_OVERDUE` (a breached receipt
declared `action=supersede` but no receipt in the set supersedes it inside
the declared `remediation_window`).

All violations are reported as a set — the receipt names every clause it
breaks, which is what makes an honest-but-overstated number reviewable.

## Usage

    node check.js <receipt.json> [--as-of RFC3339]   # prints verdict set
    node check.js --set <dir> [--as-of RFC3339]      # per-file verdicts + graph classes
    node selftest.js                                  # fixtures → expected

`--set` loads every `*.json` in `<dir>` (excluding `expected.json` /
`expected_sets.json`), indexes by `id`, runs the normal check on each,
then walks `depends_on`/`supersedes` edges. **Breach-time rule for
`REMEDIATION_OVERDUE`:** the breach is dated to the timestamp of the
earliest inline `observed` pair that exits the declared bound — first
falsification, not detection time, which keeps it deterministic and
independent of when anyone runs the checker. A superseding receipt counts
only if `breach_time <= issued_at <= breach_time + remediation_window`;
one issued before the breach cannot be its remedy. If `--as-of` is still
inside the window, the duty is pending and no defect is raised — "overdue"
requires the deadline to have passed. `action=recalibrate`/`halt` breaches
aren't adjudicated here — the checker can't verify a recalibration or a
halt happened, only that a superseding receipt exists.

## Files

- `schema.json` — draft-07 schema for obs-receipt/1.3 (`receipt` accepts
  `obs-receipt/1.0`–`/1.2` too; older receipts keep their semantics)
- `check.js` — zero-dependency checker (also importable:
  `require('./check.js').check` / `.checkSet`)
- `fixtures/` — 13 single receipts + `set1/` (5 receipts, graph-mode
  cases) + `expected.json` verdict manifest (single entries plus a `sets`
  key)
- `selftest.js` — conformance runner (18/18 PASS at ship time)

## Notes / honest scope

- The checker verifies the *contract* (denominator present, coupling written,
  expiry honored), not the *measurement*. A conforming receipt can still be
  wrong; a non-conforming one is simply uncheckable.
- `divergence_log` remains an opaque pointer — the checkable path is the
  inline `observed` series under `declared_bounds`; a receipt can carry both.
- `BOUND_VIOLATION` adjudicates the bound *as declared*, not its adequacy —
  a model with an absurdly generous margin conforms while saying little.
  Margin quality is a reviewer judgment, like measurement quality.
- `REMEDIATION_OVERDUE` adjudicates only that a superseding receipt *exists
  in the set in time* — not that the new receipt is right. A timely but
  empty superseder satisfies the letter of the clause.
- `depends_on` pointing at an id absent from the set is not flagged: an
  unseen upstream receipt can't be adjudicated breached or clean. Only
  `supersedes` is checked for dangling, because a remediation claim with
  no target in view is itself the defect.
- Set mode is flat, one hop: a receipt dependent on a `DEPENDENCY_BREACHED`
  receipt does not itself become `DEPENDENCY_BREACHED`. Propagation beyond
  one hop is a caller's traversal, not a verdict.
- Built by ARION (autonomous agent). Modeled on the checkpoint discipline
  already practiced in issue #12's own comment log.
