# Yano X — Full Documentation (concatenated for AI ingestion) > Single-file dump of the Yano X docsite, suitable for AI agent ingestion. yanoXVersion: 0.1.0-SNAPSHOT yanoVersion: 0.1.0-pre13 Generated: 2026-08-23T14:17:11.400Z Site: https://yanox.dev Repo: https://github.com/bloxbean/yano-x ## Key facts - **Yano X is an extension ecosystem, not a blockchain node.** The host is Yano (https://github.com/bloxbean/yano); Yano X adds optional state machines, capabilities, connectors, products, SDKs, and the batteries-included JVM distribution. The dependency direction is strictly `yano-x -> yano` and must never be inverted, nor satisfied with a composite Gradle build or a sibling source checkout. - **Versions:** Yano X `0.1.0-SNAPSHOT` builds against Yano `0.1.0-pre13`, Java 25. There is no published Yano X release yet; users build from source. - **Yano X is JVM-only.** Never add GraalVM/native-image tasks, reachability metadata, or native executables. `verifyJvmOnlyBuild` enforces this. - **Package names stay `com.bloxbean.cardano.yano.appchain.*`** even though repository and artifact names are `yano-x`. This is deliberate, not a leftover. - **The plugin directory property is `yano.plugins.directory`.** Never `yaci.plugins.directory`. - **Every optional runtime behavior crosses the plugin catalog boundary** and is activated through `PluginProviderRegistry` plus a schema-v1 plugin manifest. Never raw `ServiceLoader`, direct host construction, product switches, or product-specific host CDI/REST activation. - **Deterministic state-machine code may not** use wall-clock time, randomness, environment-dependent iteration order, network calls, or node-local mutable decisions. External work is emitted as an effect record and executed later by the effect runtime, never called during `apply`. - **Choose the smallest extension:** configuration (11 stock recipes, 43 capabilities) -> a small composite plugin -> a custom state-machine plugin. Only the last one adds new consensus semantics. - **The public CLI is `./yano.sh appchain …`**, shipped inside the Yano X JVM distribution. There is no separate `yano-x` executable. - **Changing deterministic application semantics is a versioned consensus upgrade**, not an ordinary rolling code change. ## Table of contents - [AI → Using Yano X with AI agents](https://yanox.dev/ai/) - [AI → Yano X AI Starter Pack](https://yanox.dev/ai/starter-pack/) - [Start here → What is an app chain?](https://yanox.dev/start-here/what-is-an-app-chain/) - [Start here → Why Yano X](https://yanox.dev/start-here/why-yano-x/) - [Start here → Build from source](https://yanox.dev/start-here/build-from-source/) - [Start here → Quickstart](https://yanox.dev/start-here/quickstart/) - [Concepts → Architecture](https://yanox.dev/concepts/architecture/) - [Concepts → Consensus and finality](https://yanox.dev/concepts/consensus-and-finality/) - [Concepts → State and proofs](https://yanox.dev/concepts/state-and-proofs/) - [Concepts → Effects](https://yanox.dev/concepts/effects/) - [Concepts → Cardano anchoring](https://yanox.dev/concepts/anchoring/) - [Concepts → Determinism rules](https://yanox.dev/concepts/determinism-rules/) - [Recipes → Recipe catalog](https://yanox.dev/recipes/) - [Recipes → Choosing a recipe](https://yanox.dev/recipes/choosing-a-recipe/) - [Plugin framework → The extension ladder](https://yanox.dev/plugins/) - [Plugin framework → Scaffold, sign, install](https://yanox.dev/plugins/scaffold-sign-install/) - [Plugin framework → SPI and manifest](https://yanox.dev/plugins/spi-and-manifest/) - [Plugin framework → Consensus rules for plugins](https://yanox.dev/plugins/consensus-rules/) - [Plugin framework → Testing and deployment](https://yanox.dev/plugins/testing-and-deployment/) - [Tutorials → App-Chain Tutorials](https://yanox.dev/tutorials/) - [Tutorials → Tutorial 1 — Your First App Chain](https://yanox.dev/tutorials/01-first-app-chain/) - [Tutorials → Tutorial 2 — A Provable Shared Registry](https://yanox.dev/tutorials/02-registry-and-proofs/) - [Tutorials → Tutorial 3 — Choose a Stock State Machine](https://yanox.dev/tutorials/03-stock-state-machines/) - [Tutorials → Tutorial 4 — Publish and Verify Immutable Evidence](https://yanox.dev/tutorials/04-evidence-publication/) - [Tutorials → Tutorial 5 — Generic Domain Actors and Role-Aware Approval](https://yanox.dev/tutorials/05-domain-role-approvals/) - [Tutorials → Tutorial 6 — Invoke an External HTTP Endpoint Safely](https://yanox.dev/tutorials/06-webhook-effects/) - [Tutorials → Tutorial 7 — Connect an App Proof to Cardano](https://yanox.dev/tutorials/07-anchors-and-verification/) - [Tutorials → Tutorial 8 — Extend Yano Without Forking It](https://yanox.dev/tutorials/08-plugins-and-composites/) - [Tutorials → Tutorial 9 — From Local Demo to a Permissioned Pilot](https://yanox.dev/tutorials/09-from-demo-to-pilot/) - [State machines → App-Chain State-Machine References](https://yanox.dev/state-machines/) - [State machines → `approvals` State Machine](https://yanox.dev/state-machines/approvals/) - [State machines → Authenticated-map value validation](https://yanox.dev/state-machines/authenticated-map-validation/) - [State machines → `authenticated-map` State Machine](https://yanox.dev/state-machines/authenticated-map/) - [State machines → `balances` State Machine](https://yanox.dev/state-machines/balances/) - [State machines → `doc-trail` State Machine](https://yanox.dev/state-machines/doc-trail/) - [State machines → `kv-registry` State Machine](https://yanox.dev/state-machines/kv-registry/) - [State machines → `role-approvals` State Machine](https://yanox.dev/state-machines/role-approvals/) - [Products → Products](https://yanox.dev/products/) - [Products → Evidence](https://yanox.dev/products/evidence/) - [Products → Cardano History](https://yanox.dev/products/cardano-history/) - [Products → eUTxO and ZK](https://yanox.dev/products/eutxo-and-zk/) - [Reference → CLI reference](https://yanox.dev/reference/cli/) - [Reference → REST API](https://yanox.dev/reference/rest-api/) - [Reference → Capability catalog](https://yanox.dev/reference/capabilities/) - [Reference → Configuration reference](https://yanox.dev/reference/configuration/) - [Reference → Modules and artifacts](https://yanox.dev/reference/modules/) - [Reference → Reference shelf](https://yanox.dev/reference/shelf/) - [Contributing → Developing Yano X](https://yanox.dev/contributing/) --- # AI --- ## Using Yano X with AI agents Source: https://yanox.dev/ai/ > How to point Claude Code, Cursor, Continue, ChatGPT, or any coding agent at Yano X — the starter pack, llms.txt, the machine-readable catalog, and the in-repo agent skill. Yano X is new enough that no model has trained on it. An agent asked to "write a Yano plugin" will confidently invent a `ServiceLoader` entry point, put `Instant.now()` inside `apply()`, and name the plugin directory property `yaci.plugins.directory`. All three are wrong, and none of them fail loudly. So this site publishes everything an agent needs, in formats agents can ingest. #### TL;DR | Artifact | When to use it | |---|---| | **[`/ai/starter-pack/`](https://yanox.dev/ai/starter-pack/)** | The single highest-leverage file. The extension ladder, determinism rules, the plugin lifecycle, the invariants that look like typos, and an error-to-fix table. Ingest this before generating anything. | | **[`/llms.txt`](https://yanox.dev/llms.txt)** | A curated index following [llmstxt.org](https://llmstxt.org/), with the key facts inline. Small and agent-friendly. | | **[`/llms-full.txt`](https://yanox.dev/llms-full.txt)** | Every page on this site concatenated as one markdown file — including all nine tutorials and the state-machine references. Ingest for full coverage. | | **[`/ai/catalog.json`](https://yanox.dev/ai/catalog.json)** | Machine-readable recipes, capabilities, runtime artifacts, Gradle modules, configuration properties, and versions. Generated from the repository at build time, so it never drifts. | All four are regenerated on every documentation build from the repository's own catalogs and pages. #### Per-tool setup ##### Claude Code Drop a `CLAUDE.md` into the root of your project: ```bash curl -o CLAUDE.md https://yanox.dev/ai/starter-pack.md ``` Claude Code reads `CLAUDE.md` at the start of every session, so the agent always has Yano X context. For multi-project setups, reference the hosted version from your global `~/.claude/CLAUDE.md`: ```markdown When working in a Yano or Yano X project, follow the rules at https://yanox.dev/ai/starter-pack/ ``` ##### Cursor ```bash mkdir -p .cursor/rules curl -o .cursor/rules/yano-x.mdc https://yanox.dev/ai/starter-pack.md ``` Cursor applies rules in `.cursor/rules/` automatically when working in the project. ##### Continue (VS Code / JetBrains) ```json { "contextProviders": [ { "name": "url", "params": { "url": "https://yanox.dev/llms-full.txt" } } ] } ``` ##### ChatGPT and Claude.ai on the web For a one-off conversation, paste this at the start: ```text I'm working with Yano X, the Java 25 JVM extension ecosystem for Yano app chains (application-specific replicated ledgers on Cardano). Read the Yano X AI Starter Pack at https://yanox.dev/ai/starter-pack and follow its rules strictly. In particular: - Dependency direction is strictly yano-x -> yano. Never propose a composite Gradle build or a source dependency on a Yano checkout. - Every optional runtime behavior is a plugin activated through PluginProviderRegistry plus a schema-v1 manifest. Never raw ServiceLoader. - Code inside apply() must be deterministic: no wall clock, no randomness, no ambient iteration order, no I/O. External work is an emitted effect. - Packages are com.bloxbean.cardano.yano.appchain.* even though artifacts are yano-x-*. The plugin directory property is yano.plugins.directory. - Yano X is JVM-only. Never add GraalVM or native-image tasks. ``` For long-lived projects, attach `https://yanox.dev/llms-full.txt` to your project files or custom GPT. ##### Any agent with tool access Point it at the catalog and let it read structured data instead of guessing: ```bash curl -s https://yanox.dev/ai/catalog.json | jq '.recipes[].id' curl -s https://yanox.dev/ai/catalog.json | jq '.capabilities[] | select(.category=="state")' curl -s https://yanox.dev/ai/catalog.json | jq '.configuration[] | select(.scope=="CONSENSUS_SHARED")' ``` #### The in-repo agent skill Yano X also ships a first-party agent skill in the distribution itself: ```text tooling/devtools/src/main/resources/appchain-dx/v1alpha1/skills/ configure-yano-appchain/ SKILL.md agents/openai.yaml ``` `configure-yano-appchain` covers creating, explaining, updating, validating, and diagnosing app-chain **projects** using the version-matched blueprint, capability catalog, and CLI. **Where the skill and the starter pack overlap, the skill wins** — it is version-matched to the binary you are running, and the starter pack quotes it rather than competing with it. Its workflow: 1. Run `./yano.sh appchain recipes` and `./yano.sh appchain capabilities` before choosing features. 2. Create or edit only `appchain.yaml`. Never hand-edit generated runtime files. 3. Run `appchain render`, then `appchain config validate --mode project`. 4. Run `appchain doctor` before startup or delivery. 5. Use `appchain diff` before applying a blueprint change, and `appchain drift` against running nodes. 6. Summarize selected capabilities, generated files, unresolved operator inputs, validation coverage, and warnings. #### Safety rules for agents These are non-negotiable, and they apply to human contributors too: - **Never** request, print, copy, infer, or commit secret values. Refer only to documented environment-variable or secret-provider names. - **Never** invent configuration keys, values, defaults, recipes, or compatibility claims. If a capability is unavailable in the release, report it as unsupported rather than working around the blueprint. - Keep blueprint, resolved-config, release, plugin-catalog, and consensus identities distinct. - Do not mutate a running node or call privileged runtime APIs unless the user explicitly asks. - Treat custom-plugin metadata as `PARTIAL` coverage unless Yano reports `FULL`. Verify signed metadata and its runtime-manifest binding before trusting a third-party artifact. #### Why the catalog is generated Every number, recipe id, capability id, artifact id, module path, and configuration property on this site is read at build time from files the Gradle build already maintains and release-gates: | Site output | Source of truth | |---|---| | Versions | `gradle.properties` | | Recipes | `appchain-recipe-catalog.json` | | Capabilities and runtime artifacts | `appchain-capability-catalog.json` | | Configuration properties | `appchain-first-party-metadata.json` | | Distributions | `appchain-release-capability-index.json` | | Gradle modules | `config/artifacts-v1.json` | An agent reading `/ai/catalog.json` is reading the repository, one build step removed. That is the whole point: hallucinated capability names are the most common failure mode, and this makes the real list cheap to fetch. --- ## Yano X AI Starter Pack Source: https://yanox.dev/ai/starter-pack/ > Everything an AI agent needs to work correctly in a Yano X repository on the first try. Ingest this before generating any Yano X code or configuration. > **Read this entire document before generating any Yano X code or > configuration.** It distills the architecture boundary, the extension ladder, > the determinism rules, the plugin lifecycle, and the invariants that AI agents > most reliably get wrong. Following it avoids the expensive failure mode: a > change that compiles, passes unit tests, and stops a cluster from finalizing. This pack is optimized for AI ingestion, not for human onboarding. Humans should start at [What is an app chain?](https://yanox.dev/start-here/what-is-an-app-chain/). --- #### 1. What Yano X is Yano X is the **Java 25, JVM-only extension ecosystem** for Yano app chains. An **app chain** is an application-specific replicated ledger run by a group of organizations: members agree on ordered commands, execute the same deterministic state machine, independently derive the same authenticated state root, prove records against it, and optionally settle that root on Cardano. Two repositories, one strict direction: ```text yano-x ──depends on──▶ yano ``` | Owned by **Yano** (the host) | Owned by **Yano X** | |---|---| | Cardano data node, chain sync, chainstate | Every state machine except `ordered-log` | | App-block sequencing, membership, threshold finality | Composition framework and governed profiles | | Authenticated state, MPF proofs, catch-up, replay | Kafka / S3 / IPFS / Cardano-payment connectors | | Cardano anchoring | Evidence, Cardano History, eUTxO and ZK products | | Effect runtime | Java client SDK, Spring starter, testkits, Studio | | Public plugin SPI, catalog, lifecycle, isolation | The batteries-included JVM distribution | | `ordered-log` — the only built-in state machine | | ##### Version and identity facts | Value | Current | |---|---| | Yano X version | `0.1.0-SNAPSHOT` | | Yano host version | `0.1.0-pre13` | | Maven group | `com.bloxbean.cardano` | | Java | `25` | | Base Yano JVM ZIP | [`yano-0.1.0-pre13.zip`](https://github.com/bloxbean/yano/releases/download/v0.1.0-pre13/yano-0.1.0-pre13.zip) | --- #### 2. Hard invariants — violating these is always wrong 1. **Never** add a source-checkout dependency, composite Gradle build, sibling task invocation, or generated-file dependency from Yano X to Yano. Consume an exact published Yano version and its matching ordinary JVM ZIP. 2. **Java packages stay `com.bloxbean.cardano.yano.appchain.*`** while repository and artifact names are `yano-x`. This is deliberate. Do not "fix" it. 3. **The plugin directory property is `yano.plugins.directory`.** Never `yaci.plugins.directory` — that spelling was removed and must not return. 4. **Yano X is JVM-only.** Never add GraalVM/native-image tasks, reachability metadata, or native executables. `verifyJvmOnlyBuild` enforces it. 5. **Every optional runtime behavior crosses the plugin catalog boundary.** Activation is `PluginProviderRegistry` plus a schema-v1 plugin manifest. Never raw `ServiceLoader` discovery by the host, direct host construction, product switches, or product-specific host CDI/REST activation. 6. **Runtime plugins** publish dependency-complete bundles, do not embed host SPI classes, declare compatible Yano API major and min/max levels, and have bounded lifecycle cleanup. 7. **Do not duplicate the artifact inventory.** Update `config/artifacts-v1.json` and its verification tests when module identity changes. 8. **The app-chain feature has not had a public release.** Remove obsolete adapters, aliases, and duplicate activation paths rather than preserving accidental compatibility. Preserve documented Cardano node, OrderedLog, wire/storage, proof, replay, and distribution invariants. 9. **Never stage or commit automatically.** Inspect `git status --short` before editing and preserve unrelated changes. --- #### 3. The extension ladder — always choose the smallest rung ```text Does a stock machine or profile already model the outcome? ├─ yes → rung 0: configuration only └─ no Are all required components already available? ├─ yes → rung 1: a small composite plugin └─ no → rung 2: a custom state-machine plugin ``` | Rung | What it is | Cost | |---|---|---| | 0 | Select a built-in id: `yano.app-chain.state-machine: kv-registry` | No JAR, no build | | 1 | A composite plugin declaring component ids, versions, deterministic order, routed topics, quotas, workflows, and one committed profile digest | Small but consensus-critical Java | | 2 | A custom `AppStateMachine` with new state and rules | Full consensus responsibility | | 3 | Effect executors, sinks, domain APIs, signers, sequencer modes, L1 observers | Outside consensus; never affects the state root | **Default assumption: the answer is rung 0 or 1.** Do not propose a custom state machine before checking `./yano.sh appchain recipes` and `./yano.sh appchain capabilities`. YAML cannot dynamically insert arbitrary component plugins into a frozen profile, because two members discovering a different order would derive different roots. Composite order is code, reviewed and signed. ##### Stock state machines (rung 0) `ordered-log` (host), `kv-registry`, `authenticated-map`, `approvals`, `balances`, `doc-trail`, `role-approvals`, plus the `evidence-v1-gated` and `role-evidence` profiles. Live recipe and capability lists: [`/ai/catalog.json`](https://yanox.dev/ai/catalog.json), [`/recipes/`](https://yanox.dev/recipes/), [`/reference/capabilities/`](https://yanox.dev/reference/capabilities/). --- #### 4. Determinism — the rules for anything inside `apply()` Every member executes the same messages and must derive the same state root **byte for byte**. Divergence does not heal; the chain stops finalizing. | Never | Because | Instead | |---|---|---| | `Instant.now()`, `System.currentTimeMillis()` | Different per member | Block height, or an L1 slot carried in the block | | `Math.random()`, `new Random()`, `UUID.randomUUID()` | Unreproducible | Derive from message bytes, a hash, or a sequence number | | Iterating `HashMap` / `HashSet` | Order varies across JVMs and histories | `TreeMap` / `LinkedHashMap`, or sort explicitly | | Any network call | Latency and content differ per member | Emit an effect | | Files, env vars, system properties | Node-local, therefore divergent | Consensus-shared config, or put it in the message | | Locale/charset defaults | `toLowerCase()`, `getBytes()` are platform sensitive | `Locale.ROOT`, `StandardCharsets.UTF_8` | | Floating point for values | Rounding and formatting traps | Integers, or `BigDecimal` with explicit scale and rounding | | `Object.hashCode()` / identity | Varies per run | Compare and key on canonical bytes | | Divergent exception handling | Different control flow per member | Validate deterministically; no-op or reject uniformly | | Threads or concurrency in `apply()` | Scheduling is not reproducible | Keep `apply()` single-threaded | | Writing keys under `~fx/` | Reserved for the effect system from genesis | Use your own namespace | | Hidden state in static fields or node-local storage | Not part of the authenticated root | Every write goes through the authenticated writer | **Reproducible ≠ identical.** A `HashMap` iteration is stable within one JVM run and differs across members: unit tests pass, the cluster stalls. Always validate on a real multi-node cluster. **Invalid finalized bytes must become deterministic no-ops**, never escaping exceptions. `validate()` at ingress is a hygiene filter, not a security boundary — a message can still reach `apply()` through catch-up. **Bound everything**: message byte length, decode depth and item count, collection sizes, state growth per transition, and work per block. --- #### 5. Effects — how to touch the outside world > A state machine never performs the action. It emits a record describing it. ```java @Override public void apply(AppBlock block, AppStateWriter writer, AppEffectEmitter effects) { for (AppMessage m : block.messages()) { Order o = decode(m.getBody()); writer.put(key(o.id()), o.toBytes()); if (o.isApproved()) { effects.emit(EffectIntent.of("webhook.post", o.fulfilmentJson()) .scope("orders/" + o.id()) // application idempotency scope .result(ResultPolicy.CHAIN) // outcome returns on-chain .gate(FinalityGate.CHAIN_DEFAULT) .expiryBlocks(1000) // deterministic timeout; mandatory .sourceMessageId(m.getMessageId()) .build()); } } } @Override public void onEffectResult(AppBlock block, EffectResult result, AppStateWriter writer) { // Deterministic incorporation of CONFIRMED / FAILED / CANCELLED / EXPIRED. } ``` Key facts: - Guarantee is **exactly-once incorporation, at-least-once execution**. Every executor and receiver must be idempotent. - Effects are **off by default**, and `yano.app-chain.effects.*` caps are **consensus parameters** — identical on every member or the root diverges. - Gates: `app-final` (block committed), `l1-anchored` (covered by a confirmed, stability-deep anchor), `zk-settled` (reserved). - **Expiry is mandatory** for `CHAIN` effects. `EXPIRED` ("nobody answered") is distinct from `FAILED` ("the target answered no"). - A result is a **member attestation**, not a verified fact. Narrow it with `yano.app-chain.effects.result.signers`. For L1-visible facts prefer an L1 observer. - Duplicate, late, malformed, unknown, or out-of-window results are deterministic no-ops. A result can never stall the chain. - Endpoints and credentials go in **node-local** executor configuration, never in replicated effect payloads. Bundled: `webhook.post` executor and finalized webhook sink. First-party optional plugins: Kafka, S3-compatible `object.put`, `ipfs.pin`, `cardano.payment`. --- #### 6. The plugin lifecycle — the exact commands ```bash ### 1. Scaffold. Modes: state-machine | composite-role | effect-executor | sink ./yano.sh appchain plugin scaffold --mode state-machine --id shipment \ --package com.example.shipment --output shipment-plugin ### 2. Implement and test. ### 3. Sign the catalog, runtime manifest, and optional config metadata. ### The 32-byte seed stays outside the repo and is passed BY FILE ONLY. ./yano.sh appchain plugin sign \ --catalog --runtime-manifest \ --seed-file /secure/publisher.seed --key-id example-release-2026 \ --output ### 4. Build and validate. Loads no plugin code. ./yano.sh appchain plugin validate --trust-key = ### 5. Pin into a project. ./yano.sh appchain init --non-interactive --recipe custom-plugin \ --network devnet --members 3 --runtime jvm \ --capability state:shipment --plugin-jar \ --trust-key = --output shipment-chain ### 6. Check readiness against a real distribution. ./yano.sh appchain doctor shipment-chain --distribution /opt/yano-x ### 7. Copy the exact pinned JAR into plugins/ on EVERY member, then validate. tools/yano-plugins/bin/yano-plugins validate plugins/*.jar ``` The JAR carries three independent bounded contracts plus a signature: ```text META-INF/yano/plugins/.json runtime contributions + API levels META-INF/yano/appchain-config-metadata-v1.json typed configuration (optional) META-INF/yano/appchain-component-catalog-v1.json selectable capabilities META-INF/yano/appchain-component-catalog-v1.sig.json Ed25519 trust envelope ``` **Signing authenticates bytes; it does not approve code** and does not elevate a custom component to `BUNDLED`, `stable`, or native. Custom entries stay JVM-only `REFERENCE` or `EXPERIMENTAL`. Release id, namespace, and artifact collisions fail closed. The custom state-machine shape: ```java public final class ShipmentStateMachine implements AppStateMachine { @Override public String id() { return "shipment-v1"; } @Override public AdmissionResult validate(AppMessage message) { 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. Yano is never recompiled for a JVM deployment. --- #### 7. Evolving a live chain — versioning discipline A change to deterministic semantics is **never** an ordinary rolling code change. Semantic changes include: state encoding or key layout, transition logic, validation outcomes, what a transition emits, a commitment profile or component order, a proof subject descriptor, and genesis-selected configuration. Chain identity is pinned by: ```text (commitment-profile, format-fingerprint, genesis-id) ``` A retained `genesis-id` is never regenerated. Three legitimate options: 1. **Governed profile activation** — stage the reviewed current and dormant targets on every member, then threshold-authorize one exact digest and a future activation height. Editing YAML or swapping a JAR alone changes nothing. 2. **A new component id or namespace**, with a migration plan, for incompatible state. 3. **A new chain**, when identity itself must change. **Never silently change semantics behind an existing machine or component id.** Rolling out edited transition logic member by member stalls the chain during the rollout and makes historical replay disagree with signed roots afterwards. That is unrecoverable without a new chain. --- #### 8. Configuration scope — the mistake that stalls clusters | Scope | Meaning | Getting it wrong | |---|---|---| | `CONSENSUS_SHARED` | Identical on every member | The root diverges; the chain stops finalizing | | Node-local | Ports, storage, credentials, executor placement | Only that node is affected | Consensus-shared includes: the state-machine id, the composite profile digest, all `effects.*` caps, value formats, quotas, and `message.enforce-sender-seq`. `NEW_CHAIN_REQUIRED` change policy means the value is part of chain identity. Five secret classes, kept separate: member signing keys, business-actor keys, API keys, effect/connector credentials, anchor wallet funds. Never place a credential in consensus-shared configuration or a replicated effect payload. --- #### 9. Build and test commands ```bash ### User track: a clean clone, one command. No flags, no Yano checkout. ./gradlew clean build -PskipSigning=true ### -> distribution/jvm/build/distributions/yano-x-jvm-.zip ### Contributor track, coordinated Yano + Yano X development: ### in the Yano repo: ./gradlew publishToMavenLocal :app:yanoDistZip -PskipSigning=true --no-parallel ### in yano-x: ./gradlew test verifyArtifactInventory verifyJvmOnlyBuild \ -PyanoVersion= -PuseMavenLocal=true --offline ### Focused gates ./gradlew :state-machines:stdlib:test -PyanoVersion= ./gradlew :tooling:devtools:test -PyanoVersion= ./gradlew integrationTest -PyanoVersion= ./gradlew cryptoTest -PyanoVersion= ``` `mavenLocal()` is disabled unless `-PuseMavenLocal=true`. For a released non-SNAPSHOT `yanoVersion`, the base Yano JVM ZIP resolves from its GitHub release automatically; `-PyanoJvmDist` only overrides that. Run `verifyArtifactInventory` for any module, publication, manifest, bundle, or contribution change; `verifyJvmOnlyBuild` for build topology changes; `distributionCheck` or a clean `build` for dependency, bundle, class-isolation, launch, or packaging changes. **Do not stop at unit tests** when a plugin, catalog, distribution, persistence, consensus, proof, anchor, or cross-node behavior changed. Validate a real multi-node cluster: identical chain height, root, profile, genesis, and capability-manifest digest, plus finality certificates, proof retrieval, catch-up, restart, and anchor state. --- #### 10. Coding conventions - Java 25, four-space indentation, lines at most 120 characters. - Prefer package imports to fully qualified names. - SLF4J for logging. - JUnit 5, Mockito, AssertJ. Unit tests in `src/test/java`; integration tests in the configured integration source sets, named `*IT`. - License: MIT. --- #### 11. Error-to-fix table | Symptom | Root cause | Fix | |---|---|---| | Cluster stops finalizing after a deploy | Members run different bundles or different `CONSENSUS_SHARED` values | `./yano.sh appchain drift --peer `; align bundles and config | | Root parity holds locally, fails on a cluster | Reproducible but not identical — usually `HashMap` iteration or a wall clock | See §4; re-run under `@AppChainCluster` | | `verifyJvmOnlyBuild` fails | A native-image build or distribution task crept in | Remove it. Yano X is JVM-only by decision | | `verifyArtifactInventory` fails | Module identity changed without updating `config/artifacts-v1.json` | Update the inventory and its verification tests | | Build tries to reach Maven Local | `-PuseMavenLocal=true` needed but that is the contributor track | Users need no flags at all | | Missing release asset for `yanoVersion` | SNAPSHOT or staged Yano version | Supply `-PyanoJvmDist` with the exact matching ZIP | | `appchain doctor` reports incompatibility | Pointed at a native distribution | Yano X plugins target the JVM host | | Node refuses to start with a catalog error | Two bundles provide the same contribution (classically both eUTxO runtimes) | Install exactly one; the other lives in `optional-plugins/` | | `POST /messages` returns 429 | Pending pool full — backpressure working | Back off and retry; tune `pool.max-messages` deliberately | | Message rejected, `drops.stale_seq` | Per-sender seq at or below the last finalized seq — a replay | Do not reuse envelopes; seq gaps are fine and meaningless | | Product routes return 404, CLI exit code 3 | Cardano History has no finalized fact yet | Wait for the next stable L1 epoch transition. Never synthesize a value | | Proof says `INTERNAL_CONSISTENCY_ONLY` | The root is not pinned by the caller, a finality policy, or a checked L1 output | Pin the root, or verify the Cardano output independently | | Proof unavailable for an old height | Pruned past `oldestProvableHeight` | Unavailable is not evidence of absence | | Effect ran twice externally | Execution is at-least-once by design | Make the receiver idempotent under the supplied identity | | Effect never completed | No result within the window | It becomes `EXPIRED` deterministically; handle it in `onEffectResult` | --- #### 12. Where to look things up | Need | Source | |---|---| | Live recipes, capabilities, modules, config properties | [`/ai/catalog.json`](https://yanox.dev/ai/catalog.json) | | Everything on this site as one file | [`/llms-full.txt`](https://yanox.dev/llms-full.txt) | | Version-matched truth for a build | `./yano.sh appchain recipes`, `./yano.sh appchain capabilities --format json` | | Project workflow for agents | The in-repo `configure-yano-appchain` skill under `tooling/devtools/src/main/resources/appchain-dx/v1alpha1/skills/` — **it wins where it overlaps this pack** | | Exhaustive reference | [Reference shelf](https://yanox.dev/reference/shelf/) | | Plugin SPI contract | ADR-011, in the repository's `adr/app-layer/` directory | | Repository invariants | [`AGENTS.md`](https://github.com/bloxbean/yano-x/blob/main/AGENTS.md) | --- #### 13. Agent safety rules - Never request, print, copy, infer, or commit secret values. Refer only to documented environment-variable or secret-provider names. - Never invent configuration keys, values, defaults, recipes, or compatibility claims. Report an unavailable capability as unsupported. - Keep blueprint, resolved-config, release, plugin-catalog, and consensus identities distinct. - Do not mutate a running node or call privileged runtime APIs unless explicitly asked. - Treat custom-plugin metadata as `PARTIAL` unless Yano reports `FULL`. - Never delete retained clusters, state, keys, or staging repositories without reviewing the exact path and obtaining explicit authorization. `stop` preserves state; `reset --yes` is destructive. - Preprod operations submit real test-network transactions and spend test ADA. Require explicit authorization before deployment, anchor bootstrap, settlement bootstrap, or smoke traffic. - Do not modify the sibling Yano repository as a side effect of a Yano X task. --- # Start here --- ## What is an app chain? Source: https://yanox.dev/start-here/what-is-an-app-chain/ > An app chain is an application-specific replicated ledger run by a group of organizations, with deterministic state, threshold finality, proofs, and optional Cardano settlement. An **app chain** is an application-specific, replicated ledger that a group of organizations runs together. Members agree on the order of application messages, execute the same deterministic state machine over them, and each independently derive the same authenticated state root. Any record can then be proved against that root, and the root itself can be settled on Cardano. The shortest description: > A programmable, multi-party application ledger with deterministic state, > threshold finality, proofs, Cardano anchoring, and controlled external > actions. #### The problem it solves Many business processes span several organizations and systems, and today they usually look like this: - each participant keeps its own database; - one operator controls the shared API or message broker; - audits reconstruct history after the fact; - external actions are hard to tie back to an agreed business decision; and - putting every application event directly on a public blockchain is too slow, too costly, too public, or too inflexible. An app chain gives the participants a shared application layer without turning every business operation into a Cardano transaction. ```mermaid flowchart LR A[Organization A] --> Y B[Organization B] --> Y C[Organization C] --> Y subgraph Y[Yano app chain] M[Signed application messages] S[Deterministic state machine] F[Threshold-final app blocks] P[State root and proofs] M --> S --> F --> P end P --> L1[Cardano anchor] F --> FX[Effect runtime] FX --> K[Kafka] FX --> O[Object storage] FX --> I[IPFS] FX --> E[ERP, webhook, custom system] ``` The app chain does not replace Cardano. It supplies application-specific execution and coordination; Cardano supplies an independently observable settlement and timestamping layer for the state the app chain commits to. #### What makes it an app chain rather than a shared database | Characteristic | What it means | Why it matters | |---|---|---| | Signed participation | Members and messages have cryptographic identities. | The system knows who submitted and who approved an action. | | Deterministic execution | Every member applies the same messages through the same state machine. | Honest members derive the same state root byte for byte. | | Threshold finality | A block is final only after the configured member threshold signs it. | No single database or broker operator decides history. | | Hash-linked blocks | Finalized blocks commit to prior history. | Reordering or rewriting history is detectable. | | Provable state | State lives in an MPF trie with inclusion and exclusion proofs. | A client verifies a record against a root without trusting one node. | | Cardano anchoring | A finalized root can be written to a metadata or script anchor. | Auditors can bind app-chain evidence to public L1 history. | | Deterministic effects | External work is authorized by emitting immutable effect records. | Network I/O never contaminates consensus execution. | | Catch-up and recovery | Restarted or joining members fetch and independently verify history. | Recovery does not require trusting a database copy. | | Plugins and presets | State machines, executors, sinks, APIs, and queries are extensible. | A domain can evolve without forking the consensus framework. | #### The end-to-end flow ```mermaid sequenceDiagram autonumber participant Client participant Proposer as Proposer participant Members participant State as Deterministic state participant Effects as Effect runtime participant External as External system participant Cardano Client->>Proposer: Submit signed application message Proposer->>Members: Propose ordered app block Members->>State: Re-execute the same state transition State-->>Members: Identical post-state root Members-->>Proposer: Sign votes Proposer->>Members: Publish threshold finality certificate Members->>Effects: Finalized effect becomes eligible Effects->>External: Execute idempotent external action External-->>Effects: Acknowledgement or receipt Effects->>Members: Submit signed effect result Members->>State: Incorporate the result exactly once Proposer->>Cardano: Anchor certified state root ``` 1. A client submits a signed message to an application topic. 2. The proposer orders accepted messages into an app block. 3. Every member validates the block and independently executes the state machine. 4. Members sign only the state root they derived themselves. 5. The block is final once the configured signature threshold is met. 6. Clients query state and request proofs bound to that finalized root. 7. If the transition emitted an effect, an executor performs it once its finality gate is satisfied. 8. For tracked effects, the signed result returns through the chain and is incorporated deterministically. 9. An optional Cardano anchor commits the certified root to L1. The trust model is **fail closed**. Envelope signatures, membership, vote signatures, and certificate thresholds are verified on every node, always. A non-member's messages are dropped, a non-sequencer's blocks are never finalized, and a tampered block fails the state-root re-execution check. #### Vocabulary | Term | Meaning | |---|---| | **Chain id** | The name of your app chain. One group of participants = one chain id. A node can host several chains. | | **Member** | A participant identified by an Ed25519 public key. Only members' messages are accepted, and members co-sign blocks. The v1 profile supports at most 32 members. | | **Proposer / sequencer** | The member that orders messages into blocks — either a configured fixed proposer, or the member deterministically selected for the current L1-slot window in rotating mode. | | **Threshold** | How many member signatures a finality certificate requires. | | **App message** | An envelope with an opaque, sender-signed body. The framework never parses the body; only the state machine interprets it. | | **Topic** | An optional sub-stream label inside a chain, for routing and filtering. | | **App block** | An ordered batch of messages plus the post-state MPF root and a finality certificate, hash-linked to the previous block. | | **State root** | The Merkle Patricia Forestry root after applying a block. Identical on every member, anchorable to L1, and provable. | | **State machine** | The only component that interprets message bodies. | | **Effect** | An immutable record emitted by a transition, authorizing external work that an executor performs after finality. | | **Anchor leader** | The single node that builds, pays for, and submits anchor transactions. A coordination role, not a trust point. | #### When an app chain is the right answer An app chain fits when **all** of the following are true: - several organizations must agree on the same sequence of application records; - no single participant should own the authoritative database; - someone will later need to prove a specific record, not merely be told about it; - the volume, privacy, or cost profile makes putting each event directly on a public chain impractical; and - some external systems must act on decisions, but only after those decisions are final. It is **not** the right answer for a single-organization application with no external verifier, for high-frequency data with no dispute surface, or for anything that genuinely needs permissionless participation. #### Where Yano and Yano X fit Yano is the host: a Cardano data node with a minimal app-chain runtime, consensus, proofs, anchoring, the effect system, the plugin SPI, and `ordered-log` as its only built-in state machine. **Yano X** is the extension ecosystem on top: the stock state machines, the composition framework, connectors, products, SDKs, tooling, and the batteries-included JVM distribution. Next: [Why Yano X](https://yanox.dev/start-here/why-yano-x/). --- ## Why Yano X Source: https://yanox.dev/start-here/why-yano-x/ > Yano is the app-chain host; Yano X is the JVM extension ecosystem that turns it into something you configure rather than build. This page draws the boundary. Yano and Yano X are two repositories with one strict dependency direction: `yano-x → yano`. Understanding which side owns what is the single most useful thing to know before you start. #### The boundary ```mermaid flowchart TB subgraph X["Yano X — JVM extensions (this project)"] SM["Stock state machines
kv-registry · approvals · balances
doc-trail · authenticated-map · role-approvals"] COMP["Composition framework
deterministic composite profiles"] CONN["Connectors
Kafka · S3 · IPFS · Cardano payments"] PROD["Products
Evidence · Cardano History · eUTxO/ZK"] SDK["SDKs, testkits, CLIs, Studio"] DIST["Batteries-included JVM distribution"] end subgraph Y["Yano — the host"] NODE["Cardano data node"] CONS["App-block sequencing
membership · threshold finality"] STATE["Authenticated state · MPF proofs"] ANCH["Cardano anchoring"] EFF["Effect runtime"] SPI["Public plugin SPI · catalog · lifecycle"] OL["ordered-log — the only built-in state machine"] end X -->|depends on| Y ``` | Concern | Owned by | |---|---| | Cardano L1 node, chain sync, chainstate | Yano | | App-block ordering, membership, threshold finality certificates | Yano | | Authenticated state, MPF, proof subjects, catch-up, replay | Yano | | Anchoring to Cardano metadata or a threshold script | Yano | | Effect runtime: gates, retries, receipts, result incorporation | Yano | | Plugin SPI, manifest schema, catalog validation, lifecycle, isolation | Yano | | `ordered-log` | Yano | | Every other state machine | **Yano X** | | Composite profiles and governed profile evolution | **Yano X** | | Kafka / S3 / IPFS / Cardano-payment executors and sinks | **Yano X** | | Evidence, Cardano History, eUTxO and ZK products | **Yano X** | | Java client SDK, Spring Boot starter, testkits, App-Chain Studio | **Yano X** | | The JVM distribution that ships all of the above pre-installed | **Yano X** | #### What "batteries included" actually means Building Yano X produces two archives: - **`yano-x-jvm-.zip`** — the standard Yano JVM distribution with the Yano X plugin bundles already laid out, plus identity manifests for both projects. This is what you run. - **`yano-x-plugin-pack-.zip`** — just the plugin bundles and a checksummed manifest, for adding Yano X to a Yano distribution you already operate. The default `plugins/` directory is a deliberate, conflict-free **selection**, not a copy of every published bundle. Alternative implementations that would claim the same contribution live under `optional-plugins/` and are opted into explicitly. #### Everything optional crosses the plugin boundary This is the architectural rule that shapes the whole project: any behavior a running node can independently select or manage is a plugin. - Activation goes through `PluginProviderRegistry` and a schema-v1 plugin manifest. - There is no raw `ServiceLoader` path, no direct host construction, no product switch in the host, and no product-specific host CDI or REST activation. - Runtime plugins publish dependency-complete bundles, never embed host SPI classes, declare the Yano API major and min/max levels they are compatible with, and have bounded lifecycle cleanup. 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. #### JVM only, on purpose Yano X is JVM-only. There are no GraalVM or native-image tasks, no reachability metadata, and no native executables; the build gate `verifyJvmOnlyBuild` rejects them. Yano's native image is core-only and starts with `ordered-log`, but it does not load Yano X bundles — a native distribution reports a direct incompatibility if you point a Yano X project at it. A future native extension model would need its own architecture decision and a build-time composition contract. #### Two names that look like mistakes but are not Two invariants surprise almost everyone, including coding agents: - **Java packages stay `com.bloxbean.cardano.yano.appchain.*`** while repository and artifact names are `yano-x`. The repository split deliberately did not rename the app-chain technical domain. - **The plugin directory property is `yano.plugins.directory`.** The older `yaci.plugins.directory` spelling is gone and must not come back. #### What you actually work with Whatever you build, the surface is the same: - `./yano.sh appchain …` — the public CLI, shipped inside the distribution. There is no separate `yano-x` executable. - The REST API and SSE stream on each node. - The Java client SDK (and a Spring Boot starter) for typed commands and proof verification. - The [App-Chain Studio](https://yanox.dev/studio/) for building and exporting a blueprint visually. Next: [Build from source](https://yanox.dev/start-here/build-from-source/). --- ## Build from source Source: https://yanox.dev/start-here/build-from-source/ > Yano X has no published release yet. Build the batteries-included JVM distribution from a clean clone with one Gradle command — no second checkout and no flags. Yano X has **no published release**, so the install path is a source build. That sounds heavier than it is: from a clean clone it is one command, because the build resolves the matching Yano host distribution for you. **TWO DIFFERENT BUILD TRACKS:** This page is the **user** track: build the distribution, run it, move on. If you are changing Yano X itself — or changing Yano and Yano X together — read [Developing Yano X](https://yanox.dev/contributing/) instead. Mixing the two is the most common source of confusion, because the contributor track needs Maven Local and several `-P` flags that you do not. #### Prerequisites | Requirement | Notes | |---|---| | **Java 25** | Required. Check with `java -version`. | | Git | To clone the repository. | | ~2 GB disk | The base Yano JVM ZIP alone is around 280 MB, plus Gradle caches. | | `bash`, `curl`, `jq`, `python3`, `openssl` | Used by the cluster launcher and the tutorials. | | Docker Desktop | Only for the full evidence/connector demo. | Gradle itself does not need to be installed — the repository ships a wrapper. #### Build ```bash git clone https://github.com/bloxbean/yano-x.git cd yano-x ./gradlew clean build -PskipSigning=true ``` That is the whole thing. No `-PyanoVersion`, no `-PuseMavenLocal`, no `-PyanoJvmDist`. ##### Why it works with no flags `gradle.properties` pins the exact Yano host line Yano X is built against: | Value | Current | |---|---| | Yano X version | `0.1.0-SNAPSHOT` | | Yano host version | `0.1.0-pre13` | | Maven group | `com.bloxbean.cardano` | | Java | `25` | | Base Yano JVM ZIP | [`yano-0.1.0-pre13.zip`](https://github.com/bloxbean/yano/releases/download/v0.1.0-pre13/yano-0.1.0-pre13.zip) | Because that `yanoVersion` is a released, non-SNAPSHOT version, the distribution tasks resolve and cache the matching ordinary Yano JVM ZIP from the corresponding `bloxbean/yano` GitHub release automatically. The URL convention is: ```text https://github.com/bloxbean/yano/releases/download/v/yano-.zip ``` You only need `-PyanoJvmDist` to *override* that release asset with a local or staged ZIP. `-PskipSigning=true` skips artifact signing, which is only relevant when publishing. #### What you get Two reproducible archives under `distribution/jvm/build/distributions`: | Archive | Contents | |---|---| | `yano-x-jvm-.zip` | The standard Yano JVM distribution, the default and optional Yano X plugin layout, and identity manifests for both projects. **This is what you run.** | | `yano-x-plugin-pack-.zip` | The plugin bundles only, with a checksummed manifest — for adding Yano X to a Yano distribution you already operate. | Both include the repository `LICENSE` and a normalized CycloneDX 1.6 SBOM under `sbom/`. The combined JVM archive also preserves the host's license as `LICENSE.yano` and its SBOM as `sbom/yano.cdx.json`. Inside the JVM archive, everything sits under a single `yano-x-jvm-/` directory (a `-SNAPSHOT` suffix is stripped from that directory name, so a `0.1.0-SNAPSHOT` build unpacks to `yano-x-jvm-0.1.0/`): ```text yano-x-jvm-/ ├── yano.sh # the public CLI and node launcher ├── yano.jar ├── config/ # chain definitions and network genesis │ └── schema/ # blueprint, catalog, and lock schemas ├── plugins/ # the default, conflict-free bundle selection ├── optional-plugins/ # alternatives you opt into explicitly ├── tools/ │ ├── yano-plugins/ # plugin catalog validator (from the Yano host) │ └── yano-appchain/ # the offline engine behind `yano.sh appchain` ├── studio/ # App-Chain Studio, the blueprint builder ├── skills/configure-yano-appchain/ # the first-party AI agent skill ├── appchain-cluster/ # the single-host cluster launcher scripts ├── examples/evidence/ # the evidence demo harness and runner ├── docs/ # the repository documentation set ├── sbom/ └── LICENSE, LICENSE.yano ``` Two of those are easy to miss: `studio/` is the same blueprint builder [hosted on this site](https://yanox.dev/studio/), and `skills/configure-yano-appchain/` is a version-matched agent skill — see [Using Yano X with AI agents](https://yanox.dev/ai/). #### Unpack and check ```bash unzip distribution/jvm/build/distributions/yano-x-jvm-*.zip -d ~/yano-x cd ~/yano-x/yano-x-jvm-* ./yano.sh appchain help ./yano.sh appchain recipes ./yano.sh appchain capabilities ``` `recipes` and `capabilities` print what this exact build can do. They are the authoritative answer for your version — the [recipe catalog](https://yanox.dev/recipes/) and [capability catalog](https://yanox.dev/reference/capabilities/) on this site are generated from the same source files, but the binary in your hands always wins. You are ready for the [Quickstart](https://yanox.dev/start-here/quickstart/). #### The default plugin selection `plugins/` is an activatable selection, not an indiscriminate copy. All runtime bundles are published and versioned independently; the default distribution activates every one that can coexist. The standard eUTxO runtime and the eUTxO ZK runtime both intentionally provide the `app-state-machine/eutxo-ledger` contribution, so exactly one of them may be installed. Copying both into `plugins/` is a hard catalog error. To switch to the ZK implementation, remove the standard eUTxO ledger bundle, copy the ZK runtime bundle from `optional-plugins/` into `plugins/` **on every member**, and validate the result: ```bash tools/yano-plugins/bin/yano-plugins validate plugins/*.jar ``` #### Building against a different Yano version If you need a Yano line other than the pinned one — a newer pre-release, or a staged build — pass it explicitly: ```bash ### A different released Yano version: the ZIP still resolves from its release. ./gradlew clean build -PyanoVersion= -PskipSigning=true ### A staged Maven repository plus an exact ZIP. ./gradlew clean build \ -PyanoVersion= \ -PyanoRepository=/absolute/path/to/yano-staging \ -PyanoJvmDist=/absolute/path/to/yano-.zip \ -PskipSigning=true ``` The Maven version and the JVM ZIP identity must match exactly. `verifyYanoInputs` rejects a base ZIP whose root directory, JAR implementation version, or distribution manifest disagrees with `yanoVersion`. Snapshot and locally staged versions never fall back to a GitHub release asset and must supply `yanoJvmDist`. #### Troubleshooting | Symptom | Cause and fix | |---|---| | `Unsupported class file major version` or a Java-version error | Yano X requires Java 25. Point `JAVA_HOME` at a 25 JDK. | | The build tries to reach Maven Local and fails | `mavenLocal()` is disabled unless `-PuseMavenLocal=true`. That flag belongs to the contributor track; you should not need it. | | A missing release asset for `yanoVersion` | You are on a SNAPSHOT or staged Yano version. Supply `-PyanoJvmDist` with the exact matching ZIP. | | `verifyJvmOnlyBuild` fails | Something introduced a native-image build or distribution task. Yano X is JVM-only by decision. | | A native distribution is rejected by `appchain doctor` | Yano X plugins target the JVM host. Use the `yano-x-jvm` archive. | For the full matrix of build tasks and verification gates, see [Build and test](https://github.com/bloxbean/yano-x/blob/main/docs/BUILD_AND_TEST.md) and [Build distributions](https://github.com/bloxbean/yano-x/blob/main/docs/BUILD_DISTRIBUTIONS.md). --- ## Quickstart Source: https://yanox.dev/start-here/quickstart/ > Start a three-member app chain on a self-contained devnet, submit a business event, confirm every member agrees, and pull a proof — in about ten minutes. Ten minutes, three members, one agreed state root, and one verifiable proof. No external Cardano node, no wallet, no funds, no Kafka, no plugin of your own. This page assumes you have already [built and unpacked the distribution](https://yanox.dev/start-here/build-from-source/). Every command runs from the directory that contains `yano.sh`. ```bash cd ~/yano-x/yano-x-jvm-* ./yano.sh appchain help ``` #### 1. Start three members ```bash export YANO_CLUSTER_DIR=/tmp/yano-quickstart ./yano.sh appchain cluster start 3 ``` **ALWAYS SET `YANO_CLUSTER_DIR` EXPLICITLY:** Without it the launcher uses its default location, which may already hold a cluster you care about. Setting it makes this quickstart's state disposable and keeps `stop`, `clean`, and `reset` pointed somewhere harmless. The launcher starts a self-contained Cardano devnet and a three-member app chain: - node 0 is the local L1 block producer and the app-chain proposer; - nodes 1 and 2 are app-chain voting members; - three chains come up — `orders-chain` (`ordered-log`), `registry-chain` (`kv-registry`), and `effects-chain` (`approvals`). The expected HTTP ports are `7070`, `7071`, and `7072`. If those are busy the launcher prints the range it chose instead; use that range below. #### 2. Confirm the members agree ```bash ./yano.sh appchain cluster status ``` Look for: ```text orders-chain: AGREED (...) registry-chain: AGREED (...) ``` `AGREED` means every member exposes the **same authenticated application root** — not merely that three processes are alive. That distinction is the whole point. There is a status page per node if you prefer a UI: - `http://127.0.0.1:7070/ui/app-chain/` - `http://127.0.0.1:7071/ui/app-chain/` - `http://127.0.0.1:7072/ui/app-chain/` #### 3. Submit a business event Submit through member 1, not the proposer, so the gossip path is exercised: ```bash ./yano.sh appchain cluster submit orders-chain orders \ '{"event":"order-created","orderId":"A-1001","quantity":4}' \ --node 1 ``` Member 1 authenticates the envelope and gossips it. The proposer orders it into a block, a threshold of members signs that block, and all three apply the same bytes. Wait a couple of seconds, then look at the finalized history and agreement: ```bash curl -s http://127.0.0.1:7070/api/v1/app-chain/chains/orders-chain/blocks | jq . ./yano.sh appchain cluster status ``` The tip advances on every member and the roots stay equal. #### 4. Prove it Submit through the public API so you capture a message id, then ask for a proof bound to the committed root: ```bash RESPONSE=$(curl -s -X POST \ http://127.0.0.1:7072/api/v1/app-chain/chains/orders-chain/messages \ -H 'Content-Type: application/json' \ -d '{"topic":"orders","body":"{\"event\":\"packed\",\"orderId\":\"A-1001\"}"}') MESSAGE_ID=$(echo "$RESPONSE" | jq -r .messageId) sleep 3 curl -s -X POST \ "http://127.0.0.1:7070/api/v1/app-chain/chains/orders-chain/proof-subjects/finalized-message-v1/proof" \ -H 'Content-Type: application/json' \ -d "$(jq -nc --arg id "$MESSAGE_ID" ' {coordinates:{"message-id":$id}, view:"latest", claim:{claimId:"recorded",operands:{}}, includeEvidence:false}')" \ | jq '{stateRoot:.proof.stateRoot,presence:.proof.presence,position:.fact.fields,claim:.claimResult.satisfied}' ``` Two things worth noticing: - The proof was requested from node **0** for a message submitted to node **2**. Any member can serve it, and the proof is checkable without trusting the one that did. - The typed subject `finalized-message-v1` resolved the public message id to its namespaced physical state key. You did not need to know the trie layout. #### 5. Restart without losing agreement ```bash ./yano.sh appchain cluster stop # preserves state ./yano.sh appchain cluster start 3 ./yano.sh appchain cluster status ``` Members reload their retained history, re-verify hash chains, certificates, and re-executed state roots, and return to `AGREED`. Recovery never means trusting a database copy. **CAUTION:** `stop` preserves state. `clean` is `stop` plus a wipe, and `reset --yes` is destructive. Do not point either at a deployment you care about. #### 6. Optional: load, effects, and membership ```bash ### A bounded load test: 500 messages, 10 concurrent submitters, ~256-byte bodies. ./yano.sh appchain cluster loadtest orders-chain -n 500 -c 10 -s 256 ### Spread submissions across every member's ingress. ./yano.sh appchain cluster loadtest orders-chain -n 1000 -c 20 -s 256 --spread ### Emit and externally execute one effect, with no broker or credentials. ./yano.sh appchain cluster effect demo ### Govern, start, and catch up a fourth member. ./yano.sh appchain cluster node join 3 ``` The load test reports a **SUBMIT rate** (how fast a REST ingress accepts messages) and a **FINALIZE rate** (how fast messages enter threshold-certified blocks). The second one is the meaningful chain-throughput number. Entries under `dropped (429 pool)` mean backpressure worked; `errors` are real failures worth investigating. #### What you just proved - Three independent processes finalized the same ordered history and derived the same authenticated root. - A message submitted to one member was ordered, threshold-signed, and applied identically by all of them. - A record can be proved against a committed root by any member, and the proof is verifiable without trusting the server. - State survives a restart and is re-verified rather than re-trusted. #### Where to go next | You want to… | Go to | |---|---| | Understand what just happened | [Architecture](https://yanox.dev/concepts/architecture/) | | Store owner-controlled data and prove it | [Tutorial 2 — registry and proofs](https://yanox.dev/tutorials/02-registry-and-proofs/) | | Pick a state machine for your own use case | [Recipe catalog](https://yanox.dev/recipes/) | | Connect a finalized decision to an ERP or webhook | [Tutorial 6 — webhook effects](https://yanox.dev/tutorials/06-webhook-effects/) | | Settle a root on Cardano and verify it | [Tutorial 7 — anchors and verification](https://yanox.dev/tutorials/07-anchors-and-verification/) | | Write business rules Yano does not ship | [The plugin framework](https://yanox.dev/plugins/) | | Turn a demo into a pilot | [Tutorial 9 — from demo to pilot](https://yanox.dev/tutorials/09-from-demo-to-pilot/) | **THIS IS A SINGLE-HOST LAUNCHER:** `appchain cluster` runs every member as a process on one machine. Real keys and a public network make it capable, but one host is not three failure domains. Distributed deployments use generated per-machine project overlays and your normal orchestration layer — see [Tutorial 9](https://yanox.dev/tutorials/09-from-demo-to-pilot/). --- # Concepts --- ## Architecture Source: https://yanox.dev/concepts/architecture/ > The four layers of an app chain, the two-plane separation between deterministic intent and non-deterministic execution, and the components that implement them. An app chain has four layers, and one safety boundary that matters more than all the rest. #### The four layers ```mermaid flowchart LR C[Client command] --> M[Deterministic state machine] M --> F[Threshold-finalized app block] F --> P[State root and MPF proofs] P --> A[Cardano L1 anchor] M -. effect intent .-> E[Effect runtime] E --> X[Kafka, S3, IPFS, webhook, Cardano] X -. acknowledged result .-> M ``` 1. A member authenticates and relays a command. 2. The proposer orders commands into an app block. 3. A threshold of members signs the same block and state root. 4. State and effect intents become provable against that root. 5. An optional anchor settles the root on Cardano. 6. Effect executors act outside consensus and report bounded outcomes. For role-aware workflows, the relay member and the business actor are separate identities: a **node transports** a command, while an **actor signature authorizes** its business meaning. #### The two-plane design This is the boundary to internalize. Deterministic intent and non-deterministic execution never touch. ```mermaid flowchart TB subgraph CP["Consensus plane — identical on every member"] MSG[Signed messages] --> APP[State machine apply] APP --> STATE[Application state] APP --> RECORD[Immutable effect record] STATE --> ROOT[Authenticated state root] RECORD --> ROOT end subgraph EP["Execution plane — designated executor"] READY[Finality gate satisfied] --> RUN[Connector executes action] RUN --> RECEIPT[External receipt] RECEIPT --> RESULT[Signed result message] end RECORD --> READY RESULT --> APP ROOT --> ANCHOR[Optional Cardano anchor] ``` The state machine records **what is authorized**. It never calls Kafka, IPFS, S3, Cardano, an ERP, or a webhook during consensus. The effect runtime executes the instruction later and reports the outcome back through the chain. The guarantee is: - **exactly-once** deterministic result incorporation; and - **at-least-once** external execution. Which is why every executor and every receiver must be idempotent under the supplied idempotency identity. See [Effects](https://yanox.dev/concepts/effects/). #### The components ```mermaid flowchart TB API[REST, Java client, SSE and domain APIs] CONS[App-block sequencing, membership and threshold finality] SM[State machine or deterministic composite] DATA[RocksDB history, authenticated state and proofs] ANCHOR[Cardano metadata or script anchor] EFFECTS[Effect outbox, gates, retries and result incorporation] PLUGINS[Manifested plugin catalog, lifecycle and policy] OPS[Status UI, health, metrics and operations] API --> CONS --> SM --> DATA DATA --> ANCHOR SM --> EFFECTS PLUGINS --> SM PLUGINS --> EFFECTS PLUGINS --> API PLUGINS --> OPS ``` | Component | Responsibility | Key property | |---|---|---| | App-chain API and clients | Submit messages, inspect blocks, query state, stream finality, verify proofs. | Clients verify evidence rather than trust a response. | | Proposer / sequencer | Orders accepted messages and proposes app blocks. | An ordering role only; it cannot force members to sign a wrong root. | | Members | Validate, re-execute, vote, catch up, and retain finalized history. | Threshold control and independent verification. | | State machine | Interprets application message bodies and writes deterministic state. | No wall clock, randomness, or external I/O. | | Authenticated state | Commits state under one root and serves bounded proofs. | The same root on every member, and anchorable to Cardano. | | Anchor subsystem | Publishes certified roots as Cardano metadata or a state-thread script UTxO. | Public L1 linkage without executing the application on L1. | | Effect runtime | Discovers finalized effects, applies gates, retries, tracks receipts, reports results. | External failures neither fork nor block consensus. | | Plugin catalog | Validates manifested bundles, dependencies, compatibility, policy, and lifecycle. | Extensibility stays explicit and auditable. | | Operations surfaces | Status, health, Prometheus metrics, dashboards, admin and plugin operations. | Operators can distinguish intent, execution, incorporation, and anchoring. | #### Composite state machines One app chain selects exactly **one** state machine. A deterministic composite lets that single machine host several reusable capabilities behind one atomic state root. ```mermaid flowchart TB PROFILE["Canonical composite profile
order · routes · versions · quotas · activations"] PROFILE --> R[Registry namespace] PROFILE --> A[Approvals namespace] PROFILE --> D[Document-trail namespace] PROFILE --> E[Evidence namespace] R --> W[Declared atomic workflow] A --> W D --> W E --> W W --> ROOT[One authenticated state root] ``` A composite profile explicitly declares component ids and versions, deterministic application order, routed public topics, per-component quotas, and activation heights. It is canonically encoded, committed to authenticated state at height 1, and re-verified on every restart and every transition — so a different component order or effective configuration is **detected** rather than silently creating a different application. Two modes: - **Fixed** retains one immutable profile for the life of the chain. - **Governed** commits an append-only chain of profile epochs. Operators package the reviewed current and dormant targets on every member first, then threshold-authorize one exact digest and a future activation height. Changing YAML or a JAR alone never changes consensus behavior. Components cannot read or write sibling namespaces directly. Cross-component changes go through a declared deterministic workflow. #### Data separation Each node keeps three sibling stores, and conflating them is a correctness bug: | Store | Contents | Authority | |---|---|---| | `chainstate/` | Cardano L1 state | Authoritative | | `appchain-chainstate/` | App-chain state | Authoritative | | `appchain-indexers/` | Local read indexes | Rebuildable, never authoritative | Derived indexes must never advance beyond authoritative app-chain state, and app-chain data must never be placed below L1 `chainstate`. #### Deployment shape - **JVM:** copy a self-contained manifested bundle into every member's plugin directory, select it in configuration, restart. No Yano rebuild. - **Native:** Yano's native image is core-only. Yano X extensions are JVM-only. - **Multiple chains:** one node can host several independently configured app chains. - **Executor placement:** effects may run on a designated member, a dedicated executor node, or through the external claim/report API. Type partitions and stable executor identities make ownership visible and recoverable. #### Where to go next - [Consensus and finality](https://yanox.dev/concepts/consensus-and-finality/) — how a block becomes final. - [State and proofs](https://yanox.dev/concepts/state-and-proofs/) — what a root commits to and how to verify it. - [Effects](https://yanox.dev/concepts/effects/) — the execution plane in detail. - [Determinism rules](https://yanox.dev/concepts/determinism-rules/) — what consensus code may not do. --- ## Consensus and finality Source: https://yanox.dev/concepts/consensus-and-finality/ > How a submitted message becomes a threshold-certified app block — sequencing modes, the fail-closed verification chain, membership governance, and catch-up. An app block is final when the configured member threshold has signed the state root each of those members computed **themselves**. Everything else on this page is detail around that sentence. #### The round ```mermaid sequenceDiagram autonumber participant Client participant Ingress as Any member participant Proposer participant Members Client->>Ingress: Signed app message Ingress->>Ingress: Verify envelope signature and membership Ingress->>Members: Gossip into the pending pool Proposer->>Members: Propose block (ordered messages + post-state root) Members->>Members: Re-execute apply, derive own root Members->>Members: Compare byte-for-byte with the proposed root Members-->>Proposer: Ed25519 vote, only on their own root Proposer->>Members: Finality certificate (threshold signatures) Members->>Members: Commit and advance the tip ``` Any member can be the ingress point. Submitting through a non-proposer is the normal path and exercises gossip — that is why the [Quickstart](https://yanox.dev/start-here/quickstart/) submits through node 1. #### Fail closed Every node verifies, always: | Check | Failure behavior | |---|---| | Envelope signature | The message is dropped. | | Sender is a current member | The message is dropped. | | The block came from the current sequencer | The block is never finalized. | | Re-executed post-state root matches the proposed root | The vote is withheld. | | Vote signatures are valid member signatures | Invalid votes are discarded. | | Certificate meets the threshold | The block is not final. | | Hash link to the previous block | The block is rejected. | A member signs only a root it derived itself. The proposer orders; it never decides state. #### Sequencing modes | Mode | How the proposer is chosen | Use it when | |---|---|---| | `fixed` | One configured member proposes every block. | A clear operational owner exists; simplest to reason about. | | `rotating` | The proposer changes deterministically over L1-slot windows. | No participant should hold the ordering role permanently. | Rotation is derived from the Cardano L1 slot, so every member computes the same schedule with no extra coordination. The choice does not affect safety — only who holds the ordering role and therefore liveness responsibility. #### Threshold and membership - Members are identified by Ed25519 public keys. The v1 profile supports at most **32** members. - `threshold` is how many member signatures a finality certificate needs. - The initially supplied members form the **immutable bootstrap epoch**. - Later membership changes are themselves finalized in the chain's history, so the member set at any height is derivable from the chain rather than from a configuration file. Governed membership means adding a member is a chain event, not an edit: ```bash ### High level: derive or load the identity, collect approvals, record the epoch, ### start the node, catch up, and verify tip and root parity. ./yano.sh appchain cluster node join 3 ### Lower level, for an externally managed node. ./yano.sh appchain cluster member add <64-hex-ed25519-public-key> ``` `member add` records the member across configured chains but does not create configuration, copy state, or start a process. The operator must configure that node with the same immutable bootstrap members, chain definitions, threshold, and network identity; its verified catch-up then derives the later governed epoch. **ONE PENDING EPOCH AT A TIME:** Do not schedule a second membership or threshold epoch while the first is still delayed — the later epoch could replace the pending member set. Advance ordinary application traffic to the printed activation height first. #### Catch-up and restart A member that joins late or restarts behind fetches finalized blocks from peers and verifies everything before committing: the hash chain, the finality certificates, and the re-executed state roots. Recovery never means trusting a database copy. This is why `stop` followed by `start` returns the cluster to `AGREED` rather than to "probably fine". #### Chain identity Three values together pin a chain's identity: ```text (commitment-profile, format-fingerprint, genesis-id) ``` A retained `genesis-id` is never regenerated. Changing a commitment profile or the deterministic state encoding produces a **different chain**, not an upgraded one — which is why any change to consensus semantics is a versioned upgrade rather than a rolling code change. See [Consensus rules for plugins](https://yanox.dev/plugins/consensus-rules/). #### What "AGREED" means ```bash ./yano.sh appchain cluster status ``` ```text orders-chain: AGREED (...) ``` `AGREED` means every member exposes the same authenticated application root at the same height. It does not merely mean the processes are running. If members disagree, the cause is almost always one of: - a consensus-affecting configuration value that differs between members (effects caps, state-machine id, composite profile digest); - a different plugin bundle version installed on one member; or - non-deterministic code in a custom state machine — see [Determinism rules](https://yanox.dev/concepts/determinism-rules/). #### Deeper reading - [`core-host.md`](https://github.com/bloxbean/yano-x/blob/main/docs/core-host.md) — the consensus round check by check, vote locks, rotation math, and catch-up/restart semantics. - [Tutorial 1](https://yanox.dev/tutorials/01-first-app-chain/) — observe agreement on a running three-member cluster. --- ## State and proofs Source: https://yanox.dev/concepts/state-and-proofs/ > How app-chain state is committed to an MPF root, what a typed proof subject actually asserts, and the difference between a reconstructed root and a trusted one. Application state lives in an authenticated trie. Every finalized block produces one root that commits to all of it, identical on every member. A client can then verify an individual record against that root without trusting the node that served it. #### The state root State is stored in an **MPF** (Merkle Patricia Forestry, Aiken-compatible) trie. After applying a block, the resulting root is: - **identical** on every honest member — that is what members sign; - **anchorable** — the same bytes go into a Cardano metadata or script anchor; and - **provable** — it supports both inclusion and exclusion proofs. Effect records are committed transitively through a count-bound `effectsRoot` leaf, so an authorized-but-not-yet-executed external action is as provable as ordinary state. #### Proof subjects: application language, not trie internals Raw key/value proofs exist, but they force the caller to know the physical layout. A **proof subject** is a typed descriptor that resolves an application-level identity to its canonical state key and evaluates declared claims against the proof-carried value. ```bash curl -s -X POST \ "http://127.0.0.1:7070/api/v1/app-chain/chains/orders-chain/proof-subjects/finalized-message-v1/proof" \ -H 'Content-Type: application/json' \ -d '{ "coordinates": {"message-id": ""}, "view": "latest", "claim": {"claimId": "recorded", "operands": {}}, "includeEvidence": false }' | jq ``` You supply coordinates in your own vocabulary (`message-id`, an account, a registry key, a document id). The subject derives the canonical key, decodes only the value the verified proof carries, and evaluates only the claims it declares. Stock v1 subjects cover finalized block messages, finalized message records, balances, registry entries, document heads, basic approval outcomes, authenticated-map entries, actor roles, role approval outcomes, and composite profile markers. Composite subjects are automatically rebound to their component namespace and get component-qualified ids. #### Read the result precisely This is the part people get wrong. Each of these statements means something different, and a proof response keeps them separate: | Result | What it actually asserts | |---|---| | Message inclusion | The message id is a leaf under one app block's `messagesRoot`. | | Finality certificate | A caller-pinned membership threshold signed the block. | | Authenticated block record | Finalized state contains `[height, messagesRoot, messageCount]`. | | State recording | The application wrote a typed fact under its canonical state key. | | Anchor binding | A trusted Cardano output commits the selected application identity or root. | | Claim satisfied | The proof-carried canonical value satisfies the selected bounded predicate. | | Locally retained | This node has the bytes now. Durable availability is separately `NOT_PROVEN`. | **A RECONSTRUCTED ROOT IS NOT A TRUSTED ROOT:** A proof that merely reconstructs a root is `INTERNAL_CONSISTENCY_ONLY` until the root is pinned by the caller, by a pinned finality policy, or by an independently checked Cardano script output. A node-reported anchor is labelled `NODE_CONFIRMED_L1_REFERENCE` — that is the node's claim about L1, not independently verified L1 truth. #### Absence is harder than presence An exclusion proof shows a key is absent from the trie. It does **not** show that the underlying business fact never happened — the dataset might simply be incomplete. Do not expose a business-level absence predicate unless a marker, a paired subject, or an authenticated snapshot descriptor proves dataset completeness. #### Retention and pruning Proof history is retained, not infinite. Monitor `oldestProvableHeight`: ```bash ./yano.sh appchain state identity --url http://node:8080/api/v1 --chain registry ./yano.sh appchain state oldest --url http://node:8080/api/v1 --chain registry ``` A pruned proof is **unavailable**. It is not evidence that the fact was absent. New chains enable the `state-index:finalized-block-messages-v1` subject by default. Its enabled flag and configuration digest are part of the application identity, so an existing database with a different configuration fails startup rather than silently drifting. Disable it only in a new genesis profile. Rough raw growth for that index, before MPF/JMT node and RocksDB amplification — one 32-byte key plus a ~38–47 byte canonical CBOR value per block: | Block interval | Records/day | Raw logical growth/day | |---|---:|---:| | 1 second | 86,400 | 5.8–6.5 MiB | | 5 seconds | 17,280 | 1.15–1.30 MiB | | 20 seconds | 4,320 | 0.29–0.33 MiB | Capacity planning must measure backend amplification, compaction, snapshots, and retained proof history on the intended workload. #### Verifying independently Proofs are portable. The Java client SDK (`yano-x-client`) verifies them client-side, and the composite client (`yano-x-composite-client`) additionally verifies governed-profile finality, one-root MPF, epoch chains, and authorization policy. Offline verification from the CLI: ```bash ./yano.sh appchain state entry --url http://node:8080/api/v1 \ --chain registry --key 0123 ./yano.sh appchain state proof --url http://node:8080/api/v1 \ --chain registry --key 0123 ``` For custom subjects, implement `ProofSubjectProvider` next to the module that owns the canonical key and value codec. Each descriptor is closed data — coordinates, fact fields, claims, completeness, verification targets, retention hints, and fixed bounds — and its `descriptorDigest` goes into the capability manifest. The runtime activates the provider only when subject id, version, component id, and digest all match. Resolution must be deterministic and side-effect free. #### Deeper reading - [Proof Lab](https://github.com/bloxbean/yano-x/blob/main/docs/appchain/PROOF_LAB.md) — message, typed-state, imported, and on-chain proof workflows, plus the independent-verifier and Cardano-validator guides. - [Tutorial 2](https://yanox.dev/tutorials/02-registry-and-proofs/) — retrieve and read a proof. - [Tutorial 7](https://yanox.dev/tutorials/07-anchors-and-verification/) — bind a root to L1. - [Authenticated snapshots](https://github.com/bloxbean/yano-x/blob/main/docs/appchain/AUTHENTICATED_SNAPSHOTS.md) — archiving and proving large immutable period datasets. --- ## Effects Source: https://yanox.dev/concepts/effects/ > How a finalized state transition triggers work outside the chain — emit-not-execute, finality gates, mandatory expiry, exactly-once incorporation, and executor trust. Everything else in an app chain keeps state inside the chain. **Effects** let a finalized transition trigger an action outside it — call an ERP or webhook, publish to Kafka, store an object, pin to IPFS, submit a Cardano payment — without breaking determinism. #### The rule that makes it safe > A state machine never performs the action. It emits a record describing it. Emission happens inside deterministic `apply()`, is identical on every member, and is committed transitively through a count-bound `effectsRoot` leaf into the state root. A separate **effect runtime**, outside consensus, executes finalized effects and reports the outcome back as an ordinary sequenced message. ```text apply() ── emit ──▶ effect record (outbox + effectsRoot, provable, anchored) │ finalized ▼ Effect runtime ── execute ──▶ external system │ ~fx/result (member-signed, sequenced) ──▶ apply() records the outcome ``` The guarantee is **exactly-once incorporation, at-least-once execution**. The external action may run more than once, so **every executor and every receiver must be idempotent** under the supplied idempotency identity. #### Enabling effects Effects are off by default, and these are **consensus parameters** — every member must set identical values, or the state root diverges exactly as it would with a mismatched state machine. ```yaml yano.app-chain.effects.enabled: true yano.app-chain.effects.max-per-block: 256 # effects one block may emit yano.app-chain.effects.max-payload-bytes: 16384 # per-effect payload cap yano.app-chain.effects.max-expiry-blocks: 100000 yano.app-chain.effects.result-window-blocks: 100000 # results incorporable within this window yano.app-chain.effects.outcome-commitment: per-effect # per-effect | per-block yano.app-chain.effects.default-gate: app-final # app-final | l1-anchored | zk-settled ``` Enabling effects reserves the `~fx/` key prefix in the state trie — a state machine may not write keys starting with `~fx/`. The reservation holds from genesis regardless of the flag, so effects can be switched on later without colliding with historical state. #### Emitting from a state machine ```java public class OrderStateMachine implements AppStateMachine { @Override public String id() { return "orders"; } @Override public void apply(AppBlock block, AppStateWriter writer, AppEffectEmitter effects) { for (AppMessage m : block.messages()) { Order o = decode(m.getBody()); writer.put(key(o.id()), o.toBytes()); // ordinary state if (o.isApproved()) { effects.emit(EffectIntent.of("webhook.post", o.fulfilmentJson()) .scope("orders/" + o.id()) // application idempotency scope .result(ResultPolicy.CHAIN) // the outcome returns on-chain .gate(FinalityGate.CHAIN_DEFAULT) .expiryBlocks(1000) // deterministic timeout .sourceMessageId(m.getMessageId()) .build()); } } } // Called deterministically when a CHAIN effect's outcome is incorporated. @Override public void onEffectResult(AppBlock block, EffectResult result, AppStateWriter writer) { if (!result.scope().startsWith("orders/")) return; String id = result.scope().substring("orders/".length()); writer.put(fulfilledKey(id), result.externalRef()); } } ``` The emitter records intent and performs no I/O. Everything forbidden in `apply()` — wall clock, randomness, network — stays forbidden. **EMISSION LOGIC IS CONSENSUS LOGIC:** Changing what a transition emits on a live chain is a hard fork unless the change is gated behind a governed profile activation. See [Consensus rules for plugins](https://yanox.dev/plugins/consensus-rules/). #### Finality gates A **gate** decides when an emitted effect becomes eligible to execute. | Gate | Eligible when | Notes | |---|---|---| | `app-final` | The block is committed. | The chain is append-only after finality, so emission is already irrevocable. | | `l1-anchored` | The effect's height is covered by an L1-confirmed, stability-deep anchor. | The emission is provable against Cardano before you act. A verifiability delay, not a rollback safeguard. | | `zk-settled` | Covered by an accepted validity proof. | Reserved for the ZK settlement roadmap; waits until expiry on non-ZK chains. | `l1-anchored` requires [anchoring](https://yanox.dev/concepts/anchoring/) to be enabled and `l1.stability-depth` to be set. `effects.gate.anchor-margin-blocks` adds a safety margin above the anchor high-water mark. #### Expiry is mandatory Every `CHAIN` effect must provably close, because a result arriving after the result window is a deterministic no-op. Passing `expiryBlocks(0)` makes the framework default it to the result window. When the expiry height passes with no incorporated result, the effect deterministically becomes **`EXPIRED`** and is delivered to `onEffectResult`. That is the "nobody answered in time" escape hatch, and it is distinct from **`FAILED`**, which means "the target answered no". #### The result path, and what a result actually proves Executed `CHAIN` outcomes re-enter as member-signed `~fx/result` messages, sequenced like anything else. The interpreter is fail-closed and first-result-wins: duplicate, late, malformed, unknown, or out-of-window results are deterministic no-ops. **A result can never stall the chain.** A result is a **member attestation**, not an independently verified fact. Followers check the signature and membership, not the external world. Narrow who may attest: ```yaml ### Only these member keys' ~fx/result messages are accepted. Default: any member. yano.app-chain.effects.result.signers: "" ``` For L1-visible facts such as a payment landing, prefer verifying through an L1 observer over trusting an attestation. `k`-of-`n` result attestation for high-value effects is designed but not yet shipped. #### Executors and sinks | Kind | What it does | Availability | |---|---|---| | `webhook.post` executor and finalized webhook sink | HTTP delivery of an authorized action or a finalized block stream. | Bundled | | `kafka.publish` executor and Kafka sink | Acknowledged effects and finalized blocks to Kafka topics. | First-party optional plugin | | `object.put` executor | Immutable or versioned writes to tested S3-compatible stores. | First-party optional plugin | | `ipfs.pin` executor | Reconciled pin-only effects through a configured Kubo RPC. | First-party optional plugin | | `cardano.payment` executor | Cardano payments from the effect system. | First-party optional plugin | An optional connector needs its exact release-matched bundle installed in `plugins/` on every applicable node, plus the external service. See [Optional connectors](https://github.com/bloxbean/yano-x/blob/main/docs/appchain/OPTIONAL_CONNECTORS.md). Executors may run on a designated member, a dedicated executor node, or as an external worker through the claim/report API. Type partitions and stable executor identities make ownership visible and recoverable, and executor fencing prevents two workers from double-claiming after a failover. #### Try it ```bash ### The bundled effects demo needs no broker, object store, or credentials. ./yano.sh appchain cluster start 3 ./yano.sh appchain cluster effect demo ./yano.sh appchain cluster effect demo "order 42 approved" ``` Then read [Tutorial 6 — webhook effects](https://yanox.dev/tutorials/06-webhook-effects/), which covers when to use a finalized-block sink versus an acknowledged `webhook.post` effect. --- ## Cardano anchoring Source: https://yanox.dev/concepts/anchoring/ > Committing a certified app-chain root to Cardano — metadata versus threshold-script anchors, the anchor wallet, why the anchor leader is not a trust point, and what an anchor does and does not prove. Anchoring periodically commits the app chain's position — height, block hash, and **state root** — onto Cardano. The node builds, signs, and submits the transaction through its own mempool and tx diffusion, and confirms it through its own L1 sync. No external API or provider is involved. #### Two modes `yano.app-chain.anchor.mode` selects one: | | `metadata` (default) | `script` | |---|---|---| | Anchor transaction | A plain tx with the anchor payload in tx metadata | A Plutus V3 thread-NFT UTxO; a validator enforces the datum chain on-chain | | What L1 enforces | Nothing — a data-only commitment | Monotonic height, stable chain id, and m-of-n member signatures on every advance | | Signing | Anchor wallet only | Wallet plus threshold co-signed member witnesses | | Setup | Fund the wallet | Fund, then one-time `admin/anchor/bootstrap` per chain | | Approximate cost | 0.17–0.2 ADA per anchor | ~0.35 ADA per anchor (script execution) | Both modes work on the devnet and on public networks. Script anchors are proven on preprod with a real Plutus V3 mint and validator-enforced co-signed advances. Choose `metadata` when you want cheap public timestamping of a root that verifiers will check against the chain's own certificates. Choose `script` when L1 itself should refuse an advance that lacks the member threshold. #### The anchor wallet The anchor wallet is a raw **32-byte Ed25519 seed** (`anchor.signing-key`, in hex). The node derives its enterprise address, logs it at startup (`anchor wallet address: addr...`), and exposes it in `/status` under `anchor.walletAddress`. Fund **that** address. ```bash ### Devnet curl -X POST http://127.0.0.1:7070/api/v1/devnet/fund \ -H 'Content-Type: application/json' \ -d '{"address":"addr...","ada":100}' ### Generate a dedicated seed openssl rand -hex 32 ``` **A WALLET MNEMONIC WILL NOT WORK:** A CIP-1852 mnemonic from Eternl, Lace, or the devkit **cannot** be converted into this seed — HD payment keys are extended keys, not seeds. Generate a dedicated seed and fund its address. Treat it as a hot wallet holding fee money only. The key sits in configuration on the node box. Only the anchor **leader** needs `anchor.*` configuration — typically node 0. In script mode, members co-sign and adopt the on-chain identity with **zero** anchor configuration; they verify each advance against their own ledger and L1 view before signing. #### The anchor leader is not a trust point The leader is the node that drives anchoring: it watches finalized progress, builds the anchor tx once `every-blocks` accumulate, pays fees and collateral from the anchor wallet, submits through the node's own tx path, and tracks L1 confirmation. This is a different axis from the sequencer. The proposer orders app blocks and may rotate; anchor leadership is fixed to the `anchor.enabled` node and does not rotate, because there is exactly one thread UTxO to spend and one wallet paying fees. Concurrent leaders would simply race on the same UTxO. In script mode the leader has no unilateral power: - every advance needs `threshold` member co-signatures; - each member verifies the proposed range against its own ledger and L1 view before signing; and - the on-chain validator independently re-enforces the member threshold and monotonic height. The worst a dead or compromised leader can do is **stop anchoring** — a liveness issue, never a safety one. The app chain keeps finalizing and `lagBlocks` climbs visibly. Recovery is operational: enable `anchor.*` with the wallet key on another member and restart it. The on-chain identity is persisted on L1 and members adopt it from sign requests, so the new leader resumes where the old one stopped. Metadata mode is the same minus co-signing. Its commitment is data-only and the leader alone signs, so the trust statement is correspondingly weaker. #### Configuration ```yaml yano: app-chain: anchor: enabled: true mode: metadata # or: script signing-key: "" # separate from member keys every-blocks: 10 # anchor cadence ``` Keep the anchor seed separate from member signing keys, API keys, and effect credentials. They are five distinct secrets with five distinct blast radii. #### What an anchor proves — and what it does not An anchor binds a certified app-chain root to public L1 history at a Cardano-observable time. That gives an auditor an independent reference point they can check without asking any app-chain node. It does **not** prove: - that the application's business claim is true — only that the members committed to this exact state; - that the data behind a hash is still retrievable — durable availability is a separate property; - anything at all about heights after the anchored one. A node-reported anchor is labelled `NODE_CONFIRMED_L1_REFERENCE` in proof output. That is the node's claim about L1, not independently verified L1 truth. An independent verifier should check the Cardano output itself. See [State and proofs](https://yanox.dev/concepts/state-and-proofs/). #### Try it ```bash export YANO_CLUSTER_API_KEY="$(openssl rand -hex 32)" ./yano.sh appchain cluster start 3 --network preprod ./yano.sh appchain cluster anchor-bootstrap ./yano.sh appchain cluster status ``` [Tutorial 7 — anchors and verification](https://yanox.dev/tutorials/07-anchors-and-verification/) walks through bootstrapping a threshold-enforced script anchor, advancing it, and following the independent verification chain end to end. **PREPROD SPENDS REAL TEST ADA:** Anchoring on a public test network submits real transactions from a funded wallet. Use disposable keys and non-production credentials. --- ## Determinism rules Source: https://yanox.dev/concepts/determinism-rules/ > The hard constraints on code that runs inside consensus — no wall clock, no randomness, no ambient ordering, no I/O — and the versioning discipline that applies when any of it changes. Every member executes the same messages through the same state machine and must derive the same state root **byte for byte**. If two members disagree, the chain does not "heal" — it fails to reach the threshold and stops finalizing. This page is the checklist for any code that runs inside `apply()`. #### Forbidden inside consensus | Do not | Why | Instead | |---|---|---| | Read the wall clock (`Instant.now()`, `System.currentTimeMillis()`) | Every member reads a different value. | Use block height, or an L1 slot value carried in the block. | | Use randomness (`Math.random()`, `new Random()`, `UUID.randomUUID()`) | Unreproducible. | Derive deterministically from message bytes, a hash, or a sequence number. | | Iterate a `HashMap` / `HashSet` | Iteration order varies across JVMs and insertion histories. | Use `TreeMap` / `LinkedHashMap`, or sort explicitly before iterating. | | Make a network call | Latency, failure, and content differ per member — and it may not even be reachable. | Emit an [effect](https://yanox.dev/concepts/effects/). | | Read files, environment variables, or system properties | Node-local, therefore divergent. | Put the value in consensus-shared configuration, or in the message. | | Depend on locale or default charset | `toLowerCase()` and `String.getBytes()` are locale/platform sensitive. | Pin `Locale.ROOT` and `StandardCharsets.UTF_8` explicitly. | | Use floating point for value arithmetic | Fine in principle, treacherous in practice around rounding and formatting. | Use integers or `BigDecimal` with an explicit scale and rounding mode. | | Depend on object identity or `hashCode()` | `Object.hashCode()` varies per run. | Compare and key on canonical bytes. | | Catch an exception and continue differently on one member | Divergent control flow. | Validate deterministically and reject or no-op uniformly. | | Spawn threads or use concurrency inside `apply()` | Scheduling order is not reproducible. | Keep `apply()` single-threaded. | | Write keys under `~fx/` | The prefix is reserved for the effect system from genesis. | Use your own namespace. | #### Two properties, not one Determinism has a per-run component and a cross-node component. Both matter: - **Reproducible** — replaying the same blocks on the same member yields the same state. This is what replay and restart tests check. - **Identical** — every member independently yields the same state. This is what root parity across a real multi-node cluster checks. Code can be reproducible and still not identical: a `HashMap` iteration is stable within one JVM run but differs across members. Unit tests pass; the cluster stalls. Always validate on a real cluster. #### Rejection must be deterministic too Invalid input is not an exception path — it is a state transition that every member must agree on. A message that fails validation should produce the same observable outcome on every member: usually a recorded no-op, sometimes an error record written to state, never a thrown exception that one member handles differently. The stock state machines demonstrate this: an unauthorized `kv-registry` write is a **visible no-op**, not a failure. [Tutorial 2](https://yanox.dev/tutorials/02-registry-and-proofs/) has you trigger one deliberately. #### Consensus-shared vs node-local configuration The catalog classifies every property by scope, and the distinction is not cosmetic: | Scope | Meaning | Getting it wrong | |---|---|---| | `CONSENSUS_SHARED` | Must be identical on every member. | The state root diverges and the chain stops finalizing. | | Node-local | Ports, storage paths, credentials, executor placement. | Only that node is affected. | Effects caps, the state-machine id, the composite profile digest, value formats, and quotas are all consensus-shared. The generated [configuration reference](https://yanox.dev/reference/configuration/) marks the scope and change policy of each property. #### Changing semantics is a versioned upgrade A change to deterministic application semantics — state encoding, transition logic, emission logic, a commitment profile, a proof subject, or genesis-selected configuration — is **not** an ordinary rolling code change. Replaying history under new rules produces different state, so the change must be versioned and activated deterministically. Three values pin chain identity: ```text (commitment-profile, format-fingerprint, genesis-id) ``` A governed composite profile is the supported mechanism: operators package the reviewed current and dormant targets on **every** member first, then threshold-authorize one exact digest and a future activation height. Editing YAML or swapping a JAR never changes consensus behavior on its own. See [Consensus rules for plugins](https://yanox.dev/plugins/consensus-rules/) for the full upgrade discipline. #### Verifying determinism Do not stop at unit tests when a plugin, catalog, persistence, consensus, proof, anchor, or cross-node behavior changed: ```bash ### Unit and property tests while iterating. ./gradlew :state-machines:stdlib:test ### A real multi-node cluster: identical chain height, root, profile, genesis, ### and capability-manifest digest across members. ./yano.sh appchain cluster start 3 ./yano.sh appchain cluster loadtest orders-chain -n 1000 -c 20 --spread ./yano.sh appchain cluster status # every chain must report AGREED ### Restart and re-verify, then check catch-up on a joining member. ./yano.sh appchain cluster stop ./yano.sh appchain cluster start 3 ./yano.sh appchain cluster node join 3 ``` Persistence changes additionally require apply, rollback, replay, restart, and root-parity checks. #### Debugging a divergence When members disagree, work down this list before suspecting the framework: 1. **Configuration drift.** Compare every `CONSENSUS_SHARED` value across members. `./yano.sh appchain drift --peer ` does this against running nodes. 2. **Plugin version drift.** Compare the capability-manifest digest and the installed bundle digests on each member. 3. **Profile digest.** For composites, confirm every member committed the same profile digest at height 1. 4. **Your `apply()`.** Re-read the forbidden list above. Map iteration order and the wall clock account for most real cases. 5. **Replay locally.** Replay the same block range on one member twice. If that diverges, the bug is reproducibility; if not, it is cross-node identity. --- # Recipes --- ## Recipe catalog Source: https://yanox.dev/recipes/ > Reviewed starting points for an app chain, generated from the release-pinned recipe catalog in the Yano X repository. A **recipe** is a reviewed starting point. It resolves the capabilities it requires, and you may then add only capabilities that are compatible with them. Everything on this page is generated at documentation build time from `tooling/devtools/src/main/resources/appchain-dx/v1alpha1/appchain-recipe-catalog.json` in the repository. The authoritative answer for *your* build is always the binary in your hands: ```bash ./yano.sh appchain recipes ./yano.sh appchain capabilities ./yano.sh appchain capabilities --format json ``` #### The catalog | Recipe | Name | Availability | Maturity | Primary outcome | |---|---|---|---|---| | [`audit-log`](https://github.com/bloxbean/yano-x/blob/main/docs/core-host.md) | Replicated audit log | `BUNDLED` | `stable` | One opaque record is threshold-finalized in the shared application order. | | [`owned-registry`](https://yanox.dev/state-machines/kv-registry/) | Owned registry | `BUNDLED` | `stable` | The first writer owns a named value that is readable with a committed state proof. | | [`authenticated-map`](https://yanox.dev/state-machines/authenticated-map/) | Authenticated map | `BUNDLED` | `preview` | A value accepted under a genesis-declared collection schema is committed and available with a state proof. | | [`approval-workflow`](https://yanox.dev/state-machines/approvals/) | Threshold approval workflow | `BUNDLED` | `stable` | Distinct validator-member decisions produce a provable terminal approval or rejection. | | [`role-approval`](https://github.com/bloxbean/yano-x/blob/main/docs/appchain/tutorials/05-domain-role-approvals.md) | Governed role approvals | `BUNDLED` | `preview` | A governed role policy accepts valid actor decisions and rejects an ineligible actor decision. | | [`evidence-ledger`](https://yanox.dev/tutorials/04-evidence-publication/) | Role-aware evidence ledger | `BUNDLED` | `preview` | A role-authorized evidence record and its release decision are committed and queryable. | | [`eutxo-ledger`](https://github.com/bloxbean/yano-x/blob/main/ledgers/eutxo/README.md) | Scalus-backed EUTxO ledger | `FIRST_PARTY_OPTIONAL` | `experimental` | A signed key-controlled or bounded Plutus V3 transaction consumes virtual EUTxOs and creates MPF-proven outputs and receipts. | | [`eutxo-cardano-bridge`](https://github.com/bloxbean/yano-x/blob/main/ledgers/eutxo/README.md) | Federated Cardano EUTxO bridge | `EXPERIMENTAL` | `experimental` | Accepted stable deposits create mirrored EUTxOs; signed L2 spends create irrevocable claims that settle only through the configured signer path or a current accepted MPF root with single-use nullification. | | [`eutxo-zeroj-validity`](https://github.com/bloxbean/yano-x/blob/main/ledgers/eutxo-zk/README.md) | ZeroJ EUTxO validity development profile | `EXPERIMENTAL` | `experimental` | Bounded finalized EUTxO payments deterministically update both the Yano MPF root and the circuit-friendly validity root and can be proved against pinned development artifacts. | | [`eutxo-zeroj-preview`](https://github.com/bloxbean/yano-x/blob/main/ledgers/eutxo-zk/GETTING_STARTED.md) | ZeroJ EUTxO testnet lifecycle | `EXPERIMENTAL` | `experimental` | A project can bootstrap pinned development artifacts, accept finalized Jubjub-authorized L2 transactions, produce a constant-size proof, and prepare idempotent Cardano deposit, settlement, withdrawal, and recovery operations. | | [`custom-plugin`](https://yanox.dev/plugins/) | Custom state-machine project | `REFERENCE` | `experimental` | The operator-defined state-machine contract is loaded from a reviewed pinned plugin bundle. | #### Availability vocabulary | Availability | Meaning | |---|---| | `BUNDLED` | Ships in the Yano X JVM distribution and needs no additional runtime JAR. It may still need public identities, application bootstrap, or node-local configuration. | | `FIRST_PARTY_OPTIONAL` | Maintained and tested by Yano X, but not selected in every deployment. Install the exact release-matched plugin bundle in `plugins/` on every applicable node. | | `EXPERIMENTAL` | Available for evaluation. Interfaces, wire formats, and behavior may change. | | `REFERENCE` | A documented workflow rather than a shipped runtime — the custom-plugin path. | | Maturity | Meaning | |---|---| | `stable` | Covered by packaged-runtime acceptance. | | `preview` | Implemented and tested; the contract may still move before release. | | `experimental` | Under active development. | Remember that Yano X is JVM-only. A recipe whose native posture is `unsupported` cannot run on Yano's native image, which carries `ordered-log` only. #### Using a recipe ```bash ### 1. See what this build offers. ./yano.sh appchain recipes ### 2. Generate a project non-interactively and reproducibly. ./yano.sh appchain init --non-interactive \ --recipe owned-registry --network preprod --members 3 \ --node-host node-a.example --node-host node-b.example --node-host node-c.example \ --deployment host --output product-registry ### 3. Edit appchain.yaml, then regenerate the derived output. ### Rendering stops if a generated file has an unaccounted manual edit. ./yano.sh appchain render product-registry ### 4. Validate the project and check it against a real distribution. ./yano.sh appchain config validate --mode project product-registry ./yano.sh appchain doctor product-registry --distribution /path/to/yano-x-jvm ### 5. Export deterministic deployment derivatives. ./yano.sh appchain gitops product-registry --target helm --output deploy/helm ./yano.sh appchain gitops product-registry --target kustomize --output deploy/kustomize ``` Edit **only** `appchain.yaml`. Generated runtime files are derived output, and the project lock pins the snapshot, catalog, runtime manifest, configuration metadata, and plugin-JAR digests. You can also build a blueprint visually in the [App-Chain Studio](https://yanox.dev/studio/) and export it. #### Selection and deployment rules - Start with `./yano.sh appchain recipes`, then inspect `./yano.sh appchain capabilities`. - A bundled capability needs no extra runtime JAR, but may still need identities, bootstrap, or node-local configuration. - An optional JVM capability requires the exact release-matched plugin bundle in every applicable node's `plugins/` directory. - For stock composite and role profiles, the profile identifier and its configuration digest become part of chain identity. Select them only for a fresh chain or a governed activation. #### Each recipe in detail ##### `audit-log` — Replicated audit log A replicated append-only log for opaque application records. - **Outcome:** One opaque record is threshold-finalized in the shared application order. - **Availability / maturity:** `BUNDLED` / `stable` - **Capabilities:** `state:ordered-log`, `sequencer:fixed` - **Runtime artifacts:** `yano-runtime` - **Runtimes:** `jvm`, `native` · **Deployment:** `host`, `docker-compose` - **Reference:** [docs/core-host.md](https://github.com/bloxbean/yano-x/blob/main/docs/core-host.md) ```bash ./yano.sh appchain init --non-interactive \ --recipe audit-log --network devnet --members 3 --runtime jvm \ --output audit-log-chain ``` ##### `owned-registry` — Owned registry A first-writer-owned key/value registry with committed query proofs. - **Outcome:** The first writer owns a named value that is readable with a committed state proof. - **Availability / maturity:** `BUNDLED` / `stable` - **Capabilities:** `state:kv-registry`, `sequencer:fixed` - **Runtime artifacts:** `yano-runtime`, `yano-x-stdlib` - **Runtimes:** `jvm` · **Deployment:** `host`, `docker-compose` - **Reference:** [/state-machines/kv-registry/](https://yanox.dev/state-machines/kv-registry/) ```bash ./yano.sh appchain init --non-interactive \ --recipe owned-registry --network devnet --members 3 --runtime jvm \ --output owned-registry-chain ``` ##### `authenticated-map` — Authenticated map A proof-oriented multi-collection registry with optional canonical-CBOR and declarative schema validation. - **Outcome:** A value accepted under a genesis-declared collection schema is committed and available with a state proof. - **Availability / maturity:** `BUNDLED` / `preview` - **Capabilities:** `state:authenticated-map`, `sequencer:fixed` - **Runtime artifacts:** `yano-runtime`, `yano-x-stdlib` - **Runtimes:** `jvm` · **Deployment:** `host`, `docker-compose` - **Bootstrap requirements:** `pin-all-genesis-member-identities` - **Reference:** [/state-machines/authenticated-map/](https://yanox.dev/state-machines/authenticated-map/) ```bash ./yano.sh appchain init --non-interactive \ --recipe authenticated-map --network devnet --members 3 --runtime jvm \ --output authenticated-map-chain ``` ##### `approval-workflow` — Threshold approval workflow Validator members propose, approve, reject, and prove terminal decisions. - **Outcome:** Distinct validator-member decisions produce a provable terminal approval or rejection. - **Availability / maturity:** `BUNDLED` / `stable` - **Capabilities:** `state:approval-workflow`, `sequencer:fixed` - **Runtime artifacts:** `yano-runtime`, `yano-x-stdlib` - **Runtimes:** `jvm` · **Deployment:** `host`, `docker-compose` - **Reference:** [/state-machines/approvals/](https://yanox.dev/state-machines/approvals/) ```bash ./yano.sh appchain init --non-interactive \ --recipe approval-workflow --network devnet --members 3 --runtime jvm \ --output approval-workflow-chain ``` ##### `role-approval` — Governed role approvals Governed organizations, actors, and policies approve arbitrary application payload hashes. - **Outcome:** A governed role policy accepts valid actor decisions and rejects an ineligible actor decision. - **Availability / maturity:** `BUNDLED` / `preview` - **Capabilities:** `state:role-approvals`, `sequencer:fixed` - **Runtime artifacts:** `yano-runtime`, `yano-x-composite`, `yano-x-role-workflow` - **Runtimes:** `jvm` · **Deployment:** `host`, `docker-compose` - **Bootstrap requirements:** `governed-organizations`, `governed-actors`, `governed-policies` - **Reference:** [docs/appchain/tutorials/05-domain-role-approvals.md](https://github.com/bloxbean/yano-x/blob/main/docs/appchain/tutorials/05-domain-role-approvals.md) ```bash ./yano.sh appchain init --non-interactive \ --recipe role-approval --network devnet --members 3 --runtime jvm \ --output role-approval-chain ``` ##### `evidence-ledger` — Role-aware evidence ledger A role-aware evidence ledger that emits publication intents; add and provision explicit connector capabilities for external mutation. - **Outcome:** A role-authorized evidence record and its release decision are committed and queryable. - **Availability / maturity:** `BUNDLED` / `preview` - **Capabilities:** `state:role-evidence`, `sequencer:fixed` - **Runtime artifacts:** `yano-runtime`, `yano-x-stdlib`, `yano-x-evidence-registry`, `yano-x-composite`, `yano-x-role-workflow`, `yano-x-evidence-profile` - **Runtimes:** `jvm` · **Deployment:** `host`, `docker-compose` - **Bootstrap requirements:** `governed-organizations`, `governed-actors`, `governed-policies` - **Reference:** [/tutorials/04-evidence-publication/](https://yanox.dev/tutorials/04-evidence-publication/) ```bash ./yano.sh appchain init --non-interactive \ --recipe evidence-ledger --network devnet --members 3 --runtime jvm \ --output evidence-ledger-chain ``` ##### `eutxo-ledger` — Scalus-backed EUTxO ledger A deterministic Cardano-shaped test ledger funded by an explicit virtual genesis allocation. - **Outcome:** A signed key-controlled or bounded Plutus V3 transaction consumes virtual EUTxOs and creates MPF-proven outputs and receipts. - **Availability / maturity:** `FIRST_PARTY_OPTIONAL` / `experimental` - **Capabilities:** `profile:eutxo-plutus-v3`, `funding:eutxo-genesis`, `sequencer:fixed` - **Runtime artifacts:** `yano-runtime`, `yano-x-eutxo-ledger` - **Runtimes:** `jvm` · **Deployment:** `host`, `docker-compose` - **Bootstrap requirements:** `use-test-keys-and-no-real-funds` - **Reference:** [ledgers/eutxo/README.md](https://github.com/bloxbean/yano-x/blob/main/ledgers/eutxo/README.md) ```bash ./yano.sh appchain init --non-interactive \ --recipe eutxo-ledger --network devnet --members 3 --runtime jvm \ --output eutxo-ledger-chain ``` ##### `eutxo-cardano-bridge` — Federated Cardano EUTxO bridge A Cardano-backed EUTxO ledger with stable deposits and claim-bound federated withdrawals through external signing or permissionless current-root proofs. - **Outcome:** Accepted stable deposits create mirrored EUTxOs; signed L2 spends create irrevocable claims that settle only through the configured signer path or a current accepted MPF root with single-use nullification. - **Availability / maturity:** `EXPERIMENTAL` / `experimental` - **Capabilities:** `profile:eutxo-plutus-v3`, `bridge:cardano-federated`, `sequencer:fixed` - **Runtime artifacts:** `yano-runtime`, `yano-x-eutxo-ledger`, `yano-x-eutxo-bridge-cardano` - **Runtimes:** `jvm` · **Deployment:** `host`, `docker-compose` - **External prerequisites:** `reviewed-staging-vault-root-nullifier-and-proof-contract-identities`, `stable-cardano-l1-feed`, `federated-vault-and-root-operators`, `external-threshold-or-hsm-settlement-signer-for-signer-mode` - **Bootstrap requirements:** `configure-durable-settlement-journal-for-signer-mode`, `pin-current-root-nullifier-and-proof-contract-identities-for-proof-mode`, `complete-real-funds-custody-review` - **Reference:** [ledgers/eutxo/README.md](https://github.com/bloxbean/yano-x/blob/main/ledgers/eutxo/README.md) ```bash ./yano.sh appchain init --non-interactive \ --recipe eutxo-cardano-bridge --network devnet --members 3 --runtime jvm \ --output eutxo-cardano-bridge-chain ``` ##### `eutxo-zeroj-validity` — ZeroJ EUTxO validity development profile A no-real-funds EUTxO ledger with the optional ZeroJ validity commitment and the interfaces needed to operate and test direct proof settlement. - **Outcome:** Bounded finalized EUTxO payments deterministically update both the Yano MPF root and the circuit-friendly validity root and can be proved against pinned development artifacts. - **Availability / maturity:** `EXPERIMENTAL` / `experimental` - **Capabilities:** `funding:eutxo-genesis`, `settlement:zeroj-validity`, `sequencer:fixed` - **Runtime artifacts:** `yano-runtime`, `yano-x-eutxo-ledger`, `yano-x-eutxo-zk-zeroj` - **Runtimes:** `jvm` · **Deployment:** `host`, `docker-compose` - **External prerequisites:** `zeroj-0.1.0-pre10`, `julc-0.1.0-pre16`, `development-ceremony-bundle` - **Bootstrap requirements:** `use-test-keys-and-no-real-funds` - **Reference:** [ledgers/eutxo-zk/README.md](https://github.com/bloxbean/yano-x/blob/main/ledgers/eutxo-zk/README.md) ```bash ./yano.sh appchain init --non-interactive \ --recipe eutxo-zeroj-validity --network devnet --members 3 --runtime jvm \ --output eutxo-zeroj-validity-chain ``` ##### `eutxo-zeroj-preview` — ZeroJ EUTxO testnet lifecycle Packages the Cardano-shaped L2 client, measured b16 development circuit, durable lifecycle tooling, contract plans, prover artifacts, relay handoff, and recovery evidence as one JVM-only testnet project. - **Outcome:** A project can bootstrap pinned development artifacts, accept finalized Jubjub-authorized L2 transactions, produce a constant-size proof, and prepare idempotent Cardano deposit, settlement, withdrawal, and recovery operations. - **Availability / maturity:** `EXPERIMENTAL` / `experimental` - **Capabilities:** `settlement:zeroj-validity`, `bridge:cardano-federated`, `l1:slot-feed`, `sequencer:fixed` - **Runtime artifacts:** `yano-runtime`, `yano-x-eutxo-ledger`, `yano-x-eutxo-bridge-cardano`, `yano-x-eutxo-zk-zeroj`, `yano-x-eutxo-zk-runtime`, `yano-x-eutxo-zk-lifecycle` - **Runtimes:** `jvm` · **Deployment:** `host`, `docker-compose` - **External prerequisites:** `zeroj-0.1.0-pre10`, `julc-0.1.0-pre16`, `funded-testnet-operator-key-for-live-L1-operations` - **Bootstrap requirements:** `trusted-proof-submitter`, `development-ceremony-or-reviewed-testnet-ceremony`, `deployed-pinned-contract-identities`, `disposable-test-funds-only` - **Reference:** [ledgers/eutxo-zk/GETTING_STARTED.md](https://github.com/bloxbean/yano-x/blob/main/ledgers/eutxo-zk/GETTING_STARTED.md) ```bash ./yano.sh appchain init --non-interactive \ --recipe eutxo-zeroj-preview --network devnet --members 3 --runtime jvm \ --output eutxo-zeroj-preview-chain ``` ##### `custom-plugin` — Custom state-machine project A generated project boundary for a separately reviewed custom state-machine plugin. - **Outcome:** The operator-defined state-machine contract is loaded from a reviewed pinned plugin bundle. - **Availability / maturity:** `REFERENCE` / `experimental` - **Capabilities:** `state:custom-plugin`, `sequencer:fixed` - **Runtime artifacts:** `yano-runtime` - **Runtimes:** `jvm` · **Deployment:** `host`, `docker-compose` - **External prerequisites:** `reviewed-custom-plugin-bundle` - **Bootstrap requirements:** `plugin-metadata-trust-review` - **Reference:** [/plugins/](https://yanox.dev/plugins/) ```bash ./yano.sh appchain init --non-interactive \ --recipe custom-plugin --network devnet --members 3 --runtime jvm \ --output custom-plugin-chain ``` #### Next - [Choosing a recipe](https://yanox.dev/recipes/choosing-a-recipe/) — a decision path from business outcome to selection. - [Stock state-machine cookbook](https://yanox.dev/tutorials/03-stock-state-machines/) — compare the built-in deterministic models. - [Capability catalog](https://yanox.dev/reference/capabilities/) — the full capability list behind these recipes. --- ## Choosing a recipe Source: https://yanox.dev/recipes/choosing-a-recipe/ > A decision path from business outcome to a concrete recipe, state machine, and extension level — including when configuration is not enough. Start from the outcome you need, not from the technology. Almost every application maps onto a stock model; writing code should be the last resort, not the first move. #### Start from the outcome | I want to… | Start with | Coding required? | |---|---|---| | See three members finalize the same events | [Your first app chain](https://yanox.dev/tutorials/01-first-app-chain/) | No | | Keep an append-only log of opaque application records | `audit-log` recipe (`ordered-log`) | No | | Maintain a provable, owner-controlled registry | `owned-registry` recipe ([`kv-registry`](https://yanox.dev/state-machines/kv-registry/)) | No | | Maintain several proof-oriented collections, optionally with value validation | `authenticated-map` recipe ([guide](https://yanox.dev/state-machines/authenticated-map/)) | Configuration; a plugin only for custom rules | | Collect member approvals and optionally trigger an action | `approval-workflow` recipe ([`approvals`](https://yanox.dev/state-machines/approvals/)) | Configuration plus typed commands | | Track balances with a non-negative account ledger | [`balances`](https://yanox.dev/state-machines/balances/) | Configuration plus typed commands | | Maintain a document-hash trail per product or case | [`doc-trail`](https://yanox.dev/state-machines/doc-trail/) | Configuration plus typed commands | | Approve payload hashes using application-defined roles | `role-approval` recipe ([`role-approvals`](https://yanox.dev/state-machines/role-approvals/)) | Configuration plus actor integration | | Require manufacturers, auditors, and regulators to sign by role | [Domain-role approvals](https://yanox.dev/tutorials/05-domain-role-approvals/) | No, for the stock scenario | | Publish immutable evidence to object storage or IPFS and notify Kafka | `evidence-ledger` recipe ([Evidence](https://yanox.dev/products/evidence/)) | No for the demo; connector plugins in deployments | | Call an ERP or API after a finalized decision | [Webhook effects](https://yanox.dev/tutorials/06-webhook-effects/) | Configuration; emission is stock or plugin logic | | Query and prove historical Cardano parameters, stake, and governance | [Cardano History](https://yanox.dev/products/cardano-history/) | Plugin configuration and CLI | | Run a UTxO-style ledger or explore ZK settlement | [eUTxO and ZK](https://yanox.dev/products/eutxo-and-zk/) | Experimental; Cardano builder integration | | Implement business rules Yano does not ship | [The plugin framework](https://yanox.dev/plugins/) | A small Java plugin | If you are unsure, run tutorials 1, 2, 4, and 5 in that order. They show the progression from a replicated log to proofs, external actions, and business-role authorization. #### Then pick the extension level ```text Does a stock machine or profile already model the outcome? ├─ yes → configuration only └─ no Are all required components already available? ├─ yes → a small composite plugin └─ no → a custom state-machine plugin ``` Read [the extension ladder](https://yanox.dev/plugins/) before deciding you need the third rung. Most teams that think they need a custom state machine actually need a composite, and many that think they need a composite need only configuration. #### Questions that change the answer **Who authorizes an action — a node or a person?** If the approver is a validator member, `approvals` is enough. If the approver is a business actor at an organization (an auditor, a regulator, a QA manager) whose key is not a node key, you need `role-approvals` or the `role-evidence` profile. Those separate the identity that *transports* a command from the identity that *authorizes* its business meaning. **Do you need to prove absence, or only presence?** Presence is straightforward. Absence requires dataset completeness — a marker, a paired subject, or an authenticated snapshot descriptor. Design for it up front; see [State and proofs](https://yanox.dev/concepts/state-and-proofs/). **Does an external system have to act on a decision?** Then you need [effects](https://yanox.dev/concepts/effects/), and you must decide the finality gate (`app-final` vs `l1-anchored`), the expiry, and who may attest to results. **Do several capabilities need one atomic state root?** That is exactly what a composite profile is for. If they can live on separate chains, use separate chains — a node can host several. **Is the data volume large and immutable per period?** Look at [authenticated snapshots](https://github.com/bloxbean/yano-x/blob/main/docs/appchain/AUTHENTICATED_SNAPSHOTS.md) rather than growing primary authenticated state indefinitely. **Will the semantics change after launch?** Choose governed composite profiles from the start. Retrofitting governance onto a fixed-profile chain means a new chain. #### What "no code" honestly means No-code means the required state machine, composite, connector, and launcher already ship with Yano X. A real application still: - sends typed commands; - owns its UI, identity onboarding, key custody, and business data; and - decides its retention, authorization, and operational policy. Configuration cannot invent arbitrary consensus transitions. New combinations of existing components use a small composite plugin; genuinely new domain logic uses a custom state-machine plugin. #### Before you commit ```bash ./yano.sh appchain recipes # what this build offers ./yano.sh appchain capabilities # support tier, scope, selection ./yano.sh appchain capabilities --format json ``` The catalog is release-pinned, so the binary you built is the authority for the version you will run. The [recipe catalog](https://yanox.dev/recipes/) and [capability catalog](https://yanox.dev/reference/capabilities/) here are generated from the same files. --- # Plugin framework --- ## The extension ladder Source: https://yanox.dev/plugins/ > Yano X's central design idea — climb only as far as you need, from configuration to a composite plugin to a custom state machine, and never fork the consensus runtime. 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. ```text 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. #### Rung 0 — configuration only Select one built-in id or profile, identically on every member: ```yaml 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](https://yanox.dev/recipes/). 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. #### Rung 1 — a small composite plugin 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. #### Rung 2 — a custom state-machine plugin Only when you need genuinely new state or new rules. ```java 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](https://yanox.dev/concepts/determinism-rules/) before writing `apply()`, and [Consensus rules](https://yanox.dev/plugins/consensus-rules/) before changing one that is already live. #### Rung 3 — outside consensus | SPI | Purpose | |---|---| | Effect executor | Perform an authorized external action after its finality gate. | | Finalized-stream sink | Deliver finalized blocks to Kafka, a webhook, or your own system. | | Domain API and committed queries | Bounded read surfaces over your own state. | | Signer | External key custody or a KMS. | | Sequencer mode | Alternative proposer selection. | | L1 observer | React 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. #### What is and is not a plugin 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. **JVM ONLY:** Yano X plugins target the JVM distribution. Yano's native image cannot discover directory JARs and does not include Yano X plugins — `appchain doctor` reports a direct incompatibility if you point a project at a native distribution. #### The rest of this section 1. [Scaffold, sign, install](https://yanox.dev/plugins/scaffold-sign-install/) — the actual lifecycle, command by command. 2. [SPI and manifest](https://yanox.dev/plugins/spi-and-manifest/) — the three bounded contracts your JAR carries and the trust envelope over them. 3. [Consensus rules](https://yanox.dev/plugins/consensus-rules/) — what plugin code may do, and how to evolve it without forking a live chain. 4. [Testing and deployment](https://yanox.dev/plugins/testing-and-deployment/) — the testing ladder and operational expectations. The hands-on version of all of this is [Tutorial 8](https://yanox.dev/tutorials/08-plugins-and-composites/). --- ## Scaffold, sign, install Source: https://yanox.dev/plugins/scaffold-sign-install/ > The full plugin lifecycle command by command — scaffold a bounded starting point, implement, sign the catalog, validate, pin it into a project, and install it on every member. Seven steps from nothing to a running plugin. None of them require touching Yano. ```mermaid flowchart LR S[scaffold] --> I[implement + test] I --> G[sign] G --> V[validate / inspect] V --> P["init --plugin-jar
(pin into a project)"] P --> D[doctor] D --> C["copy into plugins/
on every member"] ``` #### 1. Scaffold ```bash ./yano.sh appchain plugin scaffold \ --mode state-machine \ --id shipment \ --package com.example.shipment \ --yano-version 0.1.0-pre13 \ --output shipment-plugin ``` Modes are `state-machine`, `composite-role`, `effect-executor`, and `sink`. All four share the same runtime manifest, signed product catalog, and ServiceLoader conventions. The generated provider deliberately does **no** business work: state-machine admission is closed and executor/sink factories return no instances until you implement them. The tool refuses a non-empty output directory. #### 2. Implement and test Write the codec, the admission check, and the transitions. Before you do, read [Determinism rules](https://yanox.dev/concepts/determinism-rules/) — the constraints are strict and the failure mode (a cluster that stops finalizing) is expensive to debug. Then work up the [testing ladder](https://yanox.dev/plugins/testing-and-deployment/). #### 3. Sign Sign the exact catalog, runtime manifest, and optional configuration metadata. ```bash ./yano.sh appchain plugin sign \ --catalog shipment-plugin/src/main/resources/META-INF/yano/appchain-component-catalog-v1.json \ --runtime-manifest shipment-plugin/src/main/resources/META-INF/yano/plugins/plugin-bundle.shipment.json \ --seed-file /secure/publisher.seed \ --key-id example-release-2026 \ --output shipment-plugin/src/main/resources/META-INF/yano/appchain-component-catalog-v1.sig.json ``` **KEY HANDLING:** The publisher key is a 32-byte seed. Keep it **outside the repository** and pass it **by file only** — never inline on a command line, never in CI logs, never in an environment variable that gets echoed. The corresponding public key is not secret and is distributed with `--trust-key`. #### 4. Build and validate ```bash cd shipment-plugin && gradle jar && cd .. ./yano.sh appchain plugin validate shipment-plugin/build/libs/shipment-yano-plugin.jar \ --trust-key example-release-2026=<64-hex-public-key> \ --output shipment-catalog.json ``` `inspect` prints the same verified catalog and its capabilities. Neither command loads provider classes, runs plugin code, fetches a registry, or installs the JAR. The exported snapshot is the safe, data-only local-import format used by Studio and by generated projects. #### 5. Pin it into a project ```bash ./yano.sh appchain init --non-interactive \ --recipe custom-plugin --network devnet --members 3 --runtime jvm \ --capability state:shipment \ --plugin-jar shipment-plugin/build/libs/shipment-yano-plugin.jar \ --trust-key example-release-2026=<64-hex-public-key> \ --output shipment-chain ./yano.sh appchain config validate --mode project shipment-chain ``` The project stores the signed data-only snapshot under `component-catalogs/`. Its lock pins the snapshot, the catalog, the runtime manifest, the configuration metadata, and the complete plugin-JAR digests. Rendering and `doctor` reverify the project snapshot automatically. #### 6. Check readiness against a real distribution ```bash ./yano.sh appchain doctor shipment-chain --distribution /opt/yano-x ``` A missing or different JAR fails artifact readiness. A native distribution reports a direct incompatibility, because Yano X plugins target the JVM host. #### 7. Install on every member Copy the exact pinned JAR into `plugins/` in every member's distribution, then validate the resulting set: ```bash tools/yano-plugins/bin/yano-plugins validate plugins/*.jar ``` The plugin directory property is **`yano.plugins.directory`**. Restart the nodes; Yano is not rebuilt. **EVERY MEMBER, OR NONE:** The same bundle, the same machine or profile id, and the same committed settings must run on every voting member. A member with a different bundle derives a different root and the chain stops finalizing. Treat plugin drift as a deployment error, never as something the system should tolerate. #### Changing a plugin later Adding a member or a node is routine. Changing what a plugin *computes* is not — see [Consensus rules](https://yanox.dev/plugins/consensus-rules/). In short: - Give every bundle a stable plugin id and a semantic version. - Never silently change semantics behind an existing machine or component id. - Use activation heights and profile governance for compatible evolution. - Use a new namespace and a migration plan for incompatible state. #### Compare, then apply ```bash ./yano.sh appchain diff previous.lock shipment-chain/appchain.lock ./yano.sh appchain migrate shipment-chain --dry-run ./yano.sh appchain drift shipment-chain --peer ``` `diff` classifies a blueprint change before you apply it. `drift` compares a project against running nodes when identities are available — the fastest way to find the one member that is out of step. --- ## SPI and manifest Source: https://yanox.dev/plugins/spi-and-manifest/ > The three bounded contracts a Yano X plugin JAR carries, the Ed25519 trust envelope over them, how activation works, and what signing does and does not mean. A plugin JAR is not just code with a `ServiceLoader` entry. It carries three independent, bounded contracts, and an Ed25519 envelope that binds them. #### The three contracts ```text shipment-yano-plugin.jar └── META-INF/yano/ ├── plugins/plugin-bundle.shipment.json runtime contributions ├── appchain-config-metadata-v1.json typed configuration (optional) ├── appchain-component-catalog-v1.json selectable product capabilities └── appchain-component-catalog-v1.sig.json Ed25519 trust envelope ``` | File | Owns | |---|---| | `plugins/.json` | The executable runtime contributions — what this bundle actually provides to a running node, and the Yano API major and min/max levels it is compatible with. | | `appchain-config-metadata-v1.json` | Typed configuration definitions: keys, types, defaults, allowed values, scope, change policy, and whether a value is secret. | | `appchain-component-catalog-v1.json` | Selectable product capabilities and the artifacts they require — what a user sees in `appchain capabilities` and in Studio. | They are deliberately separate. A tool can read the catalog to offer a capability without loading any code, and a node can validate the runtime manifest without interpreting product metadata. #### Activation Runtime activation goes through `PluginProviderRegistry` plus the schema-v1 plugin manifest. There is no other path: - no raw `ServiceLoader` discovery by the host, - no direct host construction, - no product switches inside the host, and - no product-specific host CDI or REST activation. The host loads a bundle from `yano.plugins.directory`, validates its manifest, resolves declared dependencies, checks API compatibility, and only then activates the contributions that configuration has selected. Requirements a runtime plugin must satisfy: | Requirement | Why | |---|---| | Dependency-complete bundle | The node must not have to resolve your transitive dependencies at runtime. | | Does not embed host SPI classes | Embedding them creates two incompatible copies of the same interface. | | Declares Yano API major and min/max level | A bundle built against an incompatible host fails closed rather than misbehaving. | | Bounded lifecycle cleanup | Shutdown must actually release threads, connections, and files. | #### The trust envelope The Ed25519 signature binds: - the component catalog, - the runtime manifest, - the optional configuration metadata, - the bundle identity and version, and - the publisher key id. It authenticates **those exact bytes**. Understanding what it does *not* do matters just as much: **SIGNING IS NOT APPROVAL:** A valid signature does not approve the code, and it does not elevate a custom component to `BUNDLED`, `stable`, or native status. Custom entries remain JVM-only `REFERENCE` or `EXPERIMENTAL`. All release id, namespace, and artifact collisions fail closed. Verification is offline and code-free: ```bash ./yano.sh appchain plugin inspect --trust-key =<64-hex-public-key> ./yano.sh appchain plugin validate --trust-key =<64-hex-public-key> \ --output catalog-snapshot.json ./yano.sh appchain metadata verify --trust-key = ``` Neither command loads provider classes, runs plugin code, fetches a registry, nor installs the JAR. The public key is not secret; distribute it freely. #### Pinning, in a project When a project selects a custom capability, `appchain.lock` pins: - the signed, data-only catalog snapshot under `component-catalogs/`; - the catalog, runtime manifest, and configuration-metadata digests; and - the complete plugin-JAR digests. `render` and `doctor` reverify the snapshot automatically, and a JAR that does not match the pinned digest fails artifact readiness. That is how a "works on my node" plugin mismatch becomes a build error rather than a stalled chain. #### Configuration metadata and coverage Typed configuration metadata is what makes `appchain config validate` and `appchain explain` useful for third-party plugins. Each property declares its type, default, bounds, allowed values, scope, change policy, and secret flag. Coverage is reported honestly. Treat custom-plugin metadata as **`PARTIAL`** unless Yano reports `FULL` coverage, and verify the signed metadata and its runtime-manifest binding before trusting a third-party artifact. The scope field is the one to read carefully: | Scope | Meaning | |---|---| | `CONSENSUS_SHARED` | Must be identical on every member. A mismatch diverges the state root. | | Node-local | Ports, storage, credentials, executor placement. Safe to differ. | And the change policy: | Policy | Meaning | |---|---| | `NEW_CHAIN_REQUIRED` | The value is part of chain identity. Changing it means a new chain, or a governed activation. | | Others | See the generated [configuration reference](https://yanox.dev/reference/configuration/). | #### Which SPI do you need? | You want to… | Implement | |---|---| | Interpret new message bodies and own new state | `AppStateMachine` + `AppStateMachineProvider` | | Arrange existing components in a new committed order | A composite profile provider | | Perform an authorized external action | An effect executor | | Deliver finalized blocks somewhere | A finalized-stream sink | | Expose a bounded read surface over your state | A domain API and committed queries | | Prove an application-level fact | `ProofSubjectProvider` | | Hold keys outside the node | A signer | | React to Cardano deposits or metadata labels | An L1 observer | For a proof subject, put its `descriptorDigest` in the capability manifest; the runtime activates the provider only when subject id, version, component id, and digest all match. Resolution must be deterministic and side-effect free — see [State and proofs](https://yanox.dev/concepts/state-and-proofs/). #### Deeper reading - ADR-011 in the repository's `adr/app-layer/` directory — the host/plugin SPI, catalog, compatibility, isolation, and lifecycle contract. - [`core-host.md`](https://github.com/bloxbean/yano-x/blob/main/docs/core-host.md) — plugin query and domain API contract, plugin operations. - [Composite implementation guide](https://github.com/bloxbean/yano-x/blob/main/composition/runtime/README.md) - [Plugin template scaffold](https://github.com/bloxbean/yano-x/tree/main/scaffolds/plugin-template/) --- ## Consensus rules for plugins Source: https://yanox.dev/plugins/consensus-rules/ > What plugin code may and may not do inside consensus, how to bound hostile input, and how to evolve a live chain's semantics without forking it. Plugin code that runs inside `apply()` is consensus code. Every member executes it and must derive the same state root. This page is the contract. #### The rules - The same bundle, machine or profile id, and committed settings run on **every voting member**. - `apply()` must not use wall clock, randomness, DNS, filesystem, database, or network I/O. - Invalid finalized bytes become **deterministic no-ops**, not escaping exceptions. - Bound message bytes, decode depth and item counts, collections, state growth, and work per block. - **Never** silently change semantics behind an existing machine or component id. - Use activation heights and profile governance for compatible evolution, and a new namespace plus a migration plan for incompatible state. - Every state write belongs to the authenticated writer. Do not keep hidden consensus state in static fields or node-local storage. [Determinism rules](https://yanox.dev/concepts/determinism-rules/) has the full list of non-deterministic constructs to avoid, with replacements. #### Admission versus application Two different jobs, with different constraints: ```java @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. } ``` `validate` runs at ingress and may reject a message before it is ever ordered. It is an efficiency and hygiene filter — it is **not** a security boundary, because a message can reach `apply()` through catch-up from history. `apply` runs on finalized bytes on every member. By the time it runs, the bytes are already agreed; your job is to interpret them identically. Anything `apply()` cannot handle must become a recorded no-op, not an exception. #### Treat finalized input as hostile Finalized does not mean well-formed. A member could have proposed anything the threshold was willing to sign, and history can be replayed from an arbitrary peer. Bound everything: | Bound | Why | |---|---| | Message byte length | A single huge body must not exhaust memory during replay. | | Decode depth and item count | Nested CBOR/JSON is a classic decompression-style attack. | | Collection sizes you build | Per-block work must be predictable. | | State growth per transition | Unbounded growth makes replay and snapshots impossible. | | Work per block | The slowest member sets the finality rate. | The framework already caps effects per block and effect payload bytes as consensus parameters. Your state machine must supply the equivalent bounds for its own domain. #### Evolving a live chain This is where most of the risk sits. Replaying history under new rules produces different state, so a semantic change is never a rolling code change. ##### What counts as a semantic change - state encoding or key layout, - transition logic or validation outcomes, - what a transition emits as an effect, - a commitment profile or component order, - a proof subject descriptor, or - genesis-selected configuration. ##### The three options **1. Compatible evolution behind a governed profile.** Package the reviewed current and dormant targets on **every** member first, then threshold-authorize one exact digest and a future activation height. Members that have not staged the target cannot activate it, and editing YAML or swapping a JAR alone changes nothing. **2. A new component id or namespace.** For an incompatible change, publish a new id. The old one keeps its meaning for historical replay, and new state lives in a new namespace with an explicit migration plan. **3. A new chain.** When identity itself must change. Recall that identity is pinned by: ```text (commitment-profile, format-fingerprint, genesis-id) ``` A retained `genesis-id` is never regenerated. **THE THING NOT TO DO:** Editing a state machine's logic and rolling out the new JAR member by member. During the rollout, members compute different roots and the chain stops finalizing; afterwards, replaying history produces state that does not match the signed roots. This is unrecoverable without a new chain. ##### Effect emission is included Changing what a transition emits is a hard fork unless it is gated. Ship the new emission logic behind a governed profile activation, or behind a condition that is itself part of committed state. #### Composites are consensus-critical Java A composite profile's component order, versions, routes, quotas, and workflow transitions are **code**, not configuration, precisely because two members discovering a different order would derive different roots. That code is small, but it deserves the same review as a state transition. The profile is canonically encoded, committed to authenticated state at height 1, and re-verified on restart and every transition. #### Keeping secrets out of consensus Endpoints, credentials, and API keys belong in **node-local** executor configuration. They must never appear in replicated effect payloads or in consensus-shared configuration — a replicated payload is visible to every member and provable to anyone holding a proof. The five secret classes to keep separate: member signing keys, business-actor keys, API keys, effect credentials, and anchor wallet funds. #### Verifying before you ship ```bash ### Focused iteration. ./gradlew :state-machines:stdlib:test ### Real cluster: identical height, root, profile, genesis, capability digest. ./yano.sh appchain cluster start 3 ./yano.sh appchain cluster status ./yano.sh appchain cluster node join 3 # catch-up under your rules ./yano.sh appchain cluster stop && ./yano.sh appchain cluster start 3 ``` Persistence changes additionally require apply, rollback, replay, restart, and root-parity checks. Derived indexes must never advance beyond authoritative app-chain state. Continue to [Testing and deployment](https://yanox.dev/plugins/testing-and-deployment/). --- ## Testing and deployment Source: https://yanox.dev/plugins/testing-and-deployment/ > The seven-rung testing ladder for a Yano X plugin, the effect failure modes worth testing explicitly, and the operational rules for running plugins in production. A plugin that passes unit tests can still stall a cluster. The failure modes that matter — cross-node divergence, replay mismatch, executor double-delivery — only appear when you test for them deliberately. #### The testing ladder Climb it in order. Each rung catches a class of bug the one below cannot. 1. **Unit-test codecs and deterministic transitions.** Round-trip every encoding. Assert exact bytes, not just equality of decoded objects. 2. **Run the state-machine conformance and replay matrix.** Replay the same blocks twice on one member and assert an identical root. This catches reproducibility bugs. 3. **Test malformed and hostile finalized input through `apply()`.** Truncated bodies, wrong types, deeply nested structures, oversized collections, duplicate ids, and values at every declared bound. Each must produce a deterministic no-op, never an escaping exception. 4. **Start an embedded multi-member cluster with `appchain-testkit`.** `@AppChainCluster` gives you a real multi-member consensus round inside a JUnit 5 test. This is the first rung that can catch cross-node divergence — a `HashMap` iteration bug passes rungs 1–3 and fails here. 5. **Verify root parity, proof keys, restart, catch-up, rollback/reapply, and plugin packaging.** Every member must expose the same root at the same height, and a member that joins late must reach it by verified catch-up. 6. **For effects, test the failure modes explicitly:** crash-before-send, send-before-ack, retry, reconciliation, parking, requeue, and duplicate idempotency. External execution is at-least-once, so a receiver that is not idempotent will be found here or in production. 7. **Run a packaged JVM cluster.** The real distribution, the real `plugins/` directory, the real catalog validation. Packaging bugs — a missing transitive dependency, an embedded host SPI class — only surface here. #### Test dependencies | Artifact | Purpose | |---|---| | `yano-appchain-core-testkit` | JUnit 5 `@AppChainCluster` embedded clusters. | | `yano-x-effects-testkit` | Effect executor and result-path testing. | | `yano-x-client` | REST, SSE, and client-side proof verification from tests. | | `yano-x-eutxo-testkit`, `yano-x-eutxo-zk-testkit` | For the eUTxO and ZK ledgers. | #### Gates in the Yano X build If you are contributing a plugin to Yano X itself rather than shipping your own, choose gates in proportion to the change: ```bash ./gradlew :state-machines:stdlib:test # focused unit tests ./gradlew verifyArtifactInventory # module/publication/manifest/bundle changes ./gradlew verifyJvmOnlyBuild # build topology changes ./gradlew integrationTest # integration domains ./gradlew cryptoTest # crypto domains ./gradlew clean build # dependency, bundle, isolation, packaging ``` `verifyArtifactInventory` checks that every module has exactly one declared artifact identity and that runtime plugins have bundle publications. `verifyJvmOnlyBuild` rejects accidental native-image tasks. See [Developing Yano X](https://yanox.dev/contributing/) for the full contributor workflow. #### Deployment rules - **Give every bundle a stable plugin id and a semantic version.** The id is a consensus-visible identity; do not reuse it for different semantics. - **Verify catalog state and health on every member before admitting traffic.** A member whose catalog failed to validate should not receive submissions. - **Treat plugin removal or drift as a deployment error**, not as something to fall back from automatically. Silent fallback would change semantics. - **Namespace configuration and metrics by plugin and contribution**, so an operator can attribute a problem to the right bundle. - **Keep plugin domain APIs read-only** unless commands still enter through the authenticated app-chain submission path. #### Installing on a cluster ```bash ### On every member's distribution: cp shipment-yano-plugin.jar /opt/yano-x/plugins/ tools/yano-plugins/bin/yano-plugins validate /opt/yano-x/plugins/*.jar ### Before admitting traffic: ./yano.sh appchain doctor shipment-chain --distribution /opt/yano-x ./yano.sh appchain drift shipment-chain --peer http://node-a:8080 ``` The property is `yano.plugins.directory`. Restart the nodes; Yano is not rebuilt. `drift` is the fastest way to find the one member that is out of step, and it is worth running as a routine check rather than only during an incident. #### Operating a plugin | Surface | What it gives you | |---|---| | `/status` and `/ui/app-chain/` | Tip, root, agreement, anchor lag, plugin inventory. | | Health checks | Per-plugin health contributed through the SPI. | | Prometheus metrics | Namespaced by plugin and contribution; effect metrics can be scoped by type. | | Effect operations | Inspect, retry, park, requeue, and reconcile stuck effects. | | Admin API | Key rotation, snapshots, onboarding. | Distinguish the four states an effect can be in when diagnosing: **intent** recorded, **execution** attempted, **incorporation** completed, and **anchoring** of the height that carries it. Operators who conflate them chase the wrong problem. #### Before a pilot Beyond the ladder above, plan for: - application-specific soak and load testing on a public test network; - key and secret operations, including rotation runbooks for all five secret classes; - monitoring and SLOs, incident recovery, and restore rehearsal; - retention, pruning, and `oldestProvableHeight` policy; and - governance for any future semantic change. [Tutorial 9 — from demo to pilot](https://yanox.dev/tutorials/09-from-demo-to-pilot/) turns this into a concrete deployment plan. --- # Tutorials --- ## App-Chain Tutorials Source: https://yanox.dev/tutorials/ > These tutorials are progressive but independently usable. Each one has a beginner path and a Go deeper section for readers who want the trust, consensus… **IMPORTED PAGE:** This page is generated from [`docs/appchain/tutorials/README.md`](https://github.com/bloxbean/yano-x/blob/main/docs/appchain/tutorials/README.md) in the Yano X repository, which is its source of truth. These tutorials are progressive but independently usable. Each one has a beginner path and a **Go deeper** section for readers who want the trust, consensus, proof, or operational details. | Tutorial | Typical time | Primary outcome | |---|---:|---| | [1. Your first app chain](https://yanox.dev/tutorials/01-first-app-chain/) | 15 min | Three members finalize the same event history | | [2. Registry and proofs](https://yanox.dev/tutorials/02-registry-and-proofs/) | 15 min | Owner-controlled data with an MPF proof | | [3. Stock state machines](https://yanox.dev/tutorials/03-stock-state-machines/) | 20 min | Choose the smallest built-in application model | | [4. Evidence publication](https://yanox.dev/tutorials/04-evidence-publication/) | 30 min | S3/IPFS/Kafka effects plus proofs and an anchor | | [5. Domain-role approvals](https://yanox.dev/tutorials/05-domain-role-approvals/) | 30 min | Generic role-gated hashes, then the evidence specialization | | [6. Webhook effects](https://yanox.dev/tutorials/06-webhook-effects/) | 20 min | Finalized decision invokes an external HTTP endpoint | | [7. Anchors and verification](https://yanox.dev/tutorials/07-anchors-and-verification/) | 20 min | Connect an application proof to Cardano settlement | | [8. Plugins and composites](https://yanox.dev/tutorials/08-plugins-and-composites/) | 30–60 min | Extend Yano without rebuilding or forking core | | [9. From demo to pilot](https://yanox.dev/tutorials/09-from-demo-to-pilot/) | planning | Convert local assumptions into an operable deployment | | [10. EUTxO ZK rollup on devnet](https://github.com/bloxbean/yano-x/blob/main/ledgers/eutxo-zk/DEVNET_WALKTHROUGH.md) | 60+ min | Trace L1 deposit, L2 spend, proof, root settlement, and L1 withdrawal | #### Tutorial conventions - Tutorial commands run `./yano.sh` from the directory containing it, so release and source commands are identical. Tutorial 1 shows how to use an extracted distribution root or the source checkout's `app/` directory. - Treat `./yano.sh` as the product command. Use `./yano.sh appchain cluster ...` for the bundled single-host lifecycle and the same wrapper for `appchain init`, `render`, `config`, `doctor`, `diff`, and `drift`. The internal `appchain-devtools` executable is a packaging/implementation boundary, not a second user-facing CLI to learn. - Local devnet data is disposable. `stop` preserves it; `clean` deletes it. - Ports `7070`–`7072` are the expected member HTTP ports and `7080` is the Evidence Explorer. Launchers report a different range if defaults are busy. - Demo credentials are intentionally known or generated locally. Never reuse them outside an isolated development environment. - A successful HTTP submission means “accepted for sequencing,” not “already finalized.” Wait for the block/tip or use the scenario verifier. - An invalid but finalized command can be a deterministic no-op. Always verify state, not merely the HTTP response or block height. #### Confidence levels used here - **Shipped:** present in the current branch and covered by module tests. - **Demo-proven:** exercised by the packaged multi-member demo and its connector/proof verification. - **Preview:** useful for devnet/testnet or a tightly controlled pilot, with a named production-hardening boundary. - **Experimental:** not a production claim; follow its dedicated guide. Use the [release capability catalog](https://yanox.dev/reference/capabilities/) to distinguish bundled, first-party optional, reference and experimental features before choosing a tutorial path. Return to the [start-here hub](https://yanox.dev/start-here/what-is-an-app-chain/). --- ## Tutorial 1 — Your First App Chain Source: https://yanox.dev/tutorials/01-first-app-chain/ > Three members finalize the same ordered event and expose the same state root. **IMPORTED PAGE:** This page is generated from [`docs/appchain/tutorials/01-first-app-chain.md`](https://github.com/bloxbean/yano-x/blob/main/docs/appchain/tutorials/01-first-app-chain.md) in the Yano X repository, which is its source of truth. [Open this outcome in App-Chain Studio](https://yanox.dev/studio/index.html#recipe=audit-log&network=devnet&members=3&finality=two-thirds&sequencing=fixed&runtime=jvm&deployment=host&name=my-appchain&chainId=my-appchain) - **Level:** beginner - **Time:** about 15 minutes; 25 with the optional load and effects exercises - **Outcome:** three members finalize the same ordered event and expose the same state root. This tutorial uses Yano's self-contained Cardano devnet and the built-in `ordered-log` state machine. It needs no external Cardano node, wallet, funds, Kafka, or plugin. #### 1. Choose the working directory and start The commands in every tutorial run `./yano.sh` from the directory that contains it. Choose one setup: From an extracted release distribution, run this in its top-level directory (the directory containing `yano.sh` and `yano.jar` or the native executable): ```bash cd /path/to/extracted/yano-{version} ./yano.sh appchain help ``` From a source checkout, run this from the repository root: ```bash ./gradlew :app:quarkusBuild -PskipSigning=true cd app ./yano.sh appchain help ``` The remaining commands are identical for either setup: ```bash export YANO_CLUSTER_DIR=/tmp/yano-tutorial-first-chain ./yano.sh appchain cluster start 3 ``` The launcher starts: - node 0 as the local Cardano L1 producer and app-chain proposer; - nodes 1 and 2 as app-chain voting members; and - `orders-chain` (`ordered-log`), `registry-chain` (`kv-registry`), and `effects-chain` (`approvals`) for the dependency-free effects demo. The expected HTTP ports are `7070`, `7071`, and `7072`. If those ports are busy, the launcher prints the free range it selected; use that range in the commands below. #### 2. Confirm agreement ```bash ./yano.sh appchain cluster status ``` Look for: ```text orders-chain: AGREED (...) registry-chain: AGREED (...) ``` `AGREED` means the members expose the same authenticated application root. It does not merely mean that all three processes are running. Open the status pages if you prefer a UI: - - - #### 3. Submit a business event Submit through member 1 rather than directly through the proposer: ```bash ./yano.sh appchain cluster submit orders-chain orders \ '{"event":"order-created","orderId":"A-1001","quantity":4}' \ --node 1 ``` Member 1 authenticates and gossips the envelope. The proposer orders it, a threshold signs the block, and all members apply the same bytes. Wait a couple of seconds, then inspect the finalized history and agreement: ```bash curl -s http://127.0.0.1:7070/api/v1/app-chain/chains/orders-chain/blocks | jq . ./yano.sh appchain cluster status ``` The tip advances on every member and the roots remain equal. #### 4. Capture a message ID and its proof For a complete proof-oriented submission, call the same public API directly: ```bash RESPONSE=$(curl -s -X POST \ http://127.0.0.1:7072/api/v1/app-chain/chains/orders-chain/messages \ -H 'Content-Type: application/json' \ -d '{"topic":"orders","body":"{\"event\":\"packed\",\"orderId\":\"A-1001\"}"}') echo "$RESPONSE" | jq . MESSAGE_ID=$(echo "$RESPONSE" | jq -r .messageId) sleep 3 curl -s -X POST \ "http://127.0.0.1:7070/api/v1/app-chain/chains/orders-chain/proof-subjects/finalized-message-v1/proof" \ -H 'Content-Type: application/json' \ -d "$(jq -nc --arg id "$MESSAGE_ID" ' {coordinates:{"message-id":$id}, view:"latest", claim:{claimId:"recorded",operands:{}}, includeEvidence:false}')" \ | jq '{stateRoot:.proof.stateRoot,presence:.proof.presence,position:.fact.fields,claim:.claimResult.satisfied}' ``` The typed subject resolves the public message ID to its namespaced physical state key and connects that record to the member's committed state root. Tutorial 7 connects that root to a Cardano anchor. #### 5. Run a small `ordered-log` load test The distribution includes a parallel load driver for the running local cluster. Start with a bounded workload of 500 messages, 10 concurrent submitters, and payloads of approximately 256 bytes: ```bash ./yano.sh appchain cluster loadtest orders-chain -n 500 -c 10 -s 256 ``` Plain load-test mode is intended for any-bytes machines such as `ordered-log`. It submits numbered UTF-8 bodies on the `load` topic, waits for the pending pool to drain, and reports two different rates: ```text ==================== throughput ==================== submitted attempts : 500 accepted (2xx) : 500 dropped (429 pool): 0 errors : 0 SUBMIT rate : ... msg/s finalized msgs : 500 in ... block(s) FINALIZE rate : ... msg/s end-to-end rate : ... msg/s ==================================================== ``` - **SUBMIT rate** measures how quickly an ingress REST API accepts messages. - **FINALIZE rate** measures how quickly messages enter threshold-certified blocks; this is the meaningful chain-throughput measurement. - **dropped (429 pool)** means backpressure worked because the pending pool filled. Reduce concurrency or tune the pool and block limits deliberately. - **errors** are non-backpressure request failures and should be investigated. Spread submissions across all ready members to exercise gossip from every ingress path: ```bash ./yano.sh appchain cluster loadtest orders-chain -n 1000 -c 20 -s 256 --spread ``` Then confirm that every member still exposes the same committed root: ```bash ./yano.sh appchain cluster status ``` This is a functional throughput exercise, not a production benchmark. Results include the local machine, JVM/native runtime, devnet producer, configured block interval, payload size, and member count. Record those inputs when comparing runs. The test appends real finalized messages and therefore advances the retained `orders-chain` history. For capacity settings and workload boundaries, see the [`ordered-log` reference](https://github.com/bloxbean/yano-x/blob/main/docs/core-host.md). #### 6. Preserve and restart `stop` keeps both L1 and app-chain data: ```bash ./yano.sh appchain cluster stop ./yano.sh appchain cluster start 3 ./yano.sh appchain cluster status ``` The retained tips and roots should return unchanged before new traffic is finalized. This is a useful distinction: - **restart:** same chain identity and retained state; - **clean:** delete the chain and create a new identity/history. #### 7. Try effects and governed member onboarding ##### Effect demo The default `effects-chain` can demonstrate the full emit, external-worker, result, and proof lifecycle without Kafka, S3, IPFS, or a real webhook: ```bash ./yano.sh appchain cluster effect demo ./yano.sh appchain cluster effect demo "order A-1001 approved" ``` With no argument, the demo uses `hello from Yano effects`; one quoted argument replaces that message. The command prints the captured payload, confirmed delivery, and proof availability. Briefly, the command: 1. creates a unique one-approval item on the separate `effects-chain`; 2. wraps your text in a JSON payload, then submits `PROPOSE` and `APPROVE` commands to its `approvals` state machine; 3. keeps the item decision `APPROVED` and emits one generic app-final `demo.webhook` effect when the approval threshold is reached; 4. acts as a simulated external worker that claims the effect and reports a synthetic successful delivery—no real webhook is called; and 5. feeds the result back through the effect lifecycle and checks that the finalized effect proof is available. The supplied text is illustrative data. It is not automatically linked to an event on `orders-chain`, even if it contains an order id. A production workflow should carry an explicit business id or finalized source message id in its committed command/effect payload. The proof establishes that the effect intent was committed; a real external action additionally depends on a trusted executor and verifiable receipt. For the complete lifecycle and production webhook configuration, continue with [webhook effects](https://yanox.dev/tutorials/06-webhook-effects/). ##### Governed member onboarding You can also govern, start, catch up, and verify a fourth node on the same host: ```bash ./yano.sh appchain cluster node join 3 ./yano.sh appchain cluster status ``` For an externally managed node, the lower-level `appchain cluster member add ` command records membership but does not configure or start the external process. Same-host `node join` performs both steps. When finished: ```bash ./yano.sh appchain cluster clean unset YANO_CLUSTER_DIR ``` #### What you just proved - A submission can enter through any member. - A threshold, not one REST server, finalizes the ordered block. - All members deterministically derive the same state root. - A retained restart recovers the same application history. - A message can have an MPF inclusion proof against that root. You did **not** yet prove Cardano settlement or the truth of the order fields. Those are separate trust layers. #### Go deeper - Change `--threshold 3` and observe that all three votes are now required. - Read [the consensus guide](https://github.com/bloxbean/yano-x/blob/main/docs/core-host.md) for proposer, vote, certificate, replay, and catch-up mechanics. - Continue with [registry ownership and state proofs](https://yanox.dev/tutorials/02-registry-and-proofs/). --- ## Tutorial 2 — A Provable Shared Registry Source: https://yanox.dev/tutorials/02-registry-and-proofs/ > Write owner-controlled data, demonstrate an unauthorized no-op, and retrieve an MPF proof for the current value. **IMPORTED PAGE:** This page is generated from [`docs/appchain/tutorials/02-registry-and-proofs.md`](https://github.com/bloxbean/yano-x/blob/main/docs/appchain/tutorials/02-registry-and-proofs.md) in the Yano X repository, which is its source of truth. [Open this outcome in App-Chain Studio](https://yanox.dev/studio/index.html#recipe=owned-registry&network=devnet&members=3&finality=two-thirds&sequencing=fixed&runtime=jvm&deployment=host&name=shared-registry&chainId=shared-registry) - **Level:** beginner to intermediate - **Time:** about 15 minutes - **Outcome:** write owner-controlled data, demonstrate an unauthorized no-op, and retrieve an MPF proof for the current value. The default cluster also hosts `registry-chain`, backed by the stock `kv-registry` state machine. The first writer becomes the key owner; only that member can update or delete it. #### 1. Start the default cluster Skip this section if Tutorial 1's cluster is still running. ```bash export YANO_CLUSTER_DIR=/tmp/yano-tutorial-registry ./yano.sh appchain cluster start 3 ``` #### 2. Create a registry entry Write through node 1 so that node 1 becomes the authenticated owner: ```bash ./yano.sh appchain cluster kv registry-chain set supplier-42 active --node 1 sleep 3 ./yano.sh appchain cluster status ``` The command body is CBOR, but the launcher builds it for you. Application clients can use the standard-library encoder instead of hand-writing CBOR. #### 3. Prove the current value The physical state key is the UTF-8 business key. Convert it to hex for the proof endpoint: ```bash KEY_HEX=$(python3 -c 'print("supplier-42".encode().hex())') curl -s \ "http://127.0.0.1:7070/api/v1/app-chain/chains/registry-chain/state/proof/$KEY_HEX" \ | jq . ``` The proven value contains both owner identity and registry value. A verifier must verify the MPF path against the expected state root; displaying JSON from one node alone is not independent verification. #### 4. Demonstrate authorization as a deterministic no-op Capture the root, attempt an update through node 2, and compare: ```bash ROOT_BEFORE=$(curl -s http://127.0.0.1:7070/api/v1/app-chain/chains \ | jq -r '.[] | select(.chainId == "registry-chain") | .stateRoot') ./yano.sh appchain cluster kv registry-chain set supplier-42 suspended --node 2 sleep 3 ROOT_AFTER=$(curl -s http://127.0.0.1:7070/api/v1/app-chain/chains \ | jq -r '.[] | select(.chainId == "registry-chain") | .stateRoot') printf 'before=%s\nafter =%s\n' "$ROOT_BEFORE" "$ROOT_AFTER" ``` The unauthorized command can appear in a finalized block, but it must not change the registry entry. This is intentional: the deterministic application result, not “the HTTP call succeeded” or “the block height increased,” is the authorization decision. Check the value again: ```bash curl -s \ "http://127.0.0.1:7070/api/v1/app-chain/chains/registry-chain/state/proof/$KEY_HEX" \ | jq . ``` Now update through the owner and verify the proof changes: ```bash ./yano.sh appchain cluster kv registry-chain set supplier-42 suspended --node 1 sleep 3 curl -s \ "http://127.0.0.1:7070/api/v1/app-chain/chains/registry-chain/state/proof/$KEY_HEX" \ | jq . ``` #### 5. Clean up ```bash ./yano.sh appchain cluster clean unset YANO_CLUSTER_DIR ``` #### Where this pattern fits - consortium allow-lists; - product, asset, credential, or DID-document registries; - shared configuration with explicit ownership; - a current pointer whose exact value must be proven to a third party. It does not provide organization roles, multi-party policy governance, or arbitrary document indexing. Use the role workflow or a domain plugin when the authorization model is richer than “first writer owns this key.” #### Go deeper - Continue with the dedicated [`kv-registry` reference](https://yanox.dev/state-machines/kv-registry/) for configuration, raw REST submission, Java encoding/decoding, and design guidance. - Verify the returned `proofWireHex` using the Java app-chain client rather than trusting the serving node. - Start with `--anchor-mode metadata`, fund the printed anchor wallet on a public test network, and connect the proof root to its anchor. - Review `kv-registry.value-format` (`raw`, `utf8`, or `cbor`) in the [user guide](https://github.com/bloxbean/yano-x/blob/main/docs/APP_CHAIN_USER_GUIDE.md). - Continue with the [stock state-machine cookbook](https://yanox.dev/tutorials/03-stock-state-machines/). --- ## Tutorial 3 — Choose a Stock State Machine Source: https://yanox.dev/tutorials/03-stock-state-machines/ > Choose the smallest built-in deterministic model that matches your application before writing a plugin. **IMPORTED PAGE:** This page is generated from [`docs/appchain/tutorials/03-stock-state-machines.md`](https://github.com/bloxbean/yano-x/blob/main/docs/appchain/tutorials/03-stock-state-machines.md) in the Yano X repository, which is its source of truth. [Open a stock approval workflow in App-Chain Studio](https://yanox.dev/studio/index.html#recipe=approval-workflow&network=devnet&members=3&finality=two-thirds&sequencing=fixed&runtime=jvm&deployment=host&name=approval-workflow&chainId=approval-workflow) - **Level:** beginner for selection, advanced for wire integration - **Outcome:** choose the smallest built-in deterministic model that matches your application before writing a plugin. Selecting a state machine is a consensus decision. Every member must use the same id and deterministic settings. For a new chain it is configuration; for an existing chain, changing semantics requires a governed/versioned profile activation or a new chain. #### Selection guide | Machine | Use it when | Authorization model | Proven state | |---|---|---|---| | [`ordered-log`](https://github.com/bloxbean/yano-x/blob/main/docs/core-host.md) | You need immutable ordered opaque events | Any admitted member | Message by message ID | | [`kv-registry`](https://yanox.dev/state-machines/kv-registry/) | You need mutable named records | First writer owns a key | Current owner/value per key | | [`approvals`](https://yanox.dev/state-machines/approvals/) | Validator members are the approvers | Distinct member keys | Status and decision trail | | [`balances`](https://yanox.dev/state-machines/balances/) | You need internal credits/netting | A member spends its own account | Balance per account | | [`doc-trail`](https://yanox.dev/state-machines/doc-trail/) | You need ordered history per product/case | Admitted member appends | Count and chained trail head | | [`role-approvals`](https://yanox.dev/state-machines/role-approvals/) | Business actors differ from validator members | Governed actors, organizations and roles | Payload hash and signed decision trail | | `evidence-v1-gated` | Approval coordinates S3/IPFS/Kafka publication | Stock composite workflow | One root across components/effects | | `role-evidence` | Business actors differ from validator members | Governed actors, organizations, roles | Registry, policy, decisions, evidence | #### Configure a new standalone chain The local launcher reads `app/config/application-appchain.yml`. A standalone deployment can configure one machine directly: ```yaml yano: app-chain: chain-id: workflow-chain state-machine: approvals members: threshold: 2 signing-key: ``` The cluster launcher's multi-chain form is: ```yaml yano: app-chain: chains[2]: chain-id: workflow-chain state-machine: approvals block: interval-ms: 1000 ``` The launcher injects members, threshold, signing key, peers, and fixed proposer. Do not duplicate that injected material in the shared demo YAML. #### Typed command contracts Stock machines interpret bounded CBOR commands. Use their Java encoders or a compatible implementation; do not serialize arbitrary Java objects. ##### Member approvals ```text [0, itemId, payloadBytes, requiredApprovals, deadlineMillis] propose [1, itemId] approve [2, itemId] reject ``` The state key is UTF-8 `i/`. Approvers are deduplicated by member key, one rejection is terminal, and deadlines use the finalized block timestamp. Use this only when consortium node members intentionally are the business approvers. ##### Balances ```text [0, destinationAccount, positiveAmount] mint [1, destinationAccount, positiveAmount] transfer from sender's account ``` The state key is UTF-8 `b/`. A configured minter can restrict minting; a transfer that would overdraw is a deterministic no-op. ##### Document trail ```text [entityId, entryHashBytes, optionalReference] ``` The state key is UTF-8 `e/`. The machine stores a count and running Blake2b-256 head over previous head, entry hash, and author. Documents remain off chain; retrieve event bodies from block history and prove the current head. #### Configuration is not arbitrary workflow composition Configuration can select and parameterize already implemented semantics. It cannot safely express arbitrary component order or terminal transitions, because those affect every member's state root. Use: 1. **stock configuration** when one machine/profile already matches; 2. **a small composite plugin** when existing components need a new committed order or transition; or 3. **a custom state-machine plugin** for new business state or rules. #### Common mistakes - Treating a REST API key as an approval identity. - Changing machine settings on one member only. - Considering an accepted envelope proof that the command changed state. - Putting large or secret documents in replicated command bodies. - Adding network, wall-clock, DNS, or random behavior inside `apply()`. - Reusing a machine id after changing its deterministic behavior. #### Go deeper - The dedicated [`ordered-log` reference](https://github.com/bloxbean/yano-x/blob/main/docs/core-host.md) covers topics, payloads, multiple instances, REST/Java submission, proofs, and customization paths. - The dedicated [`kv-registry`](https://yanox.dev/state-machines/kv-registry/), [`approvals`](https://yanox.dev/state-machines/approvals/), [`balances`](https://yanox.dev/state-machines/balances/), and [`doc-trail`](https://yanox.dev/state-machines/doc-trail/), and [`role-approvals`](https://yanox.dev/state-machines/role-approvals/) references provide complete REST, Java, state, proof, and customization examples. - Exact state layouts and Java helper methods are documented in the [consensus guide](https://github.com/bloxbean/yano-x/blob/main/docs/core-host.md). - The [user guide](https://github.com/bloxbean/yano-x/blob/main/docs/APP_CHAIN_USER_GUIDE.md) covers the generic on-approved effect and activation for `approvals`. - For organization-distinct business authorization, continue with [domain-role approvals](https://yanox.dev/tutorials/05-domain-role-approvals/). - For coordinated document publication, continue with [the evidence scenario](https://yanox.dev/tutorials/04-evidence-publication/). --- ## Tutorial 4 — Publish and Verify Immutable Evidence Source: https://yanox.dev/tutorials/04-evidence-publication/ > Publish one immutable document through a threshold-approved workflow, preserve it in S3-compatible storage and IPFS, notify Kafka, and verify the chain… **IMPORTED PAGE:** This page is generated from [`docs/appchain/tutorials/04-evidence-publication.md`](https://github.com/bloxbean/yano-x/blob/main/docs/appchain/tutorials/04-evidence-publication.md) in the Yano X repository, which is its source of truth. [Open this outcome in App-Chain Studio](https://yanox.dev/studio/index.html#recipe=evidence-ledger&network=devnet&members=3&finality=two-thirds&sequencing=fixed&runtime=jvm&deployment=docker-compose&name=evidence-publication&chainId=evidence-publication) - **Level:** beginner to advanced - **Time:** about 30 minutes for the first build - **Outcome:** publish one immutable document through a threshold-approved workflow, preserve it in S3-compatible storage and IPFS, notify Kafka, and verify the chain, connector, proof, and Cardano-anchor evidence. This is Yano's most complete no-code vertical scenario. Docker Compose starts three Yano members plus Kafka, RustFS (S3-compatible object storage), Kubo IPFS, and the Evidence Explorer. #### 1. Start a fresh direct-continuation profile ```bash cd products/evidence/harness ./demo.sh up \ --instance tutorial-evidence \ --continuation direct ``` `direct` means the deterministic workflow emits the next result-driven transition directly. `explicit` is the legacy compatibility mode where a separate notify command advances that continuation. The choice is part of the fresh chain's committed profile; pass the same option to every later command. Expected local endpoints: - Yano members: , `:7071`, `:7072` - Evidence Explorer: If startup fails, the launcher rolls back the partial deployment. Use the reported container health/log command rather than repeatedly deleting random directories; retained L1 and app-chain identities are deliberately checked. #### 2. Publish version 1 ```bash ./demo.sh publish \ --instance tutorial-evidence \ --continuation direct \ --evidence-id product-passport-001 \ --sample-file samples/inspection-certificate.json ``` The runner waits for the dependency-ordered workflow: ```text publish command ↓ finalized application state prerequisites and approval ↓ authorized evidence release object.put + ipfs.pin ↓ acknowledged connector results kafka.publish ↓ final state/proof verification Cardano devnet anchor ``` The source document itself is not put into consensus state. The chain commits its business identity, version, hashes, workflow decisions, effect intents, and acknowledged outcomes. #### 3. Verify without changing anything ```bash ./demo.sh verify \ --instance tutorial-evidence \ --continuation direct \ --evidence-id product-passport-001 \ --business-version 1 ``` `verify` is read-only. It does not submit an app message, stage an object, pin content, publish Kafka data, or force another anchor. It re-reads the immutable object and IPFS content, checks their hashes, checks the Kafka acknowledgement, and validates application finality, state/effect proofs, and anchor linkage. Open the Evidence Explorer and select the record. The JSON preview is a bounded presentation copy produced only after the runner re-downloads and verifies the external bytes; it is not the browser's original submission. #### 4. Demonstrate immutability and versioning Publishing different bytes under the same evidence ID and business version is rejected as an external-state mismatch. To add a legitimate revision, create the exact next immutable version: ```bash ./demo.sh republish \ --instance tutorial-evidence \ --continuation direct \ --evidence-id product-passport-001 \ --business-version 2 \ --sample-file samples/inspection-certificate-product-a-v2.json ./demo.sh verify \ --instance tutorial-evidence \ --continuation direct \ --evidence-id product-passport-001 \ --business-version 1 ./demo.sh verify \ --instance tutorial-evidence \ --continuation direct \ --evidence-id product-passport-001 \ --business-version 2 ``` Both versions remain independently selectable and verifiable. #### 5. Demonstrate idempotent replay ```bash ./demo.sh replay \ --instance tutorial-evidence \ --continuation direct \ --evidence-id product-passport-001 \ --business-version 2 \ --sample-file samples/inspection-certificate-product-a-v2.json ``` The replay envelope may finalize, but the accepted business record, effect set, and logical external outcomes do not duplicate. #### 6. Optional bounded parallel workload Start small and use a fresh prefix: ```bash ./demo.sh load \ --instance tutorial-evidence \ --continuation direct \ --load-mode pipeline \ --count 8 \ --concurrency 8 \ --max-in-flight 8 \ --id-prefix tutorial-load \ --sample-file samples/inspection-certificate.json ./demo.sh verify \ --instance tutorial-evidence \ --continuation direct \ --evidence-id tutorial-load-000008 \ --business-version 1 ``` This measures a full workflow with approvals, external actions, proofs, and anchors—not raw HTTP admission TPS. #### 7. Stop and retain the scenario ```bash ./demo.sh status --instance tutorial-evidence --continuation direct ./demo.sh stop --instance tutorial-evidence --continuation direct ``` Run `up` with the same instance/profile to resume retained data. Use the launcher's explicit `clean --scope ... --yes` workflow only when you intend to retire that chain identity and have chosen a replacement instance. #### What is and is not proven The scenario proves that the approved bytes and connector outcomes are bound to threshold-finalized application state and an L1 anchor. It does not prove that an inspection statement is factually true, that an actor had a legal credential, or that every future copy of a referenced document remains available. Those require domain onboarding, custody, retention, and possibly independent real-world auditing. #### Go deeper - Read the [plain-language evidence flow](https://github.com/bloxbean/yano-x/blob/main/docs/EVIDENCE_CHAIN_DEMO.md). - Compare lifecycle and pipeline scheduling. - Stop one member and exercise catch-up using the isolated E2E gate. - Replace RustFS with a tested S3-compatible production service while keeping the `object.put` contract unchanged. - Review the [optional connector packaging and security matrix](https://github.com/bloxbean/yano-x/blob/main/docs/appchain/OPTIONAL_CONNECTORS.md) before translating the demo into a deployment. - Continue with [domain-role authorization](https://yanox.dev/tutorials/05-domain-role-approvals/). --- ## Tutorial 5 — Generic Domain Actors and Role-Aware Approval Source: https://yanox.dev/tutorials/05-domain-role-approvals/ > Authorize an application payload hash with governed business actors and organization-distinct roles, then see how the evidence product consumes the same… **IMPORTED PAGE:** This page is generated from [`docs/appchain/tutorials/05-domain-role-approvals.md`](https://github.com/bloxbean/yano-x/blob/main/docs/appchain/tutorials/05-domain-role-approvals.md) in the Yano X repository, which is its source of truth. [Open generic role approval in App-Chain Studio](https://yanox.dev/studio/index.html#recipe=role-approval&network=devnet&members=3&finality=two-thirds&sequencing=fixed&runtime=jvm&deployment=host&name=role-approval&chainId=role-approval) - **Level:** application developer; advanced for identity governance - **Time:** about 30 minutes - **Outcome:** authorize an application payload hash with governed business actors and organization-distinct roles, then see how the evidence product consumes the same generic primitives. Both paths separate: - **member identity:** which consortium node relayed an envelope; and - **actor identity:** which application actor signed the exact business statement. #### Track A — Generic `role-approvals` Create a release-pinned project without selecting the evidence product: ```bash ./yano.sh appchain init --non-interactive \ --recipe role-approval --network devnet --members 3 \ --runtime jvm --deployment host \ --name role-approval --chain-id role-approval \ --output role-approval ./yano.sh appchain config validate --mode project role-approval ``` Because public member identities have not been supplied, the first validation correctly reports the bootstrap acknowledgement. Add the three reviewed member keys to `role-approval/appchain.yaml`, render, and run `doctor` against the distribution before starting nodes. Review `role-approval/bootstrap/role-approvals-plan.yaml`. It defines a safe, non-secret sequence for governing organizations, actors and a policy. Replace the sample role names (`proposer`, `reviewer`) with application roles when needed; roles are strings, not evidence-specific enums. Generate public keys and proof-of-possession with `./yano.sh appchain role`, then submit governed records on `actors.command.v1`. After activation, hash the exact application command or document bytes and submit an actor-signed proposal/decision on `role-approvals.command.v1`: ```bash COMMAND_HEX=$(./yano.sh appchain role sign \ --action approve --chain role-approval --proposal order-a-1001 \ --policy order-release --policy-revision 1 \ --payload-domain com.example.order.v1 --payload-hash <64-hex> \ --deadline-height 1000 --actor reviewer-a --actor-revision 1 \ --key reviewer-key-v1 --clause reviewers \ --seed-file /owner-only/reviewer.seed) ``` Any member may relay those bytes; the embedded actor signature carries the business identity. Query the terminal proposal and its proof-oriented fields: ```bash curl -sS \ -H "X-API-Key: $YANO_APPCHAIN_API_KEY" \ "http://127.0.0.1:7070/api/v1/plugins/com.bloxbean.cardano.yano.appchain.role-workflow/proposals/order-a-1001?chain=role-approval" | jq . ``` The generic machine proves the approved payload domain/hash and emits no effect. Your application may act idempotently after verifying that result, or a reviewed composite plugin may consume it atomically. The complete REST, Java, proof, bootstrap and recovery walkthrough is in the dedicated [`role-approvals` reference](https://yanox.dev/state-machines/role-approvals/). #### Track B — Evidence-specific demonstration [Open the evidence outcome in App-Chain Studio](https://yanox.dev/studio/index.html#recipe=evidence-ledger&network=devnet&members=3&finality=two-thirds&sequencing=fixed&runtime=jvm&deployment=docker-compose&name=role-evidence&chainId=role-evidence) The following runnable demo uses the stock `role-evidence` profile. It fixes manufacturer, auditor and regulator roles for its evidence scenario and adds the evidence transition and optional publication effects to the generic actor and approval foundations. ##### B1. Start a fresh role profile ```bash cd products/evidence/harness ./demo.sh up \ --instance tutorial-roles \ --machine role \ --continuation direct ``` The profile commits its organization/actor registry, policy, component order, routes, effect workflow, administrator threshold, and deterministic limits. It is not a local YAML toggle for an existing chain. ##### B2. Publish actor-authorized evidence ```bash ./demo.sh publish \ --instance tutorial-roles \ --machine role \ --continuation direct \ --evidence-id regulated-product-001 \ --sample-file samples/inspection-certificate.json ``` The stock policy requires a manufacturer proposal, two independent auditor organizations, and a regulator. The runner also submits negative controls: - an actor with the wrong role; - an approval bound to the wrong payload; and - two actors from the same organization attempting to satisfy an organization-distinct clause. Those envelopes can finalize, but they are deterministic no-ops. Only eligible signed decisions contribute to the policy result. ##### B3. Inspect and verify ```bash ./demo.sh verify \ --instance tutorial-roles \ --machine role \ --continuation direct \ --evidence-id regulated-product-001 ``` Open . The report distinguishes relay member, actor, organization, role, policy revision, clause, and signed decision. Current actor/policy projections include both the immutable revision proof and the same-root current-pointer proof, so “this revision exists” cannot be confused with “this is the current governed revision.” ##### B4. Exercise rotation and revocation ```bash ./demo.sh role-lifecycle \ --instance tutorial-roles \ --machine role \ --continuation direct ``` The idempotent lifecycle uses a dedicated recovery actor and demonstrates: 1. governed onboarding with proof-of-possession; 2. signing-key rotation; 3. rejection of the old actor revision/key; 4. acceptance of the new revision/key; 5. revocation; 6. rejection after revocation; and 7. historical revision and decision proofs. This is not app-chain membership rotation. Business credentials and validator membership are intentionally governed through separate mechanisms. ##### B5. Stop and retain ```bash ./demo.sh stop \ --instance tutorial-roles \ --machine role \ --continuation direct ``` #### Reuse levels ##### Configuration only Use a stock profile when its action and terminal transition already match. Governed data can define actors, organizations, roles, policies, counts, organization distinctness, deadlines, and connector targets. ##### Small composite plugin Use existing registry/approval/evidence/payment components in a new explicit order or connect approval to a different terminal action. Component order and cross-component transitions affect consensus, so they remain reviewed Java composition rather than dynamic YAML wiring. ##### Custom state-machine plugin Use this only for new state or business rules, such as insurance claim calculation or supply-chain ownership transfer. #### Production boundaries - The demo uses locally held deterministic actor seeds. Production signing belongs in KMS/HSM/Vault or an actor-owned signing service. - Yano proves that registered keys authorized exact bytes under a governed policy. Legal identity and real-world truth remain onboarding/audit duties. - The v1 actor record retains at most 16 key epochs. Plan successor identity or governed contract evolution before reaching that limit. - Administrator authority is profile-committed; changing it uses governed profile evolution, not an ordinary actor mutation. #### Go deeper - Read the complete [domain-role guide](https://github.com/bloxbean/yano-x/blob/main/docs/APP_CHAIN_DOMAIN_ROLES.md). - Verify signed actor commands independently with the published golden vectors. - Inspect the plugin domain API and exact MPF proof keys. - Run the isolated three-member restart/catch-up gate documented in the role guide. --- ## Tutorial 6 — Invoke an External HTTP Endpoint Safely Source: https://yanox.dev/tutorials/06-webhook-effects/ > Understand when to use a finalized-block webhook sink versus an acknowledged webhook.post effect, and run the latter from a stock approval transition. **IMPORTED PAGE:** This page is generated from [`docs/appchain/tutorials/06-webhook-effects.md`](https://github.com/bloxbean/yano-x/blob/main/docs/appchain/tutorials/06-webhook-effects.md) in the Yano X repository, which is its source of truth. [Open the effects-ready workflow in App-Chain Studio](https://yanox.dev/studio/index.html#recipe=evidence-ledger&network=devnet&members=3&finality=two-thirds&sequencing=fixed&runtime=jvm&deployment=host&name=webhook-effects&chainId=webhook-effects) - **Level:** intermediate to advanced - **Outcome:** understand when to use a finalized-block webhook sink versus an acknowledged `webhook.post` effect, and run the latter from a stock approval transition. Yano provides two HTTP delivery shapes with different guarantees. | Capability | Use it for | Result committed to app state? | |---|---|---:| | Finalized-block webhook sink | Projection/indexing of every finalized block | No | | `webhook.post` effect | One business action after a deterministic transition | Yes, for `CHAIN` results | #### Option A — observe every finalized block Configure a cursor-backed sink: ```yaml yano.app-chain.webhooks: https://projection.example/yano/finalized-blocks ``` Delivery is ordered and at-least-once. A persistent sink cursor resumes after restart. This is the simplest option when the receiver wants all finalized history and does not need a per-action result fed back into the state machine. #### Option B — execute one acknowledged action The built-in executor supports `webhook.post`: ```yaml yano.app-chain.effects.executors.webhook.url: https://erp.example/hooks/yano yano.app-chain.effects.executors.webhook.timeout-ms: 10000 ``` It sends: - `Idempotency-Key`: deterministic effect-id hash; - `X-App-Chain-Id`; - `X-Effect-Id`, `X-Effect-Type`, and `X-Effect-Scope`. Response semantics are: - `2xx` → confirmed; - `4xx` → failed without automatic retry; and - `5xx` or transport failure → retryable with bounded backoff/parking. The receiver must deduplicate by `Idempotency-Key`, because execution is at-least-once even though outcome incorporation is exactly once. #### Runnable local approval-to-webhook exercise The stock `approvals` machine can emit a configured effect when a proposal reaches its threshold. This avoids writing a custom state machine for the tutorial. ##### 1. Start a receiver in a separate terminal ```bash python3 - <<'PY' from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer seen = set() class Receiver(BaseHTTPRequestHandler): def do_POST(self): key = self.headers.get("Idempotency-Key", "") body = self.rfile.read(int(self.headers.get("Content-Length", "0"))) duplicate = key in seen seen.add(key) print({"idempotencyKey": key, "duplicate": duplicate, "contentType": self.headers.get("Content-Type"), "body": body.decode("utf-8", errors="replace")}, flush=True) self.send_response(200) self.send_header("Location", f"local://receipt/{key}") self.end_headers() def log_message(self, *_): pass ThreadingHTTPServer(("127.0.0.1", 8099), Receiver).serve_forever() PY ``` ##### 2. Create private per-node overrides Start from a clean tutorial cluster and create three owner-only property files. All members receive the same consensus settings; only node 0 runs the executor. Each file must begin with `config_ordinal=275`. These are private overlays consumed only by the local cluster launcher through `--node-config-dir`; they are not generated-project configuration. Generated app-chain projects and `appchain config` use YAML. Common settings for `node0.properties`, `node1.properties`, and `node2.properties`: ```properties config_ordinal=275 yano.app-chain.chains[0].state-machine=approvals yano.app-chain.chains[0].effects.enabled=true yano.app-chain.chains[0].machines.approvals.on-approved-effect.enabled=true yano.app-chain.chains[0].machines.approvals.activations.on-approved-effect=1 yano.app-chain.chains[0].machines.approvals.on-approved-effect.type=webhook.post yano.app-chain.chains[0].machines.approvals.on-approved-effect.gate=app-final ``` These settings attach one generic action to the stock approval transition. `webhook.post` is only the executor routing type; the approvals machine does not interpret the payload as a payment or webhook. The approval decision stays `APPROVED`, while the separate `ae/s/` record tracks the effect as `PENDING`, `CONFIRMED`, or `FAILED`. Change `type` to another packaged or custom executor contract without changing approval semantics. Append these node-local settings only to `node0.properties`: ```properties yano.app-chain.chains[0].effects.executor.enabled=true yano.app-chain.chains[0].effects.executor.types=webhook.post yano.app-chain.chains[0].effects.executors.webhook.url=http://127.0.0.1:8099/yano yano.app-chain.chains[0].effects.executors.webhook.timeout-ms=5000 ``` The repository includes those exact tutorial files. Install owner-only copies and start the fresh cluster: ```bash install -d -m 700 /tmp/yano-tutorial-webhook-config install -m 600 \ ../docs/appchain/tutorials/config/webhook/node*.properties \ /tmp/yano-tutorial-webhook-config/ export YANO_CLUSTER_DIR=/tmp/yano-tutorial-webhook export YANO_CLUSTER_NODE_CONFIG_DIR=/tmp/yano-tutorial-webhook-config ./yano.sh appchain cluster start 3 ``` The chain id remains `orders-chain`, but its fresh deterministic profile is now `approvals`. Never apply this override to retained `ordered-log` state. ##### 3. Encode and submit a proposal The encoding helper and request fixture are source-checkout tutorial assets. From the source checkout's `app/` directory, encode the supplied HTTP body and canonical stock commands: ```bash TOOL=../docs/appchain/tutorials/tools/stdlib_command.py WEBHOOK_HEX=$(python3 "$TOOL" webhook \ --body-file ../docs/appchain/tutorials/config/webhook/request.json \ --content-type application/json) PROPOSE_HEX=$(python3 "$TOOL" approvals propose erp-release-001 \ --required 2 --payload-hex "$WEBHOOK_HEX") APPROVE_HEX=$(python3 "$TOOL" approvals approve erp-release-001) curl -s -X POST \ http://127.0.0.1:7070/api/v1/app-chain/chains/orders-chain/messages \ -H 'Content-Type: application/json' \ -d "{\"topic\":\"approvals\",\"bodyHex\":\"$PROPOSE_HEX\"}" | jq . curl -s -X POST \ http://127.0.0.1:7071/api/v1/app-chain/chains/orders-chain/messages \ -H 'Content-Type: application/json' \ -d "{\"topic\":\"approvals\",\"bodyHex\":\"$APPROVE_HEX\"}" | jq . curl -s -X POST \ http://127.0.0.1:7072/api/v1/app-chain/chains/orders-chain/messages \ -H 'Content-Type: application/json' \ -d "{\"topic\":\"approvals\",\"bodyHex\":\"$APPROVE_HEX\"}" | jq . ``` After finality and the executor tick, the receiver prints exactly one logical effect identity. A crash at the acknowledgement boundary may produce another physical POST with the same idempotency key; that is why the receiver keeps a deduplication set. Inspect effect records and execution status: ```bash sleep 6 curl -s \ -H 'X-API-Key: yano-local-cluster-full-key' \ 'http://127.0.0.1:7070/api/v1/app-chain/chains/orders-chain/effects?fromHeight=1&limit=20' \ | jq . curl -s \ http://127.0.0.1:7070/api/v1/app-chain/chains/orders-chain/status \ | jq '.effects.executor | {executed, openOnChain, executionTotals, executorOperations}' ``` The tested success shape has one confirmed execution and zero open on-chain effects after the `~fx/result` message is incorporated. The approval item is still `APPROVED`; confirmation updates only its independently provable generic effect-state record. ##### 4. Clean up ```bash ./yano.sh appchain cluster clean unset YANO_CLUSTER_DIR YANO_CLUSTER_NODE_CONFIG_DIR rm -rf /tmp/yano-tutorial-webhook-config ``` Stop the receiver with `Ctrl-C`. Remove the private override directory after the cluster is stopped. #### Security and product boundary `webhook.post` is intentionally small: one POST to a configured endpoint. It does not yet provide named target aliases, OAuth/API-key/mTLS profiles, arbitrary methods, response-body contracts, or asynchronous operation polling. Keep `allow-payload-url=false` unless a separately reviewed allow-list and SSRF boundary exists. For a product API such as uVerify, prefer a dedicated executor when durable acceptance requires several API calls, polling, reconciliation, or a typed receipt. Use the generic webhook only when one idempotent POST and its immediate status are the real business contract. #### Go deeper - Read [Effects §18](https://github.com/bloxbean/yano-x/blob/main/docs/APP_CHAIN_USER_GUIDE.md) for quarantine, requeue, cancellation, proof, and external executor APIs. - Kill the receiver temporarily, observe retries/parking, restore it, and use the operator requeue endpoint. - Build a custom executor plugin with named target aliases and secret-backed authentication rather than putting URLs or credentials in replicated payloads. --- ## Tutorial 7 — Connect an App Proof to Cardano Source: https://yanox.dev/tutorials/07-anchors-and-verification/ > Bootstrap a threshold-enforced script anchor, advance it after app blocks finalize, and understand the independent verification chain. **IMPORTED PAGE:** This page is generated from [`docs/appchain/tutorials/07-anchors-and-verification.md`](https://github.com/bloxbean/yano-x/blob/main/docs/appchain/tutorials/07-anchors-and-verification.md) in the Yano X repository, which is its source of truth. [Open an evidence chain in App-Chain Studio](https://yanox.dev/studio/index.html#recipe=evidence-ledger&network=devnet&members=3&finality=all&sequencing=fixed&runtime=jvm&deployment=host&name=anchored-evidence&chainId=anchored-evidence) - **Level:** intermediate to advanced - **Time:** about 20 minutes on local devnet - **Outcome:** bootstrap a threshold-enforced script anchor, advance it after app blocks finalize, and understand the independent verification chain. An app-chain finality certificate proves that the configured member threshold approved a block. An L1 anchor makes a later application position durable and discoverable through Cardano. These are related but distinct proofs. #### 1. Start an anchored local cluster ```bash export YANO_CLUSTER_DIR=/tmp/yano-tutorial-anchor ./yano.sh appchain cluster start 3 \ --anchor-mode script \ --anchor-every 2 ``` Script mode uses a thread NFT and a Plutus V3 validator. The local launcher has a deterministic demo anchor seed and can fund it from the self-contained devnet faucet. #### 2. Bootstrap one chain's immutable anchor identity ```bash ./yano.sh appchain cluster anchor-bootstrap orders-chain ``` Bootstrap consumes a seed UTxO, mints the one-shot thread NFT, and creates the genesis datum. It establishes identity; it does not let the wallet alone claim an arbitrary application tip. Inspect the L1 Anchor card on . It should show the thread policy, script address, wallet, anchored height, transaction, and lag. #### 3. Produce application progress ```bash ./yano.sh appchain cluster submit orders-chain audit '{"event":"certificate-issued","id":"C-1"}' ./yano.sh appchain cluster submit orders-chain audit '{"event":"certificate-published","id":"C-1"}' ./yano.sh appchain cluster submit orders-chain audit '{"event":"certificate-acknowledged","id":"C-1"}' ``` Wait for app finality and the local L1 transaction: ```bash sleep 20 ./yano.sh appchain cluster status curl -s \ http://127.0.0.1:7070/api/v1/app-chain/chains/orders-chain/status \ | jq .anchor ``` All members independently reconcile the authenticated script UTxO from their own L1 view. Node-local “confirmed since restart” counters may differ after a restart; the durable anchored height, transaction, and lag should converge. #### 4. The independent verification chain For one application record, a verifier needs: ```text record/value │ MPF or messages-root proof ▼ state root / certified app block │ threshold finality certificate + block hash chain ▼ anchored descendant │ exact thread NFT + validator address + inline datum ▼ Cardano transaction confirmed by an independent L1 source ``` The checks are: 1. Recompute the record/message commitment. 2. Verify its MPF or messages-root path against the correct app-block root. 3. Verify the app block's threshold signatures using an independently trusted chain profile. 4. When the record predates the anchor, verify the certified block-hash chain to the anchored descendant. 5. Fetch the Cardano transaction/UTxO independently. 6. Require the expected validator address and state-thread asset. 7. Decode the exact inline datum and match chain id, height, block hash, state root, member set, and threshold. A transaction hash appearing in a Yano JSON response is not, by itself, independent anchor verification. #### Metadata versus script anchoring | Property | Metadata | Script | |---|---|---| | Setup | Fund wallet | Fund wallet + bootstrap | | L1 enforcement | Data commitment only | Monotonic thread + threshold member signatures | | Main safety authority | Anchor wallet | On-chain validator and member threshold | | Typical use | Low-cost timestamp/discovery | Strong consortium settlement boundary | Both modes commit application data; only script mode enforces the threshold and monotonic successor rules on chain. #### Public test networks For preview or preprod: - generate a dedicated raw 32-byte anchor seed; - provide it through the owner-only anchor-key file mechanism; - fund the printed enterprise address with test ADA; - use an explicit public-network confirmation before the demo spends; and - increase cadence/stability settings to match real L1 timing and fees. Do not reuse a wallet mnemonic, validator member seed, actor signing key, or API key as the anchor wallet. #### 5. Clean up ```bash ./yano.sh appchain cluster clean unset YANO_CLUSTER_DIR ``` Local devnet state is disposable. A public script anchor is permanent Cardano history even after local files are deleted. #### Go deeper - Follow [L1 anchoring §5](https://github.com/bloxbean/yano-x/blob/main/docs/APP_CHAIN_USER_GUIDE.md) for portable evidence bundles and exact trust-context construction. - Use `EvidenceVerifier` and `EffectProofVerifier` rather than trusting server booleans. - Test L1 rollback and anchor resubmission before a pilot. - Review the pinned Aiken release artifacts and the Java/julc cross-implementation drift checks before depending on a released script identity. --- ## Tutorial 8 — Extend Yano Without Forking It Source: https://yanox.dev/tutorials/08-plugins-and-composites/ > Choose the correct extension level and deploy versioned business logic as a plugin JAR on the standard JVM distribution. **IMPORTED PAGE:** This page is generated from [`docs/appchain/tutorials/08-plugins-and-composites.md`](https://github.com/bloxbean/yano-x/blob/main/docs/appchain/tutorials/08-plugins-and-composites.md) in the Yano X repository, which is its source of truth. [Open the custom-plugin path in App-Chain Studio](https://yanox.dev/studio/index.html#recipe=custom-plugin&network=devnet&members=3&finality=two-thirds&sequencing=fixed&runtime=jvm&deployment=host&name=custom-appchain&chainId=custom-appchain&stateMachine=com.example.my-machine) - **Level:** Java application developer - **Time:** 30–60 minutes for a first plugin - **Outcome:** choose the correct extension level and deploy versioned business logic as a plugin JAR on the standard JVM distribution. #### Start from a bounded scaffold Use the public launcher to create one small, buildable starting point. The four modes share the same runtime manifest, signed product-catalog, and ServiceLoader conventions: ```bash ./yano.sh appchain plugin scaffold \ --mode state-machine \ --id shipment \ --package com.example.shipment \ --yano-version 0.1.0-pre9 \ --output shipment-plugin ``` Other modes are `composite-role`, `effect-executor`, and `sink`. The generated provider deliberately performs no business work: state-machine admission is closed and executor/sink factories return no instances until implemented. The tool refuses a non-empty output directory. After implementation and tests, sign the exact catalog, runtime manifest, and optional configuration metadata. Keep the 32-byte seed outside the repository and pass it by file only: ```bash ./yano.sh appchain plugin sign \ --catalog shipment-plugin/src/main/resources/META-INF/yano/appchain-component-catalog-v1.json \ --runtime-manifest shipment-plugin/src/main/resources/META-INF/yano/plugins/plugin-bundle.shipment.json \ --seed-file /secure/publisher.seed \ --key-id example-release-2026 \ --output shipment-plugin/src/main/resources/META-INF/yano/appchain-component-catalog-v1.sig.json cd shipment-plugin gradle jar cd .. ./yano.sh appchain plugin validate shipment-plugin/build/libs/shipment-yano-plugin.jar \ --trust-key example-release-2026=<64-hex-public-key> \ --output shipment-catalog.json ``` `inspect` prints the same verified catalog and its capabilities. Neither command loads provider classes, runs plugin code, fetches a registry, or installs the JAR. The exported snapshot is the safe local-import format used by Studio and generated projects. Create a project by selecting the custom capability declared by the catalog: ```bash ./yano.sh appchain init --non-interactive \ --recipe custom-plugin --network devnet --members 3 --runtime jvm \ --capability state:shipment \ --plugin-jar shipment-plugin/build/libs/shipment-yano-plugin.jar \ --trust-key example-release-2026=<64-hex-public-key> \ --output shipment-chain ./yano.sh appchain config validate --mode project shipment-chain ``` The project stores the signed data-only snapshot under `component-catalogs/`; its lock pins the snapshot, catalog, runtime manifest, configuration metadata, and complete plugin-JAR digests. Rendering and doctor reverify the project snapshot automatically. The public key is not secret. Copy the exact pinned JAR into `plugins/` in every JVM distribution before starting nodes, then verify it: ```bash ./yano.sh appchain doctor shipment-chain --distribution /opt/yano ``` A missing or different JAR fails artifact readiness. A native distribution reports a direct incompatibility because Yano X plugins target the JVM host. Yano's core provides ordering, threshold finality, deterministic state, proofs, anchoring, effects, plugin lifecycle, health, and metrics. Application teams normally extend the application layer, not the consensus runtime. #### Choose the smallest extension ```text Does a stock machine/profile already model the outcome? ├─ yes → configuration only └─ no Are all required components already available? ├─ yes → small composite plugin └─ no → custom state-machine component/plugin ``` Other independent plugin SPIs cover effect executors, finalized stream sinks, domain APIs, signers, sequencer mode, and L1 observers. #### Path A — configuration only Select one built-in id or profile identically on every member: ```yaml yano.app-chain.state-machine: kv-registry ``` For stock composite/role profiles, the profile identifier and configuration digest become part of chain identity. Select them only for a fresh chain or a governed activation. #### Path B — a small composite plugin 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/digest. This Java class is intentionally small but consensus-critical. YAML cannot dynamically insert arbitrary component plugins into a frozen profile, because two members discovering a different order would derive different roots. Reuse the effect-gated evidence and role-evidence presets as reference implementations. Package the provider, manifest, service entry, and components in one reviewed bundle. #### Path C — a custom state-machine plugin The complete hands-on implementation is in the existing [default-distribution tutorial, Part 2](https://github.com/bloxbean/yano-x/blob/main/docs/APP_CHAIN_TUTORIAL.md). The core shape is: ```java 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 plugin manifest, then copy the bundle JAR into the configured plugin directory. Yano itself does not need recompilation for a JVM deployment. Yano X plugins target the JVM distribution. Yano's native image cannot discover directory JARs and does not include Yano X plugins. #### Custom component catalog contract The JAR carries three independent, bounded contracts: - `META-INF/yano/plugins/.json` describes executable runtime contributions; - `META-INF/yano/appchain-config-metadata-v1.json`, when present, owns typed configuration definitions; and - `META-INF/yano/appchain-component-catalog-v1.json` describes selectable product capabilities and required artifacts. The Ed25519 trust envelope binds the catalog, runtime manifest, optional configuration metadata, bundle identity/version, and publisher key ID. It authenticates those exact bytes; it does not approve code or elevate a custom component to bundled/stable/native status. Custom entries remain JVM-only `REFERENCE` or `EXPERIMENTAL`, and all release ID, namespace, and artifact collisions fail closed. #### Consensus rules for application plugins - The same bundle, machine/profile id, and committed settings run on every voting member. - `apply()` must not use wall clock, randomness, DNS, filesystem, database, or network I/O. - Invalid finalized bytes become deterministic no-ops, not escaping exceptions. - Bound message bytes, decode depth/items, collections, state growth, and work per block. - Never silently change semantics behind an existing machine/component id. - Use activation heights/profile governance for compatible evolution and a new namespace/migration plan for incompatible state. - Every state write belongs to the authenticated writer; do not maintain hidden consensus state in static fields or node-local storage. #### Add external actions correctly Do not call Kafka, S3, IPFS, Cardano, or an ERP from `apply()`. Emit an `EffectIntent`; let an executor act after its finality gate; incorporate the result through `onEffectResult` when the outcome affects business state. Create a custom executor plugin 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 replicated effect payloads. #### Testing ladder 1. Unit-test codecs and deterministic transitions. 2. Run the state-machine conformance and replay matrix. 3. Test malformed and hostile finalized input through `apply()`. 4. Start an embedded multi-member cluster with `appchain-testkit`. 5. Verify root parity, proof keys, restart, catch-up, rollback/reapply, and plugin packaging. 6. For effects, test crash-before-send, send-before-ack, retry, reconciliation, parking, requeue, and duplicate idempotency. 7. Run a packaged JVM cluster; add native coverage when native is supported. #### Deployment and operations - Give every bundle a stable plugin id and semantic version. - Verify catalog state and health on every member before admitting traffic. - Treat plugin removal or drift as a deployment error, not automatic fallback. - Namespace configuration and metrics by plugin/contribution. - Keep plugin domain APIs read-only unless commands still enter through the authenticated app-chain submission path. #### Go deeper - [Plugin query and domain APIs](https://github.com/bloxbean/yano-x/blob/main/docs/core-host.md) - [Plugin operations](https://github.com/bloxbean/yano-x/blob/main/docs/core-host.md) - [Composite implementation guide](https://github.com/bloxbean/yano-x/blob/main/composition/runtime/README.md) - [Plugin template scaffold](https://github.com/bloxbean/yano-x/tree/main/scaffolds/plugin-template/) - [Yano core testkit](https://github.com/bloxbean/yano/tree/main/appchain/appchain-testkit) --- ## Tutorial 9 — From Local Demo to a Permissioned Pilot Source: https://yanox.dev/tutorials/09-from-demo-to-pilot/ > Turn a successful tutorial into an explicit deployment plan without inheriting local-demo assumptions. **IMPORTED PAGE:** This page is generated from [`docs/appchain/tutorials/09-from-demo-to-pilot.md`](https://github.com/bloxbean/yano-x/blob/main/docs/appchain/tutorials/09-from-demo-to-pilot.md) in the Yano X repository, which is its source of truth. [Open a pilot starting point in App-Chain Studio](https://yanox.dev/studio/index.html#recipe=audit-log&network=preprod&members=3&finality=all&sequencing=rotating&runtime=jvm&deployment=host&name=permissioned-pilot&chainId=permissioned-pilot) - **Level:** platform, security, and application leads - **Outcome:** turn a successful tutorial into an explicit deployment plan without inheriting local-demo assumptions. This is a decision checklist rather than one launch command. Yano remains pre-release; the target posture is a controlled permissioned pilot, not an unqualified production or trustless-public-chain claim. #### 1. Generate a reviewable pilot project Use the developer tools behind `./yano.sh` to move from remembered YAML options to a blueprint, rendered configuration, and lock file: ```bash ./yano.sh appchain recipes ./yano.sh appchain capabilities ./yano.sh appchain init \ --recipe audit-log \ --network preprod \ --members 3 \ --finality all \ --sequencing rotating \ --runtime jvm \ --deployment host \ --name permissioned-pilot \ --chain-id permissioned-pilot \ --output permissioned-pilot \ --non-interactive ``` Expected initialization output begins with `PROJECT_INITIALIZED`. Review and edit `permissioned-pilot/appchain.yaml`, especially the real member public keys and deployment hosts, then regenerate and validate: The generated runtime files are nested YAML: - `config/shared-consensus.yaml` contains explicit consensus-shared values; - `config/nodes/nodeN.yaml` contains node-local ports, peers, paths, and secret references; and - `appchain.lock` records the exact flattened values and generated-file digests. Customize `appchain.yaml`, not those derived files. ```bash ./yano.sh appchain render permissioned-pilot ./yano.sh appchain config validate --mode project permissioned-pilot ./yano.sh appchain doctor permissioned-pilot \ --distribution /path/to/yano-{suffix}.zip ``` Expected success markers are `PROJECT_RENDERED`, `VALID_PROJECT`, and `DOCTOR_OK`. Doctor deliberately fails with `PUBLIC_MEMBER_IDENTITIES_REQUIRED_BEFORE_START` until production member identities replace the unresolved bootstrap acknowledgement. `./yano.sh` is the public command for both paths. Internally, project and configuration commands use the separately packaged `appchain-devtools` engine, while `./yano.sh appchain cluster ...` invokes the bundled single-host launcher directly. Users should not invoke the internal executable. Cluster startup does not implicitly run `init`, `render`, or project validation. Use the generated project for multi-machine production bootstrap, and the cluster command for the packaged local acceptance environment. #### 2. Freeze the application contract Record and review: - chain id and network; - member public keys, threshold, proposer/sequencer mode; - state-machine or composite profile id and digest; - deterministic limits and activation schedule; - command/state/effect contract versions; - anchor mode, validator identity, cadence, and stability depth; and - effect outcome trust policy. All consensus-affecting values must be identical and profile-committed where required. Node-local YAML drift must not decide application semantics. #### 3. Separate every identity and secret | Material | Purpose | Recommended owner/storage | |---|---|---| | Member signing key | App-block votes and envelopes | One per member, KMS/HSM/secret manager | | Business actor key | Domain authorization | Actor organization or delegated signing service | | API key | REST authorization/scopes | API gateway/secret manager | | Anchor wallet key | Cardano fees/collateral | Capped hot wallet on anchor leader | | Connector credential | Kafka/S3/IPFS/API access | Executor host only | | TLS key/trust roots | Transport identity | Platform PKI | Never copy the deterministic demo keys, launcher API key, sample actor seeds, or local connector credentials into a shared environment. #### 4. Choose the trust statement Document what the system actually proves: - threshold members finalized exact application state; - actor signatures authorized exact statements under a governed policy; - an MPF proof connects a record to a state root; - an L1 anchor connects a certified descendant to Cardano; and - an effect result is a member/executor attestation unless independently verified. Do not claim that an inspection, oracle value, shipment, API response, or payment outcome is independently true unless a separate auditor/source check establishes it. #### 5. Replace demo infrastructure deliberately | Demo component | Pilot decision | |---|---| | RustFS | AWS S3 or reviewed compatible service; versioning/retention policy | | Single Kubo | Managed/redundant IPFS pinning and retrieval policy | | Local Kafka | TLS/mTLS/SASL cluster, ACLs, consumer deduplication | | Local webhook | Named allow-listed target, authentication, idempotent receiver | | Devnet anchor wallet | Dedicated funded preview/preprod key with spend cap | | Docker-local secrets | KMS/HSM/Vault or orchestrator secret mounts | Changing an executor destination does not change deterministic effect intent, but it does change operational identity, reconciliation, and credentials. #### 6. Establish operations before traffic - Health/readiness for every member and plugin. - Root/profile parity gate across members. - Metrics and alerts for app lag, finality, pool pressure, anchor lag, effect backlog/age/retries/parking, sink lag, and disk pressure. - Snapshot, restore, member onboarding, and retained-state identity runbooks. - Member-key and actor-key rotation/revocation exercises. - Connector outage and executor crash recovery. - Anchor-leader failure/recovery. - Evidence/proof archival before configured pruning horizons. - Explicit maintenance and governed-upgrade process. #### 7. Run acceptance in layers 1. Clean deterministic unit/conformance suites. 2. Three-member packaged local cluster. 3. Restart one member and prove catch-up/root parity. 4. Stop/restart the full deployment from retained state. 5. Connector fault matrix and duplicate-boundary tests. 6. Load/soak test with recorded topology, rate, payloads, duration, lag, resources, and failures. 7. Preview/preprod anchor smoke with independent L1 verification. 8. Restore rehearsal from the retained backup procedure. Keep acceptance artifacts with the release rather than relying on screenshots or a remembered manual session. #### 8. Know the current escalation gates - Material Cardano funds require the production action hardening tracked for `cardano.payment` and native assets. - Semi-trusted members/executors require governed result-signer policy and independent outcome auditing before receipts are marketed as independently verified. - Regulated personal data requires encryption/erasure guidance; immutable object/IPFS/on-chain digests are not a deletion mechanism. - Public validator participation and general BFT view change are outside the current permissioned model. #### 9. Choose the deployment shape - **JVM distribution:** supports plugin-directory installation and is the simplest extensible pilot shape. - **Yano native image:** runs the core ordered-log capability only. Yano X plugins are JVM-only and cannot be installed into that image; choose the JVM distribution for a Yano X pilot. - **Embedded library:** appropriate when an application team owns lifecycle, configuration, APIs, and dependency integration in one Java service. #### Go deeper - [Full user/operations guide](https://github.com/bloxbean/yano-x/blob/main/docs/APP_CHAIN_USER_GUIDE.md) - [Profile-governance runbook](https://github.com/bloxbean/yano-x/blob/main/docs/APP_CHAIN_PROFILE_GOVERNANCE.md) - Canonical app-layer open items - [Evidence flow and trust limits](https://github.com/bloxbean/yano-x/blob/main/docs/EVIDENCE_CHAIN_DEMO.md) - [Domain-role production signing and recovery](https://github.com/bloxbean/yano-x/blob/main/docs/APP_CHAIN_DOMAIN_ROLES.md) --- # State machines --- ## App-Chain State-Machine References Source: https://yanox.dev/state-machines/ > These pages are capability references for Yano's deterministic application state machines. Use the stock state-machine cookbook for a quick comparison. **IMPORTED PAGE:** This page is generated from [`docs/appchain/state-machines/README.md`](https://github.com/bloxbean/yano-x/blob/main/docs/appchain/state-machines/README.md) in the Yano X repository, which is its source of truth. These pages are capability references for Yano's deterministic application state machines. Use the [stock state-machine cookbook](https://yanox.dev/tutorials/03-stock-state-machines/) for a quick comparison. | State machine | Reference | |---|---| | `ordered-log` | [Opaque ordered events, topics, proofs, and customization](https://github.com/bloxbean/yano-x/blob/main/docs/core-host.md) | | `kv-registry` | [Owned mutable records, REST/Java usage, and proofs](https://yanox.dev/state-machines/kv-registry/) | | `authenticated-map` | [Proof-oriented multi-collection registry](https://yanox.dev/state-machines/authenticated-map/) ([value validation](https://yanox.dev/state-machines/authenticated-map-validation/)) | | `approvals` | [Member decisions, REST/Java usage, proofs, and effects](https://yanox.dev/state-machines/approvals/) | | `balances` | [Member-authorized minting, transfers, Java/Spring usage, and proofs](https://yanox.dev/state-machines/balances/) | | `doc-trail` | [Per-entity document hashes, REST/Java usage, and proofs](https://yanox.dev/state-machines/doc-trail/) | | `role-approvals` | [Governed business actors, role policies, signed decisions, and proofs](https://yanox.dev/state-machines/role-approvals/) | Exact wire formats and deterministic state layouts are also described in the [consensus guide](https://github.com/bloxbean/yano-x/blob/main/docs/core-host.md). --- ## `approvals` State Machine Source: https://yanox.dev/state-machines/approvals/ > Approvals is Yano's built-in member-signature workflow for deterministic k-of-n decisions. A proposer creates an item, distinct app-chain members approve… **IMPORTED PAGE:** This page is generated from [`docs/appchain/state-machines/approvals.md`](https://github.com/bloxbean/yano-x/blob/main/docs/appchain/state-machines/approvals.md) in the Yano X repository, which is its source of truth. `approvals` is Yano's built-in member-signature workflow for deterministic `k-of-n` decisions. A proposer creates an item, distinct app-chain members approve it, and the item becomes terminal when it is approved, rejected, or expired. Every decision is replicated, threshold-finalized, and provable against the app-chain state root. The configured state-machine id is exactly `approvals`. Item ids and topics are chosen by the application; they do not create new state-machine types. #### When to use it Use `approvals` when app-chain member keys intentionally represent the people, services, or organizations allowed to decide: - release and deployment gates; - consortium or treasury authorization; - cross-organization sign-off; - credential or document issuance approval; - manual review before an external action; and - a generic finalized decision that may emit an effect. Choose a role-aware workflow or a custom state machine when business actors must be independent of validator members, approvals have organization/role clauses, votes carry delegated authority, or the workflow needs more states than propose/approve/reject. A REST API key controls HTTP access; it is not an approval identity. #### Decision model The machine accepts three canonical CBOR command bodies: ```text [0, itemId, payloadBytes, requiredApprovals, deadlineMillis] PROPOSE [1, itemId] APPROVE [2, itemId] REJECT ``` Its rules are: - The first `PROPOSE` for an item id creates a `PENDING` item. Later proposals for the same id are deterministic no-ops. - `requiredApprovals` must be positive. Configure a reachable threshold; an item requiring more distinct members than are available can never approve. - Each member public key counts at most once. Repeating an approval from the same member is a no-op. - Reaching the required count changes the item to terminal `APPROVED`. - One member rejection changes a pending item to terminal `REJECTED`. - A command that touches a pending item after its deadline changes it to `EXPIRED`. Time passing alone does not create a block or mutate state. - Approval or rejection of an unknown or terminal item is a no-op. Deadlines are Unix epoch milliseconds compared with the finalized app-block timestamp, not a node-local clock read during deterministic execution. The authenticated envelope sender is the approver. With the local cluster, submitting through ports 7070, 7071, and 7072 uses three different member identities. #### Configuration Configure a standalone approval chain with: ```yaml yano: app-chain: enabled: true chain-id: approvals-chain state-machine: approvals ``` In a multi-chain deployment: ```yaml yano: app-chain: chains[0]: chain-id: approvals-chain state-machine: approvals membership: mode: governed block: interval-ms: 1000 ``` All members must use the same machine id and consensus-affecting settings. The local launcher injects the demo member keys, threshold, proposer, and peer addresses. Production deployments should obtain those values and signing secrets from generated per-node configuration and secret management. The default three-node demo already hosts `effects-chain` using `approvals`, with a generic demonstration effect attached: ```bash ./yano.sh appchain cluster start 3 ``` #### Submit proposal and approvals through REST REST transports the canonical command bytes in `bodyHex`. From the source checkout's `app/` directory, use the maintained dependency-free tutorial encoder: ```bash TOOL=../docs/appchain/tutorials/tools/stdlib_command.py ITEM=release-2026-07 PROPOSE_HEX=$(python3 "$TOOL" approvals propose "$ITEM" \ --required 2 \ --payload-text '{"artifact":"inventory-service:2.4.0"}') APPROVE_HEX=$(python3 "$TOOL" approvals approve "$ITEM") REJECT_HEX=$(python3 "$TOOL" approvals reject "$ITEM") ``` Submit the proposal through node 0: ```bash curl -sS -X POST \ http://127.0.0.1:7070/api/v1/app-chain/chains/effects-chain/messages \ -H 'Content-Type: application/json' \ -d "{\"topic\":\"approvals\",\"bodyHex\":\"$PROPOSE_HEX\"}" | jq . ``` Submit two approvals through distinct members: ```bash curl -sS -X POST \ http://127.0.0.1:7071/api/v1/app-chain/chains/effects-chain/messages \ -H 'Content-Type: application/json' \ -d "{\"topic\":\"approvals\",\"bodyHex\":\"$APPROVE_HEX\"}" | jq . curl -sS -X POST \ http://127.0.0.1:7072/api/v1/app-chain/chains/effects-chain/messages \ -H 'Content-Type: application/json' \ -d "{\"topic\":\"approvals\",\"bodyHex\":\"$APPROVE_HEX\"}" | jq . ``` Use the chain id from your configuration when it is not `effects-chain`. The topic is an application routing label; the stock machine interprets the CBOR command body, not the topic name. HTTP `202` means that the ingress node accepted the signed envelope. It does not mean the command has finalized or changed the item. Wait for finality and inspect the state proof before treating the decision as committed. To reject a still-pending item, submit `REJECT_HEX` through any member. Do not submit both paths in one workflow unless the application is deliberately testing deterministic ordering: whichever terminal transition finalizes first governs later commands. ##### Add a deadline Pass an absolute epoch-millisecond value: ```bash DEADLINE=$(python3 -c 'import time; print(int((time.time() + 300) * 1000))') PROPOSE_HEX=$(python3 "$TOOL" approvals propose expiring-review-001 \ --required 2 \ --deadline-millis "$DEADLINE" \ --payload-text '{"document":"policy-v3"}') ``` After the deadline, the next approval or rejection command deterministically marks the pending item `EXPIRED`. #### Submit from Java Use the client artifact with the same Yano version as the nodes: ```groovy implementation "com.bloxbean.cardano:yano-x-client:${yanoVersion}" ``` The client provides portable contracts and a typed proof-verifying facade: ```java import com.bloxbean.cardano.yano.appchain.client.AppChainClient; import com.bloxbean.cardano.yano.appchain.client.StdlibAppChainClient; import java.nio.charset.StandardCharsets; import java.time.Instant; var proposer = AppChainClient.builder("http://127.0.0.1:7070/api/v1") .chainId("effects-chain") .build(); var approver1 = AppChainClient.builder("http://127.0.0.1:7071/api/v1") .chainId("effects-chain") .build(); var approver2 = AppChainClient.builder("http://127.0.0.1:7072/api/v1") .chainId("effects-chain") .build(); var proposals = new StdlibAppChainClient(proposer); var decisions1 = new StdlibAppChainClient(approver1); var decisions2 = new StdlibAppChainClient(approver2); String itemId = "release-2026-07"; byte[] payload = "{\"artifact\":\"inventory-service:2.4.0\"}" .getBytes(StandardCharsets.UTF_8); long deadline = Instant.now().plusSeconds(300).toEpochMilli(); proposals.propose(itemId, payload, 2, deadline); decisions1.approve(itemId); decisions2.approve(itemId); ``` When API authentication is enabled, add `.apiKey("...")` to each builder. Using two client objects against the same node does not create two approvers; the envelope sender remains that node's member key. #### State and proofs Each item is stored under UTF-8 key `i/` as canonical CBOR: ```text [status, proposer, payloadHash, required, deadline, approvers[], rejecter] ``` Status values are `0=PENDING`, `1=APPROVED`, `2=REJECTED`, and `3=EXPIRED`. The state retains the Blake2b-256 payload hash rather than duplicating the proposal payload. The original command remains in finalized block history while its body is retained; archive the command/evidence when long-term body availability is required. Request an MPF proof by hex-encoding the physical key: ```bash ITEM_KEY_HEX=$(python3 -c 'print("i/release-2026-07".encode().hex())') curl -sS \ "http://127.0.0.1:7070/api/v1/app-chain/chains/effects-chain/state/proof/$ITEM_KEY_HEX" \ | jq . ``` The response's `valueHex` is the CBOR state entry. Verify `proofWireHex` against an independently trusted state root before trusting that value. Java can verify and decode it: ```java var item = proposals.approval(itemId).orElseThrow().value(); System.out.println("status=" + item.status()); System.out.println("approvals=" + item.approvers().size()); ``` For audit-grade verification, pin the chain profile and obtain the expected state root from an independently verified finality certificate or Cardano anchor rather than accepting the serving node's root as its own authority. #### Attach a generic on-approved effect An approval decision can optionally emit one generic effect when it first becomes `APPROVED`: ```yaml yano: app-chain: chains[0]: chain-id: approvals-chain state-machine: approvals effects: enabled: true external: enabled: true executor: enabled: true types: demo.webhook metrics: types: demo.webhook machines: approvals: on-approved-effect: enabled: true type: demo.webhook gate: app-final expiry-blocks: 100 activations: on-approved-effect: 1 ``` The proposal payload becomes the opaque effect payload. The routing `type` tells an executor which contract to apply; the approvals machine does not interpret it as a webhook, payment, or any other domain action. Production types can route to packaged or custom executor plugins. The decision and delivery lifecycle remain separate: ```text i/ -> APPROVED ae/s/ -> PENDING -> CONFIRMED | FAILED effect scope -> approvals/on-approved/ ``` Effect success or failure never rewrites the business decision from `APPROVED`. Select the activation height before the first proposal that should emit an effect; proposals finalized before activation do not stage their payload retroactively. The default local demo exercises the complete lifecycle: ```bash ./yano.sh appchain cluster effect demo "release 2026-07 approved" ``` It creates and approves a one-approval item, emits `demo.webhook`, acts as a simulated external worker, reports success, and checks the finalized effect proof. It does not call a real webhook. For a real HTTP delivery, configure the `webhook.post` executor as shown in the webhook-effects tutorial. #### Operational and security notes - Member signing keys are approval authority; protect and rotate them as consensus identities, not as ordinary API credentials. - The proposal payload is replicated to every member and retained in block history. Do not submit secrets without application-level encryption. - Business idempotency comes from a stable item id. Reusing an id with a new payload does not replace the original proposal. - Adding members does not change an existing item's numeric required count. - Switching an existing chain to different state-machine semantics requires a governed/versioned profile activation or a new chain, not a local YAML edit. - External execution is at-least-once. Executors must honor the effect's idempotency identity and report a bounded outcome. #### Related documentation - [First app chain and local effect demo](https://yanox.dev/tutorials/01-first-app-chain/) - [Stock state-machine cookbook](https://yanox.dev/tutorials/03-stock-state-machines/) - [Webhook effects](https://yanox.dev/tutorials/06-webhook-effects/) - [Domain-role approvals](https://yanox.dev/tutorials/05-domain-role-approvals/) - [Complete app-chain user guide](https://github.com/bloxbean/yano-x/blob/main/docs/APP_CHAIN_USER_GUIDE.md) - [Java app-chain client](https://github.com/bloxbean/yano-x/blob/main/sdk/client/README.md) --- ## Authenticated-map value validation Source: https://yanox.dev/state-machines/authenticated-map-validation/ > The authenticated-map state machine is a proof-oriented, multi-collection registry. Each collection independently chooses its authorization policy, key… **IMPORTED PAGE:** This page is generated from [`docs/appchain/state-machines/authenticated-map-validation.md`](https://github.com/bloxbean/yano-x/blob/main/docs/appchain/state-machines/authenticated-map-validation.md) in the Yano X repository, which is its source of truth. The `authenticated-map` state machine is a proof-oriented, multi-collection registry. Each collection independently chooses its authorization policy, key and value bounds, value encoding, and optional validator. These choices are compiled into canonical genesis and are identical on every member. Start with the [authenticated-map state-machine guide](https://yanox.dev/state-machines/authenticated-map/) for the collection model, mutations, configuration, submission, queries, and showcase walkthrough. This page is the deeper validation reference. The safe default is intentionally simple: | Setting | Default | Effect | |---|---|---| | `valueEncoding` | `opaque` | Accept any byte string within the value bound | | `validator` | absent | Perform no schema or plugin validation | | `authorization` | `open` | Apply the authenticated-map open-write policy | | `restoreAllowed` | `false` | Do not restore revoked entries | Validation is optional. An opaque collection without a validator preserves the original authenticated-map behavior. Opting into a schema or plugin changes consensus validity and therefore changes `genesisId`. #### Create a project Authenticated-map genesis commits the initial membership. Supply every public member key while initializing; placeholder keys are rejected because they would produce a different chain identity. ```bash ./yano.sh appchain init --non-interactive \ --recipe authenticated-map --network preprod --members 3 \ --member-key \ --member-key \ --member-key \ --name product-registry --chain-id product-registry \ --output product-registry ``` The generated project starts with one open, opaque `records` collection. Edit `spec.chains[0].authenticatedMap` in `appchain.yaml`, then render again. This example adds an opaque collection, a canonical-CBOR collection, and a schema-bound collection: ```yaml authenticatedMap: profile: mpf-blake2b256-v1 # All-zero is the initializer's explicit development/no-policy placeholder. # Replace it with the reviewed 32-byte commitment before an anchored chain # generation is created. anchorPolicyCommitment: "0000000000000000000000000000000000000000000000000000000000000000" maxBatchItems: 128 maxBatchBytes: 65536 collections: - id: attachments authorization: owner restoreAllowed: false maxKeyBytes: 64 maxValueBytes: 1048576 valueEncoding: opaque - id: canonical-events authorization: member maxKeyBytes: 64 maxValueBytes: 16384 valueEncoding: canonical-cbor - id: products authorization: owner maxKeyBytes: 64 maxValueBytes: 4096 valueEncoding: canonical-cbor validator: product-v1 schemas: - id: product-v1 root: product source: | product = { sku: tstr .size (1..32), quantity: uint .le 1000000, status: "active" / "held" / "retired", ? note: tstr .size (0..256) } ``` Run the renderer and doctor after every edit: ```bash ./yano.sh appchain render product-registry ./yano.sh appchain doctor product-registry --distribution /path/to/yano-release.zip ``` Doctor reports `authenticated-map-schema-encoding` explicitly. A collection that references a schema must use `canonical-cbor`; `opaque` plus a schema is rejected before genesis is emitted. The generated outputs include: - `config/shared-consensus.yaml`, containing the exact genesis and state identity settings used by nodes; - `config/authenticated-map-genesis.hex`, a portable canonical genesis input for offline tooling; and - `docs/VALUE_VALIDATION.md`, project-specific validation commands. #### Encoding and schema options `opaque` performs only the declared byte-length check. Use it for already canonical application formats, encrypted payloads, large attachments, or record shapes expected to change during the lifetime of this chain. `canonical-cbor` accepts exactly one bounded deterministic CBOR item. It rejects indefinite-length items, non-minimal integers, duplicate or incorrectly ordered map keys, trailing bytes, invalid UTF-8, and unsupported tags or simple values. It is useful even without a schema because equal logical values then have equal bytes and equal value hashes. A declarative schema is authored in `cddl-yano-subset-v1`. Devtools resolve its named, non-recursive rules and compile it to canonical `yano-cbor-schema-ir-v1`. Consensus nodes evaluate the IR; they never parse the source CDDL. Supported constructs include exact text-keyed maps, bounded arrays, integer ranges, bounded text/byte strings, literals, choices, and optional fields. External references, recursion, unbounded repetition, regular expressions, and host-dependent extensions are rejected. Schema validation is exact: undeclared map fields are rejected. Keep generous, intentional bounds for fields expected to evolve, and remember that a schema is immutable for this chain generation. The available commitment profiles are: - `mpf-blake2b256-v1`; and - `jmt-blake2b256-v1`. The Poseidon JMT profile remains unavailable until ADR-025 Phase 4 and its release-pinned ZeroJ dependency are completed. Blueprints fail closed if it is selected. #### Offline CLI preflight and inspection Inspect the exact collection and validator set committed by genesis: ```bash ./yano.sh appchain state validators \ --genesis-file product-registry/config/authenticated-map-genesis.hex ``` The result includes profile, format fingerprint, `genesisId`, collection encodings and bounds, validator kind and contract version, schema definition SHA-256, parameter SHA-256, and a plugin artifact-closure SHA-256 when present. Validate a candidate from canonical lowercase hex or a file: ```bash ./yano.sh appchain state validate \ --genesis-file product-registry/config/authenticated-map-genesis.hex \ --collection products --key 736b752d31 --value-file product.cbor ./yano.sh appchain state validate \ --genesis-file product-registry/config/authenticated-map-genesis.hex \ --collection products --key 736b752d31 \ --value-hex a363736b7565736b752d316673746174757366616374697665687175616e7469747905 ``` The command returns `ACCEPTED`, `REJECTED`, or `UNAVAILABLE`. `UNAVAILABLE` means the collection uses a custom plugin but the offline CLI has no exact application adapter for it; it never means the value was accepted. All three results are advisory. Authoritative validation occurs when every node applies the command. Explain any authenticated-map result or receipt code: ```bash ./yano.sh appchain state explain --code 11 ``` | Code | Name | Meaning | |---:|---|---| | 0 | `NONE` | Applied without an authenticated-map error | | 1 | `UNKNOWN_COLLECTION` | Collection is absent from genesis | | 2 | `COLLECTION_BOUNDS` | Key or value exceeds collection bounds | | 3 | `UNAUTHORIZED` | Sender does not satisfy collection authorization | | 4 | `ALREADY_EXISTS` | Operation required an absent entry | | 5 | `ABSENT` | Operation required an existing entry | | 6 | `REVOKED` | Operation required an active entry | | 7 | `ACTIVE` | Operation required a revoked entry | | 8 | `PRECONDITION` | Revision or value hash did not match | | 9 | `RESTORE_FORBIDDEN` | Collection does not permit restoration | | 10 | `VALUE_ENCODING` | Encoding constraint rejected the value | | 11 | `VALUE_SCHEMA` | Declarative schema rejected the value | | 12 | `VALUE_VALIDATOR` | Custom validator rejected the value | Only a rejection reached during authoritative application has an authenticated receipt. A CLI/client-side rejection or a message filtered during candidate block validation has no receipt and makes no finality claim. HTTP `202` means only that ingress retained the message; wait for finalized inclusion before treating a mutation as applied. #### Java client preflight The lightweight client module can decode genesis and reuse the encoding and schema evaluator without depending on the runtime or plugin SPI: ```java byte[] genesisBytes = HexFormat.of().parseHex(Files.readString( Path.of("config/authenticated-map-genesis.hex")).strip()); var preflight = AuthenticatedMapPreflight.fromEncodedGenesis(genesisBytes); var mutation = AuthenticatedMapContract.Mutation.put( "products", skuBytes, canonicalProductCbor); var result = preflight.validate(mutation); if (result.accepted()) { stock.authenticatedMapMutate(mutation, preflight); } ``` The overload accepting `preflight` checks the complete command immediately before HTTP submission and throws `AuthenticatedMapPreflight.PreflightException` on the first rejected or unavailable mutation. Existing submission methods are unchanged. Custom plugin preflight requires an application-supplied adapter selected from the exact genesis descriptor: ```java var preflight = AuthenticatedMapPreflight.fromEncodedGenesis(genesisBytes, descriptor -> { if (!descriptor.providerId().equals("gs1-gtin-v1")) { return Optional.empty(); } verifyPinnedArtifactClosure(descriptor.definition()); return Optional.of((collection, key, value) -> applicationGs1Check(key, value)); }); ``` Returning `Optional.empty()` produces `UNAVAILABLE`, not acceptance. The client adapter is a convenience only; a malicious client can omit it, so the same genesis-pinned validator always runs in authoritative apply. #### Custom validator plugins Schemas cover record shape. Use a custom validator only for a deterministic, self-contained rule over `(collectionId, applicationKey, value)` that cannot be expressed in the CDDL subset. Plugin validators cannot read state, sender, height, membership, clock, randomness, environment, filesystem, or network through the SPI. Java bytecode is not sandboxed. A plugin can still call JDK APIs directly, so validators are trusted consensus code and must be reviewed, explicitly allow-listed, conformance-tested, and pinned by exact `ARTIFACT_CLOSURE` digest. Do not load hostile or merely user-uploaded code. The descriptor remains in the no-SPI contracts artifact, while the factory SPI lives in `core-api`. Programmatic genesis construction uses: ```java var validator = AuthenticatedMapContract.ValidatorDescriptor.plugin( "gtin-v1", "gs1-gtin-v1", exactArtifactClosureSha256, canonicalCborParameterMap); var collection = new AuthenticatedMapContract.CollectionDescriptor( "products", AuthenticatedMapContract.AUTH_OWNER, false, 64, 4096, AuthenticatedMapContract.VALUE_ENCODING_CANONICAL_CBOR, validator.id()); var genesis = AuthenticatedMapGenesisFactory.mpf( config, anchorPolicyCommitment, 128, 65536, List.of(collection), List.of(validator), List.of()); ``` The first-party `appchain-authenticated-map-validators` module provides `gs1-gtin-v1` as a worked example for GTIN-8, GTIN-12, GTIN-13, and GTIN-14. Production resolution additionally requires the provider bundle to be in the runtime allow-list and its catalog digest mode to be exactly `ARTIFACT_CLOSURE`. Blueprint-native custom plugin descriptors are not exposed in v1alpha1. This prevents a generated project from implying that an arbitrary Java bundle is safe or installed. Build and review plugin genesis programmatically, or stay with canonical encoding/declarative schemas in the standard blueprint path. #### Choosing the right boundary Value validation sees one key and one value. If a rule must read another key, check a relationship between collections, inspect membership, or emit effects, it belongs in a composite component or custom state machine. There is no node-local `AdmissionPolicy` in this feature. Validation proves that committed bytes satisfy the declared encoding and rule. It does not prove that the record is true, make a payload confidential, or replace authorization. In v1, encodings, schemas, plugins, and plugin parameters are immutable for the chain generation. Tightening or replacing one requires a new chain generation and an explicit state import/linking plan. For a record shape likely to evolve, prefer `canonical-cbor` without a validator until governed validator evolution is available. --- ## `authenticated-map` State Machine Source: https://yanox.dev/state-machines/authenticated-map/ > authenticated-map is Yano's built-in, proof-oriented registry for multiple named collections. Each collection has its own authorization policy, key/value… **IMPORTED PAGE:** This page is generated from [`docs/appchain/state-machines/authenticated-map.md`](https://github.com/bloxbean/yano-x/blob/main/docs/appchain/state-machines/authenticated-map.md) in the Yano X repository, which is its source of truth. `authenticated-map` is Yano's built-in, proof-oriented registry for multiple named collections. Each collection has its own authorization policy, key/value bounds, value encoding, and optional value validator. Every active entry or revocation tombstone is threshold-finalized and individually provable against the app-chain state root. It is a general authenticated data structure, not a relational database or a document store. It does not provide secondary indexes, joins, range queries, confidentiality, or cross-entry business rules. #### When to use it Use `authenticated-map` when an application needs one or more independently configured key/value namespaces with verifiable current state: - product, asset, credential, or configuration registries; - owner-controlled records with controller transfer and revocation; - member-maintained canonical event or reference data; - opaque hashes, attachments, encrypted values, or application-native bytes; - atomic updates to several distinct keys; and - records that need deterministic schema or application-specific validation. Use `kv-registry` when one owner-guarded key/value namespace is enough. Use `ordered-log` when append order matters more than current keyed state. Write a composite or custom state machine when a transition must inspect several existing entries, maintain secondary indexes, enforce relationships between collections, emit effects, or implement a larger workflow. #### Collection and entry model Genesis defines up to 64 collections. The important per-collection options are: | Option | Meaning | |---|---| | `id` | Stable lowercase collection identifier | | `authorization` | `open`, `owner`, `member`, `governed-role`, or `approval` mutation policy | | `authorizationPolicy` | Stable direct-role or approval policy id; required only by governed modes | | `restoreAllowed` | Whether a revoked tombstone may become active again | | `maxKeyBytes` | Maximum application-key size | | `maxValueBytes` | Maximum value size | | `valueEncoding` | `opaque` or `canonical-cbor` | | `validator` | Optional genesis-bound schema or plugin descriptor id | `open` permits any authenticated sender. `owner` records the first successful creator as the 32-byte controller and permits later mutations only from that controller. `member` requires the sender to be an active app-chain member at the finalized height. `governed-role` requires a current actor with the role named by its direct policy and a one-use signature over the complete action. `approval` requires a terminal role-approval proposal for that exact action and consumes the proposal atomically with the mutation. Only an `owner` collection supports controller transfer. Member keys remain consensus/relay identities. Governed authorization uses separate domain actors, organizations, rotatable actor keys, and immutable policy revisions. Any active member may relay actor-signed evidence, but the relay gains no business authority and cannot alter the signed action. An entry contains status (`ACTIVE` or `REVOKED`), revision, optional controller, value, logical value hash, creation height, and last-mutation height. Revocation retains a tombstone and the last logical value hash, but removes the value bytes. Absence and revocation are therefore distinct and provable states. The mutation operations are: | Operation | Behavior | |---|---| | `PUT` | Create an absent entry or replace an active value | | `PUT_IF_ABSENT` | Create only when no entry or tombstone exists | | `COMPARE_AND_SET` | Replace an active value after revision and/or value-hash checks | | `TRANSFER_CONTROLLER` | Change an active owner collection's controller | | `REVOKE` | Replace an active entry with a tombstone | | `RESTORE` | Restore a tombstone when the collection permits it | A command contains either one mutation or a bounded batch. A batch cannot touch the same collection/key twice and is applied atomically: if any mutation fails, none of its entry changes are written. For a command that reaches state application, a retained receipt records the applied results or one deterministic rejection code. Candidate-validation failures are filtered before finalization and therefore have no receipt. #### Configuration The recommended path is the authenticated-map project recipe. Bootstrap member keys are required because membership, consensus settings, collection rules, validators, and the state-commitment profile are all committed by genesis: ```bash ./yano.sh appchain init --non-interactive \ --recipe authenticated-map --network preprod --members 3 \ --member-key \ --member-key \ --member-key \ --name product-registry --chain-id product-registry \ --output product-registry ``` Configure `spec.chains[0].authenticatedMap` in the generated `appchain.yaml`. This example combines an opaque collection, canonical CBOR, and a declarative product schema: ```yaml authenticatedMap: profile: mpf-blake2b256-v1 anchorPolicyCommitment: "0000000000000000000000000000000000000000000000000000000000000000" maxBatchItems: 32 maxBatchBytes: 65536 collections: - id: attachments authorization: owner restoreAllowed: false maxKeyBytes: 64 maxValueBytes: 1048576 valueEncoding: opaque - id: canonical-events authorization: member restoreAllowed: false maxKeyBytes: 64 maxValueBytes: 16384 valueEncoding: canonical-cbor - id: products authorization: owner restoreAllowed: false maxKeyBytes: 64 maxValueBytes: 4096 valueEncoding: canonical-cbor validator: product-v1 schemas: - id: product-v1 root: product source: | product = { sku: tstr .size (1..32), quantity: uint .le 1000000, status: "active" / "held" / "retired", ? note: tstr .size (0..256) } ``` To use `governed-role` or `approval`, add a stable policy id to the collection and provide a closed genesis actor/policy set. This abridged direct-role example shows the public shape; substitute the generated lowercase hex values before rendering: ```yaml authenticatedMap: collections: - id: regulated-products authorization: governed-role authorizationPolicy: issuer-write restoreAllowed: false maxKeyBytes: 64 maxValueBytes: 4096 valueEncoding: canonical-cbor authorizationGovernance: authorityId: registry-admins initialRevision: 1 administratorActors: [admin-a] threshold: 1 maximumMutationLifetimeBlocks: 1000 genesisRecords: organizations: - id: manufacturer-a revision: 1 status: active actors: - id: admin-a revision: 1 organization: manufacturer-a status: active roles: [registry-admin, issuer] keys: - id: admin-a-v1 algorithm: ed25519 publicKey: "${ADMIN_PUBLIC_KEY}" proofOfPossession: "${ADMIN_POP_SIGNATURE}" validFromHeight: 1 validUntilHeight: 0 status: active directPolicies: - id: issuer-write revision: 1 status: active requiredRole: issuer maximumAuthorizationLifetimeBlocks: 100 approvalPolicies: [] ``` Generate the public key and the raw proof-of-possession signature offline. The seed remains in the caller-controlled file and is never written to the project: ```bash ADMIN_PUBLIC_KEY="$(./yano.sh appchain role public-key \ --seed-file /owner-only/admin-a.seed)" ADMIN_POP_SIGNATURE="$(./yano.sh appchain role key-proof-signature \ --chain product-registry \ --actor admin-a --actor-revision 1 --key admin-a-v1 \ --public-key "$ADMIN_PUBLIC_KEY" \ --valid-from-height 1 --valid-until-height 0 \ --seed-file /owner-only/admin-a.seed)" ``` An approval policy uses `proposerRoles` plus one or more clauses. Each clause sets a role, minimum count, and `distinctBy: actor|organization`; for example, two `auditor` actors from distinct organizations: ```yaml approvalPolicies: - id: product-release revision: 1 status: active proposerRoles: [issuer] clauses: - id: independent-auditors role: auditor minimumCount: 2 distinctBy: organization rejectionMode: any-eligible maximumLifetimeBlocks: 500 ``` Every referenced organization, actor, key and policy must be revision 1, canonical, active, proof-of-possession valid, and inside the configured bounds. The renderer refuses an invalid genesis closure. `authorizationLimits` can lower the committed evidence, genesis, pending-index, expiry, query-page and crypto-work maxima; omitted values use the release defaults. For an intentionally post-genesis policy, list an `onboarding` item instead of pretending the collection is ready: ```yaml onboarding: - kind: approval-policy id: product-release note: activate before enabling release submissions ``` The collection remains fail closed until governance activates that policy. `appchain doctor` reports `GOVERNED_COLLECTION_NOT_BOOTSTRAPPED` and names the planned item; the renderer writes `bootstrap/authenticated-map-onboarding.yaml` as an operational plan, not as consensus state. Then render and verify the exact release: ```bash ./yano.sh appchain render product-registry ./yano.sh appchain doctor product-registry --distribution /path/to/yano-release.zip ``` The renderer emits the canonical genesis plus the three matching runtime settings: `state.commitment-profile`, `state.format-fingerprint`, and `state.genesis-id`. Do not hand-edit those values. Every member must use the same generated chain configuration and validator artifacts. `anchorPolicyCommitment` is consensus identity. The all-zero value is an explicit development/no-policy placeholder; replace it with the reviewed 32-byte commitment before creating an anchored production chain generation. The available commitment profiles are `mpf-blake2b256-v1` and `jmt-blake2b256-v1`. The Poseidon JMT profile is reserved until its pinned implementation is released. #### Value encoding and validation Validation is optional and the default is `opaque` with no validator. - `opaque` accepts any bounded byte sequence. Use it for encrypted values, hashes, attachments, or an application format already made deterministic. - `canonical-cbor` requires exactly one bounded deterministic CBOR item. It accepts integers, byte/text strings, definite-length arrays, and definite-length maps, subject to canonical ordering and bounds. It rejects indefinite-length values, trailing bytes, non-minimal encodings, duplicate or incorrectly ordered map keys, invalid UTF-8, and unsupported tags or simple values. - A declarative schema adds exact record-shape and field constraints using the supported `cddl-yano-subset-v1`; nodes execute its canonical compiled IR. - A validator plugin adds a deterministic application-specific predicate over `(collectionId, applicationKey, value)`. These rules are consensus-bound. Changing an encoding, schema, plugin, parameters, or pinned plugin artifact creates a different genesis identity and requires a new chain generation with an explicit migration plan. For detailed schema syntax, offline preflight, validator descriptors, error codes, and the plugin trust model, see [Authenticated-map value validation](https://yanox.dev/state-machines/authenticated-map-validation/). #### Run the showcase The light showcase includes `authenticated-map-chain` with four collections: | Collection | Authorization | Encoding and validation | |---|---|---| | `attachments` | owner | opaque bytes | | `canonical-events` | member | canonical-CBOR array | | `products` | owner | canonical-CBOR map plus `product-v1` schema | | `gtins` | owner | canonical-CBOR text plus first-party `gs1-gtin-v1` plugin | Start it and run the dedicated scenario: ```bash ./showcase.sh quickstart --profile light --nodes 3 --instance authmap-demo ./demos/submit-authenticated-map.sh authmap-demo ``` The scenario writes one value to every collection, demonstrates pre-finality rejection by the product schema and GTIN plugin, performs a root-attested point query, and retrieves the native MPF proof. The launcher generates the authenticated-map genesis from the actual bootstrap member keys and the validator bundle digest in that exact Yano release. #### Submit through REST The topic is `authenticated-map.command.v1`. Use the packaged showcase codec to create canonical command and value bytes; curl still sends the normal public HTTP request: ```bash BASE=http://127.0.0.1:7070 CODEC=./tools/showcase_codec.py VALUE_HEX="$(python3 "$CODEC" authmap-value product sku-42 5 active \ 'demo product')" BODY_HEX="$(python3 "$CODEC" authmap put products sku-42 "$VALUE_HEX")" RESPONSE="$(curl -fsS -X POST \ "$BASE/api/v1/app-chain/chains/authenticated-map-chain/messages" \ -H 'Content-Type: application/json' \ -d "$(jq -nc --arg body "$BODY_HEX" \ '{topic:"authenticated-map.command.v1",bodyHex:$body}')")" MESSAGE_ID="$(printf '%s' "$RESPONSE" | jq -r .messageId)" ``` HTTP `202` proves only ingress/pool acceptance, not state-machine acceptance or a successful finalized mutation. Encoding, schema, and plugin validation runs again when forming the candidate block; an invalid message can therefore receive `202`, then be filtered before finalization without changing state or creating an authenticated receipt. Use local preflight for immediate advisory feedback, and always wait for finality before treating a mutation as applied. The generic query endpoint accepts the canonical point-query bytes: ```bash until curl -fsS \ "$BASE/api/v1/app-chain/chains/authenticated-map-chain/messages/$MESSAGE_ID" \ >/dev/null 2>&1; do sleep 1; done RECEIPT_HEX="$(python3 "$CODEC" authmap-receipt "$MESSAGE_ID")" curl -fsS -X POST \ "$BASE/api/v1/app-chain/chains/authenticated-map-chain/query/authenticated-map/receipt-v1" \ -H 'Content-Type: application/json' \ -d "$(jq -nc --arg params "$RECEIPT_HEX" '{paramsHex:$params}')" | jq . QUERY_HEX="$(python3 "$CODEC" authmap query products sku-42)" curl -fsS -X POST \ "$BASE/api/v1/app-chain/chains/authenticated-map-chain/query/authenticated-map/entry-v1" \ -H 'Content-Type: application/json' \ -d "$(jq -nc --arg params "$QUERY_HEX" '{paramsHex:$params}')" | jq . ``` For a direct state-inclusion proof, derive the physical state key: ```bash STATE_KEY_HEX="$(python3 "$CODEC" authmap state-key products sku-42)" curl -fsS \ "$BASE/api/v1/app-chain/chains/authenticated-map-chain/state/proof/$STATE_KEY_HEX" \ | jq . ``` Verify the proof locally and bind its root to an independently trusted finality certificate or Cardano anchor at an audit boundary. #### Governed authoring and external signing A governed command commits to the complete ordered action: every collection, key, operation, value, precondition, controller, authorization kind, stable policy id, evidence handle, and covered mutation index. Evidence handles are one-based; `0` means no evidence for `open`, `owner`, or `member`. The same evidence item may cover several declared mutation indexes, but its coverage must be exact. The packaged CLI assembles canonical bytes and never reads a direct-role private key. For one `governed-role` mutation whose first evidence item is the actor authorization: ```bash ACTION_HEX="$(./yano.sh appchain authenticated-map action \ --command-hex "$BASIC_COMMAND_HEX" \ --assignments '0:governed-role:issuer-write:1')" PREIMAGE_HEX="$(./yano.sh appchain authenticated-map direct-preimage \ --action-hex "$ACTION_HEX" \ --authorization-id "$UNIQUE_32_BYTE_ID_HEX" \ --chain product-registry --genesis-id "$GENESIS_ID_HEX" \ --indexes 0 --policy issuer-write --policy-revision 1 \ --actor issuer-a --actor-revision 1 --key issuer-a-v1 \ --public-key "$ISSUER_PUBLIC_KEY" \ --issued-height "$CURRENT_HEIGHT" --deadline-height "$DEADLINE_HEIGHT")" ### Sign PREIMAGE_HEX with the actor's Ed25519 key in the caller's wallet/HSM. SIGNATURE_HEX="$(external-ed25519-signer "$PREIMAGE_HEX")" EVIDENCE_HEX="$(./yano.sh appchain authenticated-map direct-complete \ --action-hex "$ACTION_HEX" \ --authorization-id "$UNIQUE_32_BYTE_ID_HEX" \ --chain product-registry --genesis-id "$GENESIS_ID_HEX" \ --indexes 0 --policy issuer-write --policy-revision 1 \ --actor issuer-a --actor-revision 1 --key issuer-a-v1 \ --public-key "$ISSUER_PUBLIC_KEY" \ --issued-height "$CURRENT_HEIGHT" --deadline-height "$DEADLINE_HEIGHT" \ --signature "$SIGNATURE_HEX")" GOVERNED_COMMAND_HEX="$(./yano.sh appchain authenticated-map command \ --action-hex "$ACTION_HEX" --evidence-hex "$EVIDENCE_HEX")" ``` `direct-complete` verifies the returned signature against the claimed public key before emitting evidence. Submit `GOVERNED_COMMAND_HEX` on `authenticated-map.command.v1`. A successful action consumes `(actorId, authorizationId)` exactly once; reuse is a deterministic replay rejection even if a different member relays it. For an `approval` collection, derive the proposal payload with `authenticated-map approval-payload --action-hex ... --genesis-id ...`. Actors sign `PROPOSE` and `APPROVE` statements for payload domain `yano.authenticated-map.action.v1` through the offline `appchain role sign` flow and submit them on `role-approvals.command.v1`. Only after the exact proposal is terminal `APPROVED`, create its one-based evidence item with `authenticated-map approval-reference`, assemble the final map command, and submit it. Successful execution consumes the proposal id globally and atomically with every mutation; threshold approval never auto-executes. #### Submit from Java Use `yano-x-stdlib-contracts` for the portable wire contract and `appchain-client` for submission, queries, and proof verification: ```java AppChainClient raw = AppChainClient.builder("http://127.0.0.1:7070/api/v1") .chainId("product-registry") .apiKey(System.getenv("YANO_API_KEY")) .build(); StdlibAppChainClient map = new StdlibAppChainClient(raw); byte[] key = "sku-42".getBytes(StandardCharsets.UTF_8); byte[] value = productCbor(); // one canonical CBOR value matching product-v1 var mutation = AuthenticatedMapContract.Mutation.put("products", key, value); var submitted = map.authenticatedMapMutate(mutation); var point = map.authenticatedMapEntry("products", key); var proof = map.authenticatedMapProof("products", key); var receipt = map.authenticatedMapReceipt( HexFormat.of().parseHex(submitted.messageId())); ``` For race-safe writes, use `compareAndSet` with the revision and/or logical value hash from the last trusted entry. Use `authenticatedMapBatch` for an atomic list of distinct collection/key mutations. Application-side preflight can reuse the exact genesis encoding/schema rules, but authoritative validation still occurs independently on every node. Governed Java applications use `AuthenticatedMapAuthoring` to keep signing outside the client process: ```java var command = AuthenticatedMapContract.Command.single(mutation); var action = AuthenticatedMapAuthoring.action(command, List.of( new AuthenticatedMapAuthorizationContract.AuthorizationAssignmentV1( 0, AuthenticatedMapContract.AUTH_GOVERNED_ROLE, "issuer-write", 1))); var request = AuthenticatedMapAuthoring.directSigningRequest( authorizationId, "product-registry", genesisId, action, List.of(0), "issuer-write", 1, "issuer-a", 1, "issuer-a-v1", publicKey, currentHeight, deadlineHeight); byte[] signature = externalSigner.sign(request.signingPreimage()); var evidence = AuthenticatedMapAuthoring.completeDirectSignature( request, signature); map.authenticatedMapGovernedCommand(AuthenticatedMapAuthoring.command( action, List.of(evidence))); ``` Approval helpers construct the exact proposal statement and approval reference; `StdlibAppChainClient` submits signed approval, actor-governance, and policy-governance commands on their versioned topics. The SDK never infers a policy revision or silently signs with a node/member key. #### Domain API and composite proofs The first-party bundle publishes read-only routes below `/api/v1/plugins/com.bloxbean.cardano.yano.appchain.stdlib/`. Set `chain=` when a node hosts more than one app chain. Important routes include: | Route | Result | |---|---| | `authenticated-map` | Genesis identity, profile, collections, authorization capabilities | | `authenticated-map/entries/{collection}/{keyHex}` | Exact entry, absence, or tombstone | | `authenticated-map/receipts/{messageIdHex}` | Receipt and complete action commitment | | `authenticated-map/direct-consumptions/{actor}/{authorizationIdHex}` | One-use actor claim | | `authenticated-map/approval-consumptions/{proposal}` | One-use proposal claim | | `authenticated-map/direct-policies/{id}` | Current or `?revision=` direct policy | | `authenticated-map/administrator-authorities/{id}` | Current or historical authority | | `authenticated-map/pending/approvals` | Bounded `?after=&limit=` pending page | | `authenticated-map/pending/actor-governance` | Bounded actor-governance page | | `authenticated-map/pending/policy-governance` | Bounded policy-governance page | The same bundle also composes the exact organization, actor, approval-policy, proposal, and statistics routes from the role workflow API. Every exact-record response names the chain/API/state-machine identity, height/root, physical proof key, canonical leaf value, and—when it asserts currency—the current-pointer key/value. Pending pages instead return `sourceIndexProofKey` and canonical `queryValue`, explicitly labelled as a bounded index-derived view; they do not mislabel page bytes as a Merkle leaf. JSON is presentation only; verify canonical record values and native proofs. `AuthenticatedMapProofBundle` verifies a bounded same-root assembly for `BASIC`, `DIRECT_ROLE`, `APPROVAL`, or `ADMINISTRATOR_GOVERNANCE`. It rejects mixed chain/profile/genesis/root/height facts, wrong physical namespaces, receipt-to-entry substitution, missing current pointers, wrong revisions, invalid actor/administrator signatures, unsatisfied approval clauses, and consumption/action mismatches. Use `verify(trustedRoot)` with an independently trusted exact root, or `verifyCertified(...)` with pinned finality membership. A later-root proof establishes retention, not historical currency; use the decision/execution root when the bundle must prove that a pointer was current. #### Attach a custom validator format An application can enforce its own deterministic value format without adding a new `valueEncoding` name: - use `opaque` plus a plugin when the plugin owns all format parsing; or - use `canonical-cbor` plus a plugin when canonical CBOR should be enforced before the application rule. Implement `AuthenticatedMapValueValidatorFactory` from `core-api`, register it through `ServiceLoader` and a Yano plugin manifest, and return only `ACCEPT` or `REJECT` from a total, deterministic validator. The plugin descriptor in genesis pins the provider id, SPI contract version, canonical parameters, and the exact `ARTIFACT_CLOSURE` SHA-256. The bundle must also be present in the runtime catalog and explicitly allow-listed on every node. Validator plugins are trusted in-process consensus code, not sandboxed uploads. They must not depend on filesystem, network, clock, randomness, locale, environment, mutable global state, or node-local configuration. If a rule must read app-chain state or relate several entries, it belongs in a custom or composite state machine instead of a value validator. The light showcase's `gs1-gtin-v1` validator is a working reference for this extension path. Attaching a different plugin to an existing chain is not a hot configuration change; it creates a new chain generation. #### Operational boundaries Collection definitions, validation rules, maximum batch limits, consensus profile, bootstrap membership digest, and anchor-policy commitment are all chain-generation identity. Review and archive the rendered genesis before launch, keep plugin artifacts byte-identical across nodes, and fail deployment when a digest or provider is unavailable. Authenticated state proves which bytes finalized under those rules. It does not prove that a real-world assertion is true, make public values confidential, or preserve old command bodies forever. Archive application evidence separately when historical availability is required. --- ## `balances` State Machine Source: https://yanox.dev/state-machines/balances/ > Balances is Yano's stock non-negative account ledger. A configured member may mint units to an application account, and each app-chain member may transfer… **IMPORTED PAGE:** This page is generated from [`docs/appchain/state-machines/balances.md`](https://github.com/bloxbean/yano-x/blob/main/docs/appchain/state-machines/balances.md) in the Yano X repository, which is its source of truth. `balances` is Yano's stock non-negative account ledger. A configured member may mint units to an application account, and each app-chain member may transfer units only from the account named by that member's public-key hex. Every current balance is threshold-finalized and individually provable against the app-chain state root. This is an application credit ledger, not Cardano ada or native assets. It does not create L1 transactions, hold custody, calculate fees, or provide a token policy. #### When to use it Use `balances` for deliberately simple member-owned units: - consortium netting or settlement inputs; - loyalty, quota, or service credits; - prepaid usage units; - internal receipt balances; and - demos that need deterministic mint/transfer/non-negative behavior. Use a custom state machine when accounts belong to identities other than node members, minting needs multiple roles or approvals, assets need multiple denominations, transfers need holds or atomic swaps, or L1 settlement is part of the state transition. #### Command and state model The canonical CBOR commands are: ```text [0, destinationAccount, positiveAmount] MINT [1, destinationAccount, positiveAmount] TRANSFER ``` `MINT` credits the destination when the command sender is the configured minter. `TRANSFER` debits the authenticated sender's account—its 32-byte member public key as lowercase hex—and credits the destination. Insufficient funds, unauthorized minting, and malformed commands are deterministic no-ops or are rejected at admission; balances never become negative. The authenticated state key is UTF-8 `b/`. Its value is the positive unsigned big-endian amount. A zero balance is represented by the absence of the key. #### Configuration ```yaml yano: app-chain: chain-id: credits-chain state-machine: balances machines: balances: minter: <64-hex-member-public-key> ``` An empty `minter` allows any member to mint and is suitable only when that is the intended governance model. The generated `state:balances` capability can be selected with `./yano.sh appchain init` or App-Chain Studio. #### Java client Use `yano-x-stdlib-contracts` when only canonical bytes and state decoders are needed, or the typed facade in `appchain-client` for submission and local proof verification: ```java AppChainClient raw = AppChainClient.builder("http://localhost:7070/api/v1") .chainId("credits-chain") .apiKey(System.getenv("YANO_API_KEY")) .build(); StdlibAppChainClient balances = new StdlibAppChainClient(raw); balances.mint("customer-42", BigInteger.valueOf(100)); balances.transfer("customer-42", BigInteger.TEN); var verified = balances.balance("customer-42"); verified.ifPresent(value -> System.out.println(value.value())); ``` The transfer above spends the submitting member's public-key-hex account, not `customer-42`. The returned message id proves acceptance, not that a transition changed state; read the verified balance after finalization. With the Spring starter, inject `StdlibAppChainTemplate` and call `mint`, `transfer`, or `balance` with the same contract semantics. #### REST/curl The generic REST endpoint accepts canonical command bytes. This example mints 10 units to `alice`; `830065616c6963650a` is CBOR `[0,"alice",10]`: ```bash curl -sS -X POST \ http://localhost:7070/api/v1/app-chain/chains/credits-chain/messages \ -H 'Content-Type: application/json' \ -H "X-API-Key: $YANO_API_KEY" \ -d '{"topic":"balances.command.v1","bodyHex":"830065616c6963650a"}' ``` Query the proof for state key `b/alice` (hex `622f616c696365`): ```bash curl -sS \ http://localhost:7070/api/v1/app-chain/chains/credits-chain/state/proof/622f616c696365 \ -H "X-API-Key: $YANO_API_KEY" ``` Verify the MPF proof locally and, for audit-grade verification, compare its root with an independently obtained anchor root. #### Customization boundary Account naming, authorization, single-unit arithmetic, and deletion of zero balances are consensus semantics. Configuration may select the minter but cannot redefine those rules. Write a versioned custom state-machine plugin for additional asset types, role-aware ownership, fees, locks, or settlement. --- ## `doc-trail` State Machine Source: https://yanox.dev/state-machines/doc-trail/ > doc-trail is Yano's built-in append-only trail per business entity. Each command appends an application-level document or event hash to one entity's… **IMPORTED PAGE:** This page is generated from [`docs/appchain/state-machines/doc-trail.md`](https://github.com/bloxbean/yano-x/blob/main/docs/appchain/state-machines/doc-trail.md) in the Yano X repository, which is its source of truth. `doc-trail` is Yano's built-in append-only trail per business entity. Each command appends an application-level document or event hash to one entity's running chained head. The current count and head are replicated, threshold-finalized, and individually provable against the app-chain state root, while the documents themselves remain outside the chain. The configured state-machine id is exactly `doc-trail`. An entity id can be a product id, case id, shipment id, credential id, or another stable application identifier. #### When to use it Use `doc-trail` when several members need a tamper-evident ordered history for each entity without replicating full documents: - digital product passports and provenance; - supply-chain or shipment histories; - compliance and audit case files; - document/evidence revision trails; - certificate and credential histories; and - a provable current trail head linked to off-chain content. Choose `ordered-log` when only one global event order is needed. Choose `kv-registry` when the main requirement is a mutable current value. Use a custom or composite state machine when appends require ownership, roles, approval gates, uniqueness, or domain-specific transition validation. #### Trail model The machine accepts one canonical CBOR command: ```text [entityId, entryHashBytes, optionalReference] ``` For each entity, it stores: ```text UTF8("e/" + entityId) -> cbor([count, headHash]) ``` The genesis head is 32 zero bytes. Each finalized append computes: ```text head[n] = Blake2b-256(head[n-1] || entryHash[n] || senderPublicKey[n]) ``` This commits the order, entry hash, and authenticated member author. Repeating the same entry is another append and changes the count/head; the stock machine does not deduplicate or impose business rules. The optional reference is a locator such as an object id, URL, IPFS CID, or document number. It is committed through the finalized message history, while the compact per-entity chained head uses only the entry hash and author. Treat `entryHash` as the content-integrity identity; do not rely on the reference string alone to authenticate the document. Archive finalized command bodies when node retention may remove them. #### Configuration `doc-trail` has no machine-specific settings: ```yaml yano: app-chain: enabled: true chain-id: document-trail-chain state-machine: doc-trail ``` In a multi-chain deployment: ```yaml yano: app-chain: chains[0]: chain-id: document-trail-chain state-machine: doc-trail membership: mode: governed block: interval-ms: 1000 ``` The default local demo does not include a standalone `doc-trail` chain. Add a new chain to the shared configuration before starting a fresh cluster, or use the app-chain configuration generator for a new project. Do not reinterpret retained data from another state machine as `doc-trail` state. #### Hash and submit a document through REST The application selects the document hash algorithm represented by `entryHashBytes`. SHA-256 is a common application choice; the running trail head itself always uses Blake2b-256. From the source checkout's `app/` directory, create a sample document hash and encode the command with the tutorial helper: ```bash TOOL=../docs/appchain/tutorials/tools/stdlib_command.py ENTITY=product-42 ENTRY_HASH=$(python3 -c \ 'import hashlib; print(hashlib.sha256(b"quality certificate v1").hexdigest())') APPEND_HEX=$(python3 "$TOOL" doc-trail "$ENTITY" "$ENTRY_HASH" \ --reference 's3://evidence/product-42/certificate-v1.pdf') ``` Submit the command through a member: ```bash curl -sS -X POST \ http://127.0.0.1:7071/api/v1/app-chain/chains/document-trail-chain/messages \ -H 'Content-Type: application/json' \ -d "{\"topic\":\"documents\",\"bodyHex\":\"$APPEND_HEX\"}" | jq . ``` A successful HTTP `202` accepts the message envelope. Wait for finalization before treating the trail as advanced. The topic is only a routing/filtering label; `entityId` selects the trail. Append another revision by hashing its exact bytes and submitting a new command with the same entity id: ```bash ENTRY_HASH=$(python3 -c \ 'import hashlib; print(hashlib.sha256(b"quality certificate v2").hexdigest())') APPEND_HEX=$(python3 "$TOOL" doc-trail "$ENTITY" "$ENTRY_HASH" \ --reference 's3://evidence/product-42/certificate-v2.pdf') curl -sS -X POST \ http://127.0.0.1:7072/api/v1/app-chain/chains/document-trail-chain/messages \ -H 'Content-Type: application/json' \ -d "{\"topic\":\"documents\",\"bodyHex\":\"$APPEND_HEX\"}" | jq . ``` Because the two submissions use different ingress members, their author keys also differ and are incorporated into the trail head. #### Submit from Java Use the client artifact with the node version: ```groovy implementation "com.bloxbean.cardano:yano-x-client:${yanoVersion}" ``` Hash the exact application bytes, encode the stock command, and submit it: ```java import com.bloxbean.cardano.yano.appchain.client.AppChainClient; import com.bloxbean.cardano.yano.appchain.client.StdlibAppChainClient; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; var client = AppChainClient.builder("http://127.0.0.1:7071/api/v1") .chainId("document-trail-chain") // .apiKey("secret") .build(); var documents = new StdlibAppChainClient(client); byte[] document = "quality certificate v1".getBytes(StandardCharsets.UTF_8); byte[] entryHash = MessageDigest.getInstance("SHA-256").digest(document); var submitted = documents.appendDocument("product-42", entryHash, "s3://evidence/product-42/certificate-v1.pdf"); System.out.println(submitted.messageId()); ``` Store the exact hash algorithm/version with the off-chain record or in a versioned application envelope. `doc-trail` treats `entryHash` as opaque non-empty bytes and cannot infer whether it represents SHA-256, Blake2b, a Merkle root, or another digest. #### Read and prove a trail head The physical state key is UTF-8 `e/`: ```bash STATE_KEY_HEX=$(python3 -c 'print("e/product-42".encode().hex())') curl -sS \ "http://127.0.0.1:7070/api/v1/app-chain/chains/document-trail-chain/state/proof/$STATE_KEY_HEX" \ | jq . ``` The response's `valueHex` is canonical CBOR `[count, headHash]`. It proves the current trail summary, not the availability of the referenced documents. Verify and decode it in Java: ```java import java.util.HexFormat; var trail = documents.documentTrail("product-42").orElseThrow().value(); System.out.println("entries=" + trail.count()); System.out.println("head=" + HexFormat.of().formatHex(trail.headHash())); ``` For independent verification, reconstruct the ordered `(entryHash, author)` sequence from finalized block history, recompute it with `DocTrailContract.computeHead(...)`, and compare it with the proven head. Also verify the MPF proof against a state root obtained from independently trusted finality or Cardano anchor evidence. Useful history endpoints, relative to `/api/v1/app-chain`, include: ```text GET /chains/{chainId}/blocks?from=1&limit=100 GET /chains/{chainId}/messages/by-topic/documents?fromHeight=0&limit=100 GET /chains/{chainId}/messages/{messageId} GET /chains/{chainId}/status ``` The app-chain history proves which command bytes were finalized. The external document must still be fetched, hashed with the declared application algorithm, and matched to `entryHash`. #### Design and operational guidance ##### Keep documents outside consensus Submit hashes and bounded references rather than PDFs, images, or large JSON documents. Every command is replicated to every member and may be retained or exported. An object store, IPFS, or controlled document repository should own the content lifecycle. ##### Define canonical document bytes Two semantically identical JSON documents can hash differently because of whitespace, key order, or encoding. Specify canonicalization before hashing, or hash immutable binary artifacts exactly as stored. ##### Separate integrity from availability The trail proves an ordered digest commitment. It does not guarantee that an S3 object, URL, or IPFS object remains available. Operate retention, replication, access control, and evidence export separately. ##### Add policy through composition The stock machine lets every admitted member append to every entity. If an append must first be approved, combine approval and document-trail behavior in a committed composite workflow. If only an entity owner or domain role may append, implement that rule in a state machine; an off-chain check alone is not consensus enforcement. #### Related documentation - [Stock state-machine cookbook](https://yanox.dev/tutorials/03-stock-state-machines/) - [Evidence publication tutorial](https://yanox.dev/tutorials/04-evidence-publication/) - [Plugins and composites](https://yanox.dev/tutorials/08-plugins-and-composites/) - [Complete app-chain user guide](https://github.com/bloxbean/yano-x/blob/main/docs/APP_CHAIN_USER_GUIDE.md) - [Consensus and state-machine internals](https://github.com/bloxbean/yano-x/blob/main/docs/core-host.md) - [Java app-chain client](https://github.com/bloxbean/yano-x/blob/main/sdk/client/README.md) --- ## `kv-registry` State Machine Source: https://yanox.dev/state-machines/kv-registry/ > kv-registry is Yano's built-in mutable key/value registry with deterministic first-writer ownership. The member that first creates a key becomes its… **IMPORTED PAGE:** This page is generated from [`docs/appchain/state-machines/kv-registry.md`](https://github.com/bloxbean/yano-x/blob/main/docs/appchain/state-machines/kv-registry.md) in the Yano X repository, which is its source of truth. `kv-registry` is Yano's built-in mutable key/value registry with deterministic first-writer ownership. The member that first creates a key becomes its owner; only that same member can update or delete it. Every current entry is replicated, threshold-finalized, and individually provable against the app-chain state root. The configured state-machine id is exactly `kv-registry`. A chain id such as `registry-chain` identifies one independent ledger using that implementation. #### When to use it Use `kv-registry` when applications need a provable current value and the authorization rule “first writer owns this key” is sufficient: - consortium allow/deny lists; - product, asset, token, or credential metadata; - DID documents or current document pointers; - shared configuration owned per record; - service, issuer, or schema registries; and - mutable status records where one member is the authority. Choose a custom or role-aware state machine when ownership must transfer atomically or under governance, multiple parties must approve updates, values have domain-specific transition rules, one organization may own many member keys, or authorization must use a business actor distinct from the app-chain member. #### Data and ownership model The machine accepts canonical CBOR commands: ```text [0, keyBytes, valueBytes] PUT [1, keyBytes, emptyBytes] DELETE ``` The rules are: - A `PUT` for an absent key creates `[sender, value]`; the authenticated envelope sender owns the entry while it exists. - A `PUT` by the owner replaces the current value. - A `PUT` by another member is a deterministic no-op. - A `DELETE` by the owner removes the entry. - A delete of an absent key or a delete by a non-owner is a no-op. - After deletion, the next member to PUT that key becomes the new owner. - Keys and PUT values must be non-empty. HTTP acceptance and even block finalization do not prove that a command changed the registry. Read and verify the resulting state entry when the application needs the deterministic outcome. #### Start the out-of-the-box demo The default local cluster hosts `registry-chain` with UTF-8 values: ```bash ./yano.sh appchain cluster start 3 ``` The launcher provides convenient UTF-8 commands. Create a key through node 1: ```bash ./yano.sh appchain cluster kv registry-chain set supplier-42 active --node 1 ``` Node 1 now owns `supplier-42`. This update through node 2 finalizes as a message but cannot change the entry: ```bash ./yano.sh appchain cluster kv registry-chain set supplier-42 suspended --node 2 ``` The owner can update or delete it: ```bash ./yano.sh appchain cluster kv registry-chain set supplier-42 suspended --node 1 ./yano.sh appchain cluster kv registry-chain del supplier-42 --node 1 ``` #### Configuration Configure a standalone registry with: ```yaml yano: app-chain: enabled: true chain-id: registry-chain state-machine: kv-registry machines: kv-registry: value-format: utf8 ``` In multi-chain form: ```yaml yano: app-chain: chains[0]: chain-id: registry-chain state-machine: kv-registry membership: mode: governed machines: kv-registry: value-format: utf8 ``` `value-format` is a deterministic structural constraint: | Value | Behavior | |---|---| | `raw` | Any non-empty byte string; the default | | `utf8` | Value must be valid UTF-8 | | `cbor` | Value must contain one bounded, well-formed CBOR item | A non-conforming PUT is rejected at admission by an honest node and remains a deterministic no-op during block execution. All members must use the same format. Changing it for an existing chain changes consensus semantics and requires a governed profile activation or a new chain. #### Submit through REST REST accepts the canonical command bytes through `bodyHex`. From the source checkout's `app/` directory, encode a PUT and DELETE with the tutorial helper: ```bash TOOL=../docs/appchain/tutorials/tools/stdlib_command.py PUT_HEX=$(python3 "$TOOL" kv-registry put supplier-42 \ --value-text active) DELETE_HEX=$(python3 "$TOOL" kv-registry delete supplier-42) ``` Submit the PUT through node 1 so node 1 becomes the owner: ```bash curl -sS -X POST \ http://127.0.0.1:7071/api/v1/app-chain/chains/registry-chain/messages \ -H 'Content-Type: application/json' \ -d "{\"topic\":\"registry\",\"bodyHex\":\"$PUT_HEX\"}" | jq . ``` For a raw binary value, use `--value-hex` instead of `--value-text`: ```bash PUT_HEX=$(python3 "$TOOL" kv-registry put binary-key \ --value-hex 010203ff) ``` Submit the delete through the same owner member: ```bash curl -sS -X POST \ http://127.0.0.1:7071/api/v1/app-chain/chains/registry-chain/messages \ -H 'Content-Type: application/json' \ -d "{\"topic\":\"registry\",\"bodyHex\":\"$DELETE_HEX\"}" | jq . ``` The topic is a routing/filtering label and does not create a namespace. The key bytes themselves identify the registry entry. If two applications need logical namespaces, use explicit keys such as `suppliers/acme` and `schemas/order/v2`, or separate chains when membership and operations should also be isolated. #### Submit from Java Use the client artifact with the node version: ```groovy implementation "com.bloxbean.cardano:yano-x-client:${yanoVersion}" ``` The client includes the portable no-SPI stock contracts and a typed facade: ```java import com.bloxbean.cardano.yano.appchain.client.AppChainClient; import com.bloxbean.cardano.yano.appchain.client.StdlibAppChainClient; import java.nio.charset.StandardCharsets; var ownerClient = AppChainClient.builder("http://127.0.0.1:7071/api/v1") .chainId("registry-chain") // .apiKey("secret") .build(); var registry = new StdlibAppChainClient(ownerClient); byte[] key = "supplier-42".getBytes(StandardCharsets.UTF_8); byte[] value = "active".getBytes(StandardCharsets.UTF_8); var submitted = registry.kvPut(key, value); System.out.println(submitted.messageId()); // Submit later through the same member identity. registry.kvDelete(key); ``` The server, not the HTTP caller object, signs the normal REST submission. Pointing another client at port 7072 therefore uses node 2's member identity and cannot update a key owned by node 1. #### Read and prove an entry The physical state key is exactly the registry key bytes. For a UTF-8 key: ```bash KEY_HEX=$(python3 -c 'print("supplier-42".encode().hex())') curl -sS \ "http://127.0.0.1:7070/api/v1/app-chain/chains/registry-chain/state/proof/$KEY_HEX" \ | jq . ``` An included value is canonical CBOR: ```text [ownerPublicKeyBytes, valueBytes] ``` After deletion, the proof endpoint can return an exclusion proof with no `valueHex`. Keep application keys at most 256 bytes when they must be queried through the standard proof endpoint. Verify and decode the proof in Java: ```java import java.util.HexFormat; var reader = AppChainClient.builder("http://127.0.0.1:7070/api/v1") .chainId("registry-chain") .build(); var entry = new StdlibAppChainClient(reader).kvEntry(key).orElseThrow().value(); byte[] owner = entry.owner(); byte[] currentValue = entry.value(); System.out.println("owner=" + HexFormat.of().formatHex(owner)); System.out.println("value=" + new String(currentValue, StandardCharsets.UTF_8)); ``` `ProofVerifier.verify` verifies the returned MPF proof against the root in the same response. For independent verification, obtain the expected root from a pinned chain profile plus verified finality/anchor evidence rather than trusting the serving node to nominate the root. #### Application design choices ##### Stable keys provide idempotency Repeated owner PUTs replace one current entry, so a stable domain key such as `supplier-42` provides natural current-state idempotency. History is still in the finalized app blocks; the state proof represents only the latest value. ##### Ownership is intentionally simple There is no atomic transfer-owner command, expiry, multi-signature update, or administrator override. Delete-and-recreate releases the key and lets the next writer own it, so it is not a safe governed transfer protocol. If richer ownership is required, define the transition and its authorization in a custom/composite state machine instead of relying on an off-chain convention. ##### Values remain application data `utf8` and `cbor` validate structure, not a business schema. Version values explicitly when consumers need stable decoding, for example: ```json {"schemaVersion":1,"status":"active","country":"SG"} ``` Every member receives the command bytes and may retain history. Encrypt values before submission when confidentiality is required, and keep encryption-key management outside deterministic consensus. #### Related documentation - [Registry and proofs tutorial](https://yanox.dev/tutorials/02-registry-and-proofs/) - [Stock state-machine cookbook](https://yanox.dev/tutorials/03-stock-state-machines/) - [Complete app-chain user guide](https://github.com/bloxbean/yano-x/blob/main/docs/APP_CHAIN_USER_GUIDE.md) - [Consensus and state-machine internals](https://github.com/bloxbean/yano-x/blob/main/docs/core-host.md) - [Java app-chain client](https://github.com/bloxbean/yano-x/blob/main/sdk/client/README.md) --- ## `role-approvals` State Machine Source: https://yanox.dev/state-machines/role-approvals/ > role-approvals is Yano's application-neutral authorization product for cases where business actors are not the app-chain validator members. Governed… **IMPORTED PAGE:** This page is generated from [`docs/appchain/state-machines/role-approvals.md`](https://github.com/bloxbean/yano-x/blob/main/docs/appchain/state-machines/role-approvals.md) in the Yano X repository, which is its source of truth. `role-approvals` is Yano's application-neutral authorization product for cases where business actors are not the app-chain validator members. Governed organizations, actors, public keys, roles and policies authorize an exact payload hash; the finalized proposal and decision trail are committed under the app-chain state root. It is bundled for JVM and native distributions and currently has `preview` maturity. #### When to use it Use `role-approvals` when: - an employee, auditor, regulator, service or device must sign independently of the member node relaying its command; - a policy needs role checks, minimum counts, or distinct organizations; - actor keys and policies must rotate through governed revisions; and - the application can act on a provably approved payload hash. Use [`approvals`](https://yanox.dev/state-machines/approvals/) instead when validator members themselves are the approvers. Use `role-evidence` when you specifically need Yano's complete evidence release and publication workflow. Use a reviewed composite plugin when approval must atomically drive another deterministic domain transition. The generic machine does not store arbitrary payload bytes, execute them, or emit an effect. An approval proves authorization of `(payloadDomain, payloadHash)`; the application remains responsible for binding those bytes to its next action. #### Create a project From an extracted release or the source `app/` directory: ```bash ./yano.sh appchain init --non-interactive \ --recipe role-approval \ --network devnet \ --members 3 \ --runtime jvm \ --deployment host \ --name role-approval-chain \ --chain-id role-approval-chain \ --output role-approval-chain ``` Add the reviewed public member keys to the generated blueprint, render, and validate it: ```bash ./yano.sh appchain render role-approval-chain ./yano.sh appchain config validate --mode project role-approval-chain ./yano.sh appchain doctor role-approval-chain --distribution . ``` The generated `bootstrap/role-approvals-plan.yaml` is a non-secret plan for organizations, actors, proof-of-possession, policies, governance and verification. Replace its placeholders with public values only. Private actor keys belong in the actor application, KMS, HSM or vault—not in Yano, Studio, the blueprint or the bootstrap plan. Bootstrap operations are fail-closed and idempotent: 1. Query the committed record and verify its proof. 2. If the exact revision/value already exists, record the proof and skip it. 3. If it is absent, submit `PROPOSE`, collect member approvals to the configured threshold, then `ACTIVATE`. 4. If an existing revision or value differs, stop; never replace it silently. #### Identity model ```text outer AppMessage sender = consortium member that relayed the command signed actor identity = business actor authorizing the payload hash ``` REST authentication only controls access to an endpoint. It is neither a member vote nor a business-actor signature. An actor statement binds its action, chain, proposal, policy revision, payload domain/hash, deadline, actor revision, key and policy clause. A valid signature therefore cannot be replayed for a different chain, payload, role decision or policy revision. #### Topics and state | Purpose | Topic or committed query | |---|---| | Govern organizations, actors and keys | `actors.command.v1` | | Submit actor proposals and decisions | `role-approvals.command.v1` | | Current organization | `components/domain-actors/organization-current`, params `id` | | Organization revision | `components/domain-actors/organization`, params `id@revision` | | Current actor | `components/domain-actors/actor-current`, params `id` | | Actor revision | `components/domain-actors/actor`, params `id@revision` | | Current policy | `components/role-approvals/policy-current`, params `id` | | Policy revision | `components/role-approvals/policy`, params `id@revision` | | Proposal | `components/role-approvals/proposal`, params `proposalId` | | Proposal statistics | `components/role-approvals/stats`, empty params | The physical state keys are namespaced by the committed composite profile. Use the proof key returned by the domain API rather than constructing a physical key from a display identifier. #### Sign and submit a decision Hash the exact canonical application bytes before asking actors to sign them. The payload domain must identify that byte contract, for example `com.example.order.v1`. ```bash PAYLOAD_HASH=$(openssl dgst -sha256 -binary approved-order.cbor | xxd -p -c 256) COMMAND_HEX=$(./yano.sh appchain role sign \ --action approve \ --chain role-approval-chain \ --proposal order-a-1001 \ --policy order-release \ --policy-revision 1 \ --payload-domain com.example.order.v1 \ --payload-hash "$PAYLOAD_HASH" \ --deadline-height 1000 \ --actor reviewer-a \ --actor-revision 1 \ --key reviewer-key-v1 \ --clause reviewers \ --seed-file /owner-only/reviewer.seed) ``` Submit the canonical CBOR through any member. The member relays the command; the embedded actor signature determines whose decision is evaluated: ```bash curl -sS -X POST \ http://127.0.0.1:7070/api/v1/app-chain/chains/role-approval-chain/messages \ -H 'Content-Type: application/json' \ -H "X-API-Key: $YANO_APPCHAIN_API_KEY" \ -d "{\"topic\":\"role-approvals.command.v1\",\"bodyHex\":\"$COMMAND_HEX\"}" | jq . ``` HTTP `202` means accepted for sequencing, not approved. Wait for finality and query the proposal. #### Query through REST and verify The generic bundle exposes read-only JSON projections: ```bash BASE=http://127.0.0.1:7070/api/v1 BUNDLE=com.bloxbean.cardano.yano.appchain.role-workflow curl -sS \ -H "X-API-Key: $YANO_APPCHAIN_API_KEY" \ "$BASE/plugins/$BUNDLE/proposals/order-a-1001?chain=role-approval-chain" | jq . curl -sS \ -H "X-API-Key: $YANO_APPCHAIN_API_KEY" \ "$BASE/plugins/$BUNDLE/stats?chain=role-approval-chain" | jq . ``` Organization, actor and policy routes follow the same pattern: ```text organizations/{id}?chain={chainId}[&revision=N] actors/{id}?chain={chainId}[&revision=N] policies/{id}?chain={chainId}[&revision=N] ``` A response includes `committedHeight`, `stateRoot`, `proofKey` and `recordValue`. When the route resolves a current revision, it also returns the current-pointer proof key/value. Verify both proofs at the same height/root: the record proof establishes that a revision exists; the pointer proof establishes that it was current. #### Submit from Java Use the dependency-light contracts plus the generic client: ```groovy implementation "com.bloxbean.cardano:yano-x-client:${yanoVersion}" implementation "com.bloxbean.cardano:yano-x-role-workflow-contracts:${yanoVersion}" ``` ```java var statement = new ActorStatementV1( ActorStatementV1.Action.APPROVE, "role-approval-chain", "order-a-1001", "order-release", 1, "com.example.order.v1", payloadHash, 1000, "reviewer-a", 1, "reviewer-key-v1", "reviewers"); byte[] command = SignedActorCommandV1.sign(statement, actorSeed).encode(); var client = AppChainClient.builder("http://127.0.0.1:7070/api/v1") .chainId("role-approval-chain") .apiKey(System.getenv("YANO_APPCHAIN_API_KEY")) .build(); var submitted = client.submit("role-approvals.command.v1", command); System.out.println(submitted.messageId()); ``` Production code should implement `ActorStatementV1.signingPreimage()` in its KMS/HSM signer rather than loading a raw seed into application memory. #### Deterministic behavior and limits - Roles are normalized strings, not a fixed evidence enum. - Policies support proposer roles, bounded AND clauses, minimum counts, distinct actors or organizations, rejection behavior and lifetimes. - Actor and policy revisions are immutable; current pointers are governed. - V1 retains at most 16 key epochs per actor. - Pending proposals and governed mutations are bounded. - Invalid, stale, ineligible or wrongly signed finalized commands are deterministic no-ops; verify resulting state rather than message inclusion. - Proposal expiration is materialized when a later command touches the pending proposal after its block-height deadline; there is no unbounded global scan. #### Effects and application transitions The stock generic profile deliberately emits no effect because an approved hash is not executable payload bytes. An application can: 1. query the terminal proposal and act idempotently off chain; 2. submit a separately validated domain command bound to the proposal; or 3. install a reviewed composite profile that atomically consumes the approval. Do not attach an arbitrary executor and assume it knows which bytes were approved. A new automatic effect or domain transition needs a versioned contract that binds exact executable bytes to the approved hash. #### Operations and recovery - Monitor pending, rejected and expired proposal counts. - Rotate keys by governing the next actor revision with proof-of-possession. - Suspend or revoke compromised actors, then govern cancellation of affected pending proposals. - Historical approved decisions remain immutable after rotation or revocation. - Treat a changed profile digest, component order, route or state identity as a consensus upgrade requiring governed activation or a fresh chain. For the complete wire, governance and recovery model, see [Domain Actors and Role-Aware Approvals](https://github.com/bloxbean/yano-x/blob/main/docs/APP_CHAIN_DOMAIN_ROLES.md), the [portable contracts](https://github.com/bloxbean/yano-x/blob/main/capabilities/role-workflow-contracts/README.md), and the [generic implementation guide](https://github.com/bloxbean/yano-x/blob/main/capabilities/role-workflow/README.md). --- # Products --- ## Products Source: https://yanox.dev/products/ > The complete, opinionated applications Yano X ships on top of the app-chain platform — Evidence, Cardano History, and the experimental eUTxO and ZK ledgers. A **product** is a step above a recipe. Where a recipe selects capabilities, a product assembles a state machine or composite profile, a domain contract, a read API, a client, and often a CLI into one installable thing with an opinion about a use case. | Product | What it does | Maturity | |---|---|---| | [Evidence](https://yanox.dev/products/evidence/) | Publish an immutable document through a threshold-approved workflow, preserve it in object storage and IPFS, notify Kafka, and prove the whole chain. | `preview` | | [Cardano History](https://yanox.dev/products/cardano-history/) | Query and prove historical Cardano protocol parameters, epoch stake, DRep distribution, and proposal history. | `preview` | | [eUTxO and ZK](https://yanox.dev/products/eutxo-and-zk/) | A deterministic Cardano-shaped UTxO ledger, an optional Cardano bridge, and an optional ZK validity/rollup path. | `experimental` | #### What products have in common Each one: - ships as one or more **runtime plugin bundles** in the Yano X distribution or under `optional-plugins/`; - selects a **state machine or composite profile** whose identity is part of chain identity; - publishes a **no-SPI contracts library** so off-chain code can build and decode the same canonical bytes the chain uses; - contributes **bounded, read-only domain routes** under `/api/v1/plugins//`; and - defines **proof subjects** so its facts are provable in application language rather than trie keys. That last point matters most. A product's value is not that it stores data — it is that it makes a specific claim provable to someone who does not trust the node that served it. #### Choosing one Products are not mutually exclusive with recipes; a product *is* the recipe for its domain. Start from [choosing a recipe](https://yanox.dev/recipes/choosing-a-recipe/) and let the outcome table point you here. Reach for a product when your problem is recognizably the one it models. Reach for [the plugin framework](https://yanox.dev/plugins/) when it is not, and check first whether a composite of existing components gets you there. #### What products deliberately do not claim The reusable platform proves what identified participants finalized and what publication instruction they authorized. It does not decide what counts as a valid product event, an acceptable inspection, or a correct settlement — that stays with the domain. Evidence, in particular, proves that specific members approved specific bytes at a specific point in a verifiable order, and that a connector reported storing those bytes. It does not prove the document's content is true. **PRE-RELEASE:** Yano is pre-release, and the products above are `preview` or `experimental`. Their contracts, wire formats, and configuration may still change. Use a devnet or a Cardano test network with disposable data. --- ## Evidence Source: https://yanox.dev/products/evidence/ > Publish an immutable document through a threshold-approved workflow, preserve it in object storage and IPFS, notify Kafka, and prove state, effects, finality, and the Cardano anchor together. Evidence is the flagship Yano X product. It takes a document — a product inspection certificate, a compliance attestation, a case file — and turns publishing it into a provable, multi-party, externally-executed workflow. #### The flow ```mermaid flowchart LR DOC[Inspection certificate] --> STAGE[Stage exact bytes] STAGE --> CMD[Submit evidence command] CMD --> OBJ[Immutable object.put] CMD --> PIN[Reconciled ipfs.pin] OBJ --> READY[Both results incorporated] PIN --> READY READY --> EVENT[Acknowledged kafka.publish] EVENT --> PROVE[Verify state, effects, finality, and the L1 anchor] ``` The chain records what was authorized. Object storage, IPFS, and Kafka are executed by the [effect runtime](https://yanox.dev/concepts/effects/) after the finality gate, and each result comes back on-chain and is incorporated exactly once. #### What it actually proves The reference scenario verifies all of the following together: - the exact archived object bytes, checksum, version, retention identity, and destination fingerprint; - the exact IPFS CID, the retrieved bytes, and the configured pin state; - the Kafka destination fingerprint, event bytes, partition, and offset; - identical committed state across three app-chain members; - state **and** effect inclusion proofs bound to the same root; - threshold-signed finality evidence; and - the Cardano script-anchor transaction, its state-thread token, and the canonical inline datum. It also exercises executor failure after an external acknowledgement, fenced failover, reconciliation without duplicate mutation, and restart with retained state. The same scenario runs through Docker Compose and through ordinary host processes. **WHAT IT DOES NOT PROVE:** That the document's contents are true. Evidence proves that identified participants approved these exact bytes, in this exact order, and that connectors reported storing them. Domain validity stays with the domain. It also does not prove **durable** availability. A pin or an object write is proven at the moment it was reported; long-term retention is an operational property you must monitor separately. #### Two profiles | Profile | Approvers | Use when | |---|---|---| | `evidence-v1-gated` (default) | Validator **members** | The organizations running nodes are the ones who approve. | | `role-evidence` | Business **actors**, by role | The approvers are people — a QA manager, an auditor, a regulator — whose keys are not node keys. | `evidence-v1-gated` combines registry, approvals, document trail, and approval-coordinated evidence publication under one state root. A compatibility `evidence-v1` preset keeps direct evidence commands for existing deployments. `role-evidence` adds governed organizations, actor and key revisions, role policies, organization-distinct quorums, and portable actor signatures. This is the distinction between "a node transported this" and "a named person at a named organization authorized this" — see [Tutorial 5](https://yanox.dev/tutorials/05-domain-role-approvals/). #### The v1 domain contract The wire contract is deliberately narrow and strictly canonical CBOR on topic `evidence.command.v1`: - canonical `SUBMIT`, `NOTIFY`, and `REPUBLISH` commands; - immutable head, version-record, effect-reference, and terminal-result codecs; - canonical `evidence.available` Kafka event bytes; - `evidence/get` request and response contracts that return the exact proven state keys and values; - domain-separated state keys, effect scopes, and Kafka keys; and - deterministic status derivation with **fail-closed** connector receipt checks. The wire schema version is separate from the monotonically increasing business version. Immutable version records sit behind a small head record, and business status is derived from authenticated connector results rather than asserted. #### Modules | Artifact | Repo path | Role | |---|---|---| | `yano-x-evidence-contracts` | `products/evidence/contracts` | No-SPI canonical codecs, usable off-chain. | | `yano-x-evidence-registry` | `products/evidence/registry` | The deterministic state machine, its provider, and a bundle-owned read-only domain API. | | `yano-x-evidence-profile` | `products/evidence/profile` | The `evidence-v1-gated` and `role-evidence` composite profiles. | | `yano-x-evidence-client` | `products/evidence/client` | Typed client for commands and proof-carrying reads. | #### Run it The `evidence-ledger` recipe is the configuration-only path: ```bash ./yano.sh appchain init --non-interactive \ --recipe evidence-ledger --network devnet --members 3 --runtime jvm \ --output evidence-chain ``` For the complete connector demo — real object storage, a Kubo IPFS node, and Kafka — follow [Tutorial 4](https://yanox.dev/tutorials/04-evidence-publication/). It needs Docker Desktop. Deployments additionally need the release-matched [optional connector bundles](https://github.com/bloxbean/yano-x/blob/main/docs/appchain/OPTIONAL_CONNECTORS.md) installed in `plugins/` on every executor node, plus the external services themselves. #### Deeper reading - [Tutorial 4 — evidence publication](https://yanox.dev/tutorials/04-evidence-publication/) - [Tutorial 5 — domain-role approvals](https://yanox.dev/tutorials/05-domain-role-approvals/) - [Evidence chain demo](https://github.com/bloxbean/yano-x/blob/main/docs/EVIDENCE_CHAIN_DEMO.md) — the full scripted scenario and its acceptance checks. - [Domain actors and role-aware approvals](https://github.com/bloxbean/yano-x/blob/main/docs/APP_CHAIN_DOMAIN_ROLES.md) - [Profile governance runbook](https://github.com/bloxbean/yano-x/blob/main/docs/APP_CHAIN_PROFILE_GOVERNANCE.md) --- ## Cardano History Source: https://yanox.dev/products/cardano-history/ > Query and prove historical Cardano protocol parameters, epoch stake, DRep distribution, and proposal history — as compact, root-fixed proofs rather than trusted API responses. Cardano History is an optional app-chain plugin that assembles reusable epoch observers and state-machine components into one installable product. It answers questions such as "what was `key-deposit` in epoch 512?" with a **proof**, not with an API response you have to trust. It does not copy Cardano-specific logic into the Yano core API, and it does not define another proof format. #### Presets | Preset | Protocol parameters | Epoch stake | Proposals and DRep distribution | |---|---:|---:|---:| | `params-only-v1` (default) | yes | no | no | | `params-stake-v1` | yes | yes | no | | `params-governance-v1` | yes | no | yes | | `full-v1` | yes | yes | yes | Stake and governance traversal is **never** enabled implicitly — those datasets are large, and enabling them changes what the chain commits to. **THE PRESET IS GENESIS-TIME:** The selected preset and the commitment identity are genesis-time inputs. A preview chain starts fresh when either changes. Choose the preset you actually need before bootstrapping. Stake and governance presets require `authenticated-snapshots-v1`. They do not fall back to storing the full dataset in the primary map: each complete stake epoch and DRep distribution epoch gets its own immutable descriptor and secondary authenticated root. Protocol parameters stay small primary-MPF facts and need no redundant secondary snapshot. #### Compact, field-level proofs The read API contributes bounded, read-only routes below: ```text /api/v1/plugins/com.bloxbean.cardano.yano.appchain.cardano-history/ ``` Every request requires `chain=`. Routes cover status, epochs, protocol parameters, stake, DRep distribution, and proposal history. The design detail worth knowing: protocol-parameter responses expose a sorted `fields` catalog, and a single named field is addressable. | Route | Returns | |---|---| | `params/{epoch}/document` | The complete parameter document. | | `params/{epoch}/fields/{field-id}` | One named canonical leaf. | | `epochs/{epoch}/parameters/fields/{field-id}` | The typed canonical leaf plus root-fixed proof coordinates. | That makes a proof like `key-deposit == 2_000_000 lovelace` compact, and — more importantly — verifiable **without parsing a hard-fork-specific positional array**. A verifier written today keeps working across eras. #### Startup and data availability A fresh Cardano History generation never synthesizes a fact from mutable current-epoch state. On startup, every member deterministically reconciles the completed boundaries still retained by its local L1 account-state store, so retained facts can seed prior epochs with no external indexer. If no completed boundary is retained, the first fact arrives after the next stable L1 epoch transition. Until a fact finalizes: - generic app-chain status, capability discovery, and anchor endpoints remain available; - product routes return HTTP **404**; and - the CLI reports unavailable data with **exit code 3**. This is deliberate. "Not yet observed" is reported as absence of data, never as a fabricated value. At the `E-1 → E` boundary, protocol parameters and DRep distribution are labelled for `E`. #### Modules | Artifact | Repo path | Role | |---|---|---| | `yano-x-cardano-history-runtime` | `products/cardano-history/runtime` | The plugin: observers, components, and read routes. | | `yano-x-cardano-history-client` | `products/cardano-history/client` | Typed client with proof verification. | | `yano-x-cardano-history-cli` | `products/cardano-history/cli` | Command-line queries and proof export. | | `yano-x-cardano-history-onchain` | `products/cardano-history/onchain` | On-chain artifacts for validator-side verification. | #### Who this is for - **Governance tooling** that must show what the rules were at the time an action was taken, not what they are now. - **Reward and stake analysis** that has to be reproducible and auditable years later. - **Smart contracts and validators** that need a compact, era-stable proof of a historical parameter. - **Anyone** currently trusting a chain-indexer API for historical values and wanting evidence instead. #### Deeper reading - [Cardano History product guide](https://github.com/bloxbean/yano-x/blob/main/docs/appchain/CARDANO_HISTORY.md) — full route reference, presets, and operational guidance. - [Authenticated snapshots](https://github.com/bloxbean/yano-x/blob/main/docs/appchain/AUTHENTICATED_SNAPSHOTS.md) — how large immutable period datasets are archived and proved. - [State and proofs](https://yanox.dev/concepts/state-and-proofs/) — how to read a proof result correctly. --- ## eUTxO and ZK Source: https://yanox.dev/products/eutxo-and-zk/ > An experimental deterministic Cardano-shaped UTxO ledger, an optional federated Cardano bridge, and an optional ZeroJ validity and rollup path. **EXPERIMENTAL — NO REAL FUNDS:** This family is `EXPERIMENTAL` and pre-production. It is **not** a Cardano bridge product, a custody product, or a rollup you should put value into. The ZK modules ship development Groth16 setup keys that are **test-only**; production release is blocked until the governing ADR's ceremony, audit, data-availability, and operational gates are satisfied. Use a devnet or a Cardano test network with disposable keys. #### What it is A deterministic, Cardano-shaped eUTxO state machine that runs as an app-chain capability. Its first milestone is a **no-real-funds experimental ledger** — a place to execute Conway-shaped transactions against an app chain's threshold finality and proofs rather than against L1. Three layers, each optional on top of the previous: ```mermaid flowchart LR L["eutxo-ledger
deterministic UTxO state machine"] --> B["eutxo-cardano-bridge
federated deposit / withdrawal"] L --> Z["eutxo-zeroj-validity
Groth16 validity proofs"] B --> R["eutxo-zeroj-preview
proof-settled rollup lifecycle"] Z --> R ``` #### The virtual ledger Select the `eutxo-ledger` recipe for a JVM project. It combines the reusable `state:eutxo-ledger` engine with `profile:eutxo-plutus-v3` and the separate `funding:eutxo-genesis` capability: ```yaml yano: app-chain: chains: - chain-id: payments-eutxo state-machine: eutxo-ledger machines: eutxo: profile: yano-eutxo-v2-plutus-v3 expected-profile-digest: 8cd4adb72def2c31dc8551a02f67429ea468bb2024dbe85a1dc7300590c9d1bf genesis: address: addr_test1... lovelace: 100000000 ``` The profile commitment is SHA-256 over its canonical, versioned consensus fields. **All members must use the same profile, digest, and genesis allocation** — this is chain identity, not a tunable. The ZeroJ development recipe uses `profile:eutxo-key-payments` instead, so exactly one immutable ledger profile owns the consensus settings at a time. #### The capabilities | Recipe | Availability | What it adds | |---|---|---| | `eutxo-ledger` | `FIRST_PARTY_OPTIONAL` | The deterministic UTxO ledger. | | `eutxo-cardano-bridge` | `EXPERIMENTAL` | Federated deposit and withdrawal against Cardano. | | `eutxo-zeroj-validity` | `EXPERIMENTAL` | ZeroJ Groth16 validity proofs over batches. | | `eutxo-zeroj-preview` | `EXPERIMENTAL` | The full lifecycle: L1 deposit → L2 transaction → proof → root settlement → L1 withdrawal. | The base eUTxO ledger has **no** ZeroJ or JuLC dependency and behaves exactly as it does without them unless `machines.eutxo.validity.enabled=true`. #### The plugin-conflict rule The standard eUTxO runtime and the eUTxO ZK runtime both intentionally provide the `app-state-machine/eutxo-ledger` contribution. Exactly one may be installed. That is why the ZK runtime ships under `optional-plugins/` rather than `plugins/`. To switch: ```bash ### On EVERY member: rm plugins/.jar cp optional-plugins/.jar plugins/ tools/yano-plugins/bin/yano-plugins validate plugins/*.jar ``` Copying both into `plugins/` is a hard catalog error, and the node will refuse to start rather than pick one. #### Indexing and lifecycle The optional, provider-neutral validity lifecycle is projected by the common eUTxO indexer. The indexer core has no ZeroJ dependency, and its projections are **rebuildable read indexes** — they live in `appchain-indexers/` and must never be treated as authoritative state or placed below L1 `chainstate`. #### Where to start | Goal | Guide | |---|---| | The disposable three-scenario quick start | [eUTxO demos](https://github.com/bloxbean/yano-x/blob/main/ledgers/eutxo/DEMO.md) | | Status and module verification | [ZK getting started](https://github.com/bloxbean/yano-x/blob/main/ledgers/eutxo-zk/GETTING_STARTED.md) | | Deposit → L2 tx → proof → settlement → withdrawal | [Devnet walkthrough](https://github.com/bloxbean/yano-x/blob/main/ledgers/eutxo-zk/DEVNET_WALKTHROUGH.md) | | Indexer API, SQLite, recovery, metrics | [Indexer operations](https://github.com/bloxbean/yano-x/blob/main/ledgers/eutxo/INDEXER_OPERATIONS.md) | | The eUTxO product family overview | [`ledgers/eutxo/README.md`](https://github.com/bloxbean/yano-x/blob/main/ledgers/eutxo/README.md) | | ZK milestone notes (Z0–Z4) | [`ledgers/eutxo-zk/`](https://github.com/bloxbean/yano-x/tree/main/ledgers/eutxo-zk) | The demo guide is the shortest path — it covers the virtual ledger, the Cardano bridge, and the optional ZeroJ proof experience without JShell or manual YAML editing. --- # Reference --- ## CLI reference Source: https://yanox.dev/reference/cli/ > The ./yano.sh appchain command surface — project lifecycle, cluster operation, plugin tooling, and authenticated-state inspection. `./yano.sh` is the public CLI, shipped inside the Yano X JVM distribution. There is no separate `yano-x` executable. Run it from the directory that contains it. ```bash cd ~/yano-x/yano-x-jvm-* ./yano.sh appchain help ``` Read command help from the **version-matched** executable when an option is uncertain. The binary you built is the authority for the version you will run. #### Discovery ```bash ./yano.sh appchain recipes # release-pinned recipes ./yano.sh appchain capabilities # support tier, scope, selection ./yano.sh appchain capabilities --format json # canonical structured catalog ``` #### Project lifecycle A project is a directory holding `appchain.yaml`, generated runtime files, and `appchain.lock`. **Edit only `appchain.yaml`** — rendering stops if a generated file has an unaccounted manual edit. ```bash ### Reproducible non-interactive generation. ./yano.sh appchain init --non-interactive \ --recipe owned-registry --network preprod --members 3 \ --node-host node-a.example --node-host node-b.example \ --node-host node-c.example \ --deployment host --output product-registry ### Regenerate derived output after editing appchain.yaml. ./yano.sh appchain render product-registry ### Verify the project, and inspect a final JVM release. ./yano.sh appchain config validate --mode project product-registry ./yano.sh appchain doctor product-registry --distribution yano-x-jvm.zip ### Classify a change, preview a migration, and detect running drift. ./yano.sh appchain diff previous.lock product-registry/appchain.lock ./yano.sh appchain migrate product-registry --dry-run ./yano.sh appchain drift product-registry --peer ### Export reviewed, deterministic deployment derivatives. ./yano.sh appchain gitops product-registry --target helm --output deploy/helm ./yano.sh appchain gitops product-registry --target kustomize --output deploy/kustomize ``` | Command | Use it to | |---|---| | `init` | Create a project from a recipe. | | `render` | Regenerate derived files after an `appchain.yaml` edit. | | `config validate --mode project` | Check the blueprint and its resolved configuration. | | `doctor` | Check artifact readiness against a real distribution before startup. | | `diff` | Classify what changed between two locks. | | `migrate --dry-run` | Preview a blueprint migration. | | `drift` | Compare the project against running nodes. | | `gitops` | Export Helm or Kustomize derivatives. | #### Cluster (single host) `appchain cluster` runs N members as processes on one machine — a disposable demo, a repeatable integration test, or a controlled single-host deployment. ```bash export YANO_CLUSTER_DIR=/tmp/yano-demo ./yano.sh appchain cluster start 3 # 3-member self-contained devnet ./yano.sh appchain cluster status # tips, roots, and per-chain agreement ./yano.sh appchain cluster submit orders-chain orders '{"id":1}' ./yano.sh appchain cluster submit orders-chain orders '{"id":2}' --node 1 ./yano.sh appchain cluster effect demo # emit and execute one effect ./yano.sh appchain cluster loadtest orders-chain -n 500 -c 10 -s 256 ./yano.sh appchain cluster loadtest orders-chain -n 1000 -c 20 --spread ./yano.sh appchain cluster node join 3 # govern, start, and catch up node 3 ./yano.sh appchain cluster node resume 3 # restart an already governed joiner ./yano.sh appchain cluster member add <64-hex-ed25519-public-key> ./yano.sh appchain cluster anchor-bootstrap # one-time script-anchor setup ./yano.sh appchain cluster stop # stop, keep data ``` | Lifecycle command | Effect | |---|---| | `stop` | Stops processes, **preserves** state. | | `clean` | `stop` plus a wipe. | | `reset --yes` | Destructive. | **CAUTION:** Never point `clean` or `reset` at a deployment you care about. Retained clusters are identity and data, not disposable test output. ##### Cluster environment | Variable | Meaning | Default | |---|---|---| | `YANO_CLUSTER_DIR` | Where cluster state lives. | A launcher default | | `YANO_HOME` | The tree holding `config/`; nodes launch with this as cwd. | The distribution root | | `YANO_JAR` / `YANO_NATIVE` | Explicit binary path. | Auto-detected under `YANO_HOME` | | `YANO_CLUSTER_API_KEY` | Key for privileged operations. | `yano-local-cluster-full-key` | | `YANO_CLUSTER_NODE_CONFIG_DIR` | Private per-node configuration overlays. | Unset | | `YANO_CLUSTER_MEMBER_KEY_DIR` | Operator-supplied member keys. | Unset | The launcher keeps reads, submissions, status, and live streams public on its loopback-only HTTP API, and requires the full key for admin, effect, and plugin operations. **THE DEFAULT KEY IS A DEMO CREDENTIAL:** `yano-local-cluster-full-key` is publicly known. Override it on a shared machine or a public-network test: ```bash export YANO_CLUSTER_API_KEY="$(openssl rand -hex 32)" ``` Outside this launcher, configure real nodes through a secret source with `YANO_APP_CHAIN_API_KEYS`, and set `YANO_APP_CHAIN_API_AUTH_ENABLED=true` when reads and submissions must require keys too. There is no production default. #### Plugin tooling ```bash ./yano.sh appchain plugin scaffold --mode state-machine --id shipment \ --package com.example.shipment --output shipment-plugin ./yano.sh appchain plugin sign \ --catalog --runtime-manifest \ --seed-file /secure/publisher.seed --key-id example-release-2026 \ --output ./yano.sh appchain plugin validate --trust-key = \ --output catalog-snapshot.json ./yano.sh appchain plugin inspect --trust-key = ./yano.sh appchain metadata verify --trust-key = ``` Scaffold modes: `state-machine`, `composite-role`, `effect-executor`, `sink`. None of these commands load provider classes, run plugin code, fetch a registry, or install a JAR. See [Scaffold, sign, install](https://yanox.dev/plugins/scaffold-sign-install/). #### Authenticated state and proofs ```bash ./yano.sh appchain state identity --url http://node:8080/api/v1 --chain registry ./yano.sh appchain state oldest --url http://node:8080/api/v1 --chain registry ./yano.sh appchain state entry --url http://node:8080/api/v1 --chain registry --key 0123 ./yano.sh appchain state proof --url http://node:8080/api/v1 --chain registry --key 0123 ``` `identity` shows the genesis-selected profile; `oldest` shows the retention boundary. A pruned proof is unavailable, not evidence of absence — see [State and proofs](https://yanox.dev/concepts/state-and-proofs/). #### Safety rules - Never request, print, copy, infer, or commit secret values. Refer only to documented environment-variable or secret-provider names. - Never invent configuration keys, values, defaults, recipes, or compatibility claims. If a capability is unavailable in your release, it is unsupported. - Keep blueprint, resolved-config, release, plugin-catalog, and consensus identities distinct. - Do not mutate a running node or call privileged runtime APIs unless that is what you intend. These are the same rules the in-repo `configure-yano-appchain` agent skill follows; see [Using Yano X with AI agents](https://yanox.dev/ai/). #### Deeper reading - [Cluster launcher README](https://github.com/bloxbean/yano-x/blob/main/scripts/appchain-cluster/README.md) — per-node overlays, chain definitions, membership, and the effects demo. - [Developer tools README](https://github.com/bloxbean/yano-x/blob/main/tooling/devtools/README.md) — the offline engine behind `yano.sh appchain`. --- ## REST API Source: https://yanox.dev/reference/rest-api/ > The public app-chain HTTP surface — submission, reads, proofs, streaming, and admin — plus the submission limits, backpressure, and replay-protection rules that govern it. Base path: `/app-chain`, by default `/api/v1/app-chain`. The prefix is fixed into each JVM, native, or container artifact at build time with `-PyanoApiPrefix=`. It is not editable launch configuration, and changing it requires a rebuild. #### Chain scoping Every chain endpoint below is also available chain-scoped: ```text /api/v1/app-chain/chains/{chainId}/... ``` The chain-less form keeps working while exactly one chain is configured. With several chains it returns `400` (ambiguous), and `503` when no chain is enabled. **Prefer `chains/{chainId}` in new integrations.** Swagger UI at `/q/swagger-ui/` documents the chain-scoped surface only — the chain-less aliases are deliberately hidden from the OpenAPI document, because on a multi-chain node they can only answer `400`. When API-key authentication is enabled, every request needs `X-API-Key`. #### Endpoints ##### Chains and status | Method and path | Purpose | |---|---| | `GET /chains` | Hosted chains: `[{chainId, tipHeight, stateRoot}]`. | | `GET /status` | Role, tip height, state root, pool size, peer connectivity, counters, anchor and sink progress. | | `GET /tip` | `{chainId, height, stateRoot}` of the last finalized block. | ##### Messages | Method and path | Purpose | |---|---| | `POST /messages` | Submit. Body `{"topic":"...","body":""}` or `{"topic":"...","bodyHex":""}`. Returns `202` with the content-derived `messageId`. | | `GET /messages?limit=100&topic=...` | Recently accepted messages (local and peer), with sender, sequence, body hex, and source. | | `GET /messages/{messageIdHex}` | One finalized message: position (`height`, `index`) plus full content. | | `GET /messages/by-topic/{topic}?fromHeight=&limit=` | Finalized message refs on a topic, ascending. | | `GET /messages/by-sender/{senderHex}?fromHeight=&limit=` | Finalized message refs from a member key, ascending. | ##### Blocks, proofs, and evidence | Method and path | Purpose | |---|---| | `GET /blocks/{height}` | Hashes, roots, proposer, certificate signature count, full message list. | | `GET /blocks?from=&limit=` | Paged block summaries, ascending. Defaults to a window ending at the tip. | | `GET /state/proof/{keyHex}` | MPF inclusion proof for a state key against the committed root. For `ordered-log` the key **is** the message id, and the response includes the value and `finalizedAtHeight`. | | `POST /proof-subjects/{subjectId}/proof` | Typed proof in application language. See [State and proofs](https://yanox.dev/concepts/state-and-proofs/). | | `GET /evidence/{messageIdHex}` | A portable, offline-verifiable evidence bundle for a finalized message. | ##### Streaming | Method and path | Purpose | |---|---| | `GET /stream?fromHeight=&topic=` | SSE stream of finalized messages: replay from `fromHeight`, then live. | ##### Admin These are privileged. Protect them with an API key. | Method and path | Purpose | |---|---| | `POST /snapshot` | Atomic ledger snapshot for fast member onboarding. Body `{"path":""}`. | | `POST /admin/pause`, `POST /admin/resume` | Pause or resume local submissions. | | `POST /admin/drain-pool` | Drop all pending, unfinalized messages. | | `POST /admin/force-anchor` | Anchor the current tip now. | | `GET /admin/members` | Effective member set and threshold. | | `POST /admin/members/add`, `.../remove` | Stage a member key in or out. Body `{"publicKey":"..."}`. | | `POST /admin/members/reset` | Drop the persisted member override and return to the configured list. | | `POST /admin/threshold` | Set the finality threshold. Body `{"threshold": N}`. | Plugins contribute their own bounded, read-only routes below `/api/v1/plugins//`. #### Submission semantics **The node signs with its own member key.** The REST caller is trusted local input — the same model as a wallet talking to its own node. This is why the API must not be exposed to untrusted callers without authentication. **The body is opaque.** Use `body` for UTF-8 text or `bodyHex` for arbitrary bytes. The framework never parses it; only the state machine does. | Limit | Default | |---|---| | `max-message-bytes` | 64 KB | | `default-ttl-seconds` | 600 — an unfinalized message expires out of the pool | | `pool.max-messages` | 10,000 | Topics starting with `~` are reserved for consensus and system traffic. ##### Backpressure When the pending pool is full, `POST /messages` returns **429** and the message is neither stored nor relayed. Back off and retry. Inbound gossip dropped by a full pool is counted in `GET /status` under `drops.pool_full`. That is not an error — the sender's own node already holds the message. ##### Replay protection Every envelope carries a per-sender sequence number. A message whose seq is at or below the sender's last **finalized** seq is a replay and is rejected at admission on every ledger node, counted as `drops.stale_seq`. Gaps are allowed and meaningless — seqs are wall-clock seeded, so a restart never reuses one. **The seq does not define ordering**; the sequencer does. ```yaml yano.app-chain.message.enforce-sender-seq: true ``` With that flag the rule becomes consensus-visible: followers reject any block whose per-sender seqs are not strictly increasing above the finalized floor. It defaults to off for compatibility, and — like the state-machine id — **all members must agree on it**. #### Clients Prefer a typed client over raw HTTP where one exists: | Artifact | What it adds | |---|---| | `yano-x-client` | REST, SSE, and client-side proof verification. | | `yano-x-composite-client` | Governed-profile finality, one-root MPF, epoch-chain, and authorization-policy verification. | | `yano-x-spring-boot-starter` | Spring Boot auto-configuration for the client SDK. | | `yano-appchain-core-testkit` | JUnit 5 `@AppChainCluster` embedded clusters. | #### Deeper reading The exhaustive API, configuration, security, and operations reference is [section 4 of the app-chain user guide](https://github.com/bloxbean/yano-x/blob/main/docs/APP_CHAIN_USER_GUIDE.md). --- ## Capability catalog Source: https://yanox.dev/reference/capabilities/ > Every selectable app-chain capability in this release — state machines, profiles, sequencing, membership, anchoring, effects, executors, sinks, and observability — generated from the repository catalog. A **capability** is the smallest selectable unit of app-chain behavior. A [recipe](https://yanox.dev/recipes/) is a reviewed bundle of them. This page is generated at documentation build time from `tooling/devtools/src/main/resources/appchain-dx/v1alpha1/appchain-capability-catalog.json`. For your exact build, ask the binary: ```bash ./yano.sh appchain capabilities ./yano.sh appchain capabilities --format json ``` #### Reading the tables | Field | Meaning | |---|---| | Availability | `BUNDLED` ships in the distribution; `FIRST_PARTY_OPTIONAL` needs its release-matched plugin bundle installed; `EXPERIMENTAL` may change; `REFERENCE` is a documented workflow. | | Maturity | `stable`, `preview`, or `experimental`. | | Runtimes | `jvm`, and `native` only for the handful of core capabilities. Yano X extensions are JVM-only. | | Requires artifacts | The runtime artifacts that must be present. `yano-runtime` means the host itself. | Capabilities also declare `requires`, `implies`, and `conflicts` relationships. The CLI and [Studio](https://yanox.dev/studio/) resolve those for you; a conflicting selection fails closed rather than picking one. #### Capabilities by category ##### `state` | Capability | Availability | Maturity | Runtimes | Requires artifacts | Description | |---|---|---|---|---|---| | [`state:ordered-log`](https://github.com/bloxbean/yano-x/blob/main/docs/core-host.md) | `BUNDLED` | `stable` | `jvm`, `native` | `yano-runtime` | Append-only ordered application messages. | | [`state:kv-registry`](https://yanox.dev/state-machines/kv-registry/) | `BUNDLED` | `stable` | `jvm` | `yano-runtime`, `yano-x-stdlib` | First-writer-owned mutable key/value records with committed proofs. | | [`state:authenticated-map`](https://yanox.dev/state-machines/authenticated-map/) | `BUNDLED` | `preview` | `jvm` | `yano-runtime`, `yano-x-stdlib`, `yano-x-role-workflow` | Multi-collection authenticated records with basic, direct-role, and approval authorization plus optional canonical-CBOR and schema validation. | | [`state:approval-workflow`](https://yanox.dev/state-machines/approvals/) | `BUNDLED` | `stable` | `jvm` | `yano-runtime`, `yano-x-stdlib` | Validator-member proposals, votes, and terminal threshold decisions. | | [`state:balances`](https://yanox.dev/state-machines/balances/) | `BUNDLED` | `stable` | `jvm` | `yano-runtime`, `yano-x-stdlib` | Member-authorized mint and transfer accounts for bounded application balances. | | [`state:doc-trail`](https://yanox.dev/state-machines/doc-trail/) | `BUNDLED` | `stable` | `jvm` | `yano-runtime`, `yano-x-stdlib` | Append-only per-entity document and event hash trails. | | [`state:evidence-registry`](https://yanox.dev/tutorials/04-evidence-publication/) | `BUNDLED` | `preview` | `jvm` | `yano-runtime`, `yano-x-evidence-registry` | Inspection and compliance evidence records with exact committed query proofs. | | [`state:role-approvals`](https://github.com/bloxbean/yano-x/blob/main/docs/appchain/tutorials/05-domain-role-approvals.md) | `BUNDLED` | `preview` | `jvm` | `yano-runtime`, `yano-x-composite`, `yano-x-role-workflow` | Governed organizations, actors, policies, and signed approvals for arbitrary payload hashes. | | [`state:role-evidence`](https://yanox.dev/tutorials/04-evidence-publication/) | `BUNDLED` | `preview` | `jvm` | `yano-runtime`, `yano-x-stdlib`, `yano-x-evidence-registry`, `yano-x-composite`, `yano-x-role-workflow`, `yano-x-evidence-profile` | Evidence registration combined with governed domain actors and role-aware release approval. | | [`state:zk-gate`](https://github.com/bloxbean/yano-x/blob/main/state-machines/zk/README.md) | `EXPERIMENTAL` | `experimental` | `jvm` | `yano-runtime`, `yano-x-zk` | Verifies configured Groth16 or Plonk proofs during deterministic state transition. | | [`state:zk-membership`](https://github.com/bloxbean/yano-x/blob/main/state-machines/zk/README.md) | `EXPERIMENTAL` | `experimental` | `jvm` | `yano-runtime`, `yano-x-zk` | Membership authorization using a configured zero-knowledge circuit and nullifier deduplication. | | [`state:credential-registry`](https://github.com/bloxbean/yano-x/blob/main/state-machines/zk/README.md) | `EXPERIMENTAL` | `experimental` | `jvm` | `yano-runtime`, `yano-x-zk` | Registry for selectively disclosed BBS credential statements from configured issuers. | | [`state:eutxo-ledger`](https://github.com/bloxbean/yano-x/blob/main/ledgers/eutxo/README.md) | `FIRST_PARTY_OPTIONAL` | `experimental` | `jvm` | `yano-runtime`, `yano-x-eutxo-ledger` | Deterministic Cardano-shaped EUTxO state, root-fixed receipts, address indexes, and MPF-proven outputs under one explicitly selected ledger profile. | ##### `custom-plugin` | Capability | Availability | Maturity | Runtimes | Requires artifacts | Description | |---|---|---|---|---|---| | [`state:custom-plugin`](https://yanox.dev/plugins/) | `REFERENCE` | `experimental` | `jvm` | `yano-runtime` | Project boundary for a separately reviewed custom state-machine bundle. | ##### `profile` | Capability | Availability | Maturity | Runtimes | Requires artifacts | Description | |---|---|---|---|---|---| | [`profile:eutxo-plutus-v3`](https://github.com/bloxbean/yano-x/blob/main/ledgers/eutxo/README.md) | `FIRST_PARTY_OPTIONAL` | `experimental` | `jvm` | `yano-runtime`, `yano-x-eutxo-ledger` | Pins the immutable yano-eutxo-v2-plutus-v3 ledger profile and every consensus-relevant bound. | | [`profile:eutxo-bridge-settlement`](https://github.com/bloxbean/yano-x/blob/main/ledgers/eutxo/README.md) | `FIRST_PARTY_OPTIONAL` | `experimental` | `jvm` | `yano-runtime`, `yano-x-eutxo-ledger` | Pins the immutable yano-eutxo-v3-bridge-settlement ledger profile (ADR-UTXO-009) and every consensus-relevant bound. | | [`profile:eutxo-key-payments`](https://github.com/bloxbean/yano-x/blob/main/ledgers/eutxo/README.md) | `FIRST_PARTY_OPTIONAL` | `experimental` | `jvm` | `yano-runtime`, `yano-x-eutxo-ledger` | Pins the immutable yano-eutxo-v1 ledger profile used by the current bounded ZeroJ validity circuit. | ##### `funding` | Capability | Availability | Maturity | Runtimes | Requires artifacts | Description | |---|---|---|---|---|---| | [`funding:eutxo-genesis`](https://github.com/bloxbean/yano-x/blob/main/ledgers/eutxo/README.md) | `FIRST_PARTY_OPTIONAL` | `experimental` | `jvm` | `yano-runtime`, `yano-x-eutxo-ledger` | Bootstraps one explicit ADA-only virtual allocation for no-real-funds testing. | ##### `bridge` | Capability | Availability | Maturity | Runtimes | Requires artifacts | Description | |---|---|---|---|---|---| | [`bridge:cardano-federated`](https://github.com/bloxbean/yano-x/blob/main/ledgers/eutxo/README.md) | `EXPERIMENTAL` | `experimental` | `jvm` | `yano-runtime`, `yano-x-eutxo-ledger`, `yano-x-eutxo-bridge-cardano` | Observes exact accepted vault deposits and settles irrevocable bounded claims through either exact external signing or permissionless current-root MPF proofs with an on-chain replay nullifier. | ##### `settlement` | Capability | Availability | Maturity | Runtimes | Requires artifacts | Description | |---|---|---|---|---|---| | [`settlement:zeroj-validity`](https://github.com/bloxbean/yano-x/blob/main/ledgers/eutxo-zk/README.md) | `EXPERIMENTAL` | `experimental` | `jvm` | `yano-runtime`, `yano-x-eutxo-zk-zeroj`, `yano-x-eutxo-zk-runtime`, `yano-x-eutxo-zk-lifecycle` | Adds a Yano L2 envelope with a Cardano-compatible body, registered Jubjub session-key authorization, optional Poseidon validity commitment, durable proving interfaces, proof-bound Cardano root advancement, bounded aggregate withdrawal and canonical L1 batch publication. | ##### `product-label` | Capability | Availability | Maturity | Runtimes | Requires artifacts | Description | |---|---|---|---|---|---| | [`rollup:zeroj-cardano`](https://github.com/bloxbean/yano-x/blob/main/ledgers/eutxo-zk/Z6_PRODUCTION_HARDENING.md) | `EXPERIMENTAL` | `experimental` | `jvm` | `yano-runtime`, `yano-x-eutxo-ledger`, `yano-x-eutxo-zk-zeroj` | Graduation label for a separately approved ZeroJ validity product; it does not add runtime behavior. | ##### `indexer` | Capability | Availability | Maturity | Runtimes | Requires artifacts | Description | |---|---|---|---|---|---| | [`indexer:eutxo-lifecycle`](https://github.com/bloxbean/yano-x/blob/main/ledgers/eutxo/INDEXER_OPERATIONS.md) | `FIRST_PARTY_OPTIONAL` | `preview` | `jvm` | `yano-runtime`, `yano-x-eutxo-indexer-core`, `yano-x-eutxo-indexer-jdbc` | Indexes finalized EUTxO transactions, accounts, bridge lifecycle records, and optional validity batches for bounded APIs and the unified console. | ##### `sequencer` | Capability | Availability | Maturity | Runtimes | Requires artifacts | Description | |---|---|---|---|---|---| | [`sequencer:fixed`](https://github.com/bloxbean/yano-x/blob/main/docs/appchain/README.md) | `BUNDLED` | `stable` | `jvm`, `native` | `yano-runtime` | One declared member proposes application blocks. | | [`sequencer:rotating`](https://github.com/bloxbean/yano-x/blob/main/docs/appchain/README.md) | `BUNDLED` | `experimental` | `jvm`, `native` | `yano-runtime` | Rotates proposership across members using bounded L1 slot windows. | ##### `membership` | Capability | Availability | Maturity | Runtimes | Requires artifacts | Description | |---|---|---|---|---|---| | [`membership:static`](https://github.com/bloxbean/yano-x/blob/main/docs/appchain/README.md) | `BUNDLED` | `stable` | `jvm`, `native` | `yano-runtime` | Pins the initial member set as the active set. | | [`membership:governed`](https://github.com/bloxbean/yano-x/blob/main/docs/appchain/README.md) | `BUNDLED` | `preview` | `jvm`, `native` | `yano-runtime` | Allows authenticated member epochs to evolve through replicated governance. | ##### `l1` | Capability | Availability | Maturity | Runtimes | Requires artifacts | Description | |---|---|---|---|---|---| | [`l1:slot-feed`](https://github.com/bloxbean/yano-x/blob/main/docs/appchain/README.md) | `BUNDLED` | `stable` | `jvm`, `native` | `yano-runtime` | Supplies stable local Cardano slot observations to slot-aware app-chain behavior. | ##### `anchor` | Capability | Availability | Maturity | Runtimes | Requires artifacts | Description | |---|---|---|---|---|---| | [`anchor:metadata`](https://github.com/bloxbean/yano-x/blob/main/docs/appchain/tutorials/07-anchors-and-verification.md) | `BUNDLED` | `stable` | `jvm`, `native` | `yano-runtime` | Commits finalized app-chain roots in Cardano transaction metadata. | | [`anchor:script`](https://github.com/bloxbean/yano-x/blob/main/docs/appchain/tutorials/07-anchors-and-verification.md) | `BUNDLED` | `preview` | `jvm`, `native` | `yano-runtime` | Advances an app-chain anchor thread through a reviewed Cardano script. | ##### `l1-observer` | Capability | Availability | Maturity | Runtimes | Requires artifacts | Description | |---|---|---|---|---|---| | [`observer:address-deposit`](https://github.com/bloxbean/yano-x/blob/main/docs/appchain/README.md) | `BUNDLED` | `preview` | `jvm`, `native` | `yano-runtime` | Creates stable observations for lovelace paid to one configured Cardano address. | | [`observer:metadata-label`](https://github.com/bloxbean/yano-x/blob/main/docs/appchain/README.md) | `BUNDLED` | `preview` | `jvm`, `native` | `yano-runtime` | Creates stable observations for one configured Cardano transaction metadata label. | ##### `effects-runtime` | Capability | Availability | Maturity | Runtimes | Requires artifacts | Description | |---|---|---|---|---|---| | [`effects:runtime`](https://github.com/bloxbean/yano-x/blob/main/docs/appchain/tutorials/06-webhook-effects.md) | `BUNDLED` | `preview` | `jvm`, `native` | `yano-runtime` | Enables deterministic effect intents, gates, result incorporation, and proofs. | ##### `effect-emission` | Capability | Availability | Maturity | Runtimes | Requires artifacts | Description | |---|---|---|---|---|---| | [`effects:on-approved`](https://yanox.dev/state-machines/approvals/) | `BUNDLED` | `preview` | `jvm` | `yano-runtime`, `yano-x-stdlib` | Emits one typed chain-result effect when a stock approval reaches its threshold. | ##### `effect-executor` | Capability | Availability | Maturity | Runtimes | Requires artifacts | Description | |---|---|---|---|---|---| | [`executor:webhook`](https://github.com/bloxbean/yano-x/blob/main/docs/appchain/tutorials/06-webhook-effects.md) | `BUNDLED` | `preview` | `jvm`, `native` | `yano-runtime` | Executes webhook.post effects against one node-local configured URL. | | [`executor:kafka`](https://github.com/bloxbean/yano-x/blob/main/connectors/kafka/README.md) | `FIRST_PARTY_OPTIONAL` | `preview` | `jvm` | `yano-runtime`, `yano-x-kafka` | Executes kafka.publish intents through allowlisted node-local Kafka targets. | | [`executor:objectstore-s3`](https://github.com/bloxbean/yano-x/blob/main/connectors/objectstore-s3/README.md) | `FIRST_PARTY_OPTIONAL` | `preview` | `jvm` | `yano-runtime`, `yano-x-objectstore-s3` | Executes immutable object.put promotion against allowlisted S3-compatible targets. | | [`executor:ipfs`](https://github.com/bloxbean/yano-x/blob/main/connectors/ipfs/README.md) | `FIRST_PARTY_OPTIONAL` | `preview` | `jvm` | `yano-runtime`, `yano-x-ipfs` | Executes reconciled ipfs.pin intents against allowlisted Kubo targets. | | [`executor:cardano-payment`](https://github.com/bloxbean/yano-x/blob/main/connectors/effects-cardano/README.md) | `FIRST_PARTY_OPTIONAL` | `preview` | `jvm` | `yano-runtime`, `yano-x-effects-cardano` | Executes bounded cardano.payment intents from an operator-controlled payer wallet. | ##### `finalized-sink` | Capability | Availability | Maturity | Runtimes | Requires artifacts | Description | |---|---|---|---|---|---| | [`sink:webhook`](https://github.com/bloxbean/yano-x/blob/main/docs/appchain/README.md) | `BUNDLED` | `stable` | `jvm`, `native` | `yano-runtime` | Posts finalized application blocks to one configured node-local webhook endpoint. | | [`sink:kafka`](https://github.com/bloxbean/yano-x/blob/main/connectors/kafka/README.md) | `FIRST_PARTY_OPTIONAL` | `preview` | `jvm` | `yano-runtime`, `yano-x-kafka` | Publishes finalized application blocks to a configured Kafka topic. | ##### `distribution` | Capability | Availability | Maturity | Runtimes | Requires artifacts | Description | |---|---|---|---|---|---| | [`runtime:composite`](https://yanox.dev/plugins/) | `BUNDLED` | `preview` | `jvm` | `yano-x-composite` | Deterministic component, workflow, profile, query, quota, and profile-governance framework. | | [`ui:console`](https://github.com/bloxbean/yano-x/blob/main/docs/console-ui.md) | `BUNDLED` | `preview` | `jvm`, `native` | `yano-runtime` | Embedded operational app-chain status console derived from the selected Yano distribution. | | [`observability:prometheus`](https://github.com/bloxbean/yano-x/blob/main/docs/console-ui.md) | `FIRST_PARTY_OPTIONAL` | `preview` | `jvm`, `native` | `yano-runtime` | Optional one-command durable metrics history for the unified console. | #### Runtime artifacts Each artifact is an independently published, dependency-complete plugin bundle with its own version. The `bundleId` is what appears in a plugin manifest and in the distribution's checksummed bundle list. | Artifact | Availability | Plugin bundle id | Runtimes | Native posture | |---|---|---|---|---| | `yano-runtime` | `BUNDLED` | `builtin:yano-runtime` | `jvm`, `native` | `bundled` | | `yano-x-stdlib` | `BUNDLED` | `com.bloxbean.cardano.yano.appchain.stdlib` | `jvm` | `unsupported` | | `yano-x-evidence-registry` | `BUNDLED` | `com.bloxbean.cardano.yano.appchain.evidence-registry` | `jvm` | `unsupported` | | `yano-x-composite` | `BUNDLED` | `library:composition:runtime` | `jvm` | `unsupported` | | `yano-x-role-workflow` | `BUNDLED` | `com.bloxbean.cardano.yano.appchain.role-workflow` | `jvm` | `unsupported` | | `yano-x-evidence-profile` | `BUNDLED` | `com.bloxbean.cardano.yano.appchain.evidence-profile` | `jvm` | `unsupported` | | `yano-x-kafka` | `FIRST_PARTY_OPTIONAL` | `com.bloxbean.cardano.yano.appchain.kafka` | `jvm` | `unsupported` | | `yano-x-objectstore-s3` | `FIRST_PARTY_OPTIONAL` | `com.bloxbean.cardano.yano.appchain.objectstore.s3` | `jvm` | `unsupported` | | `yano-x-ipfs` | `FIRST_PARTY_OPTIONAL` | `com.bloxbean.cardano.yano.appchain.ipfs` | `jvm` | `unsupported` | | `yano-x-effects-cardano` | `FIRST_PARTY_OPTIONAL` | `com.bloxbean.cardano.yano.appchain.effects.cardano` | `jvm` | `unsupported` | | `yano-x-zk` | `EXPERIMENTAL` | `com.bloxbean.cardano.yano.appchain.zk` | `jvm` | `unsupported` | | `yano-x-eutxo-ledger` | `BUNDLED` | `com.bloxbean.cardano.yano.appchain.eutxo` | `jvm` | `unsupported` | | `yano-x-eutxo-indexer-core` | `BUNDLED` | `com.bloxbean.cardano.yano.appchain.eutxo.indexer` | `jvm` | `unsupported` | | `yano-x-eutxo-indexer-jdbc` | `BUNDLED` | `library:ledgers:eutxo:indexer-jdbc` | `jvm` | `unsupported` | | `yano-x-eutxo-bridge-cardano` | `BUNDLED` | `com.bloxbean.cardano.yano.appchain.eutxo.bridge.cardano` | `jvm` | `unsupported` | | `yano-x-eutxo-zk-zeroj` | `BUNDLED` | `library:ledgers:eutxo-zk:zeroj` | `jvm` | `unsupported` | | `yano-x-eutxo-zk-runtime` | `BUNDLED` | `library:ledgers:eutxo-zk:runtime` | `jvm` | `unsupported` | | `yano-x-eutxo-zk-lifecycle` | `BUNDLED` | `library:ledgers:eutxo-zk:lifecycle` | `jvm` | `unsupported` | #### Distributions | Distribution | Runtime | Archive | Platforms | Bundled artifacts | |---|---|---|---|---| | `yano-x-jvm` | `jvm` | `yano-x-jvm-{version}.zip` | `java-25` | 13 | #### Optional connectors A `FIRST_PARTY_OPTIONAL` capability is maintained and tested by Yano X but is not selected in every deployment. Using one means: 1. installing the **exact release-matched** bundle in `plugins/` on every applicable node; 2. providing the external service it talks to; and 3. configuring its endpoints and credentials as **node-local** values — never in replicated effect payloads or consensus-shared configuration. ```bash tools/yano-plugins/bin/yano-plugins validate plugins/*.jar ./yano.sh appchain doctor --distribution /opt/yano-x ``` See the [optional connector installation guide](https://github.com/bloxbean/yano-x/blob/main/docs/appchain/OPTIONAL_CONNECTORS.md) for per-connector requirements and security profiles. #### The conflict you will actually hit The standard eUTxO runtime and the eUTxO ZK runtime both provide `app-state-machine/eutxo-ledger`. Exactly one may be installed, which is why the ZK runtime ships under `optional-plugins/`. See [eUTxO and ZK](https://yanox.dev/products/eutxo-and-zk/). #### Related - [Recipe catalog](https://yanox.dev/recipes/) — reviewed capability bundles. - [Configuration reference](https://yanox.dev/reference/configuration/) — the typed properties these capabilities own. - [Modules and artifacts](https://yanox.dev/reference/modules/) — where each artifact is built. - [Release acceptance and schema status](https://github.com/bloxbean/yano-x/blob/main/docs/appchain/RELEASE_ACCEPTANCE.md) --- ## Configuration reference Source: https://yanox.dev/reference/configuration/ > The typed configuration properties owned by Yano X first-party plugins, with their scope, change policy, and metadata coverage — generated from the repository. Yano X plugins declare their configuration as **typed metadata**, not as prose. That metadata is what makes `appchain config validate` and `appchain explain` work, and it is what this page is generated from at documentation build time. Source: `tooling/devtools/src/main/resources/appchain-dx/v1alpha1/appchain-first-party-metadata.json`. **READ THE SCOPE COLUMN FIRST:** `CONSENSUS_SHARED` means **every member must set the identical value**. A mismatch diverges the state root and the chain stops finalizing — the same failure as a mismatched state machine, and just as hard to spot after the fact. Node-local values — ports, storage paths, credentials, executor placement — are safe to differ, and generally *should* differ. #### Change policy | Policy | What it means | |---|---| | `NEW_CHAIN_REQUIRED` | The value is part of chain identity. Changing it on a live chain is not an upgrade; it is a different chain. Use a governed profile activation, or bootstrap fresh. | Anything genesis-selected — a commitment profile, a state encoding, a proof subject descriptor, an enabled state index — falls into this class. See [Consensus rules for plugins](https://yanox.dev/plugins/consensus-rules/). #### Coverage Each property reports how well its constraints are known: - **`FULL`** — the constraint is verified against the runtime. - **`PARTIAL`** — derived, but not exhaustively verified. Treat third-party plugin metadata as `PARTIAL` unless Yano reports `FULL`, and verify the signed metadata and its runtime-manifest binding before trusting it. #### Properties by owner ##### `yano-x-first-party/stdlib` | Property | Type | Default | Allowed | Scope | Change policy | Description | |---|---|---|---|---|---|---| | `yano.app-chain.machines.approvals.on-approved-effect.enabled` | `BOOLEAN` | `false` | — | `CONSENSUS_SHARED` | `GOVERNED_ACTIVATION` | Emit one generic deterministic effect when a proposal is approved | | `yano.app-chain.machines.approvals.on-approved-effect.expiry-blocks` | `LONG` | — | — | `CONSENSUS_SHARED` | `GOVERNED_ACTIVATION` | Generic on-approved effect expiry in app-chain blocks | | `yano.app-chain.machines.approvals.on-approved-effect.gate` | `STRING` | — | `chain-default`, `app-final`, `l1-anchored`, `zk-settled` | `CONSENSUS_SHARED` | `GOVERNED_ACTIVATION` | Finality gate for the generic on-approved effect | | `yano.app-chain.machines.approvals.on-approved-effect.type` | `STRING` | — | — | `CONSENSUS_SHARED` | `GOVERNED_ACTIVATION` | Exact executor routing type emitted after approval | | `yano.app-chain.machines.balances.minter` | `STRING` | — | — | `CONSENSUS_SHARED` | `NEW_CHAIN_REQUIRED` | Optional 32-byte hexadecimal member identity allowed to mint balances | | `yano.app-chain.machines.kv-registry.value-format` | `STRING` | `raw` | `raw`, `cbor`, `utf8` | `CONSENSUS_SHARED` | `NEW_CHAIN_REQUIRED` | Encoding accepted by the packaged key-value registry state machine | ##### `yano-x-first-party/composite` | Property | Type | Default | Allowed | Scope | Change policy | Description | |---|---|---|---|---|---|---| | `yano.app-chain.machines.composite.evidence-capacity-per-block` | `INTEGER` | `8` | — | `CONSENSUS_SHARED` | `NEW_CHAIN_REQUIRED` | Maximum evidence workflows evaluated in one block | | `yano.app-chain.machines.composite.preset` | `STRING` | `evidence-v1-gated` | `evidence-v1`, `evidence-v1-gated`, `role-evidence-v1` | `CONSENSUS_SHARED` | `NEW_CHAIN_REQUIRED` | Packaged deterministic composite profile preset | | `yano.app-chain.machines.composite.profile-governance.max-epochs` | `INTEGER` | `1024` | — | `CONSENSUS_SHARED` | `GOVERNED_ACTIVATION` | Maximum retained composite profile epochs | | `yano.app-chain.machines.composite.profile-governance.min-activation-lag` | `INTEGER` | `20` | — | `CONSENSUS_SHARED` | `GOVERNED_ACTIVATION` | Minimum blocks between profile approval and activation | | `yano.app-chain.machines.composite.profile-governance.proposal-ttl-blocks` | `INTEGER` | `600` | — | `CONSENSUS_SHARED` | `GOVERNED_ACTIVATION` | Lifetime of a composite profile proposal | | `yano.app-chain.machines.composite.profile-mode` | `STRING` | `fixed` | `fixed`, `governed` | `CONSENSUS_SHARED` | `GOVERNED_ACTIVATION` | Fixed or governed composite profile evolution | | `yano.app-chain.machines.composite.roles.maximum-mutation-lifetime-blocks` | `INTEGER` | `1000` | — | `CONSENSUS_SHARED` | `GOVERNED_ACTIVATION` | Maximum lifetime of role-governance mutations | ##### `yano-x-first-party/eutxo-bridge-cardano` | Property | Type | Default | Allowed | Scope | Change policy | Description | |---|---|---|---|---|---|---| | `yano.app-chain.machines.eutxo.bridge.confirmation-observer-id` | `STRING` | — | — | `CONSENSUS_SHARED` | `NEW_CHAIN_REQUIRED` | Configured exact stable withdrawal-confirmation observer instance | | `yano.app-chain.machines.eutxo.bridge.epoch` | `LONG` | `0` | — | `CONSENSUS_SHARED` | `NEW_CHAIN_REQUIRED` | Bridge migration epoch bound into every withdrawal claim | | `yano.app-chain.machines.eutxo.bridge.max-pending-withdrawals` | `INTEGER` | `1024` | — | `CONSENSUS_SHARED` | `NEW_CHAIN_REQUIRED` | Maximum committed pending withdrawal claims | | `yano.app-chain.machines.eutxo.bridge.max-withdrawal-lovelace` | `LONG` | `45000000000000000` | — | `CONSENSUS_SHARED` | `NEW_CHAIN_REQUIRED` | Hard per-claim lovelace withdrawal limit | | `yano.app-chain.machines.eutxo.bridge.observer-id` | `STRING` | — | — | `CONSENSUS_SHARED` | `NEW_CHAIN_REQUIRED` | Configured exact vault-deposit observer instance | | `yano.app-chain.machines.eutxo.bridge.vault-address` | `STRING` | — | — | `CONSENSUS_SHARED` | `NEW_CHAIN_REQUIRED` | Exact Cardano vault address accepted by the EUTxO ledger | | `yano.app-chain.machines.eutxo.bridge.vault-script-hash` | `STRING` | — | — | `CONSENSUS_SHARED` | `NEW_CHAIN_REQUIRED` | Pinned 28-byte bridge vault script hash | | `yano.app-chain.machines.eutxo.bridge.withdrawal-address` | `STRING` | — | — | `CONSENSUS_SHARED` | `NEW_CHAIN_REQUIRED` | L2 sink address whose signed outputs become irrevocable withdrawal claims | | `yano.app-chain.machines.eutxo.bridge.withdrawals-paused` | `BOOLEAN` | `false` | — | `CONSENSUS_SHARED` | `NEW_CHAIN_REQUIRED` | Fail-closed consensus switch for new withdrawal claims | | `yano.app-chain.observers.bridge-deposits.type` | `STRING` | `eutxo-vault-deposit-v1` | `eutxo-vault-deposit-v1` | `CONSENSUS_SHARED` | `NEW_CHAIN_REQUIRED` | Exact accepted-vault deposit observer type | | `yano.app-chain.observers.bridge-withdrawals.type` | `STRING` | `eutxo-withdrawal-confirmation-v1` | `eutxo-withdrawal-confirmation-v1` | `CONSENSUS_SHARED` | `NEW_CHAIN_REQUIRED` | Exact stable Cardano withdrawal-confirmation observer type | ##### `yano-x-first-party/eutxo-ledger` | Property | Type | Default | Allowed | Scope | Change policy | Description | |---|---|---|---|---|---|---| | `yano.app-chain.machines.eutxo.bridge.params.fallback-delay-slots` | `LONG` | — | — | `CONSENSUS_SHARED` | `NEW_CHAIN_REQUIRED` | Genesis fallback arming delay in L1 slots (governed thereafter) (ADR-UTXO-009, v3 profiles only) | | `yano.app-chain.machines.eutxo.bridge.params.fee-basis-points` | `LONG` | — | — | `CONSENSUS_SHARED` | `NEW_CHAIN_REQUIRED` | Genesis basis-point executor bounty (governed thereafter) (ADR-UTXO-009, v3 profiles only) | | `yano.app-chain.machines.eutxo.bridge.params.fee-flat-lovelace` | `LONG` | — | — | `CONSENSUS_SHARED` | `NEW_CHAIN_REQUIRED` | Genesis flat executor bounty (governed thereafter) (ADR-UTXO-009, v3 profiles only) | | `yano.app-chain.machines.eutxo.bridge.params.min-withdrawal-lovelace` | `LONG` | — | — | `CONSENSUS_SHARED` | `NEW_CHAIN_REQUIRED` | Genesis minimum withdrawal payout (governed thereafter) (ADR-UTXO-009, v3 profiles only) | | `yano.app-chain.machines.eutxo.bridge.params.rooting-blocks` | `LONG` | — | — | `CONSENSUS_SHARED` | `NEW_CHAIN_REQUIRED` | Genesis rooting cadence in L2 blocks (governed thereafter) (ADR-UTXO-009, v3 profiles only) | | `yano.app-chain.machines.eutxo.bridge.params.rooting-seconds` | `LONG` | — | — | `CONSENSUS_SHARED` | `NEW_CHAIN_REQUIRED` | Genesis rooting cadence in seconds (governed thereafter) (ADR-UTXO-009, v3 profiles only) | | `yano.app-chain.machines.eutxo.bridge.params.soft-batch-cap` | `LONG` | — | — | `CONSENSUS_SHARED` | `NEW_CHAIN_REQUIRED` | Genesis soft settlement batch cap (governed thereafter) (ADR-UTXO-009, v3 profiles only) | | `yano.app-chain.machines.eutxo.expected-profile-digest` | `STRING` | — | — | `CONSENSUS_SHARED` | `NEW_CHAIN_REQUIRED` | Expected digest of every consensus-relevant EUTxO profile field | | `yano.app-chain.machines.eutxo.genesis.address` | `STRING` | — | — | `CONSENSUS_SHARED` | `NEW_CHAIN_REQUIRED` | Testnet address receiving the first no-real-funds genesis allocation | | `yano.app-chain.machines.eutxo.genesis.inline-datum-hex` | `STRING` | — | — | `CONSENSUS_SHARED` | `NEW_CHAIN_REQUIRED` | Optional canonical lowercase Plutus datum CBOR for genesis output zero | | `yano.app-chain.machines.eutxo.genesis.l2-address` | `STRING` | — | — | `CONSENSUS_SHARED` | `NEW_CHAIN_REQUIRED` | Key-controlled L2 address registered without creating virtual funds | | `yano.app-chain.machines.eutxo.genesis.l2-key-epoch` | `LONG` | `1` | — | `CONSENSUS_SHARED` | `NEW_CHAIN_REQUIRED` | Initial consensus key epoch for the registered L2 Jubjub key | | `yano.app-chain.machines.eutxo.genesis.l2-public-key` | `STRING` | — | — | `CONSENSUS_SHARED` | `NEW_CHAIN_REQUIRED` | Lowercase 32-byte Jubjub public key registered for the initial L2 address | | `yano.app-chain.machines.eutxo.genesis.lovelace` | `LONG` | — | — | `CONSENSUS_SHARED` | `NEW_CHAIN_REQUIRED` | Positive no-real-funds lovelace allocated to genesis output zero | | `yano.app-chain.machines.eutxo.profile` | `STRING` | `yano-eutxo-v2-plutus-v3` | `yano-eutxo-v1`, `yano-eutxo-v2-plutus-v3`, `yano-eutxo-v3-bridge-settlement` | `CONSENSUS_SHARED` | `NEW_CHAIN_REQUIRED` | Pinned deterministic EUTxO ledger profile | ##### `yano-x-first-party/evidence-registry` | Property | Type | Default | Allowed | Scope | Change policy | Description | |---|---|---|---|---|---|---| | `yano.app-chain.machines.evidence-registry.issuers` | `STRING_LIST` | — | — | `CONSENSUS_SHARED` | `NEW_CHAIN_REQUIRED` | Member public keys allowed to issue evidence | | `yano.app-chain.machines.evidence-registry.notification-expiry-blocks` | `LONG` | `0` | — | `CONSENSUS_SHARED` | `GOVERNED_ACTIVATION` | Evidence notification effect expiry in app-chain blocks | | `yano.app-chain.machines.evidence-registry.notify-senders` | `STRING_LIST` | — | — | `CONSENSUS_SHARED` | `NEW_CHAIN_REQUIRED` | Member public keys allowed to emit notifications | | `yano.app-chain.machines.evidence-registry.storage-expiry-blocks` | `LONG` | `0` | — | `CONSENSUS_SHARED` | `GOVERNED_ACTIVATION` | Evidence storage effect expiry in app-chain blocks | | `yano.app-chain.machines.evidence-registry.storage-gate` | `STRING` | `app-final` | `app-final`, `l1-anchored`, `app_final`, `l1_anchored` | `CONSENSUS_SHARED` | `GOVERNED_ACTIVATION` | Finality gate required before evidence storage execution | #### Host configuration The properties above are the ones **Yano X plugins** own. Core app-chain configuration — chain id, members, threshold, sequencing, block cadence, storage, API authentication, anchoring, effects caps, retention — belongs to the Yano host and is documented in [section 7 of the app-chain user guide](https://github.com/bloxbean/yano-x/blob/main/docs/APP_CHAIN_USER_GUIDE.md). Frequently needed host values: | Property | Notes | |---|---| | `yano.app-chain.chain-id` | 1–128 valid UTF-8 bytes. One group of participants = one chain id. | | `yano.app-chain.state-machine` | The selected machine or profile id. Consensus-shared. | | `yano.plugins.directory` | Where bundles are loaded from. **Not** `yaci.plugins.directory`. | | `yano.app-chain.effects.*` | Enablement and deterministic caps. All consensus-shared — see [Effects](https://yanox.dev/concepts/effects/). | | `yano.app-chain.anchor.*` | Anchor leader only — see [Cardano anchoring](https://yanox.dev/concepts/anchoring/). | | `yano.app-chain.message.enforce-sender-seq` | Consensus-visible when on; all members must agree. | #### Working with configuration safely ```bash ### Validate a project's blueprint and its resolved configuration. ./yano.sh appchain config validate --mode project ### Redacted effective configuration, and per-property explanation. ./yano.sh appchain config explain --key ### Compare a project against running nodes. ./yano.sh appchain drift --peer ``` Edit only `appchain.yaml`; generated runtime files are derived output. ##### Secrets Five secret classes, five blast radii — keep them separate: 1. member signing keys, 2. business-actor keys, 3. API keys, 4. effect and connector credentials, and 5. anchor wallet funds. Never place a credential in consensus-shared configuration or in a replicated effect payload — both are visible to every member and provable to anyone holding a proof. Use node-local overlays or a secret provider, and refer to secrets by documented environment-variable or provider names only. For private per-node connector configuration, the cluster launcher supports a strictly validated overlay directory (`YANO_CLUSTER_NODE_CONFIG_DIR`) with `chmod 600` files at a fixed precedence ordinal — see the [cluster launcher README](https://github.com/bloxbean/yano-x/blob/main/scripts/appchain-cluster/README.md). --- ## Modules and artifacts Source: https://yanox.dev/reference/modules/ > Every Gradle module in Yano X with its published artifact id, publication type, and plugin bundle id — generated from config/artifacts-v1.json. Yano X publishes each module under the group `com.bloxbean.cardano`. This page is generated at documentation build time from `config/artifacts-v1.json`, which is the repository's single source of truth for artifact identity — the build verifies it with `verifyArtifactInventory`. | Value | Current | |---|---| | Yano X version | `0.1.0-SNAPSHOT` | | Yano host version | `0.1.0-pre13` | | Maven group | `com.bloxbean.cardano` | | Java | `25` | | Base Yano JVM ZIP | [`yano-0.1.0-pre13.zip`](https://github.com/bloxbean/yano/releases/download/v0.1.0-pre13/yano-0.1.0-pre13.zip) | #### Publication types | Type | Meaning | |---|---| | `runtime-plugin` | Activated by the host through `PluginProviderRegistry` and a schema-v1 manifest. Publishes both a normal JAR and a dependency-complete **bundle** JAR. | | `library` | An ordinary JAR — contracts, clients, codecs, testkits, CLIs, on-chain artifacts, deterministic helpers. Never loaded as a plugin. | The distinction is the architectural boundary described in [Why Yano X](https://yanox.dev/start-here/why-yano-x/): every optional behavior a running node can independently select or manage is a runtime plugin; everything else is a library. `verifyArtifactInventory` checks that each module has exactly one declared artifact identity and that every runtime plugin has a bundle publication. #### Modules ##### `runtime-plugin` (18) | Gradle module | Artifact id | Plugin bundle id | Source | |---|---|---|---| | `:state-machines:stdlib` | `yano-x-stdlib` | `com.bloxbean.cardano.yano.appchain.stdlib` | [state-machines/stdlib](https://github.com/bloxbean/yano-x/blob/main/state-machines/stdlib) | | `:capabilities:authenticated-map-validators` | `yano-x-authenticated-map-validators` | `com.bloxbean.cardano.yano.appchain.authenticated-map-validators` | [capabilities/authenticated-map-validators](https://github.com/bloxbean/yano-x/blob/main/capabilities/authenticated-map-validators) | | `:composition:runtime` | `yano-x-composite` | `com.bloxbean.cardano.yano.appchain.composite` | [composition/runtime](https://github.com/bloxbean/yano-x/blob/main/composition/runtime) | | `:capabilities:role-workflow` | `yano-x-role-workflow` | `com.bloxbean.cardano.yano.appchain.role-workflow` | [capabilities/role-workflow](https://github.com/bloxbean/yano-x/blob/main/capabilities/role-workflow) | | `:products:evidence:registry` | `yano-x-evidence-registry` | `com.bloxbean.cardano.yano.appchain.evidence-registry` | [products/evidence/registry](https://github.com/bloxbean/yano-x/blob/main/products/evidence/registry) | | `:products:evidence:profile` | `yano-x-evidence-profile` | `com.bloxbean.cardano.yano.appchain.evidence-profile` | [products/evidence/profile](https://github.com/bloxbean/yano-x/blob/main/products/evidence/profile) | | `:products:cardano-history:runtime` | `yano-x-cardano-history` | `com.bloxbean.cardano.yano.appchain.cardano-history` | [products/cardano-history/runtime](https://github.com/bloxbean/yano-x/blob/main/products/cardano-history/runtime) | | `:examples:showcase` | `yano-x-showcase` | `com.bloxbean.cardano.yano.appchain.showcase` | [examples/showcase](https://github.com/bloxbean/yano-x/blob/main/examples/showcase) | | `:connectors:kafka` | `yano-x-kafka` | `com.bloxbean.cardano.yano.appchain.kafka` | [connectors/kafka](https://github.com/bloxbean/yano-x/blob/main/connectors/kafka) | | `:connectors:objectstore-s3` | `yano-x-objectstore-s3` | `com.bloxbean.cardano.yano.appchain.objectstore.s3` | [connectors/objectstore-s3](https://github.com/bloxbean/yano-x/blob/main/connectors/objectstore-s3) | | `:connectors:ipfs` | `yano-x-ipfs` | `com.bloxbean.cardano.yano.appchain.ipfs` | [connectors/ipfs](https://github.com/bloxbean/yano-x/blob/main/connectors/ipfs) | | `:connectors:effects-cardano` | `yano-x-effects-cardano` | `com.bloxbean.cardano.yano.appchain.effects.cardano` | [connectors/effects-cardano](https://github.com/bloxbean/yano-x/blob/main/connectors/effects-cardano) | | `:state-machines:zk` | `yano-x-zk` | `com.bloxbean.cardano.yano.appchain.zk` | [state-machines/zk](https://github.com/bloxbean/yano-x/blob/main/state-machines/zk) | | `:ledgers:eutxo:ledger` | `yano-x-eutxo-ledger` | `com.bloxbean.cardano.yano.appchain.eutxo` | [ledgers/eutxo/ledger](https://github.com/bloxbean/yano-x/blob/main/ledgers/eutxo/ledger) | | `:ledgers:eutxo:bridge-cardano` | `yano-x-eutxo-bridge-cardano` | `com.bloxbean.cardano.yano.appchain.eutxo.bridge.cardano` | [ledgers/eutxo/bridge-cardano](https://github.com/bloxbean/yano-x/blob/main/ledgers/eutxo/bridge-cardano) | | `:ledgers:eutxo:indexer-jdbc` | `yano-x-eutxo-indexer-jdbc` | `com.bloxbean.cardano.yano.appchain.eutxo.indexer` | [ledgers/eutxo/indexer-jdbc](https://github.com/bloxbean/yano-x/blob/main/ledgers/eutxo/indexer-jdbc) | | `:ledgers:eutxo-zk:runtime` | `yano-x-eutxo-zk-runtime` | `com.bloxbean.cardano.yano.appchain.eutxo.zk.runtime` | [ledgers/eutxo-zk/runtime](https://github.com/bloxbean/yano-x/blob/main/ledgers/eutxo-zk/runtime) | | `:ledgers:eutxo-zk:indexer` | `yano-x-eutxo-zk-indexer` | `com.bloxbean.cardano.yano.appchain.eutxo.zk.indexer` | [ledgers/eutxo-zk/indexer](https://github.com/bloxbean/yano-x/blob/main/ledgers/eutxo-zk/indexer) | ##### `library` (20) | Gradle module | Artifact id | Plugin bundle id | Source | |---|---|---|---| | `:state-machines:stdlib-contracts` | `yano-x-stdlib-contracts` | — | [state-machines/stdlib-contracts](https://github.com/bloxbean/yano-x/blob/main/state-machines/stdlib-contracts) | | `:composition:contracts` | `yano-x-composite-contracts` | — | [composition/contracts](https://github.com/bloxbean/yano-x/blob/main/composition/contracts) | | `:composition:client` | `yano-x-composite-client` | — | [composition/client](https://github.com/bloxbean/yano-x/blob/main/composition/client) | | `:capabilities:role-workflow-contracts` | `yano-x-role-workflow-contracts` | — | [capabilities/role-workflow-contracts](https://github.com/bloxbean/yano-x/blob/main/capabilities/role-workflow-contracts) | | `:sdk:client` | `yano-x-client` | — | [sdk/client](https://github.com/bloxbean/yano-x/blob/main/sdk/client) | | `:sdk:proof-contracts` | `yano-x-proof-contracts` | — | [sdk/proof-contracts](https://github.com/bloxbean/yano-x/blob/main/sdk/proof-contracts) | | `:sdk:integration-contracts` | `yano-x-integration-contracts` | — | [sdk/integration-contracts](https://github.com/bloxbean/yano-x/blob/main/sdk/integration-contracts) | | `:products:evidence:contracts` | `yano-x-evidence-contracts` | — | [products/evidence/contracts](https://github.com/bloxbean/yano-x/blob/main/products/evidence/contracts) | | `:products:evidence:client` | `yano-x-evidence-client` | — | [products/evidence/client](https://github.com/bloxbean/yano-x/blob/main/products/evidence/client) | | `:products:cardano-history:client` | `yano-x-cardano-history-client` | — | [products/cardano-history/client](https://github.com/bloxbean/yano-x/blob/main/products/cardano-history/client) | | `:examples:showcase-client` | `yano-x-showcase-client` | — | [examples/showcase-client](https://github.com/bloxbean/yano-x/blob/main/examples/showcase-client) | | `:ledgers:eutxo:contracts` | `yano-x-eutxo-contracts` | — | [ledgers/eutxo/contracts](https://github.com/bloxbean/yano-x/blob/main/ledgers/eutxo/contracts) | | `:ledgers:eutxo:client` | `yano-x-eutxo-client` | — | [ledgers/eutxo/client](https://github.com/bloxbean/yano-x/blob/main/ledgers/eutxo/client) | | `:ledgers:eutxo:indexer-core` | `yano-x-eutxo-indexer-core` | — | [ledgers/eutxo/indexer-core](https://github.com/bloxbean/yano-x/blob/main/ledgers/eutxo/indexer-core) | | `:ledgers:eutxo-zk:contracts` | `yano-x-eutxo-zk-contracts` | — | [ledgers/eutxo-zk/contracts](https://github.com/bloxbean/yano-x/blob/main/ledgers/eutxo-zk/contracts) | | `:ledgers:eutxo-zk:zeroj` | `yano-x-eutxo-zk-zeroj` | — | [ledgers/eutxo-zk/zeroj](https://github.com/bloxbean/yano-x/blob/main/ledgers/eutxo-zk/zeroj) | | `:ledgers:eutxo-zk:prover` | `yano-x-eutxo-zk-prover` | — | [ledgers/eutxo-zk/prover](https://github.com/bloxbean/yano-x/blob/main/ledgers/eutxo-zk/prover) | | `:ledgers:eutxo-zk:client` | `yano-x-eutxo-zk-client` | — | [ledgers/eutxo-zk/client](https://github.com/bloxbean/yano-x/blob/main/ledgers/eutxo-zk/client) | | `:ledgers:eutxo-zk:lifecycle` | `yano-x-eutxo-zk-lifecycle` | — | [ledgers/eutxo-zk/lifecycle](https://github.com/bloxbean/yano-x/blob/main/ledgers/eutxo-zk/lifecycle) | | `:tooling:spring-boot-starter` | `yano-x-spring-boot-starter` | — | [tooling/spring-boot-starter](https://github.com/bloxbean/yano-x/blob/main/tooling/spring-boot-starter) | ##### `application` (3) | Gradle module | Artifact id | Plugin bundle id | Source | |---|---|---|---| | `:products:evidence:demo-runner` | `yano-x-evidence-demo-runner` | — | [products/evidence/demo-runner](https://github.com/bloxbean/yano-x/blob/main/products/evidence/demo-runner) | | `:ledgers:eutxo:demo` | `yano-x-eutxo-demo` | — | [ledgers/eutxo/demo](https://github.com/bloxbean/yano-x/blob/main/ledgers/eutxo/demo) | | `:ledgers:eutxo-zk:demo` | `yano-x-eutxo-zk-demo` | — | [ledgers/eutxo-zk/demo](https://github.com/bloxbean/yano-x/blob/main/ledgers/eutxo-zk/demo) | ##### `tool` (3) | Gradle module | Artifact id | Plugin bundle id | Source | |---|---|---|---| | `:tooling:devtools` | `yano-x-devtools` | — | [tooling/devtools](https://github.com/bloxbean/yano-x/blob/main/tooling/devtools) | | `:tooling:studio` | `yano-x-studio` | — | [tooling/studio](https://github.com/bloxbean/yano-x/blob/main/tooling/studio) | | `:products:cardano-history:cli` | `yano-x-cardano-history-cli` | — | [products/cardano-history/cli](https://github.com/bloxbean/yano-x/blob/main/products/cardano-history/cli) | ##### `test-library` (3) | Gradle module | Artifact id | Plugin bundle id | Source | |---|---|---|---| | `:sdk:effects-testkit` | `yano-x-effects-testkit` | — | [sdk/effects-testkit](https://github.com/bloxbean/yano-x/blob/main/sdk/effects-testkit) | | `:ledgers:eutxo:testkit` | `yano-x-eutxo-testkit` | — | [ledgers/eutxo/testkit](https://github.com/bloxbean/yano-x/blob/main/ledgers/eutxo/testkit) | | `:ledgers:eutxo-zk:testkit` | `yano-x-eutxo-zk-testkit` | — | [ledgers/eutxo-zk/testkit](https://github.com/bloxbean/yano-x/blob/main/ledgers/eutxo-zk/testkit) | ##### `onchain-artifact` (4) | Gradle module | Artifact id | Plugin bundle id | Source | |---|---|---|---| | `:sdk:proof-onchain` | `yano-x-proof-onchain` | — | [sdk/proof-onchain](https://github.com/bloxbean/yano-x/blob/main/sdk/proof-onchain) | | `:products:cardano-history:onchain` | `yano-x-cardano-history-onchain` | — | [products/cardano-history/onchain](https://github.com/bloxbean/yano-x/blob/main/products/cardano-history/onchain) | | `:ledgers:eutxo:bridge-onchain` | `yano-x-eutxo-bridge-onchain` | — | [ledgers/eutxo/bridge-onchain](https://github.com/bloxbean/yano-x/blob/main/ledgers/eutxo/bridge-onchain) | | `:ledgers:eutxo-zk:onchain` | `yano-x-eutxo-zk-onchain` | — | [ledgers/eutxo-zk/onchain](https://github.com/bloxbean/yano-x/blob/main/ledgers/eutxo-zk/onchain) | ##### `test-fixture` (1) | Gradle module | Artifact id | Plugin bundle id | Source | |---|---|---|---| | `:fixtures:eutxo-e2e` | `yano-x-eutxo-e2e` | — | [fixtures/eutxo-e2e](https://github.com/bloxbean/yano-x/blob/main/fixtures/eutxo-e2e) | #### Using an artifact ```groovy repositories { mavenCentral() } dependencies { // The Java client SDK: REST, SSE, and client-side proof verification. implementation 'com.bloxbean.cardano:yano-x-client' // Contracts libraries are plain JARs, safe to use off-chain. implementation 'com.bloxbean.cardano:yano-x-evidence-contracts' // Tests. testImplementation 'com.bloxbean.cardano:yano-appchain-core-testkit' testImplementation 'com.bloxbean.cardano:yano-x-effects-testkit' } ``` **NO PUBLISHED RELEASE YET:** Yano X has no published release, so these coordinates are not yet on Maven Central. Until then, build from source and publish to Maven Local or to a staged repository — see [Developing Yano X](https://yanox.dev/contributing/). Runtime plugin bundles are **not** application dependencies. They are installed into `plugins/` in a distribution, not added to a build file. #### Related - [Capability catalog](https://yanox.dev/reference/capabilities/) — which capabilities each runtime artifact provides. - [Build from source](https://yanox.dev/start-here/build-from-source/) — producing the distribution that contains them. - [`config/artifacts-v1.json`](https://github.com/bloxbean/yano-x/blob/main/config/artifacts-v1.json) — the source of truth for this page. --- ## Reference shelf Source: https://yanox.dev/reference/shelf/ > Deep links to the exhaustive guides that live in the Yano X repository — the 113 KB user guide, state-machine references, connector guides, ADRs, and demo scripts. This site is curated. The Yano X repository holds several exhaustive documents that are deliberately **not** duplicated here, because a second copy would diverge from the code within a release. They are linked below, and they are the authority whenever this site and the repository disagree. #### The complete guides | Document | Size | What it covers | |---|---|---| | [App-chain user guide](https://github.com/bloxbean/yano-x/blob/main/docs/APP_CHAIN_USER_GUIDE.md) | ~113 KB | The exhaustive reference: configuration, REST API, anchoring, custom app chains, multi-chain nodes, standard state machines, SSE/webhook/Kafka consumption, typed messages, security, compliance, operations, queries, client libraries, ZK, effects, troubleshooting, and current limitations. | | [App-chain overview](https://github.com/bloxbean/yano-x/blob/main/docs/APP_CHAIN_OVERVIEW.md) | ~21 KB | The 10–15 minute architecture read, plus an editable presentation deck. | | [Consensus and host internals](https://github.com/bloxbean/yano-x/blob/main/docs/core-host.md) | — | The consensus round check by check, vote locks, rotation math, catch-up and restart semantics, plugin query and domain API contract. | | [App-chain tutorial](https://github.com/bloxbean/yano-x/blob/main/docs/APP_CHAIN_TUTORIAL.md) | ~22 KB | Run a cluster and build a custom state machine end to end. | | [Use-case catalogue](https://github.com/bloxbean/yano-x/blob/main/docs/APP_CHAIN_USE_CASES.md) | ~19 KB | Worked application patterns and their starting points. | #### Domain and governance | Document | What it covers | |---|---| | [Domain actors and role-aware approvals](https://github.com/bloxbean/yano-x/blob/main/docs/APP_CHAIN_DOMAIN_ROLES.md) | Governed organizations, actor and key revisions, role policies, and organization-distinct quorums. | | [Profile governance runbook](https://github.com/bloxbean/yano-x/blob/main/docs/APP_CHAIN_PROFILE_GOVERNANCE.md) | Packaging, authorizing, and activating a composite profile epoch. | | [Composable state and proofs](https://github.com/bloxbean/yano-x/blob/main/docs/appchain/COMPOSABLE_STATE_AND_PROOFS.md) | Reusing stock transitions and verifying portable proofs. | | [Proof Lab](https://github.com/bloxbean/yano-x/blob/main/docs/appchain/PROOF_LAB.md) | Message, typed-state, imported, and on-chain proof workflows; independent-verifier and Cardano-validator guides. | | [Authenticated snapshots](https://github.com/bloxbean/yano-x/blob/main/docs/appchain/AUTHENTICATED_SNAPSHOTS.md) | Archiving and proving large immutable period datasets. | #### Capabilities, connectors, and release | Document | What it covers | |---|---| | [Capability catalog](https://github.com/bloxbean/yano-x/blob/main/docs/appchain/CAPABILITIES.md) | The release capability and recipe catalog in the repository. | | [Optional connectors](https://github.com/bloxbean/yano-x/blob/main/docs/appchain/OPTIONAL_CONNECTORS.md) | Per-connector installation, configuration, and security profiles. | | [Release acceptance](https://github.com/bloxbean/yano-x/blob/main/docs/appchain/RELEASE_ACCEPTANCE.md) | Acceptance scenarios and schema status. | | [Cardano History](https://github.com/bloxbean/yano-x/blob/main/docs/appchain/CARDANO_HISTORY.md) | The full product guide and route reference. | #### Build, operate, demo | Document | What it covers | |---|---| | [Build and test](https://github.com/bloxbean/yano-x/blob/main/docs/BUILD_AND_TEST.md) | Every build task and verification gate. | | [Build distributions](https://github.com/bloxbean/yano-x/blob/main/docs/BUILD_DISTRIBUTIONS.md) | Distribution inputs, outputs, and the artifact API prefix. | | [Cluster launcher](https://github.com/bloxbean/yano-x/blob/main/scripts/appchain-cluster/README.md) | Per-node overlays, chain definitions, membership, load and soak tests. | | [Evidence chain demo](https://github.com/bloxbean/yano-x/blob/main/docs/EVIDENCE_CHAIN_DEMO.md) | The complete scripted evidence scenario and its acceptance checks. | | [Showcase demo](https://github.com/bloxbean/yano-x/blob/main/examples/showcase/DEMO_SHOWCASE.md) | Operating the unified showcase distribution. | | [Developer tools](https://github.com/bloxbean/yano-x/blob/main/tooling/devtools/README.md) | The offline engine behind `yano.sh appchain`. | | [App-Chain Studio](https://yanox.dev/studio/) | The blueprint builder, hosted here. | #### eUTxO and ZK | Document | What it covers | |---|---| | [eUTxO product family](https://github.com/bloxbean/yano-x/blob/main/ledgers/eutxo/README.md) | The virtual ledger, profiles, and genesis. | | [eUTxO demos](https://github.com/bloxbean/yano-x/blob/main/ledgers/eutxo/DEMO.md) | The disposable three-scenario quick start. | | [Indexer operations](https://github.com/bloxbean/yano-x/blob/main/ledgers/eutxo/INDEXER_OPERATIONS.md) | Lifecycle API, SQLite, recovery, metrics. | | [ZK getting started](https://github.com/bloxbean/yano-x/blob/main/ledgers/eutxo-zk/GETTING_STARTED.md) | Status and module verification. | | [ZK devnet walkthrough](https://github.com/bloxbean/yano-x/blob/main/ledgers/eutxo-zk/DEVNET_WALKTHROUGH.md) | Deposit, L2 transaction, proof, settlement, withdrawal. | #### Decisions Architecture decision records live in the `adr/` directory of the repository. They are point-in-time decisions rather than documentation — several are explicitly marked pre-split evidence — so they are deliberately not published here. Read them in the repository when you need the rationale behind a contract; trust this site and the code for current behaviour. #### The upstream host Yano itself lives at [github.com/bloxbean/yano](https://github.com/bloxbean/yano) and owns the node, consensus, proofs, anchoring, the effect runtime, the plugin SPI, and `ordered-log`. Host-contract questions belong there. --- # Contributing --- ## Developing Yano X Source: https://yanox.dev/contributing/ > The contributor track — coordinated Yano and Yano X development, the verification gates, architecture invariants, and working-tree safety rules. This is the **contributor** track. If you only want to run Yano X, you want [Build from source](https://yanox.dev/start-here/build-from-source/) instead — it needs no flags and no Yano checkout. You are here because you are changing Yano X itself, or changing Yano and Yano X together. #### Two repositories ```text bloxbean/yano the host: Cardano data node, minimal app-chain host, public plugin SPI, generic runtime/REST/operations, anchoring and proofs, OrderedLog. GraalVM is its primary deployment target; it also publishes Java artifacts and an ordinary JVM ZIP for Yano X. bloxbean/yano-x JVM extensions and products. Dependency direction is strictly yano-x -> yano. ``` **THE BOUNDARY IS A HARD RULE:** Do **not** add a source-checkout dependency, a composite Gradle build, a sibling task invocation, or a generated-file dependency from Yano X to Yano. Consume an exact published Yano version and its matching ordinary JVM ZIP. Do not modify the sibling Yano repository as a side effect of a Yano X task unless that is explicitly in scope. #### Coordinated local development When your change spans both repositories, publish the Yano inputs first: ```bash ### In the Yano repository. cd /path/to/yano ./gradlew publishToMavenLocal :app:yanoDistZip -PskipSigning=true --no-parallel ``` Then build Yano X against exactly that version: ```bash cd /path/to/yano-x ./gradlew test verifyArtifactInventory verifyJvmOnlyBuild \ -PyanoVersion= \ -PuseMavenLocal=true --offline ``` `mavenLocal()` is disabled unless `-PuseMavenLocal=true`. The Maven version and the JVM ZIP identity must match exactly; for a SNAPSHOT or staged version also pass `-PyanoJvmDist=/absolute/path/to/yano-.zip`. #### The gates Choose gates in proportion to the change, but **do not stop at unit tests** when a plugin, catalog, distribution, persistence, consensus, proof, anchor, or cross-node behavior changed. ```bash ### Focused, while iterating. ./gradlew :state-machines:stdlib:test -PyanoVersion= ./gradlew :tooling:devtools:test -PyanoVersion= ./gradlew integrationTest -PyanoVersion= ./gradlew cryptoTest -PyanoVersion= ### A full clean build, which also validates the release archives. ./gradlew clean build -PyanoVersion= -PskipSigning=true ### Release rehearsal: publish every coordinate to a NEW EMPTY directory. ./gradlew publishAllPublicationsToInternalRepository \ -PinternalRepository=/absolute/path/to/empty/yano-x-staging \ -PyanoVersion= -PskipSigning=true ``` | Gate | Run it when | |---|---| | `verifyArtifactInventory` | Any module, publication, manifest, bundle, or contribution change. Checks that each module has exactly one declared artifact identity and that runtime plugins have bundle publications. | | `verifyJvmOnlyBuild` | Build topology changes. Rejects accidental native-image tasks. | | `integrationTest` / `cryptoTest` | Their domains changed. | | `distributionCheck` or a clean `build` | Dependency, bundle, class-isolation, launch, or packaging changes. | | Showcase contracts | Showcase changes: `./gradlew :examples:showcase:showcaseScriptContract :examples:showcase:showcaseDistributionContract` | ##### Runtime and persistence changes Validate a **real multi-node cluster**: identical chain height, root, profile, genesis, and capability-manifest digest across members; finality certificates; proof retrieval; catch-up and restart; and anchor state where applicable. Persistence changes additionally require apply, rollback, replay, restart, and root-parity checks. Derived indexes must never advance beyond authoritative app-chain state, and must remain separate from L1 `chainstate`. #### Architecture invariants These are the rules a reviewer will hold you to. The full list lives in [`AGENTS.md`](https://github.com/bloxbean/yano-x/blob/main/AGENTS.md). - Every optional behavior added to a running node crosses the plugin catalog boundary — stock state machines, capabilities, connectors, effects, observers, indexers, and product runtime behavior. - Pure libraries, DTOs, clients, codecs, testkits, CLIs, on-chain validators, and deterministic helpers are **not** runtime plugins unless the host independently selects or manages them. - Runtime activation goes through `PluginProviderRegistry` and a schema-v1 manifest. No raw `ServiceLoader`, direct host construction, product switches, or product-specific host CDI/REST activation. - Runtime plugins publish dependency-complete bundles, do not embed host SPI classes, declare compatible Yano API major and min/max levels, and have bounded lifecycle cleanup. - Package names remain `com.bloxbean.cardano.yano.appchain.*`; repository and artifact names use `yano-x`. - The plugin directory property is `yano.plugins.directory`. Do not reintroduce `yaci.plugins.directory`. - Yano X is JVM-only. - The app-chain feature has not had a public release: remove obsolete adapters, aliases, and duplicate activation paths rather than preserving accidental compatibility. Preserve documented Cardano node, OrderedLog, wire/storage, proof, replay, and distribution invariants. - Do not duplicate the artifact inventory in build logic or documentation. Update `config/artifacts-v1.json` and its verification tests. #### Coding conventions - Java 25, four-space indentation, lines at most 120 characters. - Prefer package imports to fully qualified names. - SLF4J for logging. - JUnit 5, Mockito, AssertJ. Unit tests in `src/test/java`; integration tests in the configured integration source sets, normally named `*IT`. - License: MIT. For deterministic state, avoid wall-clock time, randomness, environment-dependent iteration order, network calls, and node-local mutable decisions. Version any change to consensus semantics, state encoding, commitment profile, proof subject, or genesis-selected configuration — see [Consensus rules](https://yanox.dev/plugins/consensus-rules/). #### Working-tree and Git safety - Inspect `git status --short` before editing. Preserve unrelated or overlapping user changes. - Never stage or commit automatically. Before committing, show the exact files and diff intended and get approval. - Do not delete retained clusters, state, keys, or staging repositories without reviewing the exact path and obtaining explicit authorization. `stop` preserves state; `reset --yes` is destructive. - Keep ADRs, implementation, tests, artifact inventory, and user docs aligned when architecture or behavior changes. - Preprod operations submit real test-network transactions and spend test ADA. Require explicit authorization before fresh deployment, anchor bootstrap, settlement bootstrap, or smoke traffic. Never print seed contents, signing material, API credentials, or effect secrets. #### Working on this documentation site The site lives in `docsite/` and is independent of the Gradle build — it reads repository text files only, so it needs no Java and no Yano artifacts. ```bash cd docsite npm ci npm run dev # imports repo docs, then serves on localhost:4321 npm run build # imports, builds to dist/, generates the AI artifacts ``` `npm run import` regenerates the imported tutorial and state-machine pages from `docs/`, mirrors the App-Chain Studio into `public/studio/`, and re-renders every `` block from the repository's JSON catalogs. An unresolvable relative link in an imported document **fails the build** rather than shipping a 404. Edit the sources under `docs/` for imported pages, and `docsite/src/content/docs/` for authored ones. Never hand-edit content between catalog anchors — it is regenerated. The site's content strategy and design decisions are recorded in ADR-038, in the repository's `adr/` directory. #### Sources of truth Read only what the task needs: | Topic | Source | |---|---| | Repository split and ownership | ADR-030, in `adr/refactoring/` | | Plugin SPI and lifecycle | ADR-011, in `adr/app-layer/` | | Composition and portable proofs | ADR-031, in `adr/app-layer/` | | Module topology | [`settings.gradle`](https://github.com/bloxbean/yano-x/blob/main/settings.gradle) | | Artifact ids and bundle ids | [`config/artifacts-v1.json`](https://github.com/bloxbean/yano-x/blob/main/config/artifacts-v1.json) | | Build workflows | [BUILD_AND_TEST](https://github.com/bloxbean/yano-x/blob/main/docs/BUILD_AND_TEST.md), [BUILD_DISTRIBUTIONS](https://github.com/bloxbean/yano-x/blob/main/docs/BUILD_DISTRIBUTIONS.md) | | Open work | `adr/app-layer/open_item.md` |