Tier gates per market
Goal: not every market justifies the same review budget. A contractual
locale may require a person to approve every unit; a mid-tier market wants the
important strings reviewed and the long tail machine-translated; a long-tail
locale ships as soon as the machine's own review is clean. Ship gates encode
that policy per market in the recipe, and one command —
kapi check --ship — enforces all of
it. The gate model is the kapi loop; the schema is the
project-file reference.
The tiers
| Tier | Policy | Gate |
|---|---|---|
| Contractual | A person approves every unit | { translated: 100, reviewed: { pct: 100, by: human } } |
| Mid-tier | Everything translated, the important 80% human-reviewed | { translated: 100, reviewed: 80 } |
| Long tail | Everything translated, AI review decisions count as reviewed | { translated: 100, reviewed: { pct: 100, by: any } } |
The recipe
Define the tiers once in the gates: registry, then assign markets with
ship_gates: rules — the most specific matching rule wins:
version: v1
name: storefront
defaults:
source_language: en
target_languages: [de, ja, fr, es, pt-BR, nl, sv]
gates:
contractual: { translated: 100, reviewed: { pct: 100, by: human } }
standard: { translated: 100, reviewed: 80 } # short form — by: human implied
machine: { translated: 100, reviewed: { pct: 100, by: any } }
ship_gates:
- when: { locales: [de, ja] } # markets with a review contract
gate: contractual
- when: { locales: [fr, es] }
gate: standard
- gate: machine # everyone else: the long tail
Rules can also select by collection, so regulated content answers to a
higher bar in every market — a when: { collections: [Legal] } rule with a
signed-off threshold outranks the locale rules for that content because it
is evaluated per (collection, locale) and the most specific rule wins.
The machine tier's by: any only passes when review decisions exist — pair
it with an AI review step (the review tool in the project's flow, or an AI
pre-review that auto-approves what it scores clean) so autonomous approvals
are recorded under an ai/… identity for kapi up to count. Without any
review pass, gate the long tail on { translated: 100 } alone. The class
semantics — why human is the default and what any admits — are in
Approver classes.
Trade-offs
- Review budget goes where the risk is. The contractual tier consumes
reviewer time proportional to its content; the long tail consumes none. A
market moves tiers by moving one line in the recipe, and the change is
reviewed in
git difflike any policy change. - The mid-tier percentage is a floor, not a selector.
reviewed: 80says at least 80% — it does not choose which 80%. Reviewers work the worklist (kapi status --review) in whatever order matters; the gate only measures the outcome. by: anyis an explicit trust decision. An autonomous AI approving its own translation is not the same evidence as a person's approval; the recipe records that you accepted the difference for that scope, rather than a default deciding it silently.- Source drift degrades tiers gracefully. An edited source string re-opens
its units in every locale; the contractual tier then waits for a person while
the machine tier recovers on the next
kapi uppass — see drift, watched end to end.
In CI
kapi check --ship evaluates every scope against its own tier and exits 3
when any gate is unmet, so one job enforces the whole strategy:
kapi check --ship # every market against its tier
kapi check --ship --locale de # scope to one market
kapi check --ship --json # structured result for a job summary
Two things the runner needs: the committed
state store (.kapi-state.json) in the checkout —
reviewed thresholds read their decisions from it — and nothing else, since
gate evaluation makes no provider calls. The workflow wiring is
Ship gates & CI.
Next
- The kapi loop — ladders, gates, approver classes, and the derived-state model.
- Ship gates & CI — the enforcement job on every PR.
- Review & approve — working the worklist the human tiers depend on.
- Project file — the full
ship_gates:schema.