HIPsHanzo Proposals
Back to HIPs
HIP-1262FinalStandards TrackApplication

Reference — Lookup Sets

Hanzo AI
Created: 2026-08-20
Requires: HIP-0026, HIP-0106, HIP-0139

HIP-1262: Reference — Lookup Sets

Abstract

/v1/reference is the lookup data a risk decision needs but cannot derive: which email domains hand out throwaway inboxes, which addresses belong to a datacentre or a Tor exit, which card scheme an issuer prefix belongs to, which browsers the fleet sees everywhere, and how current the designation lists the screening engine holds actually are (apps/reference/reference.go:15-19). It is implemented in hanzoai/cloud at apps/reference. This HIP is where HIP-1046 §8's subtree now lives; HIP-1046 keeps the cross-plane invariants.

Motivation

Every one of these facts could be hard-coded where it is consulted, and each copy would go stale on its own schedule with nobody noticing — a designation list that answers "not listed" because it was never loaded is indistinguishable from a clean world. Making the lookup a capability puts freshness on the wire and makes staleness a reported condition rather than a silent one.

Specification

The key words MUST, MUST NOT, SHOULD, SHOULD NOT and MAY are to be interpreted as in RFC 2119.

Two stores, one precedence

The Hanzo-maintained BASELINE lives in the shared warehouse — hanzo.reference_source and hanzo.reference_entry, tables with NO tenant column at all, so a cross-tenant write is unrepresentable rather than merely forbidden (apps/reference/store.go:47-49). A tenant's OWN allow and deny entries live in that organization's own SQLite file, opened per organization through cloud.OrgStore under the name reference (apps/reference/override.go, apps/reference/reference.go:34-40). Resolution MUST be override first, then baseline; first hit wins.

Nothing derived from one organization's rows may enter the baseline, ever. The only fleet-derived set is an aggregate published above a k-anonymity floor no single organization can reach alone (apps/reference/set.go:297-299).

The address

The capability answers under /v1/reference: the set list at the root, one set read, written and cleared at /{set} — a SET, which is why the name is singular — resolve, and refresh (apps/reference/reference.go:507-525). Every operation is typed. refresh writes the shared baseline every organization reads and is therefore SuperAdmin only (apps/reference/reference.go:1328-1329); it MUST stay one operation under this prefix, never an address family of its own. Today's router still serves this surface under /v1/risk/reference; that pair is carried by hanzoai/cloud openapi/misfiled.txt and closes by fold.

A version, a refusal, and freshness on the wire

Every set is a VERSION — the content digest of what was taken, so the same data is the same version whoever fetched it, and a decision records one string an auditor resolves back to a publisher, a licence and a date. A set that has never loaded MUST REFUSE rather than answer "not listed" (apps/reference/resolve.go:157), and a set whose source needs a licence we do not hold is declared as a seam that refuses (apps/reference/set.go:93-95).

Freshness rides every answer: the version, when its oldest contributing publisher was current — the oldest, because a set is exactly as fresh as its weakest source (apps/reference/resolve.go:71-73) — how old that is, and whether it is past bound. A stale set still answers, and says that it did.

Tenant, meter, events, observability, stage

Reads and override writes resolve the organization from the validated principal (apps/reference/reference.go:549) and a request without one is refused; the baseline is readable by every validated org and writable by none of them. The capability is free, in those words (plugin/reference/main.go:21, Price: cloud.Free). It publishes no events on the bus. Beyond the request span it registers nothing; staleness and refusal — the two ways this plane can be quietly wrong — are reported on the wire rather than in a private metric. Its stage is whatever its manifest row declares — HIP-0139 §8 keeps that in one place, and the beta this line used to assert had already drifted from it.

Upstreams

It forks no code; the refusal discipline generalises what luxfi/aml pkg/reference states (apps/reference/reference.go:25-28). What it MIRRORS is published data, and the catalog declares each source's licence as a fact beside its origin (apps/reference/set.go):

  • disposable-email-domains — CC0-1.0 (set.go:221-223);
  • crawler-user-agents — MIT (set.go:255-257);
  • the Tor bulk exit list — CC BY 3.0 US (set.go:242);
  • the IANA special-purpose address and AS-number registries — the registries of record (set.go:243-244, :269-271);
  • cloud and CDN operators' self-published IP feeds — machine-readable by intent, no licence stated and none claimed (set.go:234-241);
  • issuer prefixes — computed here from ISO/IEC 7812 structural facts; no issuer database is licensed or held (set.go:283-285);
  • sanctions designations — receipt only; they stay with the engine that screens (set.go:310-313).

A new source MUST enter through this catalog with its licence basis declared, or as a seam that refuses.

Rationale

The alternative was to file these operations under the model-serving plane, and an earlier cut did exactly that (manifest/apps.go:231-238): lookup data in a live product with different customers, where its growth reads as that product's growth. The other alternative — folding into risk — fails on the store rule: this capability owns two stores of its own, and the baseline's no-tenant-column shape is a property risk's per-tenant planes cannot host.

Security Considerations

The baseline is consulted by every organization's decisions, so poisoning it is the high-value attack: refresh is held to the platform's own identity, a version is a content digest so a tampered take is a different version by construction, and nothing an organization sends can reach the baseline tables — their shape has nowhere for it to go. The override plane is the tenant-side exposure: a per-org file reached only through the validated principal, so one organization's allow-list cannot bleed into another's decisions. The remaining risk is absence — an unloaded, unlicensed or stale set silently passing for a clean answer — closed by refusing the first two and reporting the third.

References

  • HIP-0026 — Identity and Access Management
  • HIP-0106 — The Hanzo Plugin Contract
  • HIP-0139 — Capability
  • HIP-1046 — Risk

Copyright

Released under CC0 1.0 Universal Public Domain Dedication.