Skip to content

Tutorial 9 — From Local Demo to a Permissioned Pilot

Open a pilot starting point in App-Chain Studio

  • Goal: turn a successful tutorial into a reviewable project and a deployment plan.
  • You’ll learn: what the project files hold, where each secret lives, the status line each project command prints, and which decisions to record before traffic.
  • Before you start: an extracted Yano X JVM distribution and Java 25. The commands run offline; nothing here starts a node or spends test ADA. Tutorial 1 helps but is not required.
  • Level: advanced; for platform, security, and application leads
  • Time: about 20 minutes for the commands, then a planning session
  • Outcome: a validated pilot project, a doctor report against your exact release, and a written plan that does not inherit local-demo assumptions.

This is a decision checklist rather than one launch command. Yano remains pre-release. The target is a controlled permissioned pilot, not an unqualified production or public-chain claim.

The project commands move you from remembered YAML options to a blueprint, rendered configuration, and a lock file. Each one prints a status line you can check.

From project to pilotIllustrative data

What you're seeing: the commands that turn a generated project into a reviewed pilot, with the status line each one prints. The “What if” scenarios show what each check refuses.

  1. Initialize init writes the blueprint appchain.yaml, the generated runtime files, and appchain.lock. Without --member-key values the members are placeholders, so the lock records the acknowledgement PUBLIC_MEMBER_IDENTITIES_REQUIRED_BEFORE_START.
    ./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
    What init generates
    FileHoldsWho edits it
    appchain.yamlpublic intent: recipe, network, members, hostsyou
    config/shared-consensus.yamlvalues identical on every membergenerated
    config/nodes/nodeN.yamlone node's ports, peers, paths, secret namesgenerated
    secrets/nodeN.env.examplenames of the private valuesgenerated
    appchain.lockresolved values and file digestsgenerated
  2. Sort the secrets Private material never enters the project. Each secrets/nodeN.env.example names the values that member must supply, and the project's .gitignore excludes secrets/*.env. Each value stays with its owner.
    Where each secret lives
    MaterialHeld byIn the project
    Member signing keythat member's secret managername only: YANO_APPCHAIN_SIGNING_KEY
    API keythat member's gateway or secret managername only: YANO_APPCHAIN_API_KEYS
    Anchor wallet keythe anchor leader onlyname only, if an anchor is selected
    Connector credentialthe executor host onlyno
    Business actor keythe actor's organizationno
    Member public keyeveryoneyes, in appchain.yaml
  3. Pin identities Edit appchain.yaml: add each member's public key and its host, in matching order. A public key is not a secret. Edit only this file; the other files are regenerated from it.
  4. Render render regenerates the derived files and the lock from appchain.yaml. With the keys pinned, the acknowledgement is gone. It refuses to overwrite a generated file that was edited by hand.
    ./yano.sh appchain render permissioned-pilot
  5. Validate Validation checks the blueprint, the lock, the catalogs, the resolved configuration, and every generated file's digest.
    ./yano.sh appchain config validate --mode project permissioned-pilot
  6. Doctor Doctor checks the project against the exact release you will run. Its last line is DOCTOR_OK, DOCTOR_WARNINGS, or DOCTOR_FAILED. Here it warns: a rotating sequencer needs a synchronized L1 slot view, which you must provide. A warning is a to-do item; a failure blocks.
    ./yano.sh appchain doctor permissioned-pilot \
      --distribution /path/to/yano-x-jvm-<version>.zip
    Readiness stages in the report
    StageStatus
    CONFIG_VALIDPASS
    ARTIFACTS_READYPASS
    IDENTITIES_READYPASS
    RUNTIME_STARTABLEPASS
    APPLICATION_BOOTSTRAPPEDNOT_REQUIRED
    EXECUTORS_READYNOT_REQUIRED
    EXTERNAL_TARGETS_READYPENDING
    OUTCOME_READYPENDING
  7. Export for VMs For existing Linux VMs, export an Ansible deployment from the same project. The export contains the consensus configuration and member placement, but no private keys: each member's nodeN.env comes from its own secret store.
    ./yano.sh appchain gitops permissioned-pilot --target ansible \
      --output permissioned-pilot-vms

What if: Skip pinning identities

The project validates, but nothing can start or be exported until the members are real.

  1. Initialize init writes the blueprint appchain.yaml, the generated runtime files, and appchain.lock. Without --member-key values the members are placeholders, so the lock records the acknowledgement PUBLIC_MEMBER_IDENTITIES_REQUIRED_BEFORE_START.
    ./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
    What init generates
    FileHoldsWho edits it
    appchain.yamlpublic intent: recipe, network, members, hostsyou
    config/shared-consensus.yamlvalues identical on every membergenerated
    config/nodes/nodeN.yamlone node's ports, peers, paths, secret namesgenerated
    secrets/nodeN.env.examplenames of the private valuesgenerated
    appchain.lockresolved values and file digestsgenerated
  2. Sort the secrets Private material never enters the project. Each secrets/nodeN.env.example names the values that member must supply, and the project's .gitignore excludes secrets/*.env. Each value stays with its owner.
    Where each secret lives
    MaterialHeld byIn the project
    Member signing keythat member's secret managername only: YANO_APPCHAIN_SIGNING_KEY
    API keythat member's gateway or secret managername only: YANO_APPCHAIN_API_KEYS
    Anchor wallet keythe anchor leader onlyname only, if an anchor is selected
    Connector credentialthe executor host onlyno
    Business actor keythe actor's organizationno
    Member public keyeveryoneyes, in appchain.yaml
  3. Doctor Doctor lists the acknowledgement as a warning, and the identity and runtime stages stay pending. The result is DOCTOR_WARNINGS, not a failure, so read the list rather than the exit code.
    ./yano.sh appchain doctor permissioned-pilot \
      --distribution /path/to/yano-x-jvm-<version>.zip
    Lines that change
    CheckStatus
    acknowledgement PUBLIC_MEMBER_IDENTITIES_REQUIRED_BEFORE_STARTWARN
    IDENTITIES_READYPENDING
    RUNTIME_STARTABLEPENDING
  4. Export refused The Ansible export needs one public key and one host per member, so it refuses the project. The generated start scripts refuse it too: “Public member identities are missing”.
    ./yano.sh appchain gitops permissioned-pilot --target ansible \
      --output permissioned-pilot-vms
    • passes: Host deployment on the JVM runtime
    • fails: One public key and one host per member

What if: Edit a generated file

Generated files are output. A hand edit is detected by its digest.

  1. Initialize init writes the blueprint appchain.yaml, the generated runtime files, and appchain.lock. Without --member-key values the members are placeholders, so the lock records the acknowledgement PUBLIC_MEMBER_IDENTITIES_REQUIRED_BEFORE_START.
    ./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
    What init generates
    FileHoldsWho edits it
    appchain.yamlpublic intent: recipe, network, members, hostsyou
    config/shared-consensus.yamlvalues identical on every membergenerated
    config/nodes/nodeN.yamlone node's ports, peers, paths, secret namesgenerated
    secrets/nodeN.env.examplenames of the private valuesgenerated
    appchain.lockresolved values and file digestsgenerated
  2. Edit a node file Suppose someone changes a port directly in config/nodes/node0.yaml instead of in appchain.yaml.
  3. Render render compares every generated file with the digest in the lock and stops: “Generated file has manual edits: config/nodes/node0.yaml; move the change into appchain.yaml or reconcile it explicitly”.
    ./yano.sh appchain render permissioned-pilot

What if: Doctor another release

Doctor pins the project to the release it was generated for.

  1. Initialize init writes the blueprint appchain.yaml, the generated runtime files, and appchain.lock. Without --member-key values the members are placeholders, so the lock records the acknowledgement PUBLIC_MEMBER_IDENTITIES_REQUIRED_BEFORE_START.
    ./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
    What init generates
    FileHoldsWho edits it
    appchain.yamlpublic intent: recipe, network, members, hostsyou
    config/shared-consensus.yamlvalues identical on every membergenerated
    config/nodes/nodeN.yamlone node's ports, peers, paths, secret namesgenerated
    secrets/nodeN.env.examplenames of the private valuesgenerated
    appchain.lockresolved values and file digestsgenerated
  2. Pin identities Edit appchain.yaml: add each member's public key and its host, in matching order. A public key is not a secret. Edit only this file; the other files are regenerated from it.
  3. Render render regenerates the derived files and the lock from appchain.yaml. With the keys pinned, the acknowledgement is gone. It refuses to overwrite a generated file that was edited by hand.
    ./yano.sh appchain render permissioned-pilot
  4. Validate Validation checks the blueprint, the lock, the catalogs, the resolved configuration, and every generated file's digest.
    ./yano.sh appchain config validate --mode project permissioned-pilot
  5. Doctor Pointed at a different release, the release capability index does not match the one in the lock, so the distribution-index check fails and the result is DOCTOR_FAILED. Use the release named in the lock.
    ./yano.sh appchain doctor permissioned-pilot \
      --distribution /path/to/another-release.zip
  • you
  • offline CLI
  • project files
  • secret stores

List what this release offers, then generate the project:

Terminal window
./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

✓ You should see a line starting with PROJECT_INITIALIZED that ends with acknowledgements=[PUBLIC_MEMBER_IDENTITIES_REQUIRED_BEFORE_START].

The generated runtime files are nested YAML:

  • config/shared-consensus.yaml holds the values every member must share;
  • config/nodes/nodeN.yaml holds node-local ports, peers, paths, and secret references; and
  • appchain.lock records the exact resolved values and the digests of the generated files.

Customize appchain.yaml, not those derived files. Each member signs with its own Ed25519 key. Every member operator creates that key on its own machine, keeps the private file there, and sends you only the public key. With OpenSSL 3 (the LibreSSL that macOS ships has no Ed25519; use Homebrew’s openssl@3):

Terminal window
umask 077
openssl genpkey -algorithm ed25519 -out member-signing-key.pem
openssl pkey -in member-signing-key.pem -pubout -outform DER \
| tail -c 32 | od -An -tx1 | tr -d ' \n'; echo

The last command prints the member’s 64-hex public key. Add the keys and the members’ hostnames under spec.chains[0].topology in appchain.yaml, in the same order, so the first key belongs to the first host:

topology:
members: 3
memberKeys:
- "<member 0 public key, 64 hex>"
- "<member 1 public key>"
- "<member 2 public key>"
nodeHosts:
- "member0.pilot.example"
- "member1.pilot.example"
- "member2.pilot.example"

If you have the keys before you run init, pass them to it instead: repeat --member-key <64-hex> and --node-host <hostname> once per member, in order.

Then regenerate and check the project:

Terminal window
./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-x-jvm-<version>

✓ You should see PROJECT_RENDERED, then VALID_PROJECT, then the doctor report. Its last line is DOCTOR_OK, DOCTOR_WARNINGS, or DOCTOR_FAILED.

Read the doctor report line by line. Each line is a status, a check, and a detail. FAIL blocks a start. WARN and PENDING are a to-do list:

  • Until real public keys are pinned, the report warns about PUBLIC_MEMBER_IDENTITIES_REQUIRED_BEFORE_START, and IDENTITIES_READY stays PENDING. The generated start scripts refuse to start the project.
  • This project’s rotating sequencer needs a synchronized L1 slot view, so EXTERNAL_TARGETS_READY stays PENDING until you provide one. The result is DOCTOR_WARNINGS, not DOCTOR_OK.

Once the report has no FAIL, export the deployment for your VMs. The output directory must be missing or empty:

Terminal window
./yano.sh appchain gitops permissioned-pilot --target ansible \
--output permissioned-pilot-ansible

✓ You should see a line starting with GITOPS_EXPORTED target=ansible. The export holds an inventory, a playbook, each node’s configuration and a systemd unit template. It refers to each member’s signing key by the variable YANO_APPCHAIN_SIGNING_KEY and contains no private key.

./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. Do not invoke the internal executable. Cluster startup does not run init, render, or project validation. Use the generated project for a multi-machine bootstrap, and the cluster command for the packaged local acceptance environment.

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.

Each member supplies its own secrets/nodeN.env from the generated secrets/nodeN.env.example. The project’s .gitignore excludes secrets/*.env, and the Ansible export never embeds private keys.

YANO_APPCHAIN_SIGNING_KEY is the 64-hex seed inside the member’s member-signing-key.pem. On the member’s own machine, next to its copy of the project, this writes it into the env file without printing it, here for node 0:

Terminal window
umask 077
SEED=$(openssl pkey -in member-signing-key.pem -outform DER \
| tail -c 32 | od -An -tx1 | tr -d ' \n')
printf 'YANO_APPCHAIN_SIGNING_KEY=%s\nYANO_APPCHAIN_API_KEYS=%s\n' \
"$SEED" "<this member's REST API key>" > permissioned-pilot/secrets/node0.env
unset SEED
MaterialPurposeRecommended owner/storage
Member signing keyApp-block votes and envelopesOne per member, KMS/HSM/secret manager
Business actor keyDomain authorizationActor organization or delegated signing service
API keyREST authorization/scopesAPI gateway/secret manager
Anchor wallet keyCardano fees/collateralCapped hot wallet on anchor leader
Connector credentialKafka/S3/IPFS/API accessExecutor host only
TLS key/trust rootsTransport identityPlatform PKI

Never copy the deterministic demo keys, launcher API key, sample actor seeds, or local connector credentials into a shared environment.

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

Section titled “5. Replace demo infrastructure deliberately”
Demo componentPilot decision
RustFSAWS S3 or reviewed compatible service; versioning/retention policy
Single KuboManaged/redundant IPFS pinning and retrieval policy
Local KafkaTLS/mTLS/SASL cluster, ACLs, consumer deduplication
Local webhookNamed allow-listed target, authentication, idempotent receiver
Devnet anchor walletDedicated funded preview/preprod key with spend cap
Docker-local secretsKMS/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.

  • 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.
  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.

  • 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 is outside the current permissioned model. Consensus recovers from an offline leader through a certified view change, but by default it assumes no dishonest members. Choose the threshold for the faults you must survive; see Yano’s consensus guide.
  • 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.
You seeWhat it means and what to do
Generated file has manual edits: ...; move the change into appchain.yaml or reconcile it explicitlyA generated file was edited by hand. Move the change into appchain.yaml and render again.
DOCTOR_FAILED with a distribution-index failureThe --distribution you pointed at is not the release the project was generated for. Use the matching release.
DOCTOR_FAILED with a tool-version failureYou ran doctor from a different release than the one the project pins. Use that release’s yano.sh.
Ansible export requires a JVM host project with one public key and hostname per memberPin every member’s public key and host in appchain.yaml, then render again.

Next: plan a real deployment with Deploy Yano X.