Adoption Lighthouse
The Trust Console answers “is the system safe?”. The Adoption Lighthouse answers a different question: is anyone actually using it — and is that growing? It aggregates enrollments, evidence, receipts, settlements, and enabled rails across time windows, reports the trajectory, and signs the result so anyone can verify the numbers.
It refuses to flatter us
Our own smoke harnesses and the adoption proof loop create agents, evidence, receipts, rails, and settlements. Counting those as “adoption” would be self-congratulatory. Any row whose identity / evidence / reference carries one of the documented markers is excluded, and the marker list is returned in the response under excluded_markers:
adopt-— the adoption proof loop and its canary railadopt-canary-— the settlement canary railsmoke:— smoke-harness artifacts
Buckets and trend
Four buckets — 24h, 7d, 30d, all — each report enrolled_agents, evidence_events, receipts, settlements, enabled_rails, and distinct_operator_prefixes. For the 24h and 7d windows the lighthouse also compares against the equal preceding window and labels each metric growing, flat, or falling.
Operator ids are masked to their first four characters — a count of distinct operators, never an identity.
GET /api/v1/raillab/lighthouse (ISSUER API key)
{
"lighthouse": {
"organic_only": true,
"excluded_markers": ["adopt-", "adopt-canary-", "smoke:"],
"buckets": { "24h": { "enrolled_agents": 3, "settlements": 41, ... }, "7d": {...}, "30d": {...}, "all": {...} },
"trend": { "24h": { "settlements": "growing", ... }, "7d": {...} },
"persistence": {
"cohorts": [{ "week": "2026-W24", "size": 12, "w1_retention": 0.5, "w2_retention": 0.33, "w3_retention": 0.25, "mature": true }],
"funnel": { "7d": { "enrolled": 40, "evidenced": 18, "receipted": 9, "settled": 6, "returned": 4 } },
"integrity": { "suspicious": false, "reasons": [] }
},
"degraded": false,
"degraded_reasons": []
},
"snapshot": { "content_hash": "…", "signature": "…", "public_key": "…", "algorithm": "ed25519" }
}Proof of Persistence — durable, not vanity
A raw count answers “how many showed up?” — a number a Sybil can inflate for free. The persistence block answers the only question that compounds: did they come back and keep doing costly work?
- Cohorts — each ISO week (UTC) of newly-seen operators, with
w1/w2/w3_retention: the fraction still active one, two, three weeks later. A cohort is the week an operator was first seen via a first Agent or Receipt.matureis false until the week-1 window has fully elapsed — immature cohorts readw1_retention: 0by construction and are never used to flag inflation. - Funnel —
enrolled → evidenced → receipted → settled → returned, deduped by operator and nested(each stage requires the prior), so it is monotonic non-increasing by construction. “Returned” means active in a week after their cohort. - Integrity — flags manufactured adoption: an enroll-and-vanishcohort (mature, size ≥ 10, with < 10% week-1 retention), or a > 80% never-settled ratio over 7d among operators past a 3-day grace (so a network is not libelled the day it grows). When
integrity.suspiciousis true the reading is never cached.
A settlement counts as activity only when SETTLED — it requires a valid rail signer signature — so the strongest persistence signal cannot be manufactured by spamming bad-signature attempts at /settle.
Verify the numbers offline
The response is signed with the same key as /api/v1/receipts/monetary. Remove the snapshot object, recompute content_hash = sha256(canonicalJson(body, keys sorted)), then verify the Ed25519 signature over utf8(content_hash). If it checks out, the adoption numbers were not tampered with in transit or at rest.
# pseudocode body = response minus "snapshot" hash = sha256(canonicalJson(body)) assert hash == snapshot.content_hash assert ed25519_verify(snapshot.signature, utf8(hash), snapshot.public_key)
Degrades, never lies
If a datastore read fails, the lighthouse returns HTTP 200 with degraded: true, the buckets it could compute, and a degraded_reasons list — a barometer that hides all data on a partial outage is worse than one that reports what it sees.
Agent autarky
const p = new PassportClient({ apiKey: "pp_…", baseUrl: "https://passport.metis.gold" });
const lh = await p.getLighthouse(); // ISSUER key
if (lh.lighthouse.buckets["7d"].settlements === 0 || lh.lighthouse.trend["7d"].settlements === "falling") {
// an autonomous operator can notice the network is cooling and adjust.
}