Files
HIPs/HIPs/hip-0516-translate.md
zeekay ad5a010f79 Three words for what a proposal is, and a generator that refuses
The same fact lived in nine hand-kept places and they disagreed about 58
proposals. This makes the file the only authority and everything else a
projection of it.

ONE VOCABULARY, in vocabulary.json, read by the lint, the generator and the
doc site. Three words, each answering "can I rely on this?" and nothing else:

    Draft    nothing implements it yet, or its own text says the work is ahead
    Final    the thing it specifies exists in the code
    Living   governance and process: amended in place, never finished

Eight spellings folded in. `Active` was the one doing real damage: on four Meta
and Process HIPs it meant living, on 48 others it meant shipped, and one word
doing two jobs is why nothing agreed. `Review` and `Last Call` described a
queue rather than a document, and a proposal under review is still one you
cannot build against.

STATUS IS EARNED, not declared. Every Standards Track proposal was checked
against the estate: 235 have their implementation, and eight do not and say so.
HIP-0095 carries a twelve-week plan ending at "mainnet launch" and has no code.
HIP-0525 specifies an all-machine meeting while apps/meet refuses a machine a
seat. HIP-0078 names a prover at a "planned location" that does not exist.
HIP-0114 is the cog.* procedure set, and that procedure set returns nothing.
HIP-0003, HIP-0102, HIP-0305 and HIP-0901 likewise: looked, found nothing.

SIXTEEN TOMBSTONES DELETED rather than statused. A dead document kept alive in
the index is one concept wearing two names. Each successor exists and carries
the text, and the 90 references that pointed at a tombstone now point at it --
whole markdown links at once, so link text cannot outlive its target. Three of
the sixteen were pure compatibility: HIP-0028 specified a migration off a
Bitnami chart, HIP-0029 dual-engine MySQL, HIP-0030 a schema-deprecation
policy. We ship no backwards compatibility, so we do not specify it.

Nine links were already broken and had been since the corpus was renumbered:
short-form ./hip-5.md paths that never existed. Two named the wrong proposal --
one cited "HIP-1: Hanzo Multimodal Models" where multimodal is HIP-0003.

THE GENERATOR REFUSES more often than it writes, because a short table looks
exactly as authoritative as a complete one. Rows must equal files; a shrinking
index must say by how much; a section whose input is missing is an error, not
an omission; nothing is written until all of it validates. Its self-test breaks
the corpus eleven ways and requires each to refuse AND leave README byte-
identical -- a guard that reports after half-writing has protected nothing.
The lint's second implementation of the same comparison is gone.

The site rendered 52 proposals with a fallback grey badge and counted them
toward none of its three stat tiles, because docs/lib/source.ts declared a
status union by hand: it admitted a `Stagnant` no file has ever carried and
omitted the one 52 files did. Five components each kept their own copy of the
colours. All six now read vocabulary.json.
2026-08-28 18:05:41 -07:00

7.0 KiB

hip, title, author, type, category, capability, status, created, requires
hip title author type category capability status created requires
0516 Translate — One Endpoint, Two Tiers, Permissive Weights Hanzo AI Standards Track Core translate Final 2026-07-26 HIP-0026, HIP-0139

HIP-0516: Translate — One Endpoint, Two Tiers, Permissive Weights

Abstract

POST /v1/translate is the one translation surface: a quality tier served by the model plane and a bulk tier served by MADLAD-400. Both sit behind one endpoint, so callers choose cost and latency, never a vendor. The translation memory beside it is normative, not a cache — it is what makes a locale rebuild idempotent under a non-deterministic model. The implementation is hanzoai/cloud apps/translate.

Motivation

LLM output is non-deterministic, so a naive rebuild rewrites every string in every locale file. That churn is the main thing a hosted translation management product was actually providing, and it must be replaced rather than dropped. The second forcing fact is licensing: the most cited open translation weights are non-commercial and unusable in a paid service, so which model serves the bulk tier is decided by license before quality gets a vote.

Specification

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

§1 The address

Every route is under /v1/translate (manifest/apps.go:437): three operations. GET and PUT /v1/translate/memory — the review lane — are typed. POST /v1/translate itself is a raw handler with prose declared beside the route (apps/translate/translate.go:59): its input is a union (text or batch, never both) that a single typed In cannot state honestly, so the capability's only product route carries a full declared description instead of an operationId and nothing else.

§2 Two tiers, no fallback

tier selects the engine and defaults to quality:

  • quality routes to the model plane (deps.AI — zen through the gateway), which carries context, terminology and tone. Our own S2ST measurements found the LLM path beat a joint translation model on fluency, so this default is evidence rather than fashion.
  • bulk routes to MADLAD-400 under CTranslate2, reached over a small JSON contract so the weights are served independently of this binary (apps/translate/engine.go). A deployment with no bulk backend answers 503 for that tier. Bulk MUST NOT fall back to quality: a caller is never quietly served — or charged — at a tier it did not ask for.

The license decides the bulk model before quality does. MADLAD-400 3B/10B and Opus-MT/Marian are permissively licensed (apache-2.0, MIT); NLLB-200, SeamlessM4T v2 and TowerInstruct are cc-by-nc-4.0 and unusable in a paid service — verified against the Hugging Face model API, not from memory, after an audit found NC weights shipping inside our repos. LibreTranslate is excluded as a component: AGPL-3.0 reaches through a hosted service.

§3 The memory is normative

Every string keys on (source_text, target, glossary_version, tier); a hit returns the stored value verbatim and never re-translates, so only new or changed source strings reach an engine — locale rebuilds are idempotent and the bill is proportional to what changed. Editing a glossary term changes the key, so a stale rendering can never be served.

The review lane rides the same memory: an entry carries a state on the ladder machine → suggested → approved → published, and a machine write may create a row or refresh one still at machine, nothing else (apps/translate/memory.go). A rebuild can never silently revert a string a human approved.

The store is one SQLite memory per org, opened through cloud.OrgStore under the name translate (apps/translate/translate.go:181) — a distinct org resolves to a distinct file, so a query in one org cannot reach another's rows. Submitted text is customer content: it lands only in that org's own memory, is not training data, and is not retained anywhere else.

§4 Tenancy

The org is the validated principal's (HIP-0026); a request without one is 401/403, and the memory read back at /v1/translate/memory is always the caller's own org's.

§5 Money

The surface declares cloud.Metered (plugin/translate/main.go) and is listed in the standing gate (spend.go:319, "per-character fee"). The two tiers bill through two planes, deliberately: quality is debited by the model plane's own token meter — a second charge here would double-bill — while bulk carries its own per-org gate and meter on the source characters that actually reached the engine (apps/translate/translate.go:340,361), priced by TRANSLATE_PRICE_UUSD_PER_1K_CHARS (default 20 micro-USD per 1000 characters, apps/translate/engine.go:234). A fully-cached rebuild reports zero characters and costs zero.

§6 Events and telemetry

It publishes nothing on the bus; a customer's webhooks receive no translate.* events. Beyond the request span it emits only its own log lines.

§7 Upstream

MADLAD-400 3B/10B weights (apache-2.0) served under CTranslate2 (MIT) are the bulk engine — spoken to over the JSON contract in §2, not linked into the binary. The memory rides github.com/hanzoai/sqlite (MIT / Apache-2.0 dual). Nothing is forked.

§8 Stage

beta: the manifest row declares Stage: Beta (manifest/apps.go:437), so per HIP-0139 §8 the capability is dropped from the public projection and its prefix answers 404 unless the caller's org holds the translate flag.

§9 Dogfooding

The locale sync that replaced the hosted translation vendor calls /v1/translate like any other client. Our own product translation is the reference deployment, which keeps us honest: a regression shows up in our own surfaces first.

Rationale

The alternative to one endpoint with a tier field is two products — an LLM translator and a bulk MT service — each with its own auth, meter and memory. That doubles every projection and, worse, splits the memory: the same source string translated on both products would have two histories and the review lane would have to reconcile them. One endpoint, one memory keyed by tier, keeps a string's history in one place.

Security Considerations

Submitted text is customer content and the memory is the disclosure surface: tenancy is a separate database file per org (§3), so a cross-tenant read requires opening the wrong file rather than forgetting a predicate. The wrong billing implementation charges twice (an edge fee on top of the model plane's tokens) or silently serves bulk work on the quality tier at quality's cost — both are refused structurally: the edge declares Metered and adds nothing, and there is no cross-tier fallback (§2). The review ladder is an integrity boundary: a machine write that could touch an approved string would let a rebuild rewrite reviewed legal or safety copy without a human in the loop.

References

  • HIP-0026 — Identity and Access Management
  • HIP-0111 — IAM authentication
  • HIP-0139 — Capability

Released under CC0 1.0 Universal Public Domain Dedication.