This is the multi-page printable view of this section. Click here to print.
Reference
- 1: Glossary
- 2: API Documentation
- 3: Tools
- 3.1: itara-cli
- 4: Wiring Configuration
- 5: Metadata Format
1 - Glossary
Clear, shared vocabulary matters: if people can talk about the project fluently, the ideas travel. If the words are missing or inconsistent, the point gets lost. This is the reference for that vocabulary.
Agent
See Wiring Agent.
Authentication
Verifying that a caller’s claimed identity (see Identity) is genuine, at the topology layer.
Authentication is connection-level and pluggable: an implementation receives whatever identity signal is available for a connection and either produces a verified identity or rejects the call outright. Used without qualification, this term refers to authenticating a calling node, not an end user or account.
Authorization
Deciding whether an authenticated caller is permitted to invoke a specific operation — a topology-layer concern about which node may call what.
Authorization is connection-level and pluggable: an implementation reads the identity authentication produced, together with the operation being invoked, and decides allow or deny. Used without qualification, this term refers to node-to-node permission, not end-user permission.
Colocation
Placing two or more components in the same process so that calls between them are direct, in-memory, and zero-overhead.
When components are colocated, the agent resolves a direct proxy at startup. At call time, the proxy fires the four observability events and calls the implementation directly — no serialization, no network hop, no transport overhead of any kind. The component code is identical regardless of whether the call is colocated or distributed.
Colocation is declared in the wiring configuration by using the direct
transport type on a connection. It is a topology decision, not a code
decision.
Component
A unit of business logic with a defined contract and a concrete implementation. It knows nothing about how it is called.
A component consists of three things: a contract (an interface declaring what it does), an implementation (a class or struct satisfying that contract), and an activator (a factory method that wires its dependencies at startup). The component code itself has no knowledge of transport, topology, or deployment. It does not know whether it is being called locally or remotely, by one caller or many, over HTTP or Kafka or a direct in-process call. That knowledge lives in the wiring configuration.
Components are the fixed units of the system. They do not split or merge — only their placement in a topology changes.
Component Scope
Defines the boundary of what a running component instance can reach, and carries what’s needed to enforce it — established and maintained exclusively by the agent.
A component’s scope determines which other components’ proxies are actually available to it, prepared by the agent from the wiring configuration. It also carries whatever the agent needs to enforce that boundary — at minimum, which node in the topology graph is currently in control. This is not identity in the authentication sense; it’s simply the agent’s own record of where in the graph execution currently is.
Colocation does not imply shared scope. Two components sharing a process for a zero-overhead direct connection (see Colocation) remain distinct — each has its own scope, and neither can assume the other’s.
Connection
A declared relationship between two nodes, specifying that one calls the other and how.
A connection is directed: it has a caller node and a callee node. It declares the transport type (direct, HTTP, Kafka, or any other supported transport) and any additional configuration the transport requires. All failure semantics — retry behaviour, timeouts, circuit breaking — are connection-level configuration, not component-level code.
Contract
The interface a component exposes to its callers. It says what the component does, not how.
A contract declares operations — inputs, outputs, and errors. It contains no reference to transport mechanism, serialization format, or deployment infrastructure. Callers depend only on the contract. The implementation behind it is invisible to them, and so is the topology.
Deployment Group
The set of nodes that run in the same process because they are connected by direct calls.
Deployment groups are derived automatically by the wiring agent and the CLI from the connection graph. A deployment group is not declared — it is a consequence of topology decisions. Nodes connected by direct calls must be colocated; the deployment group is the set of all nodes that must be together as a result.
A single component may be referenced by multiple nodes — in different deployment groups, with different topology roles. The node is the deployment identity; the component is the business logic behind it. Deployment groups are about nodes, not components.
itara inspect shows the derived deployment groups for any wiring
configuration. They are the practical answer to the question: what goes
together, and what runs separately?
Distribution
Placing components in separate processes so that calls between them travel over a transport.
When components are distributed, calls cross a process boundary. The transport — HTTP, Kafka, or any other supported mechanism — carries the call. The component code does not change. The transport cost becomes visible in the traces as the gap between the caller’s and callee’s observability spans.
Distribution is the default assumption of traditional microservice architectures. Itara makes it an explicit, reversible, config-level decision rather than an architectural commitment baked into the code.
Failure Semantics
How a connection behaves when things go wrong — declared in configuration, invisible to component code.
Failure semantics are connection-level configuration. A single pluggable implementation owns the complete failure strategy for a connection — how it handles timeouts, retries, circuit breaking, or any other failure-related concern. The component code is unaware of any of it; it simply makes calls. Idempotency is declared in the API artifact metadata and read by the proxy at startup, so methods that cannot be safely repeated are protected without any explicit handling in the business logic.
Identity
The claimed or verified origin of a call — a property of the node, not the deployment unit it happens to run in.
Colocated nodes do not automatically share an identity just because they share a process (see Colocation); different identity rules can still apply to each.
Message Format
A structural encoding scheme whose code generation tooling produces a contract’s types, declared once for the whole contract.
Some contracts define their parameter and return types by hand, in the implementation language, as ordinary classes or structs. Others declare a message format — such as Protocol Buffers — and generate those types from a schema instead. Either way, the contract is still just an interface: it says what a component does, not how it is transmitted. A message format constrains the shape of the data; it says nothing about serialization, transport, or topology.
A contract commits to a message format entirely or not at all — an API artifact does not mix hand-written and generated types across its methods. Given an API artifact, one fact answers whether its types are plain or generated, for every method it declares.
Message format is independent of serializer choice. A serializer converts a contract’s types to and from bytes; a message format determines what those types are. The two vary independently — which serializers can handle a given message format is a separate, per-serializer question.
Node
A named deployment position in the topology. It says where a component runs.
A node has an identifier and references a component. Multiple nodes may reference the same component — they are independent positions in the topology graph, not copies of each other. How many instances of a node run at any time is the orchestrator’s concern, not Itara’s.
Observability Events
Four events that fire precisely at the boundary between the business layer and the topology layer, on every call, regardless of transport.
Every call between components — direct or remote, synchronous or asynchronous
— emits four events: CALL_SENT and RETURN_RECEIVED on the caller side,
CALL_RECEIVED and RETURN_SENT on the callee side.
The placement is deliberate. CALL_SENT fires when the business layer hands
a call to the topology layer. CALL_RECEIVED fires when the topology layer
hands it to the callee’s business layer. RETURN_SENT and RETURN_RECEIVED
are the mirror on the way back. Everything that happens between those events
— serialization, network transit, deserialization — is topology overhead,
directly measurable as the gap between the caller span and the callee span.
These events are structural and non-optional: they are fired by the proxy and dispatcher at the layer boundary, not by the transport. A transport that skips them cannot exist — it never touches the event model.
Placement
The topology decision of where a component runs and how it connects to others.
Placement is what changes when you move a component from one deployment group to another, or change its transport. The component itself does not change — only its position in the topology and the communication path to and from it. Placement decisions are made in the wiring configuration.
Proxy
The object a caller holds instead of the real implementation. It is transparent to the caller.
A proxy satisfies a component contract. The caller cannot tell the difference between a proxy and the real implementation — the interface is identical. The proxy is what makes topology transparent to component code: it handles serialization, transport, context propagation, and observability events without the caller knowing any of it is happening.
For direct connections, the proxy fires the four observability events and calls the implementation directly. No serialization, no network. The proxy is still present — it is what makes observability structural and non-optional.
The Three Layers
Business logic, contracts, and topology are three distinct concerns. Itara keeps them separate.
Every Itara system has three layers:
- Business layer — the implementation: what the system actually does. Written by developers. Has no knowledge of transport, topology, or infrastructure.
- Contract layer — the interfaces: what each component exposes to its callers. Defines operations, inputs, outputs, and errors. Contains no transport or topology knowledge.
- Topology layer — the wiring: which nodes exist, how they connect, what transport they use, how failures are handled. Lives entirely in the wiring configuration and the proxies and listeners the wiring agent constructs from it. The separation is the point. Business logic does not leak into topology. Topology does not leak into business logic. Changing one does not require touching the other.
Topology
The structure of a system: which components exist, how they connect, and where they run.
Topology is the concern Itara makes explicit. In a traditional distributed system, topology is implicit — encoded across HTTP clients, retry policies, service discovery calls, and timeout configurations scattered through the codebase. With Itara, topology is declared in the wiring configuration, the single source of truth for the entire system structure.
Topology is not a deployment detail. It is an architectural artifact — declared, validated before deployment, and changeable without touching business code.
Transport
A pluggable mechanism for carrying calls between distributed components.
A transport provides two things: an outbound proxy for the caller side, and an inbound listener for the callee side. The component code never sees the transport. Transports are declared in the wiring configuration and loaded by the agent at startup. Transports can be provided as plugin artifacts.
Wiring Agent
The part of Itara that reads the wiring configuration, wires all components together before the application starts, and then steps aside.
The wiring agent runs before application code. It reads the wiring configuration, loads transport and serializer plugins, constructs proxies and listeners for every declared connection, and registers everything in the component registry. By the time the first application thread runs, the topology is fully wired and all decisions are made. The wiring agent is not present in the call path at runtime — it does not intercept calls, route dynamically, or make decisions on the fly. The proxies it created do the work; the wiring agent is done.
In Java, the wiring agent is a JVM premain agent. In Rust, it is an
itara_init() library call. The mechanism is language-specific; the
behaviour is defined by the specification.
Wiring Configuration
The single file that describes the complete topology of a system.
The wiring configuration declares which nodes exist, which components they run, and how they connect. It is the authoritative source of topology information. The agent reads it at startup. The CLI validates it before deployment. Nothing about how components communicate exists anywhere else.
Changing the wiring configuration changes the topology. No code changes.
2 - API Documentation
Full class and method documentation is generated directly from source and hosted by each language ecosystem’s own standard tooling — not duplicated here, so it never drifts out of sync with what’s actually published.
Java
itara-core and itara-agent on javadoc.io
Rust
Rust crates are not yet published to crates.io. Once they are, generated documentation will be available automatically at docs.rs under the published crate name.
3 - Tools
3.1 - itara-cli
itara is the command-line tool for working with an Itara
wiring configuration — inspecting
a topology at a glance, and validating it for logical mistakes before it’s
deployed. It’s implemented once, in Rust, and operates purely on the file
formats — the wiring config YAML and .itara metadata TOML files — so it
works identically regardless of which language (Java or Rust) actually
produced the artifacts being wired together; there’s simply nothing
language-specific for it to know about.
itara <COMMAND> [OPTIONS]
Two subcommands are available:
| Command | Purpose |
|---|---|
itara inspect | Print a human-readable summary of the topology described by a wiring config. |
itara verify | Validate the logical correctness of a wiring config, optionally cross-checked against a directory of .itara metadata files. |
Both commands take the path to a wiring config file as their first positional argument, parse it with the same loader described in the wiring configuration doc (including environment variable substitution and YAML merge-key resolution), and exit non-zero if the file can’t be read or parsed at all — before any command-specific logic runs.
Installation / building
There’s no published binary yet — build it from the itara-cli crate in
the source repository:
cargo build --release -p itara-cli
# binary at target/release/itara
or install it onto your PATH:
cargo install --path itara-cli
itara inspect
itara inspect [OPTIONS] <CONFIG>
Prints a readable summary of a wiring config’s topology: every node, every
internal connection, the deployment groups the topology implies, and an
ASCII arrow graph of how calls flow through it. Intended for humans
skimming a config, not for machine consumption — there’s no --format json
or similar; it’s plain text.
Arguments
| Argument | Required | Description |
|---|---|---|
<CONFIG> | Yes | Path to the wiring config file. |
Options
| Option | Description |
|---|---|
--no-graph | Suppress the Graph section. |
--no-groups | Suppress the Deployment groups (derived) section. |
--no-events | Suppress the Emits: / Listens to: lines within deployment groups. Only meaningful when groups are shown at all. |
--node <id> | Show only the given node and its direct connections, instead of the whole topology. Repeatable — pass it multiple times to focus on a small neighbourhood of several nodes at once. An id that doesn’t exist in the config produces a warning on stderr but isn’t fatal. |
What each section shows
Nodes — every node in the (possibly --node-filtered) config, one per
line, with its kind-specific detail (component: <id> or
virtual: <contract> @ <address>). A node with at least one external
inbound connection (a connection with no caller block) is marked
(external entry point).
Connections — every internal connection (i.e. connections with a
non-empty caller block — external inbound connections are omitted from
this list since they have no interesting from → to shape to show), as
<id>: <from> → <to> [<transport-id>].
Deployment groups (derived) — component nodes joined by one or more
direct (in-process/colocated) connections are grouped together, since
they’ll end up running in the same process. This is a connected-components
search over the subgraph of direct edges only — every other transport
type is treated as crossing a deployment boundary. Groups are labeled A,
B, C, …, Z, AA, AB, … in the order their first node is
encountered (which follows node declaration order in the file). Virtual
nodes never form or join a group — grouping only concerns deployable
components. For each node in each group, its non-direct inbound/outbound
connections are also listed:
Receives: external <transport> on <port>— an external inbound connection.Receives: <from> via <transport> on :<port>— an inbound connection from another node.Calls: <to> via <transport>— an outbound connection to another (non-virtual) node.Emits: <to> (<contract>)— an outbound connection to a virtual node (suppressed by--no-events).Listens to: <from> (<contract>)— an inbound connection from a virtual node (suppressed by--no-events).
This is the practical answer to “what goes together, what runs separately, what needs to start before what” — the basis for generating Docker Compose services, Kubernetes manifests, or startup ordering, derivable from the config alone rather than reconstructed by reading code. The full example below, from Itara’s own demo, shows a realistic case: three groups, one of them multi-node, with both synchronous and event-driven connections mixed together.
Graph — an attempt to render the topology as arrow chains, e.g.:
[external] --http:8080--> [gatewayNode] --http:8081--> [calculatorNode]
Each external entry point starts its own chain, which is walked forward
through outbound connections as far as it can go in a straight line. The
walk stops the moment it reaches a branch point (a node with more than
one outbound connection) or a merge point (a node with more than one
inbound connection) — at that point the chain is left as-is, and any
connections not swept up into a compressed chain are rendered as their own
individual line instead, annotated with (id: <connection-id>) so an
otherwise-ambiguous branch can still be traced back to a specific entry in
the config. The section is omitted entirely when the config has no
connections at all.
Exit codes
| Code | Meaning |
|---|---|
0 | Success. |
1 | The config file could not be read or parsed, or (when --node is combined with a config that fails re-validation after filtering) filtering failed. |
--node referencing an id that doesn’t exist in the config is a warning on
stderr, not a failure — inspection still proceeds with whatever’s left.
Full example
From Itara’s own demo — an order-processing system with five components
and three event channels, mixing direct, http, and kafka connections
in one config:
nodes:
- id: "fulfilmentNode"
component: "fulfilment"
- id: "inventoryNode"
component: "inventory"
- id: "notificationNode"
component: "notification"
- id: "paymentNode"
component: "payment"
- id: "orderNode"
component: "order"
- id: "orderReservedChannel"
kind: virtual
contract: "order-events/order-reserved"
address: "demo.events.order-reserved"
- id: "orderFulfilledChannel"
kind: virtual
contract: "fulfilment-events/order-fulfilled"
address: "demo.events.order-fulfilled"
- id: "orderCancelledChannel"
kind: virtual
contract: "fulfilment-events/order-cancelled"
address: "demo.events.order-cancelled"
connections:
- id: "external-to-order"
callee:
nodeId: "orderNode"
transport:
id: http
params:
host: "localhost"
port: 8081
serializer:
id: "json"
- id: "external-to-inventory"
callee:
nodeId: "inventoryNode"
transport:
id: http
params:
host: "localhost"
port: 8081
serializer:
id: "json"
- id: "order-to-inventory"
caller:
nodeId: "orderNode"
callee:
nodeId: "inventoryNode"
transport:
id: direct
- id: "order-to-fulfilment"
caller:
nodeId: "orderNode"
callee:
nodeId: "fulfilmentNode"
transport:
id: direct
- id: "order-to-payment"
caller:
nodeId: "orderNode"
callee:
nodeId: "paymentNode"
transport:
id: http
handleTimeout: true
params:
host: payment
port: 8083
serializer:
id: "json"
failureSemantics:
id: built-in
maxRetry: 3
timeout: 5s
params:
waitDuration: 500ms
retryRuntime: "true"
- id: "order-to-orderReserved"
caller:
nodeId: "orderNode"
callee:
nodeId: orderReservedChannel
transport:
id: kafka
params:
bootstrapServers: "kafka:29092"
serializer:
id: "json"
- id: "order-to-orderFulfilled"
caller:
nodeId: "orderNode"
callee:
nodeId: orderFulfilledChannel
transport:
id: kafka
params:
bootstrapServers: "kafka:29092"
serializer:
id: "json"
- id: "order-to-orderCancelled"
caller:
nodeId: "orderNode"
callee:
nodeId: orderCancelledChannel
transport:
id: kafka
params:
bootstrapServers: "kafka:29092"
serializer:
id: "json"
- id: "orderReserved-to-notification"
caller:
nodeId: "orderReservedChannel"
callee:
nodeId: "notificationNode"
transport:
id: kafka
params:
bootstrapServers: "kafka:29092"
consumerGroup: "order-consumer-group"
serializer:
id: "json"
- id: "orderFulfilled-to-notification"
caller:
nodeId: "orderFulfilledChannel"
callee:
nodeId: "notificationNode"
transport:
id: kafka
params:
bootstrapServers: "kafka:29092"
consumerGroup: "order-consumer-group"
serializer:
id: "json"
- id: "orderCancelled-to-notification"
caller:
nodeId: "orderCancelledChannel"
callee:
nodeIdto: "notificationNode"
transport:
id: kafka
params:
bootstrapServers: "kafka:29092"
consumerGroup: "order-consumer-group"
serializer:
id: "json"
$ itara inspect demo/wiring-monolith.yaml
Itara topology — demo/wiring-monolith.yaml
Nodes:
fulfilmentNode component: fulfilment
inventoryNode component: inventory (external entry point)
notificationNode component: notification
paymentNode component: payment
orderNode component: order (external entry point)
orderReservedChannel virtual: order-events/order-reserved @ demo.events.order-reserved
orderFulfilledChannel virtual: fulfilment-events/order-fulfilled @ demo.events.order-fulfilled
orderCancelledChannel virtual: fulfilment-events/order-cancelled @ demo.events.order-cancelled
Connections:
order-to-inventory: orderNode → inventoryNode [direct]
order-to-fulfilment: orderNode → fulfilmentNode [direct]
order-to-payment: orderNode → paymentNode [http]
order-to-orderReserved: orderNode → orderReservedChannel [kafka]
order-to-orderFulfilled: orderNode → orderFulfilledChannel [kafka]
order-to-orderCancelled: orderNode → orderCancelledChannel [kafka]
orderReserved-to-notification: orderReservedChannel → notificationNode [kafka]
orderFulfilled-to-notification: orderFulfilledChannel → notificationNode [kafka]
orderCancelled-to-notification: orderCancelledChannel → notificationNode [kafka]
Deployment groups (derived):
Group A: fulfilmentNode, orderNode, inventoryNode
fulfilmentNode (fulfilment)
orderNode (order)
Receives: external http on :8081
Calls: inventoryNode via direct
Calls: fulfilmentNode via direct
Calls: paymentNode via http
Emits: orderReservedChannel (order-events/order-reserved)
Emits: orderFulfilledChannel (fulfilment-events/order-fulfilled)
Emits: orderCancelledChannel (fulfilment-events/order-cancelled)
inventoryNode (inventory)
Receives: external http on :8081
Group B: notificationNode
notificationNode (notification)
Listens to: orderReservedChannel (order-events/order-reserved)
Listens to: orderFulfilledChannel (fulfilment-events/order-fulfilled)
Listens to: orderCancelledChannel (fulfilment-events/order-cancelled)
Group C: paymentNode
paymentNode (payment)
Receives: orderNode via http on :8083
Graph:
[external] --http:8081--> [orderNode]
[external] --http:8081--> [inventoryNode]
[orderNode] --direct--> [inventoryNode] (id: order-to-inventory)
[orderNode] --direct--> [fulfilmentNode] (id: order-to-fulfilment)
[orderNode] --http:8083--> [paymentNode] (id: order-to-payment)
[orderNode] --kafka--> [orderReservedChannel] (id: order-to-orderReserved)
[orderNode] --kafka--> [orderFulfilledChannel] (id: order-to-orderFulfilled)
[orderNode] --kafka--> [orderCancelledChannel] (id: order-to-orderCancelled)
[orderReservedChannel] --kafka--> [notificationNode] (id: orderReserved-to-notification)
[orderFulfilledChannel] --kafka--> [notificationNode] (id: orderFulfilled-to-notification)
[orderCancelledChannel] --kafka--> [notificationNode] (id: orderCancelled-to-notification)
fulfilmentNode and inventoryNode both join orderNode’s group despite
inventoryNode also having its own external entry point — group
membership follows the direct-connection subgraph only, independent of
what else a node is reachable from. paymentNode and notificationNode
end up in their own single-node groups because every connection reaching
them crosses a transport boundary — http for one, kafka for the
other — neither of which is direct.
itara verify
itara verify [OPTIONS] <CONFIG>
Runs a battery of logical checks against a wiring config and reports every
problem found, each as an ERROR or a WARNING. Errors indicate the
config is broken; warnings flag something suspicious that’s still valid.
Meant to be run in CI or pre-deploy — the exit code tells you pass/fail, the
output tells you why. Introducing a topology layer creates an obligation: a
wiring config that can be misconfigured silently is not a step forward.
Arguments
| Argument | Required | Description |
|---|---|---|
<CONFIG> | Yes | Path to the wiring config file. |
Options
| Option | Description |
|---|---|
--metadata-dir <path> | Path to a directory of .itara metadata files (see the metadata format reference). Enables the checks that need to cross-reference metadata — known transport/authentication/authorization types, API version compatibility, timeout capability, transport interrupt safety, and serializer compatibility. Without it, those checks are skipped and a warning is emitted saying so. |
--skip <check> | Skip a specific check by name. Repeatable. Mutually exclusive with --only. |
--only <check> | Run only the specified check(s). Repeatable. Mutually exclusive with --skip. |
An unrecognized name passed to either --skip or --only is a hard error
(exit 1) before the config file is even read, listing every valid check
name. Passing both --skip and --only in the same invocation is rejected
by argument parsing itself.
Checks
| Check name | Severity | Requires --metadata-dir | What it flags |
|---|---|---|---|
duplicate-ids | Error | No | The same node id declared more than once. |
connection-id-uniqueness | Error | No | The same connection id declared more than once. |
self-connections | Error | No | A connection whose caller.nodeId and callee.nodeId are the same node. |
direct-external-conflict | Error | No | A connection using the direct transport with no caller block — a direct call is in-process and can’t have an external caller. |
outbound-ambiguity | Error | No | A node with outbound connections to two or more different node ids that all resolve to the same component — the agent would have no way to pick one at dispatch time. |
orphaned-nodes | Error | No | A node declared in nodes that no connection (as either caller or callee) ever references. |
orphaned-connections | Error | No | A connection whose caller or callee references a node id that isn’t declared in nodes. |
virtual-no-producers | Warning | No | A virtual node with no inbound connection — nothing will ever publish to it. |
virtual-no-consumers | Warning | No | A virtual node with no outbound connection — nothing will ever consume from it. |
virtual-transport-mismatch | Warning | No | A virtual node whose inbound/outbound connections don’t all agree on the same transport type. |
unknown-transport | Error | Yes | A connection’s transport.id (other than the built-in direct) has no matching kind = "transport" metadata file. |
unknown-authentication | Error | Yes | A connection’s authentication.id (other than the built-in noop) has no matching kind = "authentication" metadata file. |
unknown-authorization | Error | Yes | A connection’s authorization.id (other than the built-in noop) has no matching kind = "authorization" metadata file. |
api-version-compatibility | Error | Yes | For a connection between two component nodes: the caller’s declared [api-dependencies] version for the callee doesn’t satisfy the callee’s declared api-version requirement (checked with semver). Also errors (rather than silently skipping) if either side’s metadata, or the specific dependency declaration, can’t be found — see caveats below. |
timeout-capability | Error and Warning | Yes | A connection declares a failureSemantics.timeout or absoluteTimeout that its transport and/or failure-semantics implementation isn’t capable of enforcing the way it’s configured — see details below. |
transport-interrupt-safety | Error | Yes | A connection configures external timeout enforcement (failureSemantics.handleTimeout = true) on a transport whose metadata declares externally-interruptible = false. |
serializer-compatibility | Warning | Yes | A connection’s serializer isn’t confirmed compatible with the callee API’s declared [contract] message format or [serializers] supported list — see details below. |
Without --metadata-dir, only the first ten checks run; the metadata-backed
checks are skipped entirely, and a single warning is added to the output:
“no –metadata-dir provided — API version, known transport, timeout, and
transport interrupt checks are skipped”.
api-version-compatibility caveats
This check only evaluates connections between two component nodes (it skips
external connections, and connections touching a virtual node on either
side). Where it does apply, every one of the following also produces its
own ERROR rather than silently skipping the connection: no metadata found
for the caller component; no metadata found for the callee component; the
caller declares no [api-dependencies] at all; the caller’s
[api-dependencies] doesn’t include an entry for the callee; the caller’s
declared dependency version isn’t valid semver; or the callee doesn’t
declare an api-version (or declares an invalid semver range for it). In
other words: if you’ve opted into this check via --metadata-dir, an
incomplete metadata picture is treated as a problem to fix, not something
to shrug off.
timeout-capability details
This check only looks at non-external, non-direct connections that declare
a failureSemantics block with a timeout and/or absoluteTimeout. Within
that scope it can raise several distinct issues on the same connection:
- Error —
transport.handleTimeout = truebut the transport’s metadata saysnative-call-timeout = false(or the transport has no metadata at all). - Error —
failureSemantics.handleTimeout = truebut the failure-semantics implementation’s metadata sayssupports-external-timeout = false(or has no metadata at all). - Error —
failureSemantics.handleTimeout = truebut the transport’s metadata saysexternally-interruptible = false. - Warning — a
timeoutis declared but neithertransport.handleTimeoutnorfailureSemantics.handleTimeoutis set — the value will be threaded through but nothing will actually enforce it. - Warning — both
transport.handleTimeoutandfailureSemantics.handleTimeoutare set — both will independently try to enforce the timeout, and which one wins is a race. - Error —
absoluteTimeoutis declared but the failure-semantics implementation doesn’t declaresupports-external-timeout = true(or has no metadata at all) — an absolute timeout can only ever be enforced externally, so this is required whenever it’s set, independent ofhandleTimeout.
serializer-compatibility details
Skips direct connections (nothing is ever serialized on them) and
connections whose callee doesn’t resolve to a component. The check itself
only applies when the callee API’s metadata declares something to check
against — a non-empty [contract].message-format or a non-empty
[serializers].supported list. An API declaring neither produces no issue
at all for this check (not even a warning): there’s no restriction to be
incompatible with. Where the check does apply, a connection’s configured
serializer is considered compatible if either:
- it appears (by id, with a version satisfying the declared semver range)
in the API’s
[serializers].supportedlist, or - the API’s declared
[contract].message-formatappears in the serializer’s own[serializer.capabilities].message-formats.
If neither holds — or if the serializer’s own metadata can’t be found at
all — the connection is flagged with a WARNING (never an ERROR): this
check reports “we can’t confirm this is safe,” not “this is broken,” since
the agent itself doesn’t enforce serializer/API compatibility at runtime
either.
Exit codes
| Code | Meaning |
|---|---|
0 | The config parsed successfully and no ERROR-severity issues were found. Warnings do not affect the exit code. |
1 | The config could not be read/parsed, an unrecognized --skip/--only check name was given, or at least one ERROR-severity issue was found. |
Output format
<✓|✗> itara verify — <path>
<N> nodes, <M> connections
<label> <message>
<label> <message>
...
<summary line>
✓ is printed when there are no errors (warnings may still be present);
✗ when there is at least one error. If the config fails to parse, only
the header, a single ERROR line for the parse failure, and a 1 error
summary are printed — none of the individual checks run. If there are no
issues at all (not even warnings — i.e. --metadata-dir was supplied and
everything passed), the line No issues found. is printed instead of a
check list and summary.
Examples
A clean run against the demo’s monolith topology, with a metadata directory supplied:
$ itara verify --metadata-dir demo/metafiles/ demo/wiring-monolith.yaml
✓ itara verify — demo/wiring-monolith.yaml
8 nodes, 11 connections
No issues found.
The same topology with two problems deliberately introduced — the
order-to-fulfilment connection removed (orphaning fulfilmentNode, since
nothing else references it) and handleTimeout dropped from
order-to-payment’s transport block while a timeout is still declared —
triggers orphaned-nodes and timeout-capability together, one an error
and one a warning:
$ itara verify --metadata-dir demo/metafiles/ demo/wiring-informed-with-error.yaml
✗ itara verify — demo/wiring-informed-with-error.yaml
8 nodes, 10 connections
ERROR node 'fulfilmentNode' is declared but not referenced in any connection
WARN connection 'orderNode' → 'paymentNode': a timeout is declared but neither the transport nor the failure semantics implementation is configured to enforce it — the timeout value will be passed to the transport but nothing will act on it
1 error, 1 warning
Narrowing to a single check, against the same demo config:
$ itara verify --metadata-dir demo/metafiles/ demo/wiring-monolith.yaml --only unknown-transport
✓ itara verify — demo/wiring-monolith.yaml
8 nodes, 11 connections
No issues found.
4 - Wiring Configuration
The wiring configuration is the YAML file that describes the topology of an Itara deployment: which nodes exist, and how they’re connected to one another (or to the outside world).
This document describes the file format itself — the schema, the required and optional fields, and what makes a document valid.
Top-level structure
nodes:
- id: "gatewayNode"
component: "gateway"
- id: "calculatorNode"
component: "calculator"
connections:
- id: "gateway-to-calculator"
caller:
nodeId: "gatewayNode"
callee:
nodeId: "calculatorNode"
transport:
id: http
params:
host: "${CALC_HOST:-localhost}"
port: "${CALC_PORT:-8081}"
serializer:
id: json
| Field | Type | Required | Description |
|---|---|---|---|
nodes | list of Node | No (defaults to empty) | Every node in the topology. |
connections | list of Connection | No (defaults to empty) | Every connection between nodes. |
An empty or comment-only file is valid and parses to an empty configuration (no nodes, no connections).
Environment variable substitution
Before the YAML is parsed, the raw file content goes through a textual substitution pass. Any occurrence of:
${VAR_NAME}— replaced with the value of environment variableVAR_NAME.${VAR_NAME:-default}— replaced with the value ofVAR_NAMEif set, otherwise the literaldefault. is substituted in-place, before the YAML parser ever sees the file. This means a substituted value is always plain text at parse time — e.g. an env var substituted into aport:field arrives as a numeric string and the YAML parser coerces it the same way a literal8081would be.
A variable with no default that isn’t set in the environment is left as
the literal placeholder text (${VAR_NAME}) in the document.
Substitution applies anywhere in the file — it’s a plain text pass over the whole document, not scoped to particular fields.
YAML merge keys and anchors
Standard YAML anchors/aliases and merge keys (<<) are supported and
resolved before the document is mapped onto the config model — so shared
blocks (e.g. a common transport.params map used by several connections)
can be factored out with a YAML anchor and merged in with <<.
Nodes
A node is either a component node or a virtual node. The two share
an id and are distinguished by the kind field.
nodes:
- id: "orderServiceNode"
component: "order-service" # component node (kind omitted → defaults to "component")
- id: "inventoryNode"
kind: component # component node (kind explicit)
component: "inventory"
- id: "orderPlacedChannel"
kind: virtual
contract: "order-events/order-placed"
address: "demo.events.order-placed"
Common fields
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | This node’s own identifier. Must be unique, non-blank and match the id character set. Used elsewhere in the file (connections[].caller.nodeId / connections[].callee.nodeId) to refer to this node. |
kind | string: component | virtual | No — defaults to component | Discriminates which node type this is. Case-insensitive (Component, VIRTUAL, etc. are all accepted and normalized). A config without a kind field is a config full of component nodes. |
Component nodes (kind: component)
A component node is a deployable component instance — something with an activator and a contract implementation.
| Field | Type | Required | Description |
|---|---|---|---|
component | string | Yes | The component id. Must match the artifact.id of a kind = "component" metadata file discoverable by the agent. |
Virtual nodes (kind: virtual)
A virtual node represents a communication channel with no component implementation of its own — a named point that decouples producers from consumers via a broker (e.g. a Kafka topic). It has no activator and no agent-managed lifecycle.
| Field | Type | Required | Description |
|---|---|---|---|
contract | string | Yes | The event contract this channel carries, in <events-artifact-id>/<contract-id> form, e.g. "order-events/order-placed". |
address | string | Yes | The broker-specific channel address, e.g. a Kafka topic name. Opaque to the wiring config itself — meaning is entirely up to the transport/broker in use. |
Id character set
Both node ids and connection ids are restricted to:
[A-Za-z0-9._-]+
i.e. letters, digits, ., _, and -. This set is deliberately
conservative — it’s Kafka’s own topic-name character set, and it stays safe
unencoded in HTTP header values and URL path segments, both of which a
transport implementation is free to use to propagate an id.
Connections
A connection declares how one node calls another — or how an external caller reaches into the topology.
connections:
- id: "gateway-to-calculator"
caller:
nodeId: "gatewayNode"
failureSemantics:
id: built-in
timeout: 2s
handleTimeout: true
absoluteTimeout: 10s
maxRetry: 3
params:
waitDuration: 500ms
callee:
nodeId: "calculatorNode"
authorization:
id: rule-table
params:
allow: "shout"
deny: "whisper"
transport:
id: http
handleTimeout: true
params:
host: "${CALC_HOST:-localhost}"
port: "${CALC_PORT:-8081}"
serializer:
id: json
params:
schemaRegistryUrl: "${SCHEMA_REGISTRY_URL:-http://localhost:8081}"
authentication:
id: shared-secret
params:
secret: "${GATEWAY_SECRET}"
Top-level connection fields
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | This connection’s own identifier — distinct from the node references inside caller/callee. Must be unique across the entire wiring configuration. Same character set as node ids. |
callee | Callee block | Yes | The called node (callee.nodeId), and optionally callee-side plugin configuration. |
caller | Caller block | No | The calling node (caller.nodeId), and optionally caller-side plugin configuration. Absent means the caller is external to the Itara topology. |
transport | TransportEntry | Yes, resolvable at connection-level or per-side | The transport this connection uses. |
serializer | SerializerEntry | Conditionally — see below | The serializer this connection uses. Required for every connection except direct ones; resolvable at connection-level or per-side. |
authentication | AuthenticationEntry | No | Authentication mechanism. Valid at connection-level, caller-side, or callee-side. Absent means noop. |
A connection with no caller block is an external connection — it
represents traffic entering the topology from outside (e.g. a public HTTP
endpoint), rather than a call between two nodes both managed by Itara.
A connection whose transport.id is (case-insensitively) "direct" is a
direct connection — an in-process, colocated call. Direct connections
never cross a process boundary, so nothing on them is ever serialized, and
they don’t require a serializer block. A direct connection cannot be
external — an in-process call has no meaning without a caller also managed
by Itara.
Caller and callee blocks
callee:
nodeId: "calculatorNode"
authorization:
id: rule-table
params: { allow: "shout", deny: "whisper" }
caller:
nodeId: "gatewayNode"
failureSemantics:
id: built-in
maxRetry: 3
Each block may additionally hold transport, serializer, and
authentication configuration, independent of the connection level.
failureSemantics is valid on caller only; authorization is valid on
callee only.
| Field | Type | Required | Description |
|---|---|---|---|
nodeId | string | Yes within its block | The node this side refers to. |
Override happens per plugin kind, not per side as a whole — a caller
block overriding just transport while serializer still falls back to
connection-level is valid. Where a side-specific declaration for a given
plugin kind is present, it replaces the connection-level declaration of
that kind for that side entirely — block-level, not field-level; nothing
merges.
Transport block
Valid at connection-level, caller-side, or callee-side (see Caller and callee blocks).
transport:
id: http
handleTimeout: true
params:
host: "${CALC_HOST:-localhost}"
port: "8081"
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | The transport type identifier. Must match the artifact.id of a kind = "transport" metadata file discoverable by the agent — except for the special value direct, which selects the built-in in-process transport and requires no corresponding artifact. |
handleTimeout | boolean | No — defaults to false | Whether the transport should enforce the per-attempt timeout natively for this connection. Only meaningful if the transport’s own metadata declares the matching capability; the actual timeout value lives in failureSemantics.timeout, not here. |
params | map of string → scalar | No — defaults to {} | Transport-specific connection parameters, passed through to the transport implementation as-is. The wiring config has no schema for this map and no knowledge of what any particular transport expects. Values must be scalars (string, number, or boolean); nested maps or lists are not valid here. |
Serializer block
Valid at connection-level, caller-side, or callee-side.
serializer:
id: json
params:
schemaRegistryUrl: "${SCHEMA_REGISTRY_URL:-http://localhost:8081}"
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes (see connection-level rule) | The serializer id. Must match the artifact.id of a kind = "serializer" metadata file discoverable by the agent. |
params | map of string → scalar | No — defaults to {} | Serializer-specific parameters, passed through as-is. Same shape and same “no schema” property as transport.params. |
The block (and its id in particular) is required on every connection
except direct ones — there is no serializer choice that’s safe to assume
silently for a connection that crosses a process boundary.
failureSemantics block
Valid on the caller block only — retry/timeout is exclusively the
caller’s concern.
failureSemantics:
id: built-in
timeout: 2s
handleTimeout: true
absoluteTimeout: 10s
maxRetry: 3
params:
waitDuration: 500ms
| Field | Type | Required | Description |
|---|---|---|---|
id | string | No — defaults to noop | The failure-semantics implementation id. Must match the artifact.id of a kind = "failure-semantics" metadata file, unless left at the noop default. |
timeout | duration string, e.g. "2s", "500ms" | No | Per-attempt timeout. |
handleTimeout | boolean | No — defaults to false | Whether the failure-semantics implementation should enforce the per-attempt timeout by externally interrupting the transport thread. Only meaningful if the implementation’s own metadata declares support for it. |
absoluteTimeout | duration string, e.g. "10s" | No | Hard ceiling on total execution time across all attempts, including retries. |
maxRetry | integer | No | Maximum number of retries. Total attempts made = maxRetry + 1. |
params | map of string → scalar | No — defaults to {} | Implementation-specific parameters (e.g. backoff configuration), passed through as-is. |
The block as a whole is optional; omitting it entirely means the noop implementation is used — no retries, no timeout enforcement, no circuit breaking.
authentication block
Valid at connection-level, caller-side, or callee-side.
authentication:
id: shared-secret
params:
secret: "${GATEWAY_SECRET}"
| Field | Type | Required | Description |
|---|---|---|---|
id | string | No — defaults to noop | The authentication mechanism id. Must match the artifact.id of a kind = "authentication" metadata file, unless left at the noop default. If the block is present, id may not be blank — omit the block entirely to get the noop default instead of supplying an empty id. |
params | map of string → scalar | No — defaults to {} | Implementation-specific parameters, passed through as-is. |
authorization block
Valid on the callee block only — authorization is exclusively the
callee’s own access-control decision.
authorization:
id: rule-table
params:
allow: "shout"
deny: "whisper"
| Field | Type | Required | Description |
|---|---|---|---|
id | string | No — defaults to noop | The authorization mechanism id. Must match the artifact.id of a kind = "authorization" metadata file, unless left at the noop default. Same non-blank rule as authentication when the block is present. |
params | map of string → scalar | No — defaults to {} | Implementation-specific parameters, passed through as-is. |
Full example
nodes:
- id: "order-service-node"
component: "order-service"
- id: "pricing-service-node"
component: "pricing-service"
- id: "orderPlacedChannel"
kind: virtual
contract: "order-events/order-placed"
address: "demo.events.order-placed"
connections:
# In-process call — order-service calls pricing-service directly.
- id: "order-to-pricing"
caller:
nodeId: "order-service-node"
callee:
nodeId: "pricing-service-node"
transport:
id: direct
# Inbound HTTP entry point — no 'caller' block, so the caller is external.
- id: "gateway-to-order"
callee:
nodeId: "order-service-node"
transport:
id: http
params:
host: "${ORDER_HOST:-localhost}"
port: "${ORDER_PORT:-8080}"
serializer:
id: json
authentication:
id: shared-secret
params:
secret: "${GATEWAY_SECRET}"
# order-service publishes onto a virtual (broker-backed) channel.
- id: "order-to-order-placed-channel"
caller:
nodeId: "order-service-node"
caller:
nodeId: "orderPlacedChannel"
transport:
id: kafka
params:
bootstrapServers: "${KAFKA_BOOTSTRAP:-localhost:9092}"
serializer:
id: protobuf
5 - Metadata Format
A .itara file is a small TOML document shipped
alongside every build artifact (a component, an API, a transport, a
serializer, an observer, etc). It declares what the artifact is — its
identity, the versions it was built against, and (depending on its kind) a
handful of kind-specific declarations that other tooling and the agent
itself use to wire things up safely.
Unlike the wiring configuration,
which is one file describing an entire deployment topology, a .itara
file describes exactly one artifact.
This document describes the file format — every section, every field, what’s required, what defaults apply, and which artifact kinds each section is meaningful for.
Wherever a field is described below as a version — artifact.version,
api-version, and every version inside a { id, version } entry —
it’s a semver string, not an arbitrary one; version-range fields (like
[serializers] supported[].version) are semver ranges checked against it.
Forward compatibility
Unknown sections, and unknown fields within known sections, are always silently ignored. This is deliberate: it lets an older agent load an artifact built with a newer version of the metadata schema without failing, simply ignoring whatever it doesn’t understand. Don’t rely on unrecognized fields causing a parse error — they won’t.
Top-level structure
[artifact]
kind = "component"
id = "inventory"
version = "1.0.0"
api-version = "1.x"
[runtime]
language = "java"
compiler = "21"
[itara]
spec-version = "0.1"
core-version = "0.1+"
| Section | Required | Meaningful for | Description |
|---|---|---|---|
[artifact] | Yes | every kind | Identity: what this artifact is, and its own version. |
[runtime] | No | every kind | What language/compiler built this artifact. |
[itara] | No | every kind | Which version of the Itara spec/core this artifact targets. |
[methods] | No | api | Per-method properties of the contract — currently, which methods are non-idempotent. |
[implemented-event-contracts] | No | component | Which event contracts this component implements. |
[contract] | No | api, events | Properties of the contract as a whole — currently, its message format. |
[serializers] | No | api | Which serializers this artifact was compiled with support for. |
[serializer] | No | serializer | What this serializer implementation is and can do. |
[transport] | No | transport | What this transport implementation is and can do. |
[failure-semantics] | No | failure-semantics | What this failure-semantics implementation can do. |
[authentication] | No | authentication | What kind of authentication mechanism this is. |
[authorization] | No | authorization | What kind of authorization mechanism this is. |
[api-dependencies] | No | component | Which API contracts this component calls, and at what version. |
Every section beyond [artifact] is optional at the parser level. The
“meaningful for” column describes intent, not an enforced restriction —
nothing about the format itself rejects a [transport] section on a
component artifact, for instance.
[artifact] (required)
[artifact]
kind = "component"
id = "inventory"
version = "1.0.0"
api-version = "1.x"
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
kind | string | Yes | — | The artifact’s kind: component, api, events, transport, serializer, observer, failure-semantics, authentication, authorization, context-handler. |
id | string | Yes | — | Component id for component/api/events artifacts (e.g. "inventory"); SPI implementation name for transport/serializer/observer/failure-semantics/authentication/authorization artifacts (e.g. "http"). |
version | string | No | "" | This artifact’s own version. |
api-version | string | No | "" | The API version this artifact exposes (for api/component producing an API) or was compiled against. |
.itara files are named without an embedded version — identity comes
purely from (kind, id).
[runtime] (optional)
[runtime]
language = "java"
compiler = "21"
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
language | string | No | "" | Implementation language, e.g. "java", "rust". |
compiler | string | No | "" | Compiler/language version used to build the artifact, e.g. "21", "1.78+". |
[itara] (optional)
[itara]
spec-version = "0.1"
core-version = "0.1+"
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
spec-version | string | No | "" | Version of the Itara spec this artifact was built against. |
core-version | string | No | "" | Version of the Itara core this artifact was built against. |
[methods] (optional — api artifacts)
[methods]
non-idempotent = ["divide", "transfer", "placeOrder"]
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
non-idempotent | array of strings | No | [] | Names of contract methods that are not safe to retry automatically. |
Absence of this section (or of a given method name within it) means the method is treated as idempotent — the safe default for retry purposes.
[implemented-event-contracts] (optional — component artifacts)
[implemented-event-contracts]
contracts = [
{ id = "order-events/order-placed", version = "1.0.0" },
{ id = "order-events/order-cancelled", version = "1.0.0" },
]
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
contracts | array of { id, version } | No | [] | Event contracts this component implements. |
Each entry:
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Full contract reference: <events-artifact-id>/<contract-id>, e.g. "order-events/order-placed". |
version | string | Yes | Version of the events artifact this implementation was written against. |
Present only on component artifacts that consume event contracts.
[contract] (optional — api and events artifacts)
[contract]
message-format = "protobuf"
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
message-format | string | No | "" | The message format the contract’s method parameter/return types (for api) or event payload types (for events) are generated from, e.g. "protobuf". An empty string is treated identically to the section being absent entirely — both mean the contract uses plain, hand-written types rather than a generated structural format. |
This is a structural property of the contract’s own types — it’s unrelated
to which serializer ids an artifact is compatible with (that’s
[serializers], below); message format and serializer choice vary
independently.
[serializers] (optional — api artifacts)
[serializers]
supported = [
{ id = "json", version = "1.x" },
{ id = "protobuf", version = "1.x" },
]
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
supported | array of { id, version } | No | [] | Serializers this API artifact was compiled with support for. |
Each entry:
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Matches a serializer’s artifact.id, e.g. "json", "protobuf". |
version | string | Yes | A semver range checked against that serializer’s own artifact.version. |
Not every language needs this section populated. Java loads serializers as
runtime plugins, so an API artifact can work with a serializer it never
declared here. Rust has no equivalent runtime plugin mechanism — a Rust
API artifact must be compiled with support for whichever serializers it
will use, so [serializers] supported is how that compiled-in support
gets declared and checked.
[serializer] (optional — serializer artifacts)
[serializer]
type = "protobuf"
[serializer.capabilities]
message-formats = ["protobuf"]
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
type | string | No | absent/null | The serialization category this implementation handles — e.g. "json", "protobuf". Distinct from artifact.id, which is the unique identifier of the specific implementation. |
capabilities | SerializerCapabilities | No | see below | See below. |
SerializerCapabilities
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
message-formats | array of strings | No | [] | Structural message formats this serializer implementation can handle generically, beyond plain hand-written types — e.g. a protobuf serializer declares ["protobuf"] here, meaning it can handle proto-generated types via reflection. |
Defaults to an empty list when the [serializer.capabilities] section is
absent — a serializer is assumed to handle plain types only, until it
explicitly declares a structural format it supports. This has no bearing
on error-payload handling, which every serializer supports unconditionally
regardless of its declared message-formats.
[transport] (optional — transport artifacts)
[transport]
type = "http"
[transport.capabilities]
native-call-timeout = true
externally-interruptible = true
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
type | string | No | absent/null | The transport category / communication protocol, e.g. "http", "kafka", "amqp". Two implementations sharing the same type are considered compatible caller/callee pairs. Distinct from artifact.id. |
capabilities | TransportCapabilities | No | see below | See below. |
TransportCapabilities
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
native-call-timeout | boolean | No | true | Whether the transport can abort an in-flight call natively when the per-call timeout expires. If false, the failure-semantics layer is responsible for enforcing the timeout externally instead. |
externally-interruptible | boolean | No | true | Whether it’s safe to externally interrupt the thread blocked on this transport’s send call — i.e. doing so won’t leave the transport in a broken state. |
Both fields default to true when the [transport.capabilities] section
is absent entirely — a transport is assumed capable unless it explicitly
declares otherwise. This is the opposite default from
SerializerCapabilities and FailureSemanticsCapabilities, both of which
default to the unsupported/empty case.
[failure-semantics] (optional — failure-semantics artifacts)
[failure-semantics]
[failure-semantics.capabilities]
supports-external-timeout = true
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
capabilities | FailureSemanticsCapabilities | No | see below | See below. |
FailureSemanticsCapabilities
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
supports-external-timeout | boolean | No | false | Whether this failure-semantics implementation can enforce the per-attempt timeout by externally interrupting the transport thread. Implementations must opt in explicitly. |
[authentication] (optional — authentication artifacts)
[authentication]
type = "mtls"
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
type | string | No | absent/null | The authentication mechanism category, e.g. "shared-secret", "mtls", "jwt". Distinct from artifact.id. |
[authorization] (optional — authorization artifacts)
[authorization]
type = "rbac"
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
type | string | No | absent/null | The authorization mechanism category, e.g. "rbac", "rule-table", "opa". Distinct from artifact.id. |
[api-dependencies] (optional — component artifacts)
[api-dependencies]
calls = [
{ id = "calculator", version = "1.0.0" },
{ id = "inventory", version = "2.1.0" },
]
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
calls | array of { id, version } | No | [] | Synchronous API contracts this component calls. |
Each entry:
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Matches the artifact.id of the callee’s kind = "api" metadata file. |
version | string | Yes | The exact version this component was compiled against — used to check compatibility against the callee’s declared api-version. |
Absence of the section (rather than an empty calls list) means the same
thing: the component declares no outbound API calls. This is valid, and
expected, for leaf components.
Full example
A component that calls one API and implements two event contracts:
[artifact]
kind = "component"
id = "order-service"
version = "1.4.0"
api-version = "1.x"
[runtime]
language = "java"
compiler = "21"
[itara]
spec-version = "0.1"
core-version = "0.2"
[api-dependencies]
calls = [
{ id = "pricing", version = "2.0.1" },
]
[implemented-event-contracts]
contracts = [
{ id = "order-events/order-placed", version = "1.0.0" },
{ id = "order-events/order-cancelled", version = "1.0.0" },
]
An API artifact declaring one non-idempotent method:
[artifact]
kind = "api"
id = "order-service"
version = "1.4.0"
[methods]
non-idempotent = ["placeOrder"]
An HTTP transport artifact:
[artifact]
kind = "transport"
id = "http"
version = "0.3.0"
[runtime]
language = "rust"
compiler = "1.78+"
[transport]
type = "http"
[transport.capabilities]
native-call-timeout = true
externally-interruptible = true