Lesson 35 · The Cost-Allocation Model · New Subsystem
Billing a shared platform
Attributing cost to customers when there is no per-customer cost to measure. ~13 min.
Builds on: L23 · L15 · L2Anchor: shared infra, focus-token configsNew: allocation, not measurementNew: blended-share billing
Every subsystem so far built or guarded the risk graph. This one is different in kind: it answers a business
question — "what should each customer pay?" — and the interesting part is that the obvious approach is impossible.
There is no per-customer cost to measure. The whole subsystem is a careful answer to that constraint, and it's a clean
study in allocation vs measurement.
Your anchor: one shared pipeline, many customers
Risk-graph runs on shared infrastructure — one Memgraph cluster, one indexer fleet, one self-hosted Erigon node. It
scales by total focus-token volume, not per-customer. Closing customer X doesn't drop the GCP bill linearly, so
there's no "unit cost" per customer to read off a meter. You can't measure what customer X costs. What you can
compute is their defensible share of platform load — and that's the whole game.
1 · The core insight: the pipeline is controller-agnostic
Here's the fact the model is built on: grep -rn 'controller_id' pkg/indexer pkg/enrichment pkg/risk returns
zero hits. The pipeline has no idea who consumes its output. Customer config drives exactly one thing into the
system: the focus-token set — which tokens that customer wants watched.
Billing is derived, never measured — and that's a feature
Because the hot path never learns "who," per-customer cost is reconstructed downstream from the focus-token lists,
not stamped during processing. That keeps billing concerns entirely out of the indexer, enrichment, and risk code — they
stay clean and customer-blind. The cost model is a separate reader that joins "which customer asked for which tokens"
with "what did those tokens cost the platform." Separation of concerns, enforced by the architecture.
2 · The allocation: blend measurable signals into a per-token share
If you can't measure per-customer cost, attribute per-token share of measurable load, then split each token across
the customers who asked for it. Each token T gets a fraction of platform load from several signals, every
one a proxy for a real cost driver:
notification + alert-storage marginal cost per fired alert
Each signal becomes a normalized share — the token's count over the platform total — and the shares blend:
holds_share(T) = HOLDS(T) / Σ HOLDS
proto_share(T) = PROTO(T) / Σ PROTO
compute_share(T) = compute_secs(T) / Σ compute_secs
⋮
blended_share(T) = Σ Weightᵢ × shareᵢ(T) // weights split the pool across signals
per_token_$(T) = blended_share(T) × PoolUSDPerMonth
Share-based, so stage and prod agree
Notice every term is a fraction of total, never a rate × absolute count. So a token gets the same allocation on
stage and prod — there's no "rate × count" drift between environments. The footprint is scale-invariant: double the
whole platform and every share is unchanged. That's a deliberate property, not a coincidence.
3 · Splitting a token across its customers (co_users)
A token's cost is platform-wide, but several customers may all watch it. So a token's dollars are split equally across
the controllers that list it:
per_controller_$ = BaseAllocationUSD
+ Σ_token ( per_token_$(T) / co_users(T) ) // T over the controller's config
If three customers all monitor USDC, each pays a third of USDC's allocation. Popular tokens are cheap per customer;
a niche token a single customer watches is borne by that one customer. Fair by construction — you pay for what you ask
for, shared with everyone else who asked for the same thing.
4 · The pool is a sales input, not an infra number
The single most important boundary: PoolUSDPerMonth — the total dollars being distributed — is chosen by
sales-ops, not derived from infra spend. The model splits whatever pool it's given; it never sets it.
Engineering owns
Sales-ops owns
the shares — defensible fractions that track measurable on-chain signals
the pool — cost-recovery (1.0× infra), margin (1.5×), or a flat negotiated $X/mo
Why this split matters
Keeping the shares (math) separate from the pool (pricing strategy) means engineering ships a number that's defensible
and auditable — "your share tracks these measured signals" — without ever taking a position on what the customer
should be charged. The measured infra spend is shown elsewhere (the Cost Explorer's infra tab) as one input sales
looks at; it does not flow into the allocation. Honest billing: the model attributes share, humans set price.
5 · What's excluded — sunk cost vs ongoing cost
One sharp modelling choice: admin edges (ADMIN_CTRL, OWNS, DEPLOYED_BY) are not billed.
They're written once at classification time (L25) — a sunk enrichment cost, not an ongoing per-token load. They're
shown on the Tokens page for transparency but excluded from the pool. The billable signals are all recurring costs:
balances refresh, structure refresh, risk recompute, alerts fire. You bill the ongoing burn, not the one-time
discovery.
A capstone that reads the whole system
Look at the signal sources: HOLDS & PROTO from Memgraph (L2), compute_secs from the risk scheduler's Prometheus metric
(L23), alerts_30d from the alert store (L15). The cost model is a meta-layer built entirely from the observable
outputs of every subsystem you've studied. It's a fitting last stop — billing the platform turns out to be a join over
everything else's telemetry.
Check yourself
1. Why can't the system simply measure what each customer costs?
2. What does it mean that the pipeline is "controller-agnostic"?
3. Since cost isn't measured per customer, how is per-customer cost obtained?
4. Each signal contributes a token's count divided by the platform total. Why use shares rather than absolute counts × a rate?
5. Three customers all list USDC in their configs. How is USDC's allocation handled?
6. PoolUSDPerMonth — the total dollars distributed — comes from where?
7. Why is keeping the shares (engineering) separate from the pool (sales) important?
8. Admin edges (ADMIN_CTRL, OWNS, DEPLOYED_BY) are excluded from the billed pool. Why?
↳ Ask your teacher
Try: "What are the two WeightingModes (measured vs flat) in the Report?" ·
"Show me ComputeByToken and how TokenCost.Footprint blends the shares." ·
"Which 10 edge types count as PROTO, and why those?" ·
"How does the Cost Explorer admin page render this?" ·
"What's BaseAllocationUSD for — a per-controller floor?"
What you can now do
Explain why a shared platform has no per-customer cost to measure, only a share of load to allocate.
Explain the controller-agnostic pipeline and why billing is derived downstream from the focus-token set.
Walk the blended-share formula (signals → normalized shares → weighted blend → × pool) and why it's scale-invariant.
Explain co_users token-splitting and the engineering-owns-share / sales-owns-pool boundary.
Explain the sunk-cost exclusion of admin edges and how the model reads telemetry from across the system.
The system, end to end and then some
Ingest → graph → enrich → risk → rules → alerts, the streaming/single-writer/recovery/observability scaffolding, the
discovery flywheel, the self-checking harnesses — and now the model that turns all of that measurable activity into a
customer's bill. You've traced the platform from a raw log all the way to an invoice line.