HIP-1203: Affiliate — Commission on Referred Spend
Abstract
affiliate is the partner program that pays ongoing commission on what your
referrals spend: an org applies, staff approve it with a rate and a share
link, and every org that signs up through the link accrues commission for the
affiliate against its metered spend, period by period, paid out in credits or
recorded cash (apps/affiliate/affiliates.go:1-38). It is apps/affiliate
in hanzoai/cloud. It records payables; it never moves money.
Motivation
Three programs in the repo share one shape — apply, approve, attribute, accrue
at-most-once per (party, counterparty, period), pay out against
pending = accrued − paid: referral is the one-time two-sided bonus,
author the OSS royalty, and this one the ongoing partner commission. Each
keeps its own store and its own HIP; what they share is the commerce ledger
path for a credits payout (apps/affiliate/affiliates.go:8-15).
Specification
The key words MUST, MUST NOT, SHOULD, SHOULD NOT and MAY are to be interpreted as in RFC 2119.
§1 Addresses
The customer surface, all typed, under /v1/affiliate: the status read
(GET /v1/affiliate), POST …/apply, POST …/attribute, POST …/click,
GET …/leaderboard, and the affiliate's own …/me, …/me/earnings,
…/me/handle and …/me/links (plugin/affiliate/openapi.json).
The operator's view, per HIP-0139 §3.2, under /v1/admin/affiliates: the
board (GET), POST …/{id}/approve, …/{id}/rate, …/{id}/suspend,
…/{id}/payout, …/sweep, and the referral-analytics board at
GET /v1/admin/affiliates/referrals. Today's router serves that last read at
/v1/admin/referrals — a root whose remaining routes the referral
capability genuinely serves (manifest/apps.go:431) — and the pair is carried
by hanzoai/cloud openapi/misfiled.txt; it closes by fold into this
capability, because the board reads this capability's own tables
(apps/affiliate/store.go:863-880).
§2 Store
The capability owns one encrypted SQLite database, opened through the one
opener (sqlpool.Open("affiliates", dir), apps/affiliate/store.go:184):
affiliate rows, the affiliate_referrals attribution spine, and
affiliate_accruals. Attribution is first-touch, one row per referred org,
self-attribution refused. An accrual is latched at-most-once per
(affiliate, referred_org, period) — a re-run in the same period converges
instead of double-accruing — and the balance and the accrual rows move in one
transaction (apps/affiliate/store.go:903-905). Payouts against the ledger
are recorded per affiliate; a payout MUST NOT exceed pending
(accrued − paid), guarded atomically.
§3 The money seam is a question
The one thing the commission loop asks of the money plane is a read: what has
this org spent this period. The seam is an interface with exactly that method,
and it MUST NOT grow a write — the deposit method it once carried is how a GET
on this surface came to mint platform credit, and
TestCommerceSeamIsReadOnly fails if the shape ever widens
(apps/affiliate/commerce.go:9-22). A credits payout is a commerce grant,
tagged grant:affiliate, issued through the shared apps/payout path; cash
methods are record-only — a human settles them.
§4 Tenancy
Customer routes resolve the affiliate from the caller's validated org and
never from input. Every /v1/admin route is gated on the one SuperAdmin
predicate — c.IsAdmin(), minted only for a verified member of the admin
org after identity sanitization (apps/affiliate/typed.go:42-47,
HIP-0118). The attribute and click writes are reachable to a new org by
design: they record which code referred it, and the code is the only thing
the caller names.
§5 Metering, events, observability, stage
The capability is free (plugin/affiliate/main.go:21, cloud.Free) — it
accrues liabilities to partners and meters nothing. It publishes no events on
the bus and delivers nothing to customer webhooks. Beyond the request span it
emits log lines only. Its stage is beta — the manifest row
declares it (manifest/apps.go:401, Stage: Beta; HIP-0139 §8).
§6 Upstream
The capability derives from no forked, embedded or mirrored OSS project. Its
non-standard-library imports beyond the app framework are
github.com/hanzoai/cek v0.2.7 (MIT — the encrypted-at-rest SQLite opener
under sqlpool) and github.com/hanzoai/namespace v1.2.0 (MIT).
Rationale
The alternative to three sibling programs is one generalized rewards engine. It would share the accrual latch and the payout guard, and it would braid three different counterparties — a partner, a referred pair, an OSS author — into one schema whose every query needs a program discriminator. Three small stores with one shared ledger path keep each program's invariants checkable in its own file.
Security Considerations
This surface is adjacent to money, and every past defect here was a write where a read belonged. The read-only commerce seam is the load-bearing shape: re-adding a write method re-opens credit minting from an affiliate route. Attribution is the fraud surface — self-attribution is refused, a referred org attributes once, and the sweep computes commission only from spend commerce reports, so a forged click or code cannot fabricate accrual. The payout guard's atomicity is what stops a concurrent double-payout; the admin gate on the one predicate is what keeps approval, rates and payouts out of tenant hands.
References
- HIP-0106 — The Hanzo Plugin Contract
- HIP-0118 — SuperAdmin & Tenant Isolation Model
- HIP-0139 — Capability
Copyright
Released under CC0 1.0 Universal Public Domain Dedication.