Skip to content

The extension ladder

Yano’s core provides ordering, threshold finality, deterministic state, proofs, anchoring, effects, plugin lifecycle, health, and metrics. Application teams extend the application layer, not the consensus runtime.

The plugin framework exists so that a domain can evolve without anyone forking Yano. Its central rule is simple:

Choose the smallest extension that models your outcome.

Does a stock machine or profile already model the outcome?
├─ yes → configuration only (rung 0)
└─ no
Are all required components already available?
├─ yes → a small composite plugin (rung 1)
└─ no → a custom state-machine plugin (rung 2)

Independent SPIs cover effect executors, finalized-stream sinks, domain APIs, signers, sequencer mode, and L1 observers. Those are rung 3 — they run outside consensus, so they never affect the state root.

Select one built-in id or profile, identically on every member:

yano.app-chain.state-machine: kv-registry

No JAR, no build, no signing. This covers ordered-log, kv-registry, authenticated-map, approvals, balances, doc-trail, role-approvals, and the evidence profiles. See the recipe catalog.

For stock composite and role profiles, the profile identifier and its configuration digest become part of chain identity — so select them for a fresh chain, or through a governed activation.

Use this when every component you need already exists, but you need them arranged differently. A composite explicitly defines:

  • component ids and versions;
  • deterministic application order;
  • routed public topics;
  • per-component quotas;
  • workflow transitions between components; and
  • one committed profile identity and digest.

The Java class is intentionally small, and intentionally consensus-critical. YAML cannot dynamically insert arbitrary component plugins into a frozen profile, because two members discovering a different order would derive different roots. The order is code, reviewed and signed.

The effect-gated evidence and role-evidence presets are the reference implementations. Package the provider, manifest, service entry, and components in one reviewed bundle.

Only when you need genuinely new state or new rules.

public final class ShipmentStateMachine implements AppStateMachine {
@Override
public String id() {
return "shipment-v1";
}
@Override
public AdmissionResult validate(AppMessage message) {
// Bounded structural validation only; never perform I/O here.
return decodeSafely(message.getBody())
? AdmissionResult.accept()
: AdmissionResult.reject("invalid shipment command");
}
@Override
public void apply(AppBlock block, AppStateWriter state) {
// Deterministic bounded transitions only.
}
}

Contribute it through AppStateMachineProvider, add the service entry and the plugin manifest, then copy the bundle JAR into the configured plugin directory on every member. Yano itself is never recompiled for a JVM deployment.

Read Determinism rules before writing apply(), and Consensus rules before changing one that is already live.

SPIPurpose
Effect executorPerform an authorized external action after its finality gate.
Finalized-stream sinkDeliver finalized blocks to Kafka, a webhook, or your own system.
Domain API and committed queriesBounded read surfaces over your own state.
SignerExternal key custody or a KMS.
Sequencer modeAlternative proposer selection.
L1 observerReact to Cardano address deposits or metadata labels.

Create a custom executor when the action needs typed target aliases, authentication, polling, reconciliation, or a domain-specific receipt. Keep endpoints and secrets in node-local executor configuration, never in replicated effect payloads.

Keep plugin domain APIs read-only unless commands still enter through the authenticated app-chain submission path.

Every optional behavior a running node can independently select or manage crosses the plugin catalog boundary — stock state machines, capabilities, connectors, effects, observers, indexers, and product runtime behavior. All of it activates through PluginProviderRegistry and a schema-v1 plugin manifest. There is no raw ServiceLoader path, no direct host construction, no product switch, and no product-specific host CDI or REST activation.

Pure libraries, DTOs, clients, codecs, testkits, CLIs, on-chain validators, and deterministic helpers are ordinary JARs. They only become plugins if the host selects or manages them.

Runtime plugins additionally must:

  • publish dependency-complete bundles;
  • not embed host SPI classes;
  • declare compatible Yano API major and min/max levels; and
  • have bounded lifecycle cleanup.
  1. Scaffold, sign, install — the actual lifecycle, command by command.
  2. SPI and manifest — the three bounded contracts your JAR carries and the trust envelope over them.
  3. Consensus rules — what plugin code may do, and how to evolve it without forking a live chain.
  4. Testing and deployment — the testing ladder and operational expectations.

The hands-on version of all of this is Tutorial 8.