---
title: Translation rendering
editUrl: https://github.com/Cratis/Stage/edit/main/Documentation/reference/translation-rendering.md
description: Cratis Stage renders inbound captures and outbound publications as Chronicle reactors, preserving typed keys, capture state and explicit refusal limits.
lastUpdated: 2026-11-05T00:00:00.000Z
---


Stage renders supported `Translate` slices as Chronicle reactors. Inbound translation turns foreign
facts into local private facts; outbound translation publishes a public contract to the outbox.
This primarily describes generated applications. The semantic-run subsection below describes the
specification executor's corresponding foreign-capture subset.

## Contracts and routing

| Modeled construct | Generated backend |
| --- | --- |
| Private event | `[EventType]`; private remains the default |
| Local public event participating in a supported translation | `[EventType]` and `[Public]` |
| Foreign event participating in a supported translation | `[EventType]` and `[EventStore("<origin>")]`; the origin string is retained verbatim |
| Inbound events-source capture | A reactor per capture, subscribed to the foreign origin's inbox, with a handler per source contract |
| Capture append | An `EventForEventSourceId` envelope containing the local private fact; the capture key supplies its destination |
| Outbound event-target projection | A reactor per projection, observing private events and appending the public target to the outbox |

A capture's reactor uses its origin store to select `inbox-<origin>`. It never produces the foreign
contract or appends that contract to the local event log. Chronicle's event result handler appends the
returned local envelopes, including their contract and append tags. Publication writes to the outbox,
not the local event log.

## Inbound subset

A supported capture has an events source, one foreign origin, a key on every source contract, and
local private append targets. Field mappings read the key's capture state, which overlays every source
contract seen for that key, so a mapped field must exist on at least one source contract and have the
target property's type; Stage does not coerce field values. Literal mappings retain their modeled types.
Capture `map`, children and nested scopes remain refused. A capture with child appends is refused
as a whole; Stage does not emit only its top-level appends.

### Typed capture keys (ESM v32)

Stage uses Screenplay 5.3.1 and admits exact ESM v1.0–v34.0 and v36.0 language/semantic pairs, including v31 and v32, not v35 or arbitrary minor versions.
A v32 capture preserves the key's declared event-source type rather than treating it as text.
Required scalar `Text`, `Uuid` and `WholeNumber` keys, and non-enumerated concepts over those
primitives, render when every source contract has the same key type and every append declares that
same identity type. A concept used only as a capture identity renders as `EventSourceId<T>` too;
no command or read-model key is needed to establish its identity role.

Optional, collection, composite, enumerated or other primitive keys, inconsistent source types,
and append types that would lose that identity are refused with `STAGE-ESM-052`. In particular,
Stage will not convert a typed v32 key to text to make an inferred text append fit. Existing v9
text-key translations retain their behavior. The specification executor retains the same typed
identity in captured facts and typed givens; the live host still refuses foreign inbox contracts
with `STAGE-ESM-033` rather than claiming transport support.

### Semantic runs with source-keyed foreign captures (ESM v33)

The specification executor admits `key ... is event source` for otherwise supported captures whose
source contracts are all foreign. For unrouted given events and `when append` fixtures without a
source, it takes the scalar key as the foreign source: text is preserved, and numeric keys use
invariant text. Captured local facts retain the key's declared identity type. An explicit source
must match the key's invariant text exactly, with ordinal comparison; a mismatch is a contract
rejection before capture state changes or local effects are appended. Missing and null keys retain
the reference contract rejection.

An explicit fixture route disables source inference, not delivery validation. Both given history
and the delivered event are checked against their actual source. Capture history stays independent
per key, and identical redelivery produces no additional local fact.

Inbox deliveries already carry an explicit text source; they are never inferred from their payload.
In-process inbox/runtime specs cover matching sources, mismatch rejection and identical redelivery.
Subscription transport, checkpoint and service-level differential proofs still require CI; these
fixture and runtime checks do not establish them. The model validator rejects local-event
source-keyed captures before Stage admission.
Existing translation-shape and origin refusals remain in force. This is not admission of every v33
construct.

### Capture state

The generated inbox reactor decides every guard from the key's previous capture state, the same state
the reference evaluator keeps (Screenplay decision 0080):

- The first record of a key is `added`; `changed` and transition guards never hold for it.
- A record whose overlaid state equals the previous state appends nothing, guarded or not.
- `any changed`, `all changed` and `from ... to ...` compare the previous and current state of the key.
- `when removed` never holds for a capture over events, so the target is not appended.

The reactor rebuilds the previous state from the inbox it observes: it reads the captured foreign
event types from that inbox, keeps the records before the delivered event's sequence number whose
capture key matches, and overlays them in order. The inbox is append-only and the read is bounded by
the delivered position, so a retry, a crash redelivery or a replay of the same event computes the same
state and the same effects.

Two alternatives were rejected. Private output history (the local facts already appended for the key)
is not capture state: another producer's fact suppresses an `added`, and a capture with no output for a
record cannot remember that record. A generated read model of the last-seen record per key is
eventually consistent with the reactor that reads it, so a delivery can observe a model that has not
yet seen the previous record, and replay rebuilds it independently of the reactor. Chronicle's client
capture builder reads external systems, not inbox events, so it does not fit an events source.

Without `key is event source`, the cost is one inbox read per delivery that grows with the inbox
length: matching keys may occur in different foreign streams. With this marker (ESM v33), the reactor
first checks that the key's invariant text equals the delivered event source, using an ordinal
comparison. An empty or mismatched key is rejected before inbox access or effects. It then reads only
that source and the captured event types, so history cost grows with that source's history rather
than the whole inbox. Both paths retain key matching and the strict sequence cutoff. A foreign
partition's latest snapshot is unsafe: it can include changes after the delivered position and
would change the state seen by retries or redeliveries.

The reactor remains `[OnceOnly]`, so a full observer replay does not re-append. Each local effect carries a Chronicle
idempotency key derived from the reactor delivery, capture id and modeled append position. Retrying
that delivery after a partial append, a lost acknowledgment or an append followed by a lost checkpoint
uses the same keys: already-appended effects succeed without appending duplicates. Different effects
and different deliveries have different keys, even when an earlier append's guard produces no effect.

This requires Chronicle client and kernel **19.99.1 or later** with idempotent appends. The generated
application pins that kernel image. A kernel without the capability is refused; Stage does not fall
back to unkeyed appends. This protects capture effects in the destination event log, not arbitrary
external side effects. Publication, capture state reconstruction and replay exclusion are unchanged.

Guards whose result Stage cannot reproduce exactly are refused by name with `STAGE-ESM-024`:
expression guards, guard paths with `.` traversal, guards over list or record fields, and transitions
over values whose text form is not text, a number or a boolean. Stage does not replace a refused guard
with an unconditional append.

## Outbound subset

A supported event-target projection publishes a local public event from private source events. Its scope
is per event source, its key is the event-source identity, and its top-level mappings use `set` with a
source property, literal or event-source identity. `every`, joins, child scopes, composite or custom keys,
`clear`, non-set operations and event-target reducers remain refused.

Each source event must supply every required target property, considering all `from` clauses for that
event together. A publication that needs another source event to supply a required property is refused
with `STAGE-ESM-032`, naming the projection. Stage does not reconstruct missing required values from
private-event history or assume an earlier public fact exists. Optional properties may retain their
previous public value, or remain null before the first publication.

Before publishing, the reactor reads the last public fact of the target type for the same event source
from the outbox. It appends only when the mapped public record differs. Identical consecutive records
are suppressed across reactor reactivation; another key remains independent, and a change back to an
older value is published again. This uses durable outbox history, not process-local state. An unsuccessful
outbox append fails the handler. This comparison does not make the private-log append and publication
one transaction or provide concurrent exactly-once delivery.

## Diagnostics and specifications

| Diagnostic | Refused shape |
| --- | --- |
| `STAGE-ESM-024` | Unsupported Translate shape or capture guard; external-system captures, guards Stage cannot reproduce exactly, mapped fields of another type, and mixed translation/command or read surfaces |
| `STAGE-ESM-032` | Public contracts outside the supported translation subset; unsupported event-target projections and event-target reducers |
| `STAGE-ESM-033` | Foreign contracts outside the supported subset, including a capture spanning several origin stores |
| `STAGE-ESM-011` | Translate specifications outside the isolated handler-fixture subset described below |
| `STAGE-ESM-052` | v32 capture keys without a faithful scalar identity conversion, or source/append identity types that disagree |

A direct `when append` can render a `Specification` that constructs and calls the generated
reactor handler. Inbound fixtures place given foreign records in the inbox before the delivered
event, so first observation, unchanged suppression and changed records are exercised through the
reactor's own state read, and assert the captured `EventForEventSourceId` sequence: count, type,
payload, and source id. A fixture registers only the contracts its selected handler resolves.
Outbound fixtures provide explicitly keyed private-log history and assert the actual public
objects appended to the outbox, including their payloads and source ids. Public outbox history
is not yet supported in generated specifications. Explicit `then none`
is a zero-output assertion, not a skipped check.

A given-only Translate specification can also assert exactly one read-model presence or absence
over explicitly sourced, unrouted private events declared by that slice. It executes the generated
projection and read-model assertions without inventing an action or seeding expected state. The
read model may belong to a separate StateView in the same application. This bounded projection
fixture does not settle foreign Given events or implicitly execute a capture. Other fixtures,
routes, multiple read-model expectations and mixed event, query or invocation outcomes remain refused.

These are isolated handler tests, not running Chronicle subscriptions. Capture specifications pass
Chronicle delivery identity to the generated handler. They do not prove inbox transport,
checkpointing, or replay delivery. Input fan-out, downstream captures/publications/reactions,
append-time constraints, history that triggers settlement, and authored recovery/redelivery
specifications remain explicit `STAGE-ESM-011` refusals. Expected outcomes must belong to the selected handler;
a public outcome from another slice is not approximated as a local append. Unordered outcomes,
caller/clock/error/return assertions, and values outside the existing typed event-fixture subset
are also refused.

When all errors are specification-execution gaps classified by `SemanticRefusalScope`
(`STAGE-ESM-011`, `STAGE-ESM-024` or `STAGE-ESM-051` on specifications), Stage omits those
specifications, reports their identities in `Refusals`, and changes their diagnostics to warnings.
These exclusions do not require the render-around opt-in and do not make the plan refused.
Application-contract or safety errors remain blocking refusal diagnostics; render-around does not
permit dropping those contracts.

See [Build a renderer target](/stage/guides/build-renderer-target/) for the admission table and
[Event reaction rendering](/stage/reference/reaction-rendering/) for Automation reactors.
