Skip to content

Translation rendering

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.

Modeled constructGenerated 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 captureA reactor per capture, subscribed to the foreign origin’s inbox, with a handler per source contract
Capture appendAn EventForEventSourceId envelope containing the local private fact; the capture key supplies its destination
Outbound event-target projectionA 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.

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.

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)

Section titled “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.

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.

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.

DiagnosticRefused shape
STAGE-ESM-024Unsupported 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-032Public contracts outside the supported translation subset; unsupported event-target projections and event-target reducers
STAGE-ESM-033Foreign contracts outside the supported subset, including a capture spanning several origin stores
STAGE-ESM-011Translate specifications outside the isolated handler-fixture subset described below
STAGE-ESM-052v32 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 for the admission table and Event reaction rendering for Automation reactors.