Skip to main content

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

TierPolicyGate
ContractualA person approves every unit{ translated: 100, reviewed: { pct: 100, by: human } }
Mid-tierEverything translated, the important 80% human-reviewed{ translated: 100, reviewed: 80 }
Long tailEverything 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:

kapi.yaml
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 diff like any policy change.
  • The mid-tier percentage is a floor, not a selector. reviewed: 80 says 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: any is 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 up pass — 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