open-standards/wai/wai-audit/web/native-sink.test.mjs
Transaction Science ebbd647fa1 wai: native-sink, the extension text, its corpus README, and the page's replay
extensions/native-sink.md: what a native host's sink does for a stream it plays through
its platform's player. Verification (§4): a media playlist against its playlist receipt
and the chain of its live revisions, an object against the content hash its URL states,
the host told per object whether it may hand the bytes to the player. Observations and
the log (§5), the rule that ends a session nobody can see or attends to (§6), the energy
a native host states (§7: the platform's decoder is outside every counter an application
reads, so the sink decode is the unmetered marker there), the reasons (§8), and the C
interface every native integration shares (§9).

The corpus gains a live revision that rebinds an alias, a parent served under another
name, and page-parity: wai-audit/web/native-sink.test.mjs replays it through the page's
integration of stream-audit, which signs the same bundle byte for byte.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-25 22:21:10 -04:00

65 lines
3.1 KiB
JavaScript

// The page's integration and the native sink (wai/extensions/native-sink.md §10) are two
// implementations of one text: `node --test web/native-sink.test.mjs` replays the native corpus's
// `page-parity` case (wai/live-conformance/native-sink/) through `AuditSession`, call for call, and
// requires the bundle the native sink signed, byte for byte, and its CMCD batch.
import test from "node:test";
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import * as au from "./audit.js";
const doc = JSON.parse(readFileSync(new URL("../../live-conformance/native-sink/vectors.json", import.meta.url), "utf8"));
const bytesOf = (name) => au.unhex(doc.objects[name]);
/** energy-measurement §2.7's class bytes of a call's entry: the marker here. */
function classBytes(entry) {
assert.ok(entry.energy_provenance === undefined || entry.energy_provenance === null, "the parity case states markers only");
return au.CLASS.UNMETERED;
}
test("the native sink's page-parity session, replayed by the page's integration, signs the same bundle", async () => {
const c = doc.cases.find((x) => x.name === "page-parity");
assert.ok(c, "the corpus holds the page-parity case");
let clock = c.start_us;
const key = await au.keyFromSeed(au.unhex(c.seed));
const s = await au.AuditSession.start({ channel: c.config.channel, owner: c.config.owner, windowUs: c.config.window_us, key, now: () => clock, cmcdEveryMs: 0 });
// The native session's log opens with `start` when the session does (native-sink §5.2).
s.event("start", c.start_us);
let bundle = null;
for (const { do: d, answer } of c.calls) {
switch (d.call) {
case "playlist":
assert.equal(d.receipt, null, "the page reads playlists, and checks no receipt");
s.playlist(new TextDecoder().decode(bytesOf(d.body)), d.url);
break;
case "object": {
clock = d.meta.t_us;
const r = await s.object({ url: d.meta.url, bytes: bytesOf(d.body), media: d.meta.media, at: d.meta.t_us, status: d.meta.status ?? 200, cmsdStatic: d.meta.cmsd_static ?? null, cmsdDynamic: d.meta.cmsd_dynamic ?? null });
if (answer.key) assert.equal(r, null, "a key is not acknowledged");
else assert.equal(au.hex(r), answer.sha256, "the object's SHA-256");
break;
}
case "event":
s.event(d.name, d.t_us);
break;
case "observe":
clock = d.obs.t_us;
s.observe({ at: d.obs.t_us, playing: d.obs.playing, visibleBp: d.obs.visible_bp, audible: d.obs.audible, programMs: d.obs.program_ms, seeked: d.obs.seeked ?? false });
break;
case "energy":
s.energy(d.entry.role, d.entry.joules_micro, classBytes(d.entry));
break;
case "cmcd":
await Promise.all(s.pending);
assert.equal(au.cmcdBatch(s.log), answer.cmcd, "the CMCD batch");
break;
case "end":
bundle = await s.close(d.ended, d.t_us);
assert.equal(JSON.stringify(bundle), answer.bundle, "the bundle, byte for byte");
break;
default:
assert.fail(`the page has no call ${d.call}`);
}
}
assert.ok(bundle, "the session ended");
assert.equal(c.outcome.outcome, "accept");
});