All communication between services goes through NATS. There are three primary patterns: RPCs, operations, and events. Trellis also exposes feeds for caller-authorized live views.
RPCs (request/reply)
RPCs are synchronous operations. A caller sends a request and waits for a response.
- Subject convention:
rpc.v1.<LogicalName>(e.g.,rpc.v1.User.Find) - Callers use logical method names (e.g.,
"User.Find"), not raw subjects - Timeout-bounded (default 5 seconds)
- Both sides use
Result<T, E>; errors are values, not exceptions
Operations (async workflows)
Operations are caller-visible async workflows. Unlike RPCs, they accept a request, emit typed progress updates, and complete with a terminal result that the caller watches or waits for.
- Subject convention:
operations.v1.<Domain>.<Action>(e.g.,operations.v1.Billing.Refund) - Callers receive an
OperationRefand can callwatch()for a progress stream orwait()to block until completion - Operation state is durable; a caller reconnecting after a disconnect can resume watching an in-flight operation
- Callers see only their own operations; subject permissions prevent cross-caller access
- Starting an operation is controlled by its
callcapabilities. - The
observecapability gatesget,wait, andwatchaccess to an operation’s progress and terminal result. Whenobserveis omitted, Trellis defaults it to the same set ascall. - The
cancelcapability gates operation cancellation. - The
controlcapability gates named signals. - An explicit empty
observe,cancel, orcontrollist means no extra capability beyond authentication is required for that action.
Operations are distinct from jobs. An operation is the public async surface that a caller starts and observes. A job is service-private background work, invisible to callers.
Events (JetStream pub/sub)
Events announce state changes. Publishers fire and forget. Live listeners receive new messages through direct subscriptions, while durable service processing uses contract-declared eventConsumers groups that Trellis provisions as JetStream pull consumers with at-least-once delivery.
- Subject convention:
events.v1.<Domain>.<Action>[.<tokens>] - Handlers must be idempotent
- Subject tokens enable filtered subscriptions (e.g., subscribe to
events.v1.Document.Uploaded.pdf.*for only PDF uploads) - Services that need replay, redelivery, or independent durable cursors declare explicit
eventConsumersgroups in their contract. A plainuses.events.subscribegrant authorizes the event surface but does not create a durable cursor.
Events are event-type authorized. They are a good fit for service-to-service cache invalidation, projections, and background processing. They are not the right security boundary for per-user object ownership, such as “only events for devices this user owns”.
Feeds (authorized live views)
Feeds expose live streams that the owning service authorizes against the caller. A caller sends typed input to a feed request subject and receives typed frames on its authenticated reply inbox.
- Subject convention:
feeds.v1.<Domain>.<LiveView> - Callers use
trellis.feed("Device.Events").input(...).subscribe()rather than raw subjects - The service checks the authenticated caller, requested filter, and emitted frames
- Feed permissions grant request access to the feed, not raw
events.v1.*subscriptions
Use feeds for reactive UIs and other caller-visible streams where application authorization matters. The service may implement the feed by listening to domain events, polling local state, or combining multiple sources, but the feed remains the caller-visible authorization boundary.
Cross-contract dependencies (uses)
A contract declares which other contracts it depends on through uses:
uses: {
required: {
auth: auth.use({
events: { subscribe: ["Auth.Connections.Opened"] },
}),
devices: devices.use({
feeds: { subscribe: ["Device.Events"] },
}),
},
optional: {
core: core.use({
rpc: { call: ["Trellis.Surface.Status"] },
}),
},
}, Contracts must declare dependencies with grouped uses.required and uses.optional:
- required dependencies fail closed during runtime permission derivation when the referenced contract or surface is unknown
- authority planning can collect pending evidence before every required dependency is live. If a required dependency manifest is known but inactive, Trellis can use it to show the requested surfaces for review. If it is unknown or only stale incompatible inactive manifests remain, Trellis records the unresolved contract id as a blocker until that service uploads current evidence.
- optional dependencies participate in contract identity but grant no authority when the target contract or surface is missing
- if the same alias appears in both groups, the required entry wins
If a dependency is not declared in uses, Trellis will not grant that cross-contract access when the contract boundary is added to deployment authority.
Approved service boundaries only become runtime-active when the full required dependency closure resolves from known manifests and fits deployment authority; until then service bootstrap waits instead of receiving NATS credentials. If a service instance presents a different digest for the same contract id, Trellis checks same-lineage compatibility. Incompatible same-contract replacement is an authority migration: strict deployments record a pending plan, while mutable-dev deployments record and auto-accept that plan for unreleased local iteration.
Surface availability
Authorization and availability are separate. A caller may be authorized for a surface even when no enabled service instance is currently connected to implement it. Apps that need to explain this distinction can call the advisory Trellis.Surface.Status RPC after declaring it in uses:
const status = await client.rpc.trellis.surfaceStatus({
contractId: "orders-service@v1",
kind: "feed",
surface: "Orders.Live",
action: "subscribe",
}).orThrow(); The status response distinguishes unknown_contract, unknown_surface, unauthorized, unavailable, and available. It does not grant permissions; the caller still needs the normal contract-derived capabilities for the actual RPC, operation, event, or feed.