Skip to main content

kapi push

Send local changes to Bowrain Server. kapi push is pure transport: it moves project state, the content, the terms edits, the voice binding, and never drafts anything. With the project's bowrain.converge policy at its default on-push, the server starts a run of its own when the push lands.

Usage

kapi push [paths...] [flags]

Examples

# Push all local changes to the server
kapi push

# Push specific files
kapi push src/locales/en/

# Show what would be pushed without uploading
kapi push --dry-run

# Re-upload every block, ignoring what the server already holds
kapi push --force

# Push only local terms edits (no content, no automations)
kapi push --concepts

# Example output:
# Pushed 47 blocks (scanned 12 files)

Options

FlagDescriptionDefault
--forceRe-upload everything, even unchanged blocksfalse
--dry-runShow what would be uploaded without sendingfalse
--streamTarget stream (default: auto-detected from git/CI)$auto
--conceptsSync only local terms edits to the workspace (direct edits + governed change-set); no content transport, no automationsfalse

The protocol

A push declares its tree and uploads only what the server lacks. The same protocol serves kapi push, kapi up, and the server-side connectors, and kapi pull reads the same tree back.

  1. Scan. kapi reads the recipe's collections:, extracts every block, and computes each item's content hash.
  2. Declare the tree. The client sends the server its whole tree: every tracked item, its path, its collection and the point it sits at, and its hash. The server diffs that against the tree it holds and answers with the items it needs.
  3. Upload. Only the items the server asked for travel, in chunks, to presigned upload URLs (or through the server when the blob store is not reachable from the client). Media assets referenced by the content travel the same way.
  4. Commit. The client commits a manifest naming what it uploaded. The server validates it, answers 202, and a worker applies the push: content is stored, and the output's ingest field says whether the push was applied or queued when the command returned.

Because the tree is declared whole, the server reconciles what a diff alone could not:

  • Deletions. A path present in the last tree and absent from this one is retired on the server, on this stream.
  • Renames. An item with the same content at a new path is a rename, so its targets, decisions and history move with it instead of starting over.
  • Collections and points. Each item arrives with the collection and the coordinates the recipe resolved for it; the workspace's profile cards are derived from that declaration. The server refuses content at a point whose axis no recipe declares.

.kapi/work/cache/sync-cache.json records the last tree the client declared and the ref it was committed as. It is gitignored and safe to delete: the next push declares the tree again and the server tells it what it lacks.

Decisions the server accepts

Push carries the project's committed decision record with the content it judges. The server is authoritative for review, so an approval or a sign-off arriving this way is held to the same rules as one made in the web app: you need review permission for that language in that project, and the workspace's separation of duties applies with you as the decider.

The content lands either way. A verdict the server does not accept is reported per language, and the unit stays a translation awaiting review:

Pushed 47 blocks (12 uploaded), 512 words (scanned 12 files)
2 approvals not accepted for fr-FR: no review permission
1 sign-off not accepted for de-DE: separation of duties
3 local record(s) now match the platform; they will not be sent again

The last line is the project's record following the server's answer, so the same refused verdicts are not sent again on every push.

Taking a sign-off back is held to the same permission. A push that lowers a target the server holds at signed-off, with the translation and the source it was signed off for unchanged, withdraws that sign-off, and the withdrawal lands only when you hold review permission for that language. Otherwise the sign-off stands, the push reports the demotion it did not apply, and the project's record is restored to what the server holds:

1 demotion not accepted for nb-NO: withdrawing a sign-off needs review permission
1 local record(s) now match the platform; they will not be sent again

A rejection applies to the translation it names. When the server has replaced that translation since your working copy last pulled, the rejection changes nothing there. The unit keeps its rung and its record, the push reports the rejection it did not apply, and kapi replaces the project's record with the one the server holds. Pull before rejecting, so that the rejection names the translation on the server.

1 demotion not accepted for nb: the rejection names a translation the platform no longer holds
1 local record(s) now match the platform; they will not be sent again

An edited translation is not a withdrawal: it lands at translated, as an edit made in the web app does. Taking back an approval at reviewed is ordinary translation work and needs no review permission.

kapi up prints the same lines after its push phase, on a connected project and after kapi up --local. With --json it writes them as one governance record in its NDJSON stream, with the field names kapi push --json uses:

{"type":"governance","verdicts_refused":[{"locale":"nb","kind":"demotion","reason":"the rejection names a translation the platform no longer holds","count":1}],"verdicts_retired":1}

Terms edits

When the project is claimed into a workspace and a baseline was pulled (see kapi pull), push also reconciles local terms edits in the bound terms store against that baseline. Ordinary edits, such as definitions, notes, proposed terms and non-governed relations, apply directly through the concept endpoints. Governed edits, such as a term set to forbidden or preferred, an un-forbidding, a replaced_by relation, or a concept delete, are bundled into a single submitted change-set proposal for review, the same separation of duties the Context hub enforces. Push reports what applied directly versus what was proposed, with a link to review the change-set.

The voice binding

When the recipe binds a voice profile (defaults.voice, conventionally .kapi/voice.yaml, or a profile's own voice:), push carries it into the workspace Context hub, matched by profile name: created on first push, a no-op when the content is unchanged, and otherwise applied as a new profile version. The previous server-side state is archived in the version history, never overwritten, and vocabulary rules the server promoted from corrections are preserved. The output reports whether the voice was carried, skipped, or would be pushed on a dry run.

Exit codes

  • 0: success (changes pushed or already up to date)
  • 1: error (server rejected, network error, and so on)
  • kapi pull: fetch the server's changes into the tree
  • kapi status: coverage, ship standing, and what is pending
  • kapi diff: the changed blocks, per file
  • kapi up: push, run on the server, pull

When to use

Push to Bowrain Server to:

  • Share your work with your team, and let the server catch the project up
  • Send terms edits: ordinary edits land directly; governed edits travel up as a change-set proposal
  • Carry the voice profile the recipe binds
  • Integrate with CI/CD pipelines, where kapi up is usually the better verb because it also waits for the run and pulls the results

Best practices

  1. Run kapi status before pushing to see what changed
  2. Pull first if working with a team, so the tree you declare is current
  3. Use --dry-run when unsure about what will be uploaded