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.
1. Generate a reviewable pilot project
Section titled “1. Generate a reviewable pilot project”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.
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.
- Initialize
initwrites the blueprintappchain.yaml, the generated runtime files, andappchain.lock. Without--member-keyvalues the members are placeholders, so the lock records the acknowledgementPUBLIC_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-interactiveWhat initgeneratesFile Holds Who edits it appchain.yamlpublic intent: recipe, network, members, hosts you config/shared-consensus.yamlvalues identical on every member generated config/nodes/nodeN.yamlone node's ports, peers, paths, secret names generated secrets/nodeN.env.examplenames of the private values generated appchain.lockresolved values and file digests generated - Sort the secrets Private material never enters the project. Each
secrets/nodeN.env.examplenames the values that member must supply, and the project's.gitignoreexcludessecrets/*.env. Each value stays with its owner.Where each secret lives Material Held by In the project Member signing key that member's secret manager name only: YANO_APPCHAIN_SIGNING_KEYAPI key that member's gateway or secret manager name only: YANO_APPCHAIN_API_KEYSAnchor wallet key the anchor leader only name only, if an anchor is selected Connector credential the executor host only no Business actor key the actor's organization no Member public key everyone yes, in appchain.yaml - 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. - Render
renderregenerates the derived files and the lock fromappchain.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 - 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 - Doctor Doctor checks the project against the exact release you will run. Its last line is
DOCTOR_OK,DOCTOR_WARNINGS, orDOCTOR_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>.zipReadiness stages in the report Stage Status CONFIG_VALIDPASS ARTIFACTS_READYPASS IDENTITIES_READYPASS RUNTIME_STARTABLEPASS APPLICATION_BOOTSTRAPPEDNOT_REQUIRED EXECUTORS_READYNOT_REQUIRED EXTERNAL_TARGETS_READYPENDING OUTCOME_READYPENDING - 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.envcomes 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.
- Initialize
initwrites the blueprintappchain.yaml, the generated runtime files, andappchain.lock. Without--member-keyvalues the members are placeholders, so the lock records the acknowledgementPUBLIC_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-interactiveWhat initgeneratesFile Holds Who edits it appchain.yamlpublic intent: recipe, network, members, hosts you config/shared-consensus.yamlvalues identical on every member generated config/nodes/nodeN.yamlone node's ports, peers, paths, secret names generated secrets/nodeN.env.examplenames of the private values generated appchain.lockresolved values and file digests generated - Sort the secrets Private material never enters the project. Each
secrets/nodeN.env.examplenames the values that member must supply, and the project's.gitignoreexcludessecrets/*.env. Each value stays with its owner.Where each secret lives Material Held by In the project Member signing key that member's secret manager name only: YANO_APPCHAIN_SIGNING_KEYAPI key that member's gateway or secret manager name only: YANO_APPCHAIN_API_KEYSAnchor wallet key the anchor leader only name only, if an anchor is selected Connector credential the executor host only no Business actor key the actor's organization no Member public key everyone yes, in appchain.yaml - 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>.zipLines that change Check Status acknowledgementPUBLIC_MEMBER_IDENTITIES_REQUIRED_BEFORE_STARTWARN IDENTITIES_READYPENDING RUNTIME_STARTABLEPENDING - 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.
- Initialize
initwrites the blueprintappchain.yaml, the generated runtime files, andappchain.lock. Without--member-keyvalues the members are placeholders, so the lock records the acknowledgementPUBLIC_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-interactiveWhat initgeneratesFile Holds Who edits it appchain.yamlpublic intent: recipe, network, members, hosts you config/shared-consensus.yamlvalues identical on every member generated config/nodes/nodeN.yamlone node's ports, peers, paths, secret names generated secrets/nodeN.env.examplenames of the private values generated appchain.lockresolved values and file digests generated - Edit a node file Suppose someone changes a port directly in
config/nodes/node0.yamlinstead of inappchain.yaml. - Render
rendercompares 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.
- Initialize
initwrites the blueprintappchain.yaml, the generated runtime files, andappchain.lock. Without--member-keyvalues the members are placeholders, so the lock records the acknowledgementPUBLIC_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-interactiveWhat initgeneratesFile Holds Who edits it appchain.yamlpublic intent: recipe, network, members, hosts you config/shared-consensus.yamlvalues identical on every member generated config/nodes/nodeN.yamlone node's ports, peers, paths, secret names generated secrets/nodeN.env.examplenames of the private values generated appchain.lockresolved values and file digests generated - 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. - Render
renderregenerates the derived files and the lock fromappchain.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 - 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 - Doctor Pointed at a different release, the release capability index does not match the one in the lock, so the
distribution-indexcheck fails and the result isDOCTOR_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:
./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_INITIALIZEDthat ends withacknowledgements=[PUBLIC_MEMBER_IDENTITIES_REQUIRED_BEFORE_START].
The generated runtime files are nested YAML:
config/shared-consensus.yamlholds the values every member must share;config/nodes/nodeN.yamlholds node-local ports, peers, paths, and secret references; andappchain.lockrecords 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):
umask 077openssl genpkey -algorithm ed25519 -out member-signing-key.pemopenssl pkey -in member-signing-key.pem -pubout -outform DER \ | tail -c 32 | od -An -tx1 | tr -d ' \n'; echoThe 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:
./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, thenVALID_PROJECT, then the doctor report. Its last line isDOCTOR_OK,DOCTOR_WARNINGS, orDOCTOR_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, andIDENTITIES_READYstaysPENDING. The generated start scripts refuse to start the project. - This project’s rotating sequencer needs a synchronized L1 slot view, so
EXTERNAL_TARGETS_READYstaysPENDINGuntil you provide one. The result isDOCTOR_WARNINGS, notDOCTOR_OK.
Once the report has no FAIL, export the deployment for your VMs. The output
directory must be missing or empty:
./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 variableYANO_APPCHAIN_SIGNING_KEYand 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.
2. Freeze the application contract
Section titled “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
Section titled “3. Separate every identity and secret”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:
umask 077SEED=$(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.envunset SEED| 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
Section titled “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
Section titled “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
Section titled “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
Section titled “7. Run acceptance in layers”- Clean deterministic unit/conformance suites.
- Three-member packaged local cluster.
- Restart one member and prove catch-up/root parity.
- Stop/restart the full deployment from retained state.
- Connector fault matrix and duplicate-boundary tests.
- Load/soak test with recorded topology, rate, payloads, duration, lag, resources, and failures.
- Preview/preprod anchor smoke with independent L1 verification.
- 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
Section titled “8. Know the current escalation gates”- Material Cardano funds require the production action hardening tracked for
cardano.paymentand 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.
9. Choose the deployment shape
Section titled “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.
Troubleshooting
Section titled “Troubleshooting”| You see | What it means and what to do |
|---|---|
Generated file has manual edits: ...; move the change into appchain.yaml or reconcile it explicitly | A generated file was edited by hand. Move the change into appchain.yaml and render again. |
DOCTOR_FAILED with a distribution-index failure | The --distribution you pointed at is not the release the project was generated for. Use the matching release. |
DOCTOR_FAILED with a tool-version failure | You 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 member | Pin every member’s public key and host in appchain.yaml, then render again. |
Go deeper
Section titled “Go deeper”- Full user/operations guide
- Profile-governance runbook
- Canonical app-layer open items
- Evidence flow and trust limits
- Domain-role production signing and recovery
Next: plan a real deployment with Deploy Yano X.