wai: proof that an ad was presented — a player-signed presentation claim, and a spend that counts only presented impressions (E7) #398
Loading…
Reference in a new issue
No description provided.
Delete branch "wai/ad-presentation"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
E7: proof that an ad was presented
live-ads §12 said
quartiles_reached"is the decider's statement of the beacons it received; a receipt binds it and its billing, not the beacons". This PR adds a player-signed presentation claim for each impression and an ad-policy option under which a receipt's spend counts only impressions whose claim verifies.What is in it
wai/extensions/ad-presentation.mdregisters the recordwai.live.ad_presentation.WPC1(§5). It names the impression by the break digest, the session and the slot of its form-2wai.video.ssaireceipt. It carries:quartiles_reached.redemption_contextis a digest of this impression and this session key, so one token answers one impression. Type 0x0002 is verified with the issuer's key as RFC 9578 §6.4 specifies; type 0x0001 can only be verified by its issuer.WAP1version 2 addspresentation(claim / attest / audible),viewability,token_typeandissuer.live-ads.md: policy version 2; the session derived from the session key; §9.3 step 12 counts the presented spend under a policy that requires claims. Version-1 policies and every existing receipt verify as before, andlive-conformance/ads/is byte-for-byte unchanged.live_ads, which now takesnum-bigint, already an optional dependency foroer):src/live_ads/presentation.rs: the claim, its rules and signature, quartile, run, attestation, DER SPKI and RSASSA-PSS, and the ledger;live_ads.rs: policy v2 and the billing rule inverify;presentation_corpus.rs, the binwai_ad_presentation_vectors, andtests/ad_presentation.rs.wai/live-conformance/presentation/: 5 rooms. The issuer key and the first test vector are from RFC 9578 Appendix A.2, and that vector is verified first.claim_not_viewable: 40% of pixels; two 1.5 s runs separated by a pause; a TV assumption under the video rule; muted when audibility is required; 90% under the cross-media rule;claim_duplicate;claim_key_unbound: signed by another session's key;claim_asset_mismatch;claim_quartile_mismatch;bad_signature, after which the impression isclaim_absent;claim_malformed: a signed claim that skips without a resumption flag, and a truncated claim;claim_no_decision;attestation_absent;attestation_invalid: another impression's token, and an altered authenticator;ad_policy_malformed.funds_exceededwhen both are.ad_presentation_verify.py: an independent verifier written from the text (standard library only, run with-I). It verifies rooms, policies and receipts throughlive_ads_verify.py, which now reads policy v2 and takes a claims judge. It judges every claim itself and implements RSASSA-PSS and DER parsing itself.joule-moq-web/web/ads/presentation.js.IntersectionObserver, page visibility and focus, volume and mute, current time and playback-quality frames.presentation.test.mjsturns observation sequences into three of the corpus's claims, byte for byte, and checks the attestation challenge against the corpus's token.wai-conformance.yml, withsetup-node22 and paths for the extension andweb/ads.joule-moq-web/e2e/run.shruns the producer test with the page's other unit tests, andjoule-moq-web.ymladds the corpus path.REGISTRY.md(record andWPC1), the registry export, a SPEC §5 paragraph, and the CHANGELOG.Design decisions
funds_exceededon the presented spend among them) refuse a room.Standards read at source
Not read, or not found:
Checks
rb … cargo test --locked --no-default-features --features live_ads --librb … cargo test --locked --no-default-features --features live_ads --test ad_presentation --test live_adsrb … wai_ad_presentation_vectors verify ../live-conformance/presentation,wai_live_ads_vectors verify ../live-conformance/adsrb … cargo test --locked --lib codecs::registryrb --pull … wai -- registry export, thenregistry export --checkrb … cargo clippy --locked --no-default-features --features live_ads --all-targetsuseless_vecfound and fixed)rb … cargo metadata --locked --offline --format-version 1python3 -I live-conformance/ad_presentation_verify.py live-conformance/presentationlive-conformance/*_verify.pyon its corpus, after merging mainnode --test web/ads/presentation.test.mjscheck-public-names.shon every touched file and the commit messagesFalsifications (committed state; each mutation alone, then restored)
Rust, on the build host: with each of these mutations,
cargo test --test ad_presentationfails with exit 101:Python: the verifier mutates copies of the corpus itself, and each mutated copy fails. It also drops each of its own checks in turn (rules, signature, session, duplicate, asset, quartile, viewability, attestation absent, challenge, authenticator), and the corpus catches every weakened verifier. When the "rules" check was first dropped, the corpus did not catch it; I added a signed claim that breaks a span rule.
JS: with each of these mutations,
node --testexits 1:origin_info.Not done
attach()with a realIntersectionObserver.IntersectionObserverdoes not see what other elements cover the player; §10 says so.Existing issue on main (not in this PR)
WAI_NAME_DENYLIST=… scripts/check-public-names.shover all ofwai/reports hits inwai/wai-rs/src/live_provenance.rs(lines 381–402, 648, 657, 1780–1781), which came from #381. That file is untouched here.🤖 Generated with Claude Code