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}