open-standards/map/spec/receipt.md
Transaction Science 84768074ca
Some checks failed
energy meters on real hardware / RAPL and NVML on jetson-hub (push) Has been cancelled
energy meters on real hardware / sink decode table · RAPL on jetson-hub (NVML surveyed) (push) Has been cancelled
energy meters on real hardware / sink decode table · per-task energy and the GPU rail on macOS (push) Has been cancelled
repository checks / lockfiles, advisories, tests in CI, workflow hygiene, spec and section references, public text (push) Has been cancelled
jouleclaw public surface must stay documented / Public-surface audit (jouleclaw-rs) (push) Has been cancelled
joule-code conformance gate / joule-moq-relay, its own energy on a counter (macos) (push) Successful in 2m15s
wai-app and Create / wai-app · does this change touch it (push) Successful in 1m45s
wai-app and Create / wai-app · tests, clippy, the line language's verifier (linux) (push) Has been cancelled
wai-app and Create / wai-app · Create, browser end-to-end (macos) (push) Has been cancelled
joule-code conformance gate / Build + round-trip C1-C4 vectors (push) Successful in 5m43s
joule-code conformance gate / joule-moq-ingest, every target (push) Has been cancelled
joule-code conformance gate / joule-moq-relay, every target (push) Has been cancelled
Every text that states the IOReport liveness rule states it as the meters apply it
The meters count an IOReport interval only within three of the probe's ticks, and
the texts that state the rule said nothing of it, so an implementation following
any of them would call a lump-refreshed rail live once load stretched its intervals.
Each now says what an interval is (from one sample in hand to the next), that only
one no longer than three of the probe's ticks (75 ms at its 25 ms tick) counts, that
a longer one counts as nothing (not among the intervals, not as an advance, not
towards the 300 ms), and that the 300 ms is the summed length of the intervals that
count.

The normative clauses in arl/ARL-S.md, jcp/SPEC.md, joule-code/spec/receipt.md,
joule-ui/SPEC.md, jouleclaw/SPEC.md, JCR-1-spec.md, map/spec/receipt.md,
mesh/SPEC.md (a block quote), proof/SPEC.md and sense/SPEC.md; proof-schema's
quotation of proof/SPEC.md's; jouleclaw-energy's conformance README; and the proof
and sandbox READMEs, which say the cap is IOReport's alone. Map's and Joule Code's
unreleased changelog entries, which record the rule as it was added, are amended,
since neither release has gone out. No wire or conformance-vector change.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-07 08:20:02 -05:00

70 lines
3.4 KiB
Markdown

# Map sub-spec — The receipt
Normative refinement of [SPEC §6](../SPEC.md). Reference: `map-receipt`.
Every resolve/decode of a tile emits a signed energy receipt. Receipts seal as
**JCR-1** envelopes — the family's shared signed-receipt format (canonical
det-CBOR + COSE_Sign1/EdDSA, `alg = -8`) — so a Map receipt verifies with the
same tooling as a Joule Code, Sandbox, or JouleClaw receipt. The JCR-1 wire
profile is `"map/1"`.
## 1. Shape
```
Receipt = {
cell: CellKey, // rendered as its content id on the wire
content_id: ContentId, // the tile that closed the request
tier: Tier, // F0..F4 — the closing tier
energy: { measured_uj: Option<u64>, estimated_uj: u64, provenance },
sequence: u64,
nonce: [u8; 8],
}
```
## 2. The provenance coupling (REQUIRED)
| Provenance | `measured_uj` | Meaning |
|--------------|---------------|---------|
| `HwShunt` | **required** | Read from a hardware energy interface (shunt, or a RAPL/NVML/SMC/IOReport counter while it is live — below). |
| `ModelBased` | optional | Computed from a calibrated per-operation model. |
| `Estimator` | **forbidden** | A coarse constant; honestly flagged as not measured. |
`Receipt::seal` validates this coupling *before* signing, so a dishonest receipt
(`HwShunt` with no measured figure; `Estimator` carrying one) cannot be sealed
(conformance **C-neg**). The spec never claims an accuracy the platform cannot
deliver.
A reading from a vendor energy counter (RAPL, NVML, Apple IOReport, ...) **MAY**
be graded as a hardware reading (`HwShunt`, with `measured_uj`) only while the
counter is demonstrably live for the domain the figure claims: the implementation
**MUST** have observed the counter covering that domain advance under load (for
Apple IOReport: the CPU energy rail itself advancing in at least 5 sample
intervals, in at least 4 of every 5, over at least 300 ms while the probing
thread burns CPU; an interval runs from when one sample is in hand to when the
next is, and only one no longer than three of the probe's ticks, 75 ms at its
25 ms tick, counts: a longer one counts as nothing, neither among the
intervals, nor as an advance, nor towards the 300 ms, which is the summed
length of the intervals that count). A counter that is present but not live — frozen, or
republished in lumps — **MUST NOT** produce a hardware-graded figure; the
implementation falls back to its estimator or reports the work unmetered, and
**SHOULD** record why. A figure **MUST NOT** claim coverage of a domain its
counter does not read; where the format has a coverage field, such a figure is
Partial. `Receipt::seal` cannot check liveness; that duty is the meter's,
upstream of the receipt.
## 3. Reference estimator (non-normative)
Microjoules per resolved tile, ordered by tier — `map_receipt::UJ_BY_TIER`:
| Tier | µJ (order of magnitude) |
|------|--------------------------|
| F0 Cache | 1 (picojoules) |
| F1 Base | 1 000 (microjoules) |
| F2 Field | 200 000 (sub-millijoules) |
| F3 Feature | 5 000 000 (millijoules) |
| F4 Photoreal | 2 000 000 000 (joules) |
A session's total cost is the sum of its tile receipts. The standard is that the
figure is **reported and honestly tagged** — not the constant. Conformance suite
**C5** pins receipt sealing, verification, and the signed core in
[`../conformance/v1/receipt_vectors.json`](../conformance/v1/receipt_vectors.json).