# WAI Extension: Sink Energy > Status: Draft. Registers the record `wai.sink.energy`: what decoding cost at the sink, > per stream and per segment, with the acquisition class of > [energy-measurement](energy-measurement.md) and its uncertainty, and how one viewing > session's energy splits into its origin, its relay, the sink's decode and its display. > Three signed records: a sink's **decode table** (§4), a measured figure per registered > capability, decode path and sink energy profile, produced by a pinned procedure and > committed with its raw readings; a **segment receipt** (§5), the decode energy of one > segment of one stream, measured, taken from the sink's own table, estimated, or stated > as unmetered; and a **session receipt** (§6), the per-hop split over the four hop roles a > stream log uses (origin, relay, sink decode, display), each hop a figure with its own > class, the unmetered marker where the hop took place unmeasured, or absent where the sink > states nothing about it. The counters, scopes, idle baselines > and windows are energy-measurement §9's sink profiles. No new transport, no change to > the wire format or to SPEC §4's algorithm. The reference implementation is the > `sink_energy` module of `wai-rs` (feature `sink_energy`), whose readers are in > `sink_meter` and whose table procedure is the binary `wai_sink_table`; the corpus is > [`live-conformance/sink-energy/`](../live-conformance/sink-energy/), with the measured > tables of the reference sinks under its `tables/`, and its second implementation is > `live-conformance/sink_energy_verify.py`. > Keywords **MUST**, **MUST NOT**, **SHOULD**, **MAY** are RFC 2119/8174. > > Texts read for this revision: RFC 8032 (Ed25519); RFC 8785 (JSON Canonicalization > Scheme); RFC 8259; FIPS 180-4 (SHA-256); JCGM 100:2008 (GUM) §4.3.7 and F.2.2.1; > Commission Delegated Regulation (EU) 2019/2013, for the on-mode power a display's > energy label states; arXiv 2402.09001, 2603.18305 and 2511.00707, for §4.6's > outside measurements of decode energy. A section number in this text is this > extension's unless it names another document. ## 1. Scope In scope: the three records, their signatures and identities (§3); the decode table, the procedure that produces it, how each row's status and figure follow from its rounds, and outside measurements of decode energy (§4); the segment receipt and its four methods (§5); the session receipt and its per-hop split (§6); verification (§7); the reasons (§8); and what a conforming implementation publishes (§9). Out of scope: a transport; how a counter works, which energy-measurement §9 states per platform; the energy of networks, which no sink can read and this extension never attributes to a byte; and what a party does with a figure. Relationship to other documents: - **[energy-measurement](energy-measurement.md).** Every figure carries a class of its §2, encoded as its §2.7 writes the JSON members. A measured figure follows its §3, §3.1, §4 and §6.2. The profiles are its §9. A record here carries its report's values inline, under the figure's own signature (its §6). - **[browser-sink](browser-sink.md) §6.** A page's `energy` entry for `" "` **MAY** be a row of its sink's decode table whose status is `figure`: `joules_micro` the row's, `decodes` 1, `window_us`, `class`, `relative_ppm` and `filtering` the row's class and evidence, `coverage` and `fabrics` the row's. - **[jwp-receipts](jwp-receipts.md) §2.1.** A segment receipt names what it decoded by that section's content hash. - **[live-metering](live-metering.md) §3.** A relay's hop entry is read from a hop's per-hop session record, `WMH1`. - **stream-termination §2, §3.5 and §4.6.** The hop roles, the hop entries' figure, marker and absence, and each role's figure for one segment of a stream log. This extension's hops are that table (§6.2), not a second one, and a segment receipt here is the sink-decode hop's figure for its segment. - **[staged-measurement](staged-measurement.md) §8.** A decode figure never includes a display (its paragraph 8.4). The display is a hop of its own (§6.3), always an estimate. Relationship to the core spec: `wai.sink.energy` is a **record** ([SPEC](../SPEC.md) §5). It names no payload format and is not dispatchable. ## 2. Terms - **Sink**: the party that decodes and presents. **Reference sink**: a machine the measured tables of the corpus were taken on. - **Path**: `software` (the decode runs on general-purpose processors) or `hardware` (a fixed-function decoder of the platform does it). - **Profile**: a sink energy profile of energy-measurement §9: `macos-task`, `macos-gpu`, `linux-rapl`, `linux-nvml`, and the estimators `estimator-cpu-time/1` and `estimator-display/1`. - **Unit**: what a figure is per: `image` (one picture of `width` × `height`), `frame` (one video frame of `width` × `height`), `content-ms` (one millisecond of audio content) or `byte` (one byte of decoded text). A row or a segment states how many units its figure covers (`units`). - **Class**, **evidence**, **unmetered marker**, **bracket**, **liveness**, **ratio**, **verdict**: as in energy-measurement §2–§4 and §6.2. The class is written as jwp-receipts §2.4.5 writes it: `joules_micro`, `energy_provenance`, `energy_uncertainty` (`{ "relative_ppm", "window_us" }`), `energy_counter_filtering` and `energy_calibration_ref`, each `null` where the class carries none; unmetered is `joules_micro` 0 with all four others `null`. - **Bracket summary**: `{ "n", "mean_uw", "sd_uw", "live_intervals", "longest_live_run" }`, energy-measurement §6.2's integers for one bracket. - Hexadecimal is lowercase. Every number in a record is an integer from 0 to 2⁵³ − 1. `⌊x⌋` is the floor; every division below is exact integer arithmetic. ## 3. The records, their signatures and identities Each record is one JSON object (RFC 8259). Its **body** is the object without its `sig` member. A record is signed with Ed25519 (RFC 8032) by the key its `signer` member names, 64 hexadecimal digits, over ``` domain ‖ JCS(body) ``` where JCS is RFC 8785's canonical form, and `sig` is the 64-byte signature as 128 hexadecimal digits. A signature is verified as WAI verifies every signature (SPEC §7, *Signatures*). The record's **identity** is ``` SHA-256(id-domain ‖ JCS(body) ‖ signature bytes) ``` | record | first member | domain | id-domain | |---|---|---|---| | decode table (§4) | `"wai_sink_table": 1` | `wai:sink-table\x01` | `wai:sink-table-id\x01` | | segment receipt (§5) | `"wai_sink_segment": 1` | `wai:sink-segment\x01` | `wai:sink-segment-id\x01` | | session receipt (§6) | `"wai_sink_session": 1` | `wai:sink-session\x01` | `wai:sink-session-id\x01` | The first member names the record and its version; every member the tables below list is required, and a record with a member missing, a member they do not list, or a value of another type is refused (`record_malformed`). Terms are compared as exact strings. ## 4. The decode table A decode table is a sink's measured decode energy: for each registered capability and decode path, on each profile the sink can read, one figure per completion of a stated work item, from repeated bracketed measurements, with every round's values in the record and every raw reading in a file it binds. ### 4.1 The record | member | type | meaning | |---|---|---| | `wai_sink_table` | integer | `1` | | `signer` | 32 B hex | the key that signs the table | | `procedure` | term | `wai-sink-table/1` (§4.4) | | `procedure_sha256` | 32 B hex | SHA-256 of the procedure text committed with the table | | `readings_sha256` | 32 B hex | SHA-256 of the readings file committed with the table (§4.3) | | `machine` | object | `{ "os": string, "arch": string, "processor": string, "cores": integer, "memory_gib": integer }` | | `build` | object | the decoders' build, as jwp-receipts §2.4.8 writes one: `{ "profile": "release" or "debug", "toolchain": string or null, "target": string or null }` | | `measured` | string | the date the rounds were taken, `YYYY-MM-DD` | | `rounds` | integer | rounds per row: odd, at least 7 | | `sample_us` | integer | the sampling interval every bracket was read at, in microseconds | | `counters` | array | the sink's survey: one `{ "profile": term, "status": term, "detail": string }` per counter it looked for, in ascending order of (`profile`, `detail`); `status` is one of energy-measurement §9.1's five, or `implausible` (§9.3 there) | | `rows` | array | the rows (§4.2), in strictly ascending order of (`capability`, `path`, `decoder`, `profile`), strings by their bytes and a `null` profile first | ### 4.2 A row | member | type | meaning | |---|---|---| | `capability` | string | a capability the registry lists (`registry/capabilities.json`) | | `path` | term | `software` or `hardware` | | `decoder` | string | the decoder: `builtin:@` as jwp-receipts §2.4.12 writes one, or `platform:` for a decoder of the platform, `` 1 to 64 of `a`–`z`, `0`–`9` and `-` | | `profile` | term or null | the profile read: `macos-task`, `macos-gpu`, `linux-rapl` or `linux-nvml` (energy-measurement §9.2, §9.3), whose minimum windows are 1 s, 1 s, 1 s and 5 s; `null` only in a row `absent` for `no_decoder` | | `fabrics` | array | the fabrics the profile's reading covers (energy-measurement §9.1), exactly: `["cpu"]` for `macos-task`, `linux-rapl` and `estimator-cpu-time/1`, `["gpu"]` for `macos-gpu` and `linux-nvml`; empty in an absent row | | `coverage` | term or null | `Partial` (§4.5); `null` in an absent row | | `unit` | term | `image`, `frame`, `content-ms` or `byte` | | `units` | integer | units per completion, at least 1 | | `width`, `height` | integer or null | the decoded picture's size for `image` and `frame`; `null` for the other units | | `workers` | integer | the burst's workers `N`, a power of two; 0 in an absent row | | `status` | term | `figure`, `relative`, `unmetered` or `absent` (§4.5) | | `reason` | term or null | why a row is `unmetered` or `absent` (§4.5, §8); `null` otherwise | | `rounds` | array | each round's `{ "idle": summary, "busy": summary, "window_us": integer, "completions": integer }`, in the order taken; empty in an absent row | | `median` | integer or null | the index in `rounds` of the median round (§4.5); `null` unless `figure` or `relative` | | `range` | array or null | `[least, greatest]` of the rounds' figures; `null` unless `figure` or `relative` | | `joules_micro`, `energy_provenance`, `energy_uncertainty`, `energy_counter_filtering`, `energy_calibration_ref` | — | the figure per completion and its class (§2); unmetered unless `figure` or `relative` | ### 4.3 From readings to a round The readings file is the JCS form (RFC 8785) of a JSON object, and `readings_sha256` is the SHA-256 of those bytes. The object is `{ "wai_sink_readings": 1, "rows": [ … ] }`, one entry per row of the table in the table's order, each `{ "capability", "path", "decoder", "profile", "rounds": [ { "idle": [[d, t], …], "busy": [[d, t], …], "completions": integer } … ] }`. Each `[d, t]` is one interval of the bracket: `d` the counter's advance over it, in nanojoules, across its wrap; `t` its length, in microseconds, on a monotonic clock. The busy bracket's first read is taken as the window opens and its last as it closes, so the window is the sum of its intervals' `t`, and `completions` is the count of completions between those two reads. A round's values follow from its intervals, with `n` the intervals and `pᵢ = ⌊(2000·dᵢ + tᵢ) / (2·tᵢ)⌋` each interval's power in microwatts: - `mean_uw = ⌊(2·Σp + n) / (2n)⌋`, the mean rounded to the nearest, a half up; - `sd_uw` the least integer `s` with `s² · n · (n − 1) ≥ n·Σp² − (Σp)²`, the sample standard deviation rounded up (0 when `n` < 2); - `live_intervals` the intervals with `d > 0`, and `longest_live_run` the longest run of consecutive ones: an energy counter advances in an interval when its second read exceeds its first (energy-measurement §3.1); - `window_us` the sum of the busy intervals' `t`, and `completions` as read. A verifier holding the readings file recomputes every round and requires the table's values (`table_readings_mismatch`), and requires its SHA-256 to be `readings_sha256`. ### 4.4 The procedure `wai-sink-table/1` 1. **Survey.** For each profile the platform has, read the counter at the sampling interval for at least 2 s with nothing of the sink's running, and record its status under energy-measurement §9.1. A profile whose counter is not `live` gives no rows but `absent` ones. A profile whose own sampling interval is not the table's (`linux-nvml`'s is 1 s) is surveyed, and gives no rows in this table. 2. **Work items.** For each capability with a decoder in the sink, a fixed input, written by the procedure from fixed seeds or taken from a committed vector, and decoded once with its output checked against its reference before any round: a decode whose output is wrong is not timed and not metered. A **completion** is one decode of the whole item. The procedure text states each item, its digest, its units and its size. 3. **Rounds.** For each round, and in each round for each row in an order rotated by one each round: settle for 2 s, so that a processor package's draw falls back from the burst before it (read 200 ms after a burst, the reference Linux sink's package still drew several times its resting draw); read the **idle bracket** of the row's counter over the decode workers parked at their start barrier; release `N` workers together as the window opens, and read the **busy bracket** while each decodes the item over and over, counting each completion; close the window; stop and join the workers. The window opens from rest (energy-measurement §2.3). `N` is the sink's worker count rounded down to a power of two (jwp-receipts §2.4.12, `threads`). 4. **Record.** Write every interval to the readings file, every round's values and each row's status, figure and class to the table (§4.5), and sign the table. The procedure text committed with a table states the steps above as the tool ran them: its command line, the sampling interval and bracket lengths, the rounds, the work items with their digests, the workers, and the decoders' build. ### 4.5 A row's status and figure **Absent.** A row is `absent` when there is nothing to measure: `no_decoder` (the sink has no decoder for the capability on the path; `profile` `null`), `no_work_item` (the sink decodes it, and the procedure defines no work item for it), `no_hardware_path` (the platform's decoder for it is not a hardware one on this sink, or its output differed from the software path's), or `no_counter` (the row's profile is not `live` in the survey). An absent row has no rounds, `workers` 0, and the unmetered marker; it states what was not measured, never a figure of zero. **A round's figure.** Every other row has exactly `rounds` rounds. Round `r` is the bracket `{ idle, busy, window_us, completions, in_flight: workers, opening: from-rest }` of energy-measurement §6.2. It **gives a figure** `F_r`, with least declaration `p_r`, when it passes, in order: 1. `too_few_samples`: each bracket has at least 2 intervals; 2. `rail_not_live`: each bracket's rail is live: `sd_uw` above 0, `live_intervals` at least 5, `longest_live_run` at least 2, `5 · live_intervals ≥ 4 · n`, and the bracket spans at least 300 ms (the idle bracket `n · sample_us`, the busy bracket `window_us`); 3. `window_short`: `window_us` is at least the profile's minimum window; 4. `empty_burst`: `completions` is at least 1; 5. `no_marginal`: the busy mean is above the idle mean; 6. `below_attribution_floor`: `M ≥ sd_idle` (energy-measurement §4); 7. `figure_zero`: `F_r`, §6.2's figure, is at least 1; 8. `not_declarable`: `F_r` is at most 2⁵³ − 1 and §6.2's least `p_r` at most 2³² − 1. **The row.** If any round gives no figure, the row is `unmetered`, and its `reason` is the reason of the first round in `rounds` that gives none, the first rule it breaks. Otherwise the rounds are ordered by (`F_r`, `r`) and the **median** is the middle one. The row's verdict is the median round's (energy-measurement §6.2): clean gives `figure`, usable for relative comparison gives `relative`, and noisy or unusable gives `unmetered` with reason `noisy`. A `figure` or `relative` row seals: - `joules_micro` = `F_median`, and `range` = `[min F_r, max F_r]`; - `energy_provenance` `OnChipCounter` with the profile's filtering state, or `CalibratedInstrument` with its calibration reference where the profile reads an instrument; - `energy_uncertainty.window_us` = the median round's `window_us`, and `relative_ppm ≥ max(p_median, q)`, where `q` is the least integer with `12 · q² · F_median² ≥ 10¹² · (max F_r − min F_r)²`: the rounds' spread read as a rectangular distribution over its range (GUM §4.3.7), relative to the figure. A row whose `max(p_median, q)` exceeds 2³² − 1 cannot declare it (energy-measurement §2.7): it is `unmetered`, with reason `not_declarable`, and like every unmetered row it keeps its rounds and has `median` and `range` `null`. A `relative` row's figure is for comparing two rows of the same table measured round by round together; a reader **MUST NOT** present it as an absolute figure (energy-measurement §4; staged-measurement §8, its paragraph 8.3). An `unmetered` row keeps its rounds, so the measurement that failed stays auditable. **Coverage.** A figure is `Whole` only where its profile's scope covers every fabric the decode runs on. No profile of this revision does: none reads a media engine (energy-measurement §9.2), and none reads memory. So every row that is not absent, and every segment that is not unmetered, is `Partial` in this revision, and a `Whole` one is refused (`table_row_figure`, `segment_method_class`); `Whole` is kept for a profile a later revision registers. ### 4.6 Outside measurements of decode energy (informative) The three points below are outside measurements of decode energy. Their figures are not figures of a WAI decoder or of a reference sink: no record of this extension states them, and no rule of it reads them. Each is cited by its arXiv identifier and the table it is read from, with what was measured and against what. 1. **A platform's hardware decoder, against software decoders of the same format.** arXiv 2402.09001, Table IV. Over AVC, VP9, HEVC and AV1, each in a random-access and a low-delay configuration, a hardware decoder spent from 0.89% to 8.86% of the energy of a software decoder of the same format, and from 4.44% to 8.86% of the energy of the optimised software decoders with vector instructions enabled. The figures are dynamic energy: the energy measured at idle is subtracted. The software decoders ran single-threaded on a desktop processor, read by the processor's own energy counter; the hardware decoders ran on a single-board computer's video unit, read at its power input by an external meter. The point sets two decode paths side by side as two machines built them. 2. **Fewer frames, chosen by the encoder.** arXiv 2603.18305, Table V, its row for all sequences. Decoding energy was 17.60% lower, encoding energy 17.46% lower and the bitrate 3.38% lower, each a Bjøntegaard delta at equal matched PSNR (mPSNR, computed against the source), against encoding each sequence at its source's 120 frames per second. A trained classifier picks, per sequence and per rate-factor setting (the table covers four), the frame rate the encoder codes at, the source's or a lower one; where it is lower, the decoder decodes fewer frames than the source has. One software encoder and one software decoder of HEVC were measured, by the processor's own energy counter, idle subtracted. 3. **Lower resolution or coarser quantisation, chosen by the encoder.** arXiv 2511.00707, Table 4, its row ρ = 0.05. Decoding energy was 53.54% lower and encoding energy 51.22% lower, for an average loss of 1.68 VMAF points and 3.67 dB PSNR, against the configuration chosen with no loss allowed (ρ = 0). Per sequence, the encoder takes, of the resolutions and quantisers whose predicted VMAF is within 5% of the best predicted, the one of least predicted energy. One software encoder and one software decoder of VVC were measured on UHD sources at 60 frames per second, by a software tool reading the processor's energy counter and the graphics processor's; the paper does not state whether the idle energy is subtracted, nor whether Table 4's savings are the measured or the predicted energy of the configurations chosen. How they bear on this extension: - Points 2 and 3 are choices of what to encode, and each costs frames or picture quality. Point 1 is a choice of decoder for the same stream. None of the three changes what a decoder computes from a given stream. - SPEC §4's *Energy order* already ranks first a classical decode that the sink's platform answers supported, smooth and power-efficient. Point 1 measures what a hardware decode spent beside a software one; the platform's answer stays a hint that orders, never an energy figure or evidence of hardware (SPEC §4). - A `hardware` row of this revision reads no media engine (§4.5, *Coverage*): its figure is the processors' part of a fixed-function decode. It and point 1 measure different things, and are not set against each other as like for like. ## 5. The segment receipt A segment receipt is a sink's statement of what one segment of one stream cost it to decode: the sink-decode hop's figure for that segment (role 3 of §6.2). A sink signs one per segment it decoded, in order, each naming the one before it. Where the sink also keeps a stream log, the log's segment record states the same figure and class for role 3 (stream-termination §4.6), and the receipt here is where its bracket, table row or estimator inputs are. ### 5.1 The record | member | type | meaning | |---|---|---| | `wai_sink_segment` | integer | `1` | | `signer` | 32 B hex | the sink's key | | `session` | 32 B hex | fresh random bytes per viewing session, derived from nothing that identifies a person, a device or an account | | `stream` | 32 B hex | the stream: for a track of a room, live-metering §2's track digest; otherwise SHA-256 of the stream's address as the sink fetched it, without query or fragment | | `segment` | integer | the segment's index in this session's reading of the stream, from 0 | | `prev` | 32 B hex or null | the identity (§3) of this session's receipt for segment `segment − 1` of the stream; `null` exactly when `segment` is 0 | | `object` | 32 B hex | the content hash (jwp-receipts §2.1) of the segment as decoded | | `capability`, `path`, `decoder`, `unit`, `units`, `width`, `height` | — | as a table row has them (§4.2), for this segment: `units` is the units the segment decoded | | `media_us` | integer | the segment's content duration, in microseconds, at least 1 | | `method` | term | `measured`, `table`, `estimator` or `unmetered` (§5.2) | | `profile` | term or null | the profile the figure comes from; `null` exactly when `unmetered` | | `fabrics`, `coverage` | — | as a table row has them; empty and `null` exactly when `unmetered` | | `joules_micro`, `energy_provenance`, `energy_uncertainty`, `energy_counter_filtering`, `energy_calibration_ref` | — | the segment's decode energy and its class (§2) | | `bracket` | object or null | `measured`: `{ "idle": summary, "busy": summary, "window_us": integer, "sample_us": integer }`; otherwise `null` | | `table` | object or null | `table`: `{ "id": 32 B hex, "row": integer, "per_completion_uj": integer, "units_per_completion": integer }`; otherwise `null` | | `estimator` | object or null | `estimator`: `{ "method": "estimator-cpu-time/1", "cpu_us": integer, "active_mw": integer, "basis": string, "relative_ppm": integer }`; otherwise `null` | | `reason` | term or null | `unmetered`: why (§8, *unmetered reasons*); otherwise `null` | ### 5.2 The four methods **`measured`.** The sink read its counter over the segment's own decode, under a profile of energy-measurement §9.2 or §9.3, and seals `OnChipCounter` (or `CalibratedInstrument`). The busy window is **bounded**: it opens as the segment's first unit is submitted to the decoder and closes as its last is output, and no unit of another segment is decoded inside it. The work the window paid for is then exactly the segment: one completion, with no part-done unit at either edge, which removes the completion-count term by construction (energy-measurement §2.3). The idle bracket is the last the sink read under the profile with its decode parked. With `M`, `T`, `n_i`, `n_b`, `s_i`, `s_b` as in energy-measurement §6.2, these are its §6.2 forms with `Wn = Wd = 1` and `N = 0`: ``` F = ⌊(2·M·T + 10⁶) / (2·10⁶)⌋ the segment's figure, µJ p = the least integer with p² · K ≥ P, where K = 12 · n_i · n_b · F² P = 12 · T² · (s_i² · n_b + s_b² · n_i) + 10¹² · n_i · n_b ``` The receipt is accepted when both brackets are live (as rule 2 of §4.5 reads them, the idle span `n · sample_us`), `window_us` is at least the profile's minimum window, `M ≥ s_i`, `joules_micro` is `F` and at least 1, `energy_uncertainty.window_us` is `T`, and `relative_ppm ≥ p`. Below a ratio of 10 the figure is sealed and verifiable but not publishable (energy-measurement §4): a reader derives the verdict from `bracket`. **`table`.** The sink took its figure from a row of a decode table: ``` joules_micro = ⌊(2 · per_completion_uj · units + units_per_completion) / (2 · units_per_completion)⌋ ``` at least 1. Its class is `ModelBased` when the table's `signer` is the receipt's `signer` — a model calibrated on the host it describes (energy-measurement §2) — and `Estimator` when it is another key's table, a figure measured on another machine. Neither carries evidence. A verifier holding the table requires: its identity is `table.id`; row `table.row` exists, has status `figure`, and the receipt's `capability`, `path`, `decoder`, `profile`, `unit`, `width`, `height`, `fabrics` and `coverage`; `per_completion_uj` is the row's `joules_micro` and `units_per_completion` its `units`. **`estimator`.** Energy-measurement §9.4's `estimator-cpu-time/1`: `profile` names it, `joules_micro` is its `E` over `cpu_us` and `active_mw` and at least 1, `basis` is non-empty, `relative_ppm` is at least 1, and the class is `Estimator`. A reader presents the figure with the stated uncertainty `estimator.relative_ppm`. **`unmetered`.** No figure: the unmetered marker, `profile`, `fabrics`, `coverage`, `bracket`, `table` and `estimator` empty, and `reason` one of §8's unmetered reasons. `HwShunt` is never a segment's class (energy-measurement §2.6, `hwshunt_forbidden`). ### 5.3 The chain A verifier holding a session's receipts for one stream requires (`segment_chain_broken`): the same `session`, `stream` and `signer` throughout; `segment` 0 to `k` with none missing or repeated; and each `prev` the identity of the receipt before it. A stream whose sink stopped decoding has a last segment; it has no receipt after it. ## 6. The session receipt and the per-hop split ### 6.1 The record | member | type | meaning | |---|---|---| | `wai_sink_session` | integer | `1` | | `signer`, `session` | 32 B hex | as in the segment receipts it closes | | `streams` | array | per stream, `{ "stream": 32 B hex, "segments": integer, "last": 32 B hex }`: its segment receipts' count and the identity of its last; at least one, in strictly ascending order of `stream` | | `media_us` | integer | the sum of the segments' `media_us` | | `presented_us` | integer | the time the session's content was on screen, in microseconds | | `display` | object or null | the display's declaration (§6.3), or `null` | | `hops` | array | the session's hop entries (§6.2), in strictly ascending order of `role`, each role at most once | ### 6.2 The hops, and a hop entry The hops are the four roles of a delivery's energy, and they are one table with a stream log's hop entries (stream-termination §2, *Hop*, and §3.5): **1** origin, **2** relay, **3** sink decode, **4** display. A hop is in one of three states, by the same rule: - **a figure**: an entry with a figure and its own class; - **the unmetered marker**: an entry with the marker (energy-measurement §2), for a hop the sink knows took place and has no figure for; it says the hop happened and was not measured, and it is never a figure of zero joules; - **absent**: no entry, for a hop the sink states nothing about. Absence is not the marker: it says nothing, and in particular not that the hop cost nothing. | member | type | meaning | |---|---|---| | `role` | integer | 1 origin, 2 relay, 3 sink decode, 4 display | | `joules_micro`, `energy_provenance`, `energy_uncertainty`, `energy_counter_filtering`, `energy_calibration_ref` | — | the hop's figure and its own class (§2), or the marker | | `unmetered` | term or null | for the marker, why the hop has no figure: `not_reported`, `hop_unmetered`, `hop_shared`, `unmetered` or `display_undeclared`; `null` for a figure | | `coverage` | term or null | `null` for the marker; for a figure, `Whole` for the display (the estimate is of the whole panel's draw), `Partial` for the origin and the relay (a hop's record does not state what its figure covers), and the sink decode's by §6.3 | | `source` | object or null | for the origin and the relay with a figure: the hop record the entry restates (§6.4); otherwise `null` | Which hops have an entry: - the **sink decode** always has one: a session's sink decoded its segments; - the **display** has one exactly when `presented_us` is above 0, and none when nothing was presented; - the **origin** always has one: the session's segments came from one; - the **relay** has one where the sink knows its segments came through one, and none otherwise. A figure entry's figure is at least 1 and its class coheres with it (energy-measurement §2); a marker entry has `coverage` and `source` `null` and a reason its role may have: `not_reported` (the sink holds no figure for the hop), `hop_unmetered` (the hop's own record carries the marker) or `hop_shared` (§6.4) for the origin and the relay; `unmetered` for the sink decode; `display_undeclared` or `unmetered` for the display (`split_part_invalid`). Each hop carries its own class: the receipt states no total over its hops, and a reader **MUST NOT** add hops of different classes into one figure, nor add the display to any decode figure (energy-measurement §9.5). ### 6.3 The figure of each hop **Sink decode (3).** Read from the session's segment receipts (§5), each of which is the sink-decode hop's figure for its segment, the figure a stream log's segment receipt states for role 3 (stream-termination §4.6). With the metered segments those whose `method` is not `unmetered`: - every segment unmetered: the entry is the marker, `unmetered`; - otherwise `joules_micro` is the sum of the metered segments' figures, and the class is the weakest among theirs in energy-measurement §2's order. Where every metered segment is `CalibratedInstrument` with one calibration reference, the entry is that class with that reference. Where every metered segment is a hardware class otherwise, the entry is `OnChipCounter`, its filtering `On` if any segment's is `On`, `Off` if every metered segment is `OnChipCounter` with `Off`, and `Unknown` otherwise. A hardware entry declares `window_us` the longest of the segments' windows and `relative_ppm` at least `⌈Σ(Fᵢ · pᵢ) / ΣFᵢ⌉` over the metered segments' figures and declarations: linear in the parts, so it assumes their errors fully correlated, as jwp-receipts §2.4.1 composes a group's; - `coverage` is `Whole` only when no segment is unmetered and every metered segment is `Whole`. **Display (4).** Energy-measurement §9.5's `estimator-display/1` over the session's `presented_us`, with the declaration ``` { "method": "estimator-display/1", "panel_class": "backlit" or "emissive", "on_mode_mw": integer, "reference_nits": integer, "nits": integer, "basis": string, "relative_ppm": integer } ``` `on_mode_mw`, `reference_nits` and `relative_ppm` at least 1, `nits` at least 0 and `basis` non-empty. The entry is a figure with class `Estimator` when the declaration is present and its `E` is at least 1; the marker `display_undeclared` when `display` is `null`, and the marker `unmetered` when `E` is 0. A reader presents the figure with the stated uncertainty `display.relative_ppm`, as an estimate, apart. **Origin (1)** and **relay (2).** A hop's own signed figure, where this session was the hop's sole user (§6.4), or the marker: `not_reported` when the sink holds no such figure, `hop_unmetered` when the hop's record carries the marker, `hop_shared` when the session shared the hop with other sessions. ### 6.4 A hop's figure, where the session was its sole user ``` source = { "kind": "live-metering-hop" or "other", "record": 32 B hex, "hop_joules": integer, "hop_class": class label, "share_num": integer, "share_den": integer } ``` `record` is the hop's record's digest: for `live-metering-hop` the digest of live-metering §3.5, and for `other` the SHA-256 of the record's bytes as published. `hop_joules` and `hop_class` are its figure and class label, `hop_joules` at least 1. `share_num` is the busy time this session accrued on the hop and `share_den` the busy time every user of the hop accrued over the hop's span, `1 ≤ share_num ≤ share_den`. A hop's figure is about every session it carried, and it is not divided among them by the time each kept it busy: busy time does not apportion energy (energy-measurement §9.1). The entry restates the hop's figure only where this session was the hop's sole user, at least 99% of its busy time: ``` 100 · share_num ≥ 99 · share_den joules_micro = ⌊(2 · hop_joules · share_num + share_den) / (2 · share_den)⌋ ``` and then `joules_micro` is at least 1, its attribution is `rail` (energy-measurement §9.1), and its class and evidence are the hop's: `energy_provenance` is `hop_class`. A hop this session shared below that gives no figure: its entry is the marker, with reason `hop_shared`. A `HwShunt` hop gives `hwshunt_forbidden`. For `live-metering-hop` the record is a per-hop session record `WMH1` (live-metering §3.1), and `share_den` is the sum of its nine `subscriber_us`: `share_num` is the sink's statement of its own subscriber-microseconds on that hop, which the record does not hold (live-metering §3 keeps no per-viewer entry). A verifier holding the record's bytes requires: they read as a per-hop record of the room their own `room` field names, its signature verified (live-metering §3.6); their digest is `record`; its `joules` is `hop_joules`, its class label is `hop_class` and its evidence the entry's; and `share_den` is its subscriber-microseconds' sum (`split_hop_mismatch`). For `other`, the arithmetic and the class rule are checked from the receipt alone. ### 6.5 Against the segment receipts A verifier holding the session's segment receipts requires (`session_streams_mismatch`) that each verifies (§7), names this `session` and `signer`, and that per stream they form a chain (§5.3) whose count is `segments` and whose last receipt's identity is `last`, with no stream left out; and that `media_us` is the sum of their `media_us`. It then requires the sink-decode entry to be what §6.3 gives (`split_decode_mismatch`). ## 7. Verification A verifier reads a record and checks it in the order below; the first rule broken is the refusal. 1. **Shape** (`record_malformed`). The record is a JSON object with no duplicate member; it has the member §3 names it by, with the value 1 (JCS fixes no order of members, so "first" in §3 is the order a producer writes, not a rule); it has exactly the members its tables list, each of the type they give. Beyond the types: - a hex member has the stated length in lowercase hexadecimal digits; a term is one of the terms its table lists; an integer is from 0 to 2⁵³ − 1; - a bracket summary has exactly its five integer members, and a round exactly its four members; - `width` and `height` are integers of at least 1 for `image` and `frame`, and `null` for the other units; `units` is at least 1; - `decoder` follows its grammar (§4.2); - every member of a `fabrics` array is one of `cpu`, `gpu`, `media` and `npu` (energy-measurement §9.1), none twice; - `measured` is a date `YYYY-MM-DD`; - a `source` (§6.4) and a `display` (§6.3) have exactly their members, of their types; - each of `hops` has exactly the members of §6.2, its `role` from 1 to 4, the entries in strictly ascending order of `role`; - a `capability` is one `registry/capabilities.json` lists; - a table's `counters` are in ascending order of (`profile`, `detail`), and its `machine` and `build` have exactly their members; - a segment's `media_us` is at least 1, and its `prev` is `null` exactly when its `segment` is 0; - a session's `streams` are at least one, in strictly ascending order of `stream`; - `hop_class` is a string. 2. **Signature** (`signature_invalid`): `sig` over the body under `signer` (§3). 3. **Classes.** For each class the record carries, in this order — a table's rows in order; the segment's; a session's hop entries in the order of their role, each entry's class and then its `source`'s `hop_class` — three rules in turn: a `HwShunt` label is refused (`hwshunt_forbidden`); then a class beside a `joules_micro` of 0, or a non-zero `joules_micro` beside no class, is refused (`unmetered_as_zero`); then a label energy-measurement §2 does not name, a class whose evidence members are not exactly those its §2.7 requires (as jwp-receipts §2.4.5 writes them, `null` where a class carries none), an `energy_uncertainty` with a member of 0 or a `relative_ppm` above 2³² − 1, a hardware declaration below §2.3's resolution floor, or a `hop_class` that is not a label of a class, is refused (`energy_class_invalid`). 4. **A decode table.** `rounds` is odd and at least 7, and each row not `absent` has exactly that many rounds (`table_rounds_invalid`); the rows are in strictly ascending order (`table_row_order`); then each row in order, by §4.5: - its status and reason (`table_row_status`): an absent row has a reason of the four absent ones, no rounds, `workers` 0, `profile` `null` exactly for `no_decoder`, empty `fabrics`, `coverage`, `median` and `range` `null`; an absent or `unmetered` row carries the unmetered marker; any other row has `workers` a power of two and the status and reason its rounds give; and a row's profile has an entry with status `live` in `counters` exactly when the row is not absent for `no_counter` (`null` profiles aside); - its figure (`table_row_figure`): `fabrics` its profile's, and for `figure` and `relative` rows `coverage` `Partial`, `median` (an index into `rounds`, from 0) and `range` the ones its rounds give, `joules_micro` the median's figure, the class `OnChipCounter` with filtering `Unknown` (energy-measurement §9.2, §9.3) or `CalibratedInstrument`, and `window_us` the median round's; for `unmetered` rows `coverage` `Partial` and `median` and `range` `null`; - its declaration (`table_row_declaration`): `relative_ppm ≥ max(p_median, q)`; and then, once every row has passed, holding the readings file: its SHA-256 is `readings_sha256`, it has one entry per row naming the row's `capability`, `path`, `decoder` and `profile`, each row's rounds as many as the table's, and every round's values are the ones §4.3 gives (`table_readings_mismatch`). 5. **A segment receipt.** - Its method (`segment_method_class`): `measured` has `bracket` and no `table`, `estimator` or `reason`, a profile of §9.2 or §9.3 of energy-measurement, and a hardware class; `table` has `table` and no other, a profile of those, and `ModelBased` or `Estimator`; `estimator` has `estimator` and no other, the profile `estimator-cpu-time/1`, and `Estimator`; `unmetered` has none of the three, `profile`, `coverage` `null`, empty `fabrics`, the unmetered marker and a `reason` of §8's unmetered reasons. Every metered segment has `fabrics` its profile's and `coverage` `Partial`, and `reason` `null`. - `measured` (§5.2): both brackets live (`segment_rail_not_live`); `window_us` at least the profile's minimum window (`segment_window_short`); `M ≥ s_i` (`segment_below_floor`); `joules_micro` `F`, at least 1, and the declared `window_us` `T` (`segment_figure_mismatch`); `relative_ppm ≥ p` (`segment_declaration_below`). - `table` (§5.2): a verifier **holds** the table whose identity is `table.id` when one is given to it; a held table that does not pass steps 1–4 is refused here (`segment_table_mismatch`), and `table.row` is an index into its `rows`, from 0. The figure is the formula's and at least 1, the class `ModelBased` exactly when the verifier holds the table and its `signer` is the receipt's, and otherwise `Estimator` where it holds the table; and, holding the table, everything §5.2 requires of the row (`segment_table_mismatch`). A verifier that does not hold the table checks the formula only. - `estimator` (§5.2): the figure is `E` over its inputs and at least 1, `basis` non-empty and `relative_ppm` at least 1 (`segment_estimator_mismatch`). - Holding the stream's receipts: §5.3 (`segment_chain_broken`), after each receipt has passed every step above. 6. **A session receipt**, in order: - the hop entries as §6.2 reads them (`split_part_invalid`): the sink decode and the origin have an entry, the display has one exactly when `presented_us` is above 0; then, in the order of their role, a figure entry has `unmetered` `null`, a figure of at least 1, the `coverage` §6.2 gives it (for the sink decode, either of the two here, its value checked against §6.3 below) and a `source` exactly when its role is the origin's or the relay's; a marker entry has `coverage` and `source` `null` and a reason its role may have; - the display declaration, where there is one, within its ranges (§6.3), and the display entry, where there is one, against §6.3 (`split_display_mismatch`); - the origin's then the relay's entry, where they have a figure, against §6.4: the sole-user rule, the figure, the class and evidence, and, holding the record, the record (`split_hop_mismatch`); - holding the segment receipts (a verifier given none holds none), §6.5: each receipt verifies (steps 1–5) and the streams agree (`session_streams_mismatch`), then the sink-decode entry is what §6.3 gives (`split_decode_mismatch`). An accepted record establishes that its signer stated it. The arithmetic between its values is checked, and so are its readings, its table and its hop records where the verifier holds them. The values themselves — the counter readings, the completions, the declared inputs of an estimate — are the signer's statements (energy-measurement §5): a record that invents every value consistently passes every check. ## 8. Reasons | reason | refused when | |---|---| | `record_malformed` | a record does not read in its shape (§3, §7 step 1) | | `signature_invalid` | its signature does not verify (§3) | | `hwshunt_forbidden` | a class, or a hop's class, is `HwShunt` (energy-measurement §2.6) | | `energy_class_invalid` | a class label is unknown, or its evidence is missing, extra or below the resolution floor | | `unmetered_as_zero` | a class sits beside a zero figure, or a non-zero figure beside no class | | `table_rounds_invalid` | `rounds` is even or below 7, or a row has another count of rounds | | `table_row_order` | the rows are not in strictly ascending order | | `table_row_status` | a row's status or reason is not the one its rounds give (§4.5) | | `table_row_figure` | a row's median, range, figure, class or window is not the one its rounds give | | `table_row_declaration` | a row declares less than `max(p_median, q)` | | `table_readings_mismatch` | the readings file's digest, or a round recomputed from it, differs | | `segment_method_class` | a segment's members or class do not fit its method | | `segment_rail_not_live` | a measured segment's bracket is not live | | `segment_window_short` | a measured segment's window is below its profile's minimum | | `segment_below_floor` | a measured segment's marginal is below the idle deviation | | `segment_figure_mismatch` | a measured segment's figure is not its bracket's `F`, or its window not `T` | | `segment_declaration_below` | a measured segment declares less than its bracket's `p` | | `segment_table_mismatch` | a table segment's figure, class or row disagrees with its table | | `segment_estimator_mismatch` | an estimated segment's figure or inputs are not the method's | | `segment_chain_broken` | a stream's receipts do not form its chain | | `split_part_invalid` | a hop entry is missing where §6.2 requires one, or is neither a figure nor a marker with its reason | | `split_display_mismatch` | the display entry is not the estimator's over the declaration | | `split_hop_mismatch` | an origin or relay entry's busy time, class or record disagrees | | `split_decode_mismatch` | the sink-decode entry is not what the segment receipts give | | `session_streams_mismatch` | the streams, counts, last receipts or `media_us` disagree with the segment receipts | The **row reasons** a table row states are §4.5's: `no_decoder`, `no_work_item`, `no_hardware_path`, `no_counter`, the round rules `too_few_samples`, `rail_not_live`, `window_short`, `empty_burst`, `no_marginal`, `below_attribution_floor`, `figure_zero` and `not_declarable`, and `noisy`. The **unmetered reasons** a segment states are `no_counter`, `rail_not_live`, `window_short`, `no_marginal`, `below_attribution_floor`, `figure_zero`, `not_declarable` and `not_reported`. ## 9. Conformance - A producer of a decode table publishes the table, its readings file and its procedure text together, and a table whose readings are not published **MUST NOT** be presented as measured. - A sink that states a segment's decode energy signs a segment receipt for every segment it decoded on the stream, unmetered ones included, so a gap shows in the chain. - The corpus holds decode tables, segment chains and session receipts, accepted and refused, each refusal under its reason; every reason of §8 refuses at least one vector. It also holds the measured tables of the reference sinks, each with its readings and its procedure, which both implementations verify. - Continuous integration runs the integer checks on every change, and a hardware job on the runners whose label names a reference sink's platform. Where the job's counter is not readable on its runner, the job reports the measurement as **skipped**, with the counter's status, and never as passed. ## 10. Security and privacy considerations - A decode table names its machine (operating system, architecture, processor, cores and memory) and publishes its power levels and its build in cleartext: it is about a reference machine, and its signer chooses to publish that. A sink that measures its own table and keeps it is not required to publish it. - A segment receipt names no person, device or account: its `session` is random per viewing session. Its `stream` digest and `object` hash name what was decoded, and its timings and energy show how the sink decoded it; a sink discloses them to whoever it gives the receipt. - Energy readings are a side channel (energy-measurement §2.4). The profiles read counters at 200 ms or slower, the records carry bracket statistics rather than single instants, and a reader **MUST NOT** take a sink's receipts as permission to read its counters.