Review
Review is the promote step of catching content up. A run drafts and checks, and parks what a machine cannot decide; a person promotes it here. Every decision is recorded against the workspace, feeds the context graph, and decides what a locale can ship as.
The narration uses terminology that has since been retired. The interface and the behaviour shown are unchanged.
The review session
A project has one review surface: the review session. It is a dedicated page rather than a mode of the editor, and every entry point that says "review", whether a run's hand-off, a dashboard card, or a file's own review link, opens it.
The session walks every pending block across the project's items and locales in one keyboard-first flow. Each step shows the source beside the draft, the checks that fired on it, the voice findings, and the terms in force at the block's point. A collection can be carried into the session from the project overview, so a reviewer who owns the help centre reviews only the help centre; item and locale filters narrow the queue further.
For each block:
- Approve marks the target reviewed for that locale. It needs the
reviewpermission for that language, which the built-in Reviewer template carries; a translator can edit and reject, but not approve. - Sign off marks it signed off, the rung above reviewed, for a ship gate
that asks for more than a review. It needs the same
reviewpermission, and it can be given straight from a translated target or after an approval. - Reject sends it back to draft for another pass.
- Edit fixes the draft in place. A fix is a correction made at a point, and corrections are what the context learns from (see Voice and corrections).
Review status is tracked per locale. A block's status is a property of each target, so approving the French target never changes the German one, and a reviewer scoped to French can approve only French.
Correct the context from the session
Some of what a reviewer notices is about the rules rather than the draft. Three actions in the session act on the context instead of the block:
- Mark a source term. Records that a word in the source is a term, with the
status it should carry. Setting a governed status such as
forbiddenorpreferredopens a change-set rather than applying directly, so someone other than the author approves it. - Propose a source change. A fix that belongs in the source, not in one target, is routed to the source owner as a proposal. It invalidates every locale of that block once accepted, and the proposal shows that reach before anyone accepts it.
- Suggest a voice rule. A recurring fix becomes a candidate rule for the voice profile in force at the point, reviewed on the Context hub's Voice section.
Bulk approval
A workspace with one reviewer and many drafts does not need to click through every block. Approve everything passing approves every pending block whose checks report no error-severity finding and whose voice score meets the profile's compliance bar. Blocks that fail a check or fall below the bar are left pending for a person. The same predicate decides a locale's ship state below, so what bulk approval accepts is exactly what was already shippable on machine review. The response names each bar that left a target pending, including the targets the caller wrote themselves in a workspace that blocks self-approval.
Machine authorship
A draft a run produced carries no human author, because a run writes it with nobody acting. Separation of duties (see Members and roles) stops a person from approving a translation they wrote by hand, and leaves the one person in a small workspace free to approve what the machine wrote. It applies to every way of promoting a target: block by block, over a selection, through Approve everything passing, to a sign-off, which is judged on the author of the wording exactly as an approval is, and to decisions pushed from a working copy.
A decision that arrives by kapi push is held to the same
review permission and the same separation-of-duties policy as one made here, and
the push reports the approvals and sign-offs the workspace did not accept. The
content lands either way, as a translation awaiting review.
Taking a sign-off back is review-level work here, and it is by push as well. A push that lowers a signed-off target without changing its translation lands only from someone who holds review permission for that language; otherwise the sign-off stands and the push reports the demotion it did not apply. An edited translation drops to awaiting review on either route, because a sign-off judged the wording it was given.
A rejection that arrives by push applies only to the translation it names. When that translation has since been replaced here, the unit keeps its rung and its record, and the push reports the rejection it did not apply.
What an approval leaves behind
Two records, written for every approval whichever route made it:
- A decision, in the project's ledger, naming who decided, when, and the
hash of the exact wording they blessed. That record travels to a working copy
on
kapi pull, and it is what promotes the wording into the workspace content memory. - An audit entry, in the workspace audit log, naming the actor, the block, the locale, and the rungs the target moved between. A rejection, a sign-off and a withdrawn approval are recorded the same way, each under its own name. Approve everything passing records the pass and its counts per language, because the pass is what the person decided; which blocks it took is in the ledger.
The reviewer is summoned
When a run parks work, the reviewer is told: a review task opens, an email goes out, and the app shows a banner and a badge sized to what is actually pending. The summons names the count and the locales, not the whole project.
Approval continues the loop
Approving the last pending block of a locale closes its review task. When that empties the project's whole review queue, a completing run starts on its own and delivers the approved content, so review never ends in a dead end that needs another click.
A run parks what it cannot decide; the session promotes it; the last approval starts the run that delivers.
Source review: the first worklist
Target review is the second gate. The loop settles the source before it
translates (the loop is source-first), so a run has an upstream
worklist: source review. When a run finds source that has not cleared the
project's source_gate, it holds the fan-out for that content and routes it to
a person instead of paying to translate an unsettled source into every
language.
When a run holds on source:
- The run parks with the reason
source_not_readyand records how many blocks are blocked on source. In the Runs view this reads as parked, N blocks need source review; the run still exits without error. - The loop opens a source review task for a member with source-edit permission. It is the same task queue as translate, review and the fix-up tasks, and appears on the task board and in My Tasks.
A source reviewer works the same content on the source side: terms, do-not-
translate marks, placeholders, and context, before the fan-out. Clearing the
task lifts the affected source to the gate; the next run translates the
now-approved content, memory first. A project that wants raw fan-out sets
source_gate: none.
The review inbox
The workspace-level Review inbox rolls up every project awaiting review, most-pending first, each linking into its own session. It reads the same project-scoped standing the dashboard shows, so pooled review and review assigned to a person are both visible, not only what is assigned to you.
Ship states
Review decides what a locale can ship as. Bowrain derives one ship state per locale, per project and per collection, from what the work left behind:
| State | Meaning |
|---|---|
governed | Every block translated, no failing check, nothing stale, a terminology result for every block, every target carrying a review decision, and terms governing the locale. |
approved | The same, in a locale no terms govern: a person approved every target, and only the checks stand behind the approval. |
ai_shippable | Every block translated, no failing check, and a terminology result wherever terms govern, but review is incomplete: shippable on machine review only. |
pending | Anything less: partial coverage, a failing check, a stale decision, a turned-down translation, a block terms govern with no terminology result, or nothing to ship yet. |
The rule is the same everywhere it is read. A locale is pending when any
block is untranslated, has an error-severity finding, carries a decision made
against content that has since changed, or holds a translation a reviewer
turned down. A block in a locale terms govern with no terminology result, such
as a target made only of inline codes, also holds the locale at pending. A
block whose target uses a forbidden term counts as a failing check.
Otherwise the locale is ai_shippable while some targets await approval. With
every target approved, it is governed where terms govern the locale and
approved where none do. A voice score does not change the state: a block
under the voice profile's bar is held out of bulk approval instead.
The state appears on the project dashboard, per collection, in the Runs view's
per-locale summary, and in the workspace loop rollup. It is also published at
the delivery edge: GET /api/v1/projects/:id/ship.json serves a per-locale
manifest a site's language picker can read to hide locales that are not
shippable and to badge those shipped on machine review. The entry for a locale
no terms govern adds "not_governed": ["terms"]. An approved locale reads as
shippable and verified, and that field is how a build tells it apart from a
governed one.
The same manifest is what kapi status --ship --emit ship.json writes at build
time, so a site built from a checkout and a site reading the server agree.
Set the bar where the content sits. A legal notice waits for a person
(governed); a help article may ship on checks (ai_shippable). The project's
ship_gate and per-scope ship_gates in the recipe carry that decision (see
Project model).
Review in place
Strings read differently in a component than in a table. A collection can declare where its strings can be read in place:
collections:
- name: web-ui
channel: app
preview:
kind: storybook
url: https://storybook.example.com
content:
- path: "i18n/en/*.json"
format: json
preview names a host and how to find a view in it. A storybook host
publishes an index that maps components to stories, so an item resolves to the
stories of the components its blocks name. The offer to review in place appears
on the page a reviewer opens a file from; the reviewer's own translations are
pushed into the running page as they review. A collection with no preview
block offers no in-place reading. The block is declared per collection because
a repository publishes one host per surface it ships.
Checks in review
Checks run in the loop, and their findings travel with the block into the session: each finding carries a severity, a type, and a message, and a block with an error-severity finding cannot be approved in bulk. A reviewer can re-run the checks over a file from the session after editing.
Voice review
Block review and voice review are different decisions. Promoting or rejecting a candidate rule, or a rule change bundled into a change-set, happens on the Context hub's Voice section, by someone other than the rule's author.
Related
- Keeping content caught up: where a run parks, and why
- Voice and corrections: how a correction becomes a rule
- Members and roles: who may review which point
- The loop in CI: the ship gate, in a pipeline