Commit Graph

10 Commits

Author SHA1 Message Date
martbost 3f48bdfd99 Traffic Desk: tag ad destinations so clicks are attributable
The Traffic Desk has been unmeasurable since it shipped. track.js reads ?v=
before it reads document.referrer, so every angle-linked ad click was filed
under "angle:graveyard" (or two/phone/pocket) and the referring domain was
discarded. Ad clicks, flyer QR scans, massifly angle links and Telegram angle
links all collapsed into the same four buckets — about 11,400 landings with
no attributable source between them — while networkadspace.com showed one
single visit against roughly 45,000 served impressions.

That reads as "the ads produce nothing." What it actually means is that
nothing carried a source.

Destinations now carry src=nas-banner or src=nas-text. `src` is checked
before `v` in track.js so it wins cleanly, and the landing page reads ?v=
separately so the angle hook is untouched. Split by format because we have
believed text outperforms banners for weeks without being able to show it.

This is the prerequisite for everything else here: pooled Ad Intel
benchmarks, judging which placements are worth expanding, and telling a
member what their impressions bought.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 06:37:41 -05:00
martbost 59bd4bd4ab Show recorded figures for closed campaigns, not the wiped counter
My own sweep created this. Deactivating an ad ZEROES `remaining` on the
network, and for banners — where that column counts UP — zero reads as
"served nothing". So every campaign the sweep closed flipped to 0 served /
2,500 left, despite the ledger correctly recording served=2500,
completed=true.

Closed campaigns now read from what we recorded at the time rather than
re-deriving from a counter that no longer holds anything. Live campaigns
still compute from the network, per placement type.

Also removed the em-dash placeholder that blanked LEFT for stopped rows —
there is a real number to show — and made stop()'s error path assume fully
served rather than fully unserved, since guessing high would refund
impressions that had actually been delivered.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 05:33:55 -05:00
martbost 26f2c91684 Banners must stop when they have delivered what was bought
Marty: "if it's just gonna keep running, why would anyone use impressions to
place the ad at all?" He is right, and my previous answer — calling the
over-delivery a bonus — papered over a real flaw.

Banner placements on this network have no cap. Left alone, 2,500 impressions
buys an ad that runs until expiry, which makes the allowance decorative and
the whole level ladder (2,500 at Scintilla to 150,000 at Corona) worth
nothing. Nobody would ever spend the larger allowance for an identical
outcome.

Two changes so the impressions are genuinely the thing being spent:

sweepCompleted() deactivates any campaign that has served its purchased
amount. Runs 90s after boot and every 15 minutes, batches its stat reads, and
only touches campaigns that are live, un-stopped and provably at or past what
was bought. Nothing is refunded — they delivered in full. It re-reads the
ledger after the network calls, since deactivating takes real time and
another member may have launched in that window.

New campaigns get days:30 instead of days:365. The allowance is monthly, so a
campaign outliving the month it was paid from is the same bug by another
route. Belt and braces — whichever ends it first.

Copy corrected too: "still serving as a bonus" became "served in full —
closing out", and the footnote now states plainly that a campaign ends when
it has served what was bought.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 05:23:38 -05:00
martbost 37c9fc615c Traffic Desk: the network counts banners and text ads in OPPOSITE directions
Marty reported banner impressions never moving while text ads did. They were
serving the whole time — we were reading the counter backwards.

Measured against the live network, because none of this is documented:
  TEXT   `remaining` counts DOWN from the purchase to zero.
         ad 2689: 1,111 left mid-flight, 0 once complete.
  BANNER `remaining` counts UP as impressions deliver, straight past the
         amount bought. ad 2707: 3,521 one day, 6,129 the next, on a 2,500
         buy. Every live banner sits above its purchase and rising.

Reading both as count-down made every banner report "0 served" while quietly
delivering thousands — and made stop() treat delivered impressions as
unserved, so stopping a finished banner refunded the lot. That was giving
back inventory that had already been spent on the network.

interpret(kind, bought, stat) now resolves both directions in one place, used
by stop() and by the table. served + left always reconciles to what was
bought and neither can exceed it.

This also supersedes the earlier "counter drift" note: the drift WAS the
count-up, seen through a count-down lens.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 04:56:51 -05:00
martbost 343f1edd57 Beta-tester safety: preview caps on the level override, and close a race in stop()
Two things needed before testers get level overrides.

1. The override decided what a position could SPEND as well as what it could
   SEE. A tester at Scintilla overridden to Corona would have had 150,000 real
   network impressions instead of 2,500, and a 200,000-impression grant pool to
   hand to real members — real inventory, spent for real. Ad allowances and
   split-test launches are now metered on trueLevel, and grant pools are capped
   to small preview amounts (5,000 impressions / 25 copy / 2 pages / 5 ad
   batches) when a position is overridden: enough to exercise the whole flow,
   trivial to lose.

2. suite-traffic.stop() read the ledger, made TWO network round trips to the ad
   network, then wrote back the object it had read seconds earlier — silently
   discarding any campaign another member launched in that window, with the
   impressions already spent on the network. It now re-reads after the awaits
   and mutates the fresh copy.

Worth recording that this was the ONLY such race: Node's single thread makes a
synchronous read-modify-write atomic, so the meter, grants and split ledgers
were never at risk. The danger was only ever the await gap.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-28 16:01:53 -05:00
martbost 6a7c5161ff Give the top of the ladder a real prize: Team Grants + Network Intelligence
Marty's read was right — the upper tiers felt flat, and the reason is that
they scaled QUANTITY, not KIND. L1->L2 was a category change (you couldn't
write, now you can). Everything above L4 was a multiplier on a capability
already owned at L2: sound more like you, measure it, more pages, five at
once. Meanwhile the price doubles each rung — Corona costs ~17x Culmen. No
batch button is worth 17x a voice profile.

The fix is a change of axis. Every tool from L1 to L8 was a "me" tool, but
past the first levels this business isn't about you: get two, help your two,
teach them to teach. So the top now operates on the ORGANISATION.

L7 Team Grants — hand Suite capacity down to anyone in your own org.
Deliberately from a separate pool that the level grants, NOT out of the
leader's own allowance: making someone choose between equipping their team
and using their own tools means nobody ever grants anything. Recipients are
verified in-org against the contract. Grants can lift a tool the recipient's
level hasn't unlocked — that's the point, not a loophole. Being helped is
made visible to the recipient, since half the value is knowing an upline
backed you.

L8 Network Intelligence — every team ad pooled and anonymised: which angles,
headlines, creative and formats actually earn their impressions. No member
alone has enough traffic to learn anything from display; together they do.
It can't be bought, and it compounds monthly with no further work. Holds two
lines: nothing identifies anyone, and nothing under 2,000 impressions gets a
reported rate — under-evidenced rows are counted and disclosed rather than
silently dropped. The written readout is hand-authored, not generated:
these are conclusions about money, and a model paraphrasing a table is
exactly where an invented number slips in.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-28 12:26:39 -05:00
martbost 8380696574 Traffic Desk: clamp refunds to what was actually bought
A few rows on the network carry a remaining larger than their assigned (a
pre-existing counter quirk), which surfaced as '1,002 returned' on a
1,000-impression ad. Derive served from bought - unserved instead of
trusting the network counter.

Verified live: a text ad with emoji round-tripped through the database
byte-for-byte, then stopped and refunded in full.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-28 10:31:49 -05:00
martbost d5c9e33e0e Traffic Desk: AI-written text ads alongside banners
Text placements out-perform banners on this network — 357 text ads average
~171 clicks each against ~150 for banners, on far less inventory — so
members can now run them too.

The format is measured, not guessed. Across 60 live text ads: Subject caps
at 20 characters and Body is ALWAYS exactly three <br>-joined lines of at
most 24 characters. Every ad in the sample obeyed it.

Emoji: the NAS columns are latin1, but so is the connection, so UTF-8 bytes
pass through and render — which is how the existing emoji ads got there. The
bridge's clean() would have deleted every emoji via iconv //TRANSLIT//IGNORE,
so text copy now uses clean_ad_text(), which preserves them and strips angle
brackets instead. The bridge builds the <br> separators itself from a lines[]
array, so a member can never inject markup.

The engine writes plain text to a tight budget and we apply emoji ourselves:
models cannot count characters, least of all with emoji, so decoration is
arithmetic we control. It also lets the emoji rotate independently of the
copy. The palette carries no money emoji — the network is full of them, but
implying earnings outranks matching the neighbours.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-28 10:19:36 -05:00
martbost 931eb4336b Traffic Desk: stop-and-refund, plus never let a banner dead-end
Three fixes from live testing of the Traffic Desk:

- /p/<id> with no page built now 302s to /join/<id> instead of returning
  raw JSON 404. Any /p/ link already on a banner, flyer, or in a DM must
  always land somewhere useful.
- The personal-page ad destination is only offered (client) and only
  accepted (server) once a page actually exists; otherwise the member is
  pointed at the Page Builder.
- Members can stop a running banner and get the unserved impressions back
  in their monthly balance. Counters are read before deactivation, since
  deactivating zeroes `remaining` and would look fully served.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-28 10:03:38 -05:00
martbost f7276589e9 Traffic Desk ships: signed NAS bridge client, monthly impression allowance by level (2.5k Scintilla -> 150k Corona), team-kit creative only (no member uploads = no moderation risk), local grant ledger, live serve stats; tile moved to L1 since credits are included at every paid level
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-28 09:50:03 -05:00