This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Reference

Wiring configuration, .itara metadata, the CLI, the glossary, and the specification.

1 - Glossary

The vocabulary of the Itara project — shared terms for contributors, adopters, and anyone explaining Itara to others.

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

Generated class and method reference for the Java and Rust implementations.

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

Reference documentation for itara-cli and any future Itara tooling.

3.1 - itara-cli

The command-line tool for inspecting and validating an Itara wiring configuration.

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:

CommandPurpose
itara inspectPrint a human-readable summary of the topology described by a wiring config.
itara verifyValidate 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

ArgumentRequiredDescription
<CONFIG>YesPath to the wiring config file.

Options

OptionDescription
--no-graphSuppress the Graph section.
--no-groupsSuppress the Deployment groups (derived) section.
--no-eventsSuppress 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

CodeMeaning
0Success.
1The 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

ArgumentRequiredDescription
<CONFIG>YesPath to the wiring config file.

Options

OptionDescription
--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 nameSeverityRequires --metadata-dirWhat it flags
duplicate-idsErrorNoThe same node id declared more than once.
connection-id-uniquenessErrorNoThe same connection id declared more than once.
self-connectionsErrorNoA connection whose caller.nodeId and callee.nodeId are the same node.
direct-external-conflictErrorNoA connection using the direct transport with no caller block — a direct call is in-process and can’t have an external caller.
outbound-ambiguityErrorNoA 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-nodesErrorNoA node declared in nodes that no connection (as either caller or callee) ever references.
orphaned-connectionsErrorNoA connection whose caller or callee references a node id that isn’t declared in nodes.
virtual-no-producersWarningNoA virtual node with no inbound connection — nothing will ever publish to it.
virtual-no-consumersWarningNoA virtual node with no outbound connection — nothing will ever consume from it.
virtual-transport-mismatchWarningNoA virtual node whose inbound/outbound connections don’t all agree on the same transport type.
unknown-transportErrorYesA connection’s transport.id (other than the built-in direct) has no matching kind = "transport" metadata file.
unknown-authenticationErrorYesA connection’s authentication.id (other than the built-in noop) has no matching kind = "authentication" metadata file.
unknown-authorizationErrorYesA connection’s authorization.id (other than the built-in noop) has no matching kind = "authorization" metadata file.
api-version-compatibilityErrorYesFor 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-capabilityError and WarningYesA 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-safetyErrorYesA connection configures external timeout enforcement (failureSemantics.handleTimeout = true) on a transport whose metadata declares externally-interruptible = false.
serializer-compatibilityWarningYesA 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 = true but the transport’s metadata says native-call-timeout = false (or the transport has no metadata at all).
  • Error — failureSemantics.handleTimeout = true but the failure-semantics implementation’s metadata says supports-external-timeout = false (or has no metadata at all).
  • Error — failureSemantics.handleTimeout = true but the transport’s metadata says externally-interruptible = false.
  • Warning — a timeout is declared but neither transport.handleTimeout nor failureSemantics.handleTimeout is set — the value will be threaded through but nothing will actually enforce it.
  • Warning — both transport.handleTimeout and failureSemantics.handleTimeout are set — both will independently try to enforce the timeout, and which one wins is a race.
  • Error — absoluteTimeout is declared but the failure-semantics implementation doesn’t declare supports-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 of handleTimeout.

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].supported list, or
  • the API’s declared [contract].message-format appears 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

CodeMeaning
0The config parsed successfully and no ERROR-severity issues were found. Warnings do not affect the exit code.
1The 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 YAML format that declares topology — nodes, connections, and everything a connection carries.

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
FieldTypeRequiredDescription
nodeslist of NodeNo (defaults to empty)Every node in the topology.
connectionslist of ConnectionNo (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 variable VAR_NAME.
  • ${VAR_NAME:-default} — replaced with the value of VAR_NAME if set, otherwise the literal default. 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 a port: field arrives as a numeric string and the YAML parser coerces it the same way a literal 8081 would 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

FieldTypeRequiredDescription
idstringYesThis 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.
kindstring: component | virtualNo — defaults to componentDiscriminates 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.

FieldTypeRequiredDescription
componentstringYesThe 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.

FieldTypeRequiredDescription
contractstringYesThe event contract this channel carries, in <events-artifact-id>/<contract-id> form, e.g. "order-events/order-placed".
addressstringYesThe 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

FieldTypeRequiredDescription
idstringYesThis 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.
calleeCallee blockYesThe called node (callee.nodeId), and optionally callee-side plugin configuration.
callerCaller blockNoThe calling node (caller.nodeId), and optionally caller-side plugin configuration. Absent means the caller is external to the Itara topology.
transportTransportEntryYes, resolvable at connection-level or per-sideThe transport this connection uses.
serializerSerializerEntryConditionally — see belowThe serializer this connection uses. Required for every connection except direct ones; resolvable at connection-level or per-side.
authenticationAuthenticationEntryNoAuthentication 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.

FieldTypeRequiredDescription
nodeIdstringYes within its blockThe 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"
FieldTypeRequiredDescription
idstringYesThe 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.
handleTimeoutbooleanNo — defaults to falseWhether 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.
paramsmap of string → scalarNo — 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}"
FieldTypeRequiredDescription
idstringYes (see connection-level rule)The serializer id. Must match the artifact.id of a kind = "serializer" metadata file discoverable by the agent.
paramsmap of string → scalarNo — 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
FieldTypeRequiredDescription
idstringNo — defaults to noopThe failure-semantics implementation id. Must match the artifact.id of a kind = "failure-semantics" metadata file, unless left at the noop default.
timeoutduration string, e.g. "2s", "500ms"NoPer-attempt timeout.
handleTimeoutbooleanNo — defaults to falseWhether 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.
absoluteTimeoutduration string, e.g. "10s"NoHard ceiling on total execution time across all attempts, including retries.
maxRetryintegerNoMaximum number of retries. Total attempts made = maxRetry + 1.
paramsmap of string → scalarNo — 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}"
FieldTypeRequiredDescription
idstringNo — defaults to noopThe 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.
paramsmap of string → scalarNo — 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"
FieldTypeRequiredDescription
idstringNo — defaults to noopThe 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.
paramsmap of string → scalarNo — 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

Identity and properties for anything that takes part in an Itara-managed topology — components, contracts, events, and plugins alike.

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+"
SectionRequiredMeaningful forDescription
[artifact]Yesevery kindIdentity: what this artifact is, and its own version.
[runtime]Noevery kindWhat language/compiler built this artifact.
[itara]Noevery kindWhich version of the Itara spec/core this artifact targets.
[methods]NoapiPer-method properties of the contract — currently, which methods are non-idempotent.
[implemented-event-contracts]NocomponentWhich event contracts this component implements.
[contract]Noapi, eventsProperties of the contract as a whole — currently, its message format.
[serializers]NoapiWhich serializers this artifact was compiled with support for.
[serializer]NoserializerWhat this serializer implementation is and can do.
[transport]NotransportWhat this transport implementation is and can do.
[failure-semantics]Nofailure-semanticsWhat this failure-semantics implementation can do.
[authentication]NoauthenticationWhat kind of authentication mechanism this is.
[authorization]NoauthorizationWhat kind of authorization mechanism this is.
[api-dependencies]NocomponentWhich 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"
FieldTypeRequiredDefaultDescription
kindstringYes—The artifact’s kind: component, api, events, transport, serializer, observer, failure-semantics, authentication, authorization, context-handler.
idstringYes—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").
versionstringNo""This artifact’s own version.
api-versionstringNo""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"
FieldTypeRequiredDefaultDescription
languagestringNo""Implementation language, e.g. "java", "rust".
compilerstringNo""Compiler/language version used to build the artifact, e.g. "21", "1.78+".

[itara] (optional)

[itara]
spec-version = "0.1"
core-version = "0.1+"
FieldTypeRequiredDefaultDescription
spec-versionstringNo""Version of the Itara spec this artifact was built against.
core-versionstringNo""Version of the Itara core this artifact was built against.

[methods] (optional — api artifacts)

[methods]
non-idempotent = ["divide", "transfer", "placeOrder"]
FieldTypeRequiredDefaultDescription
non-idempotentarray of stringsNo[]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" },
]
FieldTypeRequiredDefaultDescription
contractsarray of { id, version }No[]Event contracts this component implements.

Each entry:

FieldTypeRequiredDescription
idstringYesFull contract reference: <events-artifact-id>/<contract-id>, e.g. "order-events/order-placed".
versionstringYesVersion 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"
FieldTypeRequiredDefaultDescription
message-formatstringNo""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" },
]
FieldTypeRequiredDefaultDescription
supportedarray of { id, version }No[]Serializers this API artifact was compiled with support for.

Each entry:

FieldTypeRequiredDescription
idstringYesMatches a serializer’s artifact.id, e.g. "json", "protobuf".
versionstringYesA 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"]
FieldTypeRequiredDefaultDescription
typestringNoabsent/nullThe serialization category this implementation handles — e.g. "json", "protobuf". Distinct from artifact.id, which is the unique identifier of the specific implementation.
capabilitiesSerializerCapabilitiesNosee belowSee below.

SerializerCapabilities

FieldTypeRequiredDefaultDescription
message-formatsarray of stringsNo[]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
FieldTypeRequiredDefaultDescription
typestringNoabsent/nullThe 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.
capabilitiesTransportCapabilitiesNosee belowSee below.

TransportCapabilities

FieldTypeRequiredDefaultDescription
native-call-timeoutbooleanNotrueWhether 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-interruptiblebooleanNotrueWhether 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
FieldTypeRequiredDefaultDescription
capabilitiesFailureSemanticsCapabilitiesNosee belowSee below.

FailureSemanticsCapabilities

FieldTypeRequiredDefaultDescription
supports-external-timeoutbooleanNofalseWhether 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"
FieldTypeRequiredDefaultDescription
typestringNoabsent/nullThe authentication mechanism category, e.g. "shared-secret", "mtls", "jwt". Distinct from artifact.id.

[authorization] (optional — authorization artifacts)

[authorization]
type = "rbac"
FieldTypeRequiredDefaultDescription
typestringNoabsent/nullThe 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" },
]
FieldTypeRequiredDefaultDescription
callsarray of { id, version }No[]Synchronous API contracts this component calls.

Each entry:

FieldTypeRequiredDescription
idstringYesMatches the artifact.id of the callee’s kind = "api" metadata file.
versionstringYesThe 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