code wiki / _hdl_build / nx_adnet_invoice.nx

nx_adnet_invoice.nx source

↩ module page · 183 lines · 9250 B

1// nx_adnet_invoice.nx -- LIB: the BILLING PIPELINE for the sovereign ad network (debt 1785513943). 2// THE GAP IT CLOSED: nx_adnet_bill held correct CPM/CPC math that NOTHING ever called -- grep found zero 3// production callers, only its own gate. The network served slots and journalled served/viewable/click 4// events, but no organ ever read those journals, applied a rate, or produced an invoice, so no advertiser 5// could be charged at all. Correct math nobody calls is not a billing system. 6// 7// This COMPOSES (rule 22) the already-gated parts rather than re-implementing them: ad_event_count for the 8// per-AD counts, anb_invoice_line_v for the viewable-basis money math. It adds exactly one thing -- the 9// RATE CARD, and the loop that joins rates to measured events. 10// 11// RATES ARE DATA, NEVER CODE (rule 11): knowledge/status/adnet_rates.conf, one row per ad: 12// <ad_id>\t<cpm_milli>\t<cpc_milli>\t<cap_milli> ('#' comment rows ignored) 13// DELIBERATELY NOT under sites/ -- a rate card is commercially sensitive and /synth/ is publicly served. 14// 15// HONEST-BILLING LAW: an ad with NO rate row is REFUSED, never defaulted to zero and never guessed. A 16// missing price and a price of zero are different facts and must not collapse into the same invoice line. 17// license_tier: ORIGINAL 18import "nx_syscalls.nx" 19import "_hdl_build/nx_adnet_slot.nx" 20import "_hdl_build/nx_adnet_bill.nx" 21 22// bounded decimal parse. -1 = not a clean non-negative integer (so it flows into the REFUSE path). 23func ainv_num(s: *u8) -> i64 { 24 var i: i64 = 0 25 var v: i64 = 0 26 var any: i64 = 0 27 while s[i] != (0 as u8) { 28 let c: u8 = s[i] 29 if c < (48 as u8) { return 0 - 1 } 30 if c > (57 as u8) { return 0 - 1 } 31 v = v * 10 + ((c as i64) - 48) 32 any = 1 33 i = i + 1 34 if i > 18 { return 0 - 1 } 35 } 36 if any == 0 { return 0 - 1 } 37 return v 38} 39 40// look up one rate field for an ad. Returns the value, or -1 when the ad has no rate row / bad field. 41// Field index follows the rate-card contract: 1=cpm_milli 2=cpc_milli 3=cap_milli. 42func ainv_rate(rates: *u8, rlen: i64, ad_id: *u8, idx: i64) -> i64 { 43 let f0: *u8 = sys_mmap(128) 44 let fv: *u8 = sys_mmap(64) 45 var ls: i64 = 0 46 while ls < rlen { 47 let le: i64 = ad_eol(rates, ls, rlen) 48 if le > ls { 49 let row: *u8 = ((rates as i64) + ls) as *u8 50 if row[0] != (35 as u8) { 51 aslot_field_b(row, le - ls, 0, f0, 128) 52 if ad_streq(f0, ad_id) == 1 { 53 aslot_field_b(row, le - ls, idx, fv, 64) 54 return ainv_num(fv) 55 } 56 } 57 } 58 ls = le + 1 59 } 60 return 0 - 1 61} 62 63// ---- SPEND, the number a budget ceiling needs ---------------------------------------------------- 64// THE DECLARED GAP THIS CLOSES: camp_verdict returns CAMP_EXHAUSTED when spent >= budget, but the serving 65// path had no spend to give it, so the budget branch could never fire in production. Flight dates and 66// pause worked; the ceiling did not. 67// 68// ★ ARCHITECTURAL BOUND, deliberate: THE SERVE PATH MUST NEVER CALL THIS. It scans three journals, which 69// is O(all events) per call -- with 158k served rows already on disk that is a per-request table scan on 70// the hot path. This is a RECONCILER function: a beat computes spend and writes it into the campaign 71// row, and serving reads that one field in O(1). Putting a journal scan behind an ad impression is how 72// an ad network takes its own site down. 73// 74// Returns spend in milli-units for one ad, or -1 = UNMEASURABLE (no rate row, or an impossible pair). 75// -1 is what camp_verdict treats as MALFORMED -> does not serve. Refusing to serve on an unmeasurable 76// budget is correct: assuming zero spend is exactly how an exhausted campaign keeps running. 77func ainv_spent_milli(rates: *u8, rlen: i64, servedlog: *u8, slen: i64, viewlog: *u8, vlen: i64, clklog: *u8, clen: i64, ad_id: *u8) -> i64 { 78 if aslot_id_ok(ad_id) == 0 { return 0 - 1 } 79 let cpm: i64 = ainv_rate(rates, rlen, ad_id, 1) 80 if cpm < 0 { return 0 - 1 } 81 var cpc: i64 = ainv_rate(rates, rlen, ad_id, 2) 82 if cpc < 0 { cpc = 0 } 83 let served: i64 = ad_event_count(servedlog, slen, ad_id) 84 let viewable: i64 = ad_event_count(viewlog, vlen, ad_id) 85 let clicks: i64 = ad_event_count(clklog, clen, ad_id) 86 if served < 0 { return 0 - 1 } 87 if viewable < 0 { return 0 - 1 } 88 // the same anti-forgery invariant the invoice enforces: a creative cannot be seen more times than it 89 // was sent, so this pair is broken rather than large, and a broken measurement must not authorise spend. 90 if viewable > served { return 0 - 1 } 91 return anb_total_milli(viewable, clicks, cpm, cpc) 92} 93 94// One invoice line for one ad, joining the rate card to the three measured journals. 95// Returns bytes written. An ad with no rate row emits an explicit REFUSED line rather than nothing -- 96// a silently omitted advertiser is how unbilled inventory hides. 97func ainv_line_for(rates: *u8, rlen: i64, servedlog: *u8, slen: i64, viewlog: *u8, vlen: i64, clklog: *u8, clen: i64, ad_id: *u8, out: *u8, cap: i64) -> i64 { 98 if cap < 384 { return 0 } 99 if aslot_id_ok(ad_id) == 0 { return 0 } 100 let cpm: i64 = ainv_rate(rates, rlen, ad_id, 1) 101 let cpc: i64 = ainv_rate(rates, rlen, ad_id, 2) 102 let capm: i64 = ainv_rate(rates, rlen, ad_id, 3) 103 if cpm < 0 { 104 var o: i64 = 0 105 o = ad_cat(out, o, "adnet-bill id=" as *u8) 106 o = ad_cat(out, o, ad_id) 107 o = ad_cat(out, o, " basis=viewable due_milli=REFUSED reason=no-rate-row" as *u8) 108 out[o] = 10 as u8 109 return o + 1 110 } 111 let served: i64 = ad_event_count(servedlog, slen, ad_id) 112 let viewable: i64 = ad_event_count(viewlog, vlen, ad_id) 113 let clicks: i64 = ad_event_count(clklog, clen, ad_id) 114 var cpc2: i64 = cpc 115 if cpc2 < 0 { cpc2 = 0 } 116 return anb_invoice_line_v(ad_id, served, viewable, clicks, cpm, cpc2, capm, out, cap) 117} 118 119// Full report over every ad in the live inventory (directive rows skipped). Returns bytes written. 120func ainv_report(inv: *u8, ilen: i64, rates: *u8, rlen: i64, servedlog: *u8, slen: i64, viewlog: *u8, vlen: i64, clklog: *u8, clen: i64, out: *u8, cap: i64) -> i64 { 121 var o: i64 = 0 122 let id: *u8 = sys_mmap(128) 123 var ls: i64 = 0 124 while ls < ilen { 125 let le: i64 = ad_eol(inv, ls, ilen) 126 if le > ls { 127 let row: *u8 = ((inv as i64) + ls) as *u8 128 if row[0] != (64 as u8) { 129 aslot_field_b(row, le - ls, 0, id, 128) 130 if aslot_id_ok(id) == 1 { 131 if o + 384 < cap { 132 let w: i64 = ainv_line_for(rates, rlen, servedlog, slen, viewlog, vlen, clklog, clen, id, ((out as i64) + o) as *u8, cap - o) 133 o = o + w 134 } 135 } 136 } 137 } 138 ls = le + 1 139 } 140 out[o] = 0 as u8 141 return o 142} 143 144// ---- PER-ADVERTISER REPORT ------------------------------------------------------------------------ 145// THE PROMISE THIS BACKS: the public /advertise page tells a prospective client "you see the delivery 146// ratio -- every invoice line shows served and viewable side by side". That was true of what the invoice 147// CONTAINS and false of what an advertiser could REACH: the only report was the operator's whole-network 148// view. A claim on a public page with no surface behind it is a promise you will be asked to honour. 149// 150// Scopes strictly to campaigns whose advertiser_bk (campaign row field 1) matches. An advertiser must 151// never see another advertiser's spend, so the filter is a WHITELIST over their own campaign rows rather 152// than a redaction pass over everyone's -- redaction fails open the first time a field moves. 153// 154// ★ AUTH IS NOT DONE HERE ON PURPOSE. This takes an advertiser_bk the CALLER has already authenticated. 155// Baking a token check into a reporting function is how the check gets skipped by the second caller. 156// The route that exposes this MUST establish identity first; until then it is operator-only. 157func ainv_report_for_advertiser(camps: *u8, clen: i64, adv_bk: *u8, rates: *u8, rlen: i64, servedlog: *u8, slen: i64, viewlog: *u8, vlen: i64, clklog: *u8, cllen: i64, out: *u8, cap: i64) -> i64 { 158 var o: i64 = 0 159 if aslot_id_ok(adv_bk) == 0 { out[0] = 0 as u8; return 0 } 160 let cid: *u8 = sys_mmap(128) 161 let cadv: *u8 = sys_mmap(128) 162 var ls: i64 = 0 163 while ls < clen { 164 let le: i64 = ad_eol(camps, ls, clen) 165 if le > ls { 166 let row: *u8 = ((camps as i64) + ls) as *u8 167 if row[0] != (35 as u8) { 168 aslot_field_b(row, le - ls, 1, cadv, 128) 169 if ad_streq(cadv, adv_bk) == 1 { 170 aslot_field_b(row, le - ls, 0, cid, 128) 171 if aslot_id_ok(cid) == 1 { 172 if o + 384 < cap { 173 let w: i64 = ainv_line_for(rates, rlen, servedlog, slen, viewlog, vlen, clklog, cllen, cid, ((out as i64) + o) as *u8, cap - o) 174 o = o + w 175 } 176 } 177 } 178 } 179 } 180 ls = le + 1 181 } 182 out[o] = 0 as u8 183 return o 184}