wai: the revenue share — share contract and statement, recomputable from the ledger's invoices and the channel's program-as-run (wai.delivery.revenue_share) #539
Loading…
Reference in a new issue
No description provided.
Delete branch "wai/fast-revenue-share"
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?
What this is
wai.delivery.revenue_share: what each content owner of a linear channel is owed from the ads sold in the channel's breaks, as a statement any party holding the public records recomputes. It joins the two halves of evidence WAI already signs, the program-as-run (wai.video.channel) and the impressions (wai.live.adsbreak-form receipts with ad-presentation claims, andwai.video.stitched_presentationclaims), through the invoices ofwai.delivery.ledger, and splits the revenue by a contract every owner signed.wai/extensions/revenue-share.md, §1–§13, with a reasons table (§11).WRC1: operator, period, currency, channel id and the channel signer's key, the carriers (rooms of live-ads by room id; stitchers by key), owners, splits (an operator share plus owner shares, integer millionths, summing to 10^6), the asset → split map, the rule for an impression in an entry that is not a programme (preceding,following,between) and the between split. Signed by the operator; countersigned by each owner (64 zero bytes = not yet countersigned). In force only when every owner has countersigned.WRS1: contract digest, party (an owner or the operator), period, currency, the invoice digests and run identities it rests on, one 67-byte line per (split, class) the party shares in (impressions, revenue, the party's ppm, its amount, cited count, citation digest), and the total. Signed by the operator.wai.video.channelreceipt (§4): its signed bytes, identity and verification written out for a verifier that is notwai-rs.ChannelReceiptgainsto_json/from_json/payload; its signed format and every existing receipt are unchanged.wai-rs/src/revenue_share.rs(featurerevenue_share=receipt_ledger+video_channel), its corpus generatorsrc/revenue_share/corpus.rs, binwai_revenue_share_vectors, testtests/revenue_share.rs.contract.create(wai contract create),contract.update(wai contract accept, an owner's countersignature),statement.create(wai statement create,--dry-run),statement.verify(wai statement verify). Placementlocal(they read and write files on this machine), so Studio binds none;actions.generated.jsregenerated.wai/live-conformance/share/(vectors.json + README), generated from fixed seeds and the ledger corpus's sources. Second implementation:wai/live-conformance/revenue_share_verify.py(python3 -I), from the text only.REGISTRY.mdentry + registry export, SPEC §5 paragraph, CHANGELOG entry, CI step inwai-conformance.yml, andcli_opswithquickstart,revenue_shareinwai-catalogue.yml.How attribution works (§5, §6)
adsandstitchedsources of accepted ledger documents (receipt-ledger §7.1), from the contract's carriers only (an invoice may bill several channels). Classes: presented (counted under a claims policy, or a stitched presentation with a time), beacons (billable under a policy without claims), unplaced (a stitched presentation whose marker states no time: DASH carriage 3, or aSTART-DATEoutside the stated form). Price = the invoice's tariffadsprice for the unit that billed it (presented_impressionsorbillable_impressions). Classes are never summed together.t0+ the break'st(the whole break is attributed by its start); a stitched presentation is at its marker'sSTART-DATE(RFC 3339 §5.6 date-time in capitals,Zor±hh:mm, up to 9 fraction digits truncated to µs).outside_window.ad/fillerentry gives, by the rule, the last programme before it, the first after it, or none (between); none → the between split. Unplaced impressions go to the between split too, in their own class. An asset the contract does not map →asset_unmapped.How rounding works (§7.3)
Per (split, class), once over the statement's period, never per impression: revenue R is divided among the split's parties (its owners in listed order, then the operator if its share is above 0) by the largest-remainder rule: quota = R × ppm, base = ⌊quota / 10^6⌋, leftover = R − Σ base (< number of parties), the parties with the largest remainders get one more each, ties to the earlier party. Parts are integers and sum to R exactly; the operator's and each owner's statements state the same revenue for a split and class, and their parts sum to it. The verifier's outcome states each line's full division.
Refusals (§11)
Added:
contract_invalid,split_not_whole(shares that do not sum),not_countersigned,contract_overlap(two contracts sharing a channel or a carrier in an overlapping period),share_statement_invalid,contract_mismatch,outside_contract,run_invalid(a program-as-run that does not verify),run_foreign,run_overlap,invoice_foreign,impression_duplicate(one impression billed by two invoices of a statement, e.g. under two tariffs),outside_window(an impression outside any aired window),asset_unmapped,share_statement_mismatch. Reused with the meaning the text gives:truncated,malformed,unknown_version,bad_key,bad_signature,outside_period,line_unsourced(a line with no source),citation_mismatch; and every receipt-ledger code at a ledger document, at its place. Codes that already meant something else in other extensions (statement_invalid,statement_mismatch,ledger_mismatch,shares_not_whole,share_invalid) were avoided.Composition with the receipt ledger (§9)
The statement's sources are ledger sources, verified by the ledger; its prices are the tariff's; its records are the invoice's, and it cites the invoice. It adds only the instant of each impression on the program-as-run and the split the contract gives. The ledger document a statement is given is the one the ledger service keeps with each invoice (
GET /v1/invoices/{digest}), unchanged. No ledger record or source kind changes.Decisions
wai.delivery.revenue_share), aswai.delivery.ledgernames its tariff and invoice. File namerevenue-share.md.preceding/followingfall back to it when no such programme exists, and unplaced presentations go to it in their own class.contractsin the verification document: overlap is checked against the contracts the verifier holds; a verifier holding fewer than the operator signed cannot see an overlap with the ones it lacks (§13 says so).ChannelReceiptJSON carriage is new text describing existing bytes; the carriage refuses the legacyHwShuntlabel, as stitched-presentation's form-1 carriage does.Checks
cargo test --locked --no-default-features --features revenue_share --lib revenue_share::… --lib wai_video_channel::… --features revenue_share --test revenue_sharewai_revenue_share_vectors verify ../live-conformance/shareWAI_FEATUREScargo check --locked --no-default-features --features revenue_share --testscargo test --locked --no-default-features --lib cataloguecargo test --locked --no-default-features --lib codecs::cargo test --locked --no-default-features --features quickstart,revenue_share --bin wai --test cli_ops… --features quickstart --bin wai --test cli_ops--all-targets(quickstart,revenue_share), filtered to touched filescargo clippy … --message-format short | grep <touched files>wai registry exportthen--checkwai ops export --out ../catalogue/ops.jsonthen--checkwai breaking --against 564a42df8python3 -I live-conformance/revenue_share_verify.py live-conformance/sharenode scripts/actions.mjs --checkpython3 -I scripts/check-wai-ops-doors.pycheck-tests-in-ci.py,check-workflow-concurrency.sh,check-spec-refs.py,check-wai-section-refs.py,check-no-vanity-metrics.sh,test-public-text-checks.sh,check-comparative-prose.sh,check-orphan-sources.pyruby -ryaml -e 'ARGV.each{…}' .github/workflows/*.ymlcheck-public-names.shon the 25 changed files; commit messages and this body against the same patternsNo
Cargo.lockchanges: no dependency added (the--lockedruns above pass). Nosrc/ffi*.rs/cbindgen.tomlchange, soinclude/wai.his untouched.Falsifications
All after committing, on the Framework box, each mutation an exact one-match edit of
src/revenue_share.rsinside the rb snapshot, the corpus testevery_vector_gets_its_stated_outcomerun, then the file restored withgit checkout --and touched; the baseline passes before and after (fastb-falsify.sh).contract_overlapcontract_offerednot_countersigned)contract_split_shortcontract_operator_key_small_orderstatement_signaturerun_aired_misstatedruns_overlappinginvoice_of_another_sellerimpression_billed_twiceimpression_outside_the_periodfollowingread as precedingowner_b_followingowner_a_octoberowner_a_octoberowner_a_octoberowner_a_octoberline_unsourcedcheck droppedline_impressions_raisedowner_a_octobervectors.jsonedited (a total raised by 1)the_committed_corpus_regenerates…; andrevenue_share_verify.pyFAIL owner_a_octoberThe Python verifier falsifies itself on every run: 61 mutated copies of the corpus (statement bytes flipped at six offsets per accepted vector, a run or a ledger document dropped, a stated total, division or attribution changed, contract bytes flipped, a run renamed or its signature flipped, a refusal's reason changed) all fail, and each of its 21 checks dropped in turn is caught by the corpus.
Not in this PR
Payouts, accounts, an owner invoicing the operator (no ledger source kind for it; adding one would need a new tariff version), invoicing a buyer, currency conversion, the hosted service, the feed import and guide (FAST-A), any deploy. Placing a DASH (carriage 3) presentation in time: its marker names no date, so such presentations are kept in the class
unplaced. A per-owner count of impressions that aired in its breaks but did not count (claims absent) is not stated: the ledger's ad report already states each verdict.For the lead
revenue_shareis not inquickstart(the archive build), so the releasedwaireports the four operations as not built; the catalogue job runscli_opswithquickstart,revenue_share. Adding it to the archive build pulls inreceipt_ledger's feature set.WAI_FEATURESlist, the--testprebuild list and the per-feature loop getrevenue_shareappended at the end;ops.json,actions.generated.js,REGISTRY.md,registry/capabilities.jsonandvocab/v2.ttlare generated, so after merging both, regenerate them rather than hand-merge.🤖 Generated with Claude Code