role-approvals
role-approvals lets business actors approve things when those actors are not
the app ledger’s member nodes. Governed organizations, actors, keys, roles and
policies decide whether an exact payload hash is approved. The proposal and
its decision trail can be proved against the state root.
At a glance
Section titled “At a glance”| Machine id | role-approvals: the composite profile role-approvals-v1, with components domain-actors and role-approvals |
| Maturity | preview, JVM distribution |
| Topics | role-approvals.command.v1 (actor statements and policy governance); actors.command.v1 (organizations, actors and keys) |
| Actions | PROPOSE, APPROVE, REJECT, CANCEL, each an Ed25519-signed actor statement |
| Statuses | PENDING, APPROVED, REJECTED, CANCELLED, EXPIRED |
| State | proposal, policy, actor and organization records inside the composite; use the domain API’s proofKey |
| Proof subjects | role-approval-outcome-v1 (claims status, payload-digest); actor-role-assignment-v1 (claims status, has-role) |
| Outcome codes | RoleWorkflowResultCode; this profile does not store them per command |
| Effects | none |
How it works
Section titled “How it works”What you're seeing: policy order-release: a buyer proposes; two reviewer actors from distinct organizations must approve. The checks run in evaluation order. A failed check makes the command a final no-op; this profile keeps no per-command result, so read the proposal to see what happened.
- Sign offline Actor
buyer-ahashes the exact order bytes and signs a PROPOSE statement in its own wallet or HSM. The statement binds the chain, proposal id, policyorder-releaserevision 1, payload domain and hash, deadline height, actor revision and key.You sign one statement for one payload hash. Your key never reaches a member node.Nothing yet: the actor signs on its own system.Nothing to check yet../yano.sh appchain role sign --action propose --chain role-approval-chain --proposal order-1001 --policy order-release --policy-revision 1 --payload-domain com.example.order.v1 --payload-hash <hex> --deadline-height <tip + 90> --actor buyer-a --actor-revision 1 --key buyer-key-v1 --seed-file <file> - Relay Any member can relay the bytes on
role-approvals.command.v1. The member signs the envelope, but it gains no business authority: the actor’s signature inside decides. 202 only means queued.Hand the command bytes to any member’s API.You check the command’s shape and relay it. You cannot change it without breaking the signature.Still nothing final. - Propose In the final block every member evaluates the statement. All checks pass, so proposal
order-1001is created PENDING. The proposer is recorded but is not an approver.Your proposal is open. Reviewers can now sign for it.Your relay counted for nothing except transport.A PENDING proposal with no decisions.- passes: The statement names this chain
- passes: Its deadline height has not passed
- passes: The block’s signature-work budget has room
- passes: The actor, its organization and key are current and active
- passes: The signature verifies
- passes: No proposal with this id exists
- passes: The deadline is above the current height
- passes: The policy exists and the revision is current
- passes: The policy is ACTIVE
- passes: The deadline is within the policy’s lifetime
- passes: The actor holds a proposer role
- passes: Pending-proposal capacity has room
role-approvals state Proposal Status Accepted decisions order-1001PENDING none - First review
reviewer-afrom organizationaudit-onesigns APPROVE for clausereviewers. The decision is accepted: one organization of the two the clause needs.Your approval counts foraudit-one.Another relay; any member will do.One accepted decision, fromaudit-one.- passes: The statement names this chain
- passes: Its deadline height has not passed
- passes: The block’s signature-work budget has room
- passes: The actor, its organization and key are current and active
- passes: The signature verifies
- passes: The proposal exists
- passes: The proposal is PENDING
- passes: Policy, payload and deadline match the proposal
- passes: This actor has not decided on it yet
- passes: The actor has the clause’s role
- passes: No accepted decision in this clause from the same organization
role-approvals state Proposal Status Accepted decisions order-1001PENDING reviewer-a(audit-one) - Second review
reviewer-bfromaudit-twoapproves. Two distinct organizations satisfy the clause, so the proposal becomes APPROVED, a terminal status.The policy is satisfied.Relayed like the others.APPROVED, with two decisions from two organizations.- passes: The statement names this chain
- passes: Its deadline height has not passed
- passes: The block’s signature-work budget has room
- passes: The actor, its organization and key are current and active
- passes: The signature verifies
- passes: The proposal exists
- passes: The proposal is PENDING
- passes: Policy, payload and deadline match the proposal
- passes: This actor has not decided on it yet
- passes: The actor has the clause’s role
- passes: No accepted decision in this clause from the same organization
role-approvals state Proposal Status Accepted decisions order-1001APPROVED reviewer-a(audit-one),reviewer-b(audit-two) - Verify A verifier reads the proposal through the domain API and checks its proof. Subject
role-approval-outcome-v1supports the claimsstatus= APPROVED andpayload-digest= the hash of the exact order bytes. The machine stores the hash, not the order.Your application can now act on the approved hash.Members serve the proof; they do not vouch for it.Check the proof against a root you trust, then compare the payload hash with the bytes you hold.
What if: Same organization twice
reviewer-a2 also works for audit-one.
- Propose In the final block every member evaluates the statement. All checks pass, so proposal
order-1001is created PENDING. The proposer is recorded but is not an approver.Your proposal is open. Reviewers can now sign for it.Your relay counted for nothing except transport.A PENDING proposal with no decisions.- passes: The statement names this chain
- passes: Its deadline height has not passed
- passes: The block’s signature-work budget has room
- passes: The actor, its organization and key are current and active
- passes: The signature verifies
- passes: No proposal with this id exists
- passes: The deadline is above the current height
- passes: The policy exists and the revision is current
- passes: The policy is ACTIVE
- passes: The deadline is within the policy’s lifetime
- passes: The actor holds a proposer role
- passes: Pending-proposal capacity has room
role-approvals state Proposal Status Accepted decisions order-1001PENDING none - First review
reviewer-afrom organizationaudit-onesigns APPROVE for clausereviewers. The decision is accepted: one organization of the two the clause needs.Your approval counts foraudit-one.Another relay; any member will do.One accepted decision, fromaudit-one.- passes: The statement names this chain
- passes: Its deadline height has not passed
- passes: The block’s signature-work budget has room
- passes: The actor, its organization and key are current and active
- passes: The signature verifies
- passes: The proposal exists
- passes: The proposal is PENDING
- passes: Policy, payload and deadline match the proposal
- passes: This actor has not decided on it yet
- passes: The actor has the clause’s role
- passes: No accepted decision in this clause from the same organization
role-approvals state Proposal Status Accepted decisions order-1001PENDING reviewer-a(audit-one) - Second review
reviewer-a2approves. Its organization already has an accepted decision in this clause, so it does not count.Still one decision. The second organization is missing.- passes: The statement names this chain
- passes: Its deadline height has not passed
- passes: The block’s signature-work budget has room
- passes: The actor, its organization and key are current and active
- passes: The signature verifies
- passes: The proposal exists
- passes: The proposal is PENDING
- passes: Policy, payload and deadline match the proposal
- passes: This actor has not decided on it yet
- passes: The actor has the clause’s role
- fails: No accepted decision in this clause from the same organization
DISTINCTNESS_DUPLICATE
role-approvals state Proposal Status Accepted decisions order-1001PENDING reviewer-a(audit-one)
What if: A different payload
reviewer-b signs a hash that is not the proposal’s.
- Propose In the final block every member evaluates the statement. All checks pass, so proposal
order-1001is created PENDING. The proposer is recorded but is not an approver.Your proposal is open. Reviewers can now sign for it.Your relay counted for nothing except transport.A PENDING proposal with no decisions.- passes: The statement names this chain
- passes: Its deadline height has not passed
- passes: The block’s signature-work budget has room
- passes: The actor, its organization and key are current and active
- passes: The signature verifies
- passes: No proposal with this id exists
- passes: The deadline is above the current height
- passes: The policy exists and the revision is current
- passes: The policy is ACTIVE
- passes: The deadline is within the policy’s lifetime
- passes: The actor holds a proposer role
- passes: Pending-proposal capacity has room
role-approvals state Proposal Status Accepted decisions order-1001PENDING none - First review
reviewer-afrom organizationaudit-onesigns APPROVE for clausereviewers. The decision is accepted: one organization of the two the clause needs.Your approval counts foraudit-one.Another relay; any member will do.One accepted decision, fromaudit-one.- passes: The statement names this chain
- passes: Its deadline height has not passed
- passes: The block’s signature-work budget has room
- passes: The actor, its organization and key are current and active
- passes: The signature verifies
- passes: The proposal exists
- passes: The proposal is PENDING
- passes: Policy, payload and deadline match the proposal
- passes: This actor has not decided on it yet
- passes: The actor has the clause’s role
- passes: No accepted decision in this clause from the same organization
role-approvals state Proposal Status Accepted decisions order-1001PENDING reviewer-a(audit-one) - Second review
reviewer-b’s statement carries another payload hash. It does not match the proposal, so it cannot count toward it.Still one decision.- passes: The statement names this chain
- passes: Its deadline height has not passed
- passes: The block’s signature-work budget has room
- passes: The actor, its organization and key are current and active
- passes: The signature verifies
- passes: The proposal exists
- passes: The proposal is PENDING
- fails: Policy, payload and deadline match the proposal
CONFLICT - not reached: This actor has not decided on it yet
- not reached: The actor has the clause’s role
- not reached: No accepted decision in this clause from the same organization
role-approvals state Proposal Status Accepted decisions order-1001PENDING reviewer-a(audit-one)
What if: A revoked actor
reviewer-b’s actor record was revoked through governance before it signed.
- Propose In the final block every member evaluates the statement. All checks pass, so proposal
order-1001is created PENDING. The proposer is recorded but is not an approver.Your proposal is open. Reviewers can now sign for it.Your relay counted for nothing except transport.A PENDING proposal with no decisions.- passes: The statement names this chain
- passes: Its deadline height has not passed
- passes: The block’s signature-work budget has room
- passes: The actor, its organization and key are current and active
- passes: The signature verifies
- passes: No proposal with this id exists
- passes: The deadline is above the current height
- passes: The policy exists and the revision is current
- passes: The policy is ACTIVE
- passes: The deadline is within the policy’s lifetime
- passes: The actor holds a proposer role
- passes: Pending-proposal capacity has room
role-approvals state Proposal Status Accepted decisions order-1001PENDING none - First review
reviewer-afrom organizationaudit-onesigns APPROVE for clausereviewers. The decision is accepted: one organization of the two the clause needs.Your approval counts foraudit-one.Another relay; any member will do.One accepted decision, fromaudit-one.- passes: The statement names this chain
- passes: Its deadline height has not passed
- passes: The block’s signature-work budget has room
- passes: The actor, its organization and key are current and active
- passes: The signature verifies
- passes: The proposal exists
- passes: The proposal is PENDING
- passes: Policy, payload and deadline match the proposal
- passes: This actor has not decided on it yet
- passes: The actor has the clause’s role
- passes: No accepted decision in this clause from the same organization
role-approvals state Proposal Status Accepted decisions order-1001PENDING reviewer-a(audit-one) - Second review The current revision of
reviewer-bis not ACTIVE, so the actor is not eligible. The signature is never checked.Still one decision.- passes: The statement names this chain
- passes: Its deadline height has not passed
- passes: The block’s signature-work budget has room
- fails: The actor, its organization and key are current and active
UNAUTHORIZED_ACTOR - not reached: The signature verifies
role-approvals state Proposal Status Accepted decisions order-1001PENDING reviewer-a(audit-one)
What if: The relay edits the bytes
A relay member changes the clause in the statement before relaying it.
- Propose In the final block every member evaluates the statement. All checks pass, so proposal
order-1001is created PENDING. The proposer is recorded but is not an approver.Your proposal is open. Reviewers can now sign for it.Your relay counted for nothing except transport.A PENDING proposal with no decisions.- passes: The statement names this chain
- passes: Its deadline height has not passed
- passes: The block’s signature-work budget has room
- passes: The actor, its organization and key are current and active
- passes: The signature verifies
- passes: No proposal with this id exists
- passes: The deadline is above the current height
- passes: The policy exists and the revision is current
- passes: The policy is ACTIVE
- passes: The deadline is within the policy’s lifetime
- passes: The actor holds a proposer role
- passes: Pending-proposal capacity has room
role-approvals state Proposal Status Accepted decisions order-1001PENDING none - First review
reviewer-afrom organizationaudit-onesigns APPROVE for clausereviewers. The decision is accepted: one organization of the two the clause needs.Your approval counts foraudit-one.Another relay; any member will do.One accepted decision, fromaudit-one.- passes: The statement names this chain
- passes: Its deadline height has not passed
- passes: The block’s signature-work budget has room
- passes: The actor, its organization and key are current and active
- passes: The signature verifies
- passes: The proposal exists
- passes: The proposal is PENDING
- passes: Policy, payload and deadline match the proposal
- passes: This actor has not decided on it yet
- passes: The actor has the clause’s role
- passes: No accepted decision in this clause from the same organization
role-approvals state Proposal Status Accepted decisions order-1001PENDING reviewer-a(audit-one) - Second review The edited statement no longer matches
reviewer-b’s signature. A relay cannot change a signed decision.Editing a statement only makes it worthless.- passes: The statement names this chain
- passes: Its deadline height has not passed
- passes: The block’s signature-work budget has room
- passes: The actor, its organization and key are current and active
- fails: The signature verifies
INVALID_SIGNATURE
role-approvals state Proposal Status Accepted decisions order-1001PENDING reviewer-a(audit-one)
What if: Signed for another chain
A statement signed for a staging chain is replayed here.
- Propose In the final block every member evaluates the statement. All checks pass, so proposal
order-1001is created PENDING. The proposer is recorded but is not an approver.Your proposal is open. Reviewers can now sign for it.Your relay counted for nothing except transport.A PENDING proposal with no decisions.- passes: The statement names this chain
- passes: Its deadline height has not passed
- passes: The block’s signature-work budget has room
- passes: The actor, its organization and key are current and active
- passes: The signature verifies
- passes: No proposal with this id exists
- passes: The deadline is above the current height
- passes: The policy exists and the revision is current
- passes: The policy is ACTIVE
- passes: The deadline is within the policy’s lifetime
- passes: The actor holds a proposer role
- passes: Pending-proposal capacity has room
role-approvals state Proposal Status Accepted decisions order-1001PENDING none - Replay The statement names a different chain id, so it is refused before any other check. A signature cannot be reused across chains.No decisions.
- fails: The statement names this chain
WRONG_GENESIS - not reached: Its deadline height has not passed
- not reached: The block’s signature-work budget has room
- not reached: The actor, its organization and key are current and active
- not reached: The signature verifies
role-approvals state Proposal Status Accepted decisions order-1001PENDING none - fails: The statement names this chain
What if: A deadline too far away
order-release allows proposals to stay open for at most 100 blocks.
- Propose
buyer-aproposes with a deadline 500 blocks ahead. That exceeds the policy’s lifetime, so no proposal is created.No proposal exists.- passes: The statement names this chain
- passes: Its deadline height has not passed
- passes: The block’s signature-work budget has room
- passes: The actor, its organization and key are current and active
- passes: The signature verifies
- passes: No proposal with this id exists
- passes: The deadline is above the current height
- passes: The policy exists and the revision is current
- passes: The policy is ACTIVE
- fails: The deadline is within the policy’s lifetime
LIMIT_EXCEEDED - not reached: The actor holds a proposer role
- not reached: Pending-proposal capacity has room
role-approvals state Proposal Status Accepted decisions empty - Propose again A new statement with a deadline inside the lifetime creates the proposal.
- passes: The statement names this chain
- passes: Its deadline height has not passed
- passes: The block’s signature-work budget has room
- passes: The actor, its organization and key are current and active
- passes: The signature verifies
- passes: No proposal with this id exists
- passes: The deadline is above the current height
- passes: The policy exists and the revision is current
- passes: The policy is ACTIVE
- passes: The deadline is within the policy’s lifetime
- passes: The actor holds a proposer role
- passes: Pending-proposal capacity has room
role-approvals state Proposal Status Accepted decisions order-1001PENDING none
What if: Nobody approves in time
Expiry runs at the start of every block, not when someone touches the proposal.
- Propose In the final block every member evaluates the statement. All checks pass, so proposal
order-1001is created PENDING. The proposer is recorded but is not an approver.Your proposal is open. Reviewers can now sign for it.Your relay counted for nothing except transport.A PENDING proposal with no decisions.- passes: The statement names this chain
- passes: Its deadline height has not passed
- passes: The block’s signature-work budget has room
- passes: The actor, its organization and key are current and active
- passes: The signature verifies
- passes: No proposal with this id exists
- passes: The deadline is above the current height
- passes: The policy exists and the revision is current
- passes: The policy is ACTIVE
- passes: The deadline is within the policy’s lifetime
- passes: The actor holds a proposer role
- passes: Pending-proposal capacity has room
role-approvals state Proposal Status Accepted decisions order-1001PENDING none - First review
reviewer-afrom organizationaudit-onesigns APPROVE for clausereviewers. The decision is accepted: one organization of the two the clause needs.Your approval counts foraudit-one.Another relay; any member will do.One accepted decision, fromaudit-one.- passes: The statement names this chain
- passes: Its deadline height has not passed
- passes: The block’s signature-work budget has room
- passes: The actor, its organization and key are current and active
- passes: The signature verifies
- passes: The proposal exists
- passes: The proposal is PENDING
- passes: Policy, payload and deadline match the proposal
- passes: This actor has not decided on it yet
- passes: The actor has the clause’s role
- passes: No accepted decision in this clause from the same organization
role-approvals state Proposal Status Accepted decisions order-1001PENDING reviewer-a(audit-one) - The deadline passes The next block above the deadline height starts with a maintenance pass. It marks every pending proposal whose deadline is below the block height EXPIRED, whatever messages the block carries.EXPIRED, with the one decision it collected.
role-approvals state Proposal Status Accepted decisions order-1001EXPIRED reviewer-a(audit-one) - A late review
reviewer-bapproves after the deadline. The statement’s own deadline has passed, so it is refused at the second check.Still EXPIRED.- passes: The statement names this chain
- fails: Its deadline height has not passed
EXPIRED - not reached: The block’s signature-work budget has room
- not reached: The actor, its organization and key are current and active
- not reached: The signature verifies
role-approvals state Proposal Status Accepted decisions order-1001EXPIRED reviewer-a(audit-one)
What if: Cancel
Only the proposer can cancel its pending proposal.
- Propose In the final block every member evaluates the statement. All checks pass, so proposal
order-1001is created PENDING. The proposer is recorded but is not an approver.Your proposal is open. Reviewers can now sign for it.Your relay counted for nothing except transport.A PENDING proposal with no decisions.- passes: The statement names this chain
- passes: Its deadline height has not passed
- passes: The block’s signature-work budget has room
- passes: The actor, its organization and key are current and active
- passes: The signature verifies
- passes: No proposal with this id exists
- passes: The deadline is above the current height
- passes: The policy exists and the revision is current
- passes: The policy is ACTIVE
- passes: The deadline is within the policy’s lifetime
- passes: The actor holds a proposer role
- passes: Pending-proposal capacity has room
role-approvals state Proposal Status Accepted decisions order-1001PENDING none - A reviewer cancels
reviewer-asends CANCEL. Only the proposer may cancel.Still PENDING.- passes: The statement names this chain
- passes: Its deadline height has not passed
- passes: The block’s signature-work budget has room
- passes: The actor, its organization and key are current and active
- passes: The signature verifies
- passes: The proposal exists
- passes: The proposal is PENDING
- passes: Policy, payload and deadline match the proposal
- fails: The sender is the proposer
UNAUTHORIZED_ACTOR
role-approvals state Proposal Status Accepted decisions order-1001PENDING none - The proposer cancels
buyer-asends CANCEL for its own proposal. It becomes CANCELLED, a terminal status.- passes: The statement names this chain
- passes: Its deadline height has not passed
- passes: The block’s signature-work budget has room
- passes: The actor, its organization and key are current and active
- passes: The signature verifies
- passes: The proposal exists
- passes: The proposal is PENDING
- passes: Policy, payload and deadline match the proposal
- passes: The sender is the proposer
role-approvals state Proposal Status Accepted decisions order-1001CANCELLED none
- business actor
- relay member
- deterministic state machine
- verifier
- approved
- no-op or terminal
Lifecycle
Section titled “Lifecycle”| Status | Reached by | Terminal |
|---|---|---|
PENDING | an accepted PROPOSE | no |
APPROVED | an accepted APPROVE that satisfies every clause | yes |
REJECTED | an accepted REJECT, when the policy allows rejection | yes |
CANCELLED | CANCEL by the proposer, or a governed cancellation | yes |
EXPIRED | the first block whose height is above the deadline height | yes |
Expiry runs at the start of every block: a maintenance pass marks each pending
proposal whose deadline height is below the block height EXPIRED, whatever
the block contains. A block needs at least one message on the chain, so with
no traffic the status changes at the next block.
Each actor makes one decision per proposal. Sending the same statement again
is EXACT_REPLAY; sending a different one is CONFLICT. The proposer is not
an approver unless it also signs an APPROVE.
Evaluation order and outcome codes
Section titled “Evaluation order and outcome codes”Every statement passes these checks first:
| Order | Check | Outcome when it fails |
|---|---|---|
| 1 | The statement names this chain | WRONG_GENESIS |
| 2 | Its deadline height is not below the block height | EXPIRED |
| 3 | The block’s signature-work budget has room | CRYPTO_WORK_EXCEEDED |
| 4 | The actor revision, its organization and its key are current and active | UNAUTHORIZED_ACTOR |
| 5 | The signature verifies | INVALID_SIGNATURE |
Then, by action:
| Action | Checks, in order |
|---|---|
PROPOSE | no proposal with this id (EXACT_REPLAY or CONFLICT); deadline above the current height (EXPIRED); the policy exists (UNKNOWN_RECORD) and the revision is current (WRONG_REVISION); the policy is active (UNAUTHORIZED_ACTOR); the deadline is within the policy’s lifetime (LIMIT_EXCEEDED); the actor holds a proposer role (ROLE_MISMATCH); pending capacity has room (CAPACITY_EXCEEDED) |
APPROVE, REJECT | the proposal exists (UNKNOWN_RECORD) and is pending (TERMINAL); policy, payload and deadline match it (CONFLICT); the actor has not decided yet (EXACT_REPLAY or CONFLICT); the actor has the clause’s role (ROLE_MISMATCH); no decision in this clause from the same organization when the clause is distinct by organization (DISTINCTNESS_DUPLICATE); for REJECT, the policy allows rejection (ROLE_MISMATCH) |
CANCEL | the proposal exists (UNKNOWN_RECORD) and is pending (TERMINAL); it matches (CONFLICT); the sender is the proposer (UNAUTHORIZED_ACTOR) |
A failed check makes the command a final no-op. This profile keeps no per-command result, so read the proposal to see what happened: its status and its accepted decisions.
When to use it
Section titled “When to use it”Use role-approvals when:
- an employee, auditor, regulator, service or device must sign independently of the member node that relays its command;
- a policy needs roles, 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 when member nodes themselves are the approvers.
Use role-evidence for the complete evidence release and publication flow, and
a reviewed composite when an approval must atomically drive another transition.
The machine stores no payload, runs nothing and emits no effect. An approval
proves authorization of (payloadDomain, payloadHash); your application binds
those bytes to its next action.
Identity model
Section titled “Identity model”outer envelope sender = the member that relayed the commandsigned actor identity = the business actor authorizing the payload hashREST authentication controls access to an endpoint. It is neither a member vote nor an actor signature. An actor statement binds its action, chain, proposal, policy and revision, payload domain and hash, deadline, actor revision, key and clause, so a signature cannot be reused for another chain, payload or decision.
Create a project
Section titled “Create a project”From the top-level directory of the extracted release:
./yano.sh appchain init --non-interactive \ --recipe role-approval --network devnet --members 3 \ --runtime jvm --deployment host \ --http-port-base 7070 --server-port-base 13337 \ --name role-approval-chain --chain-id role-approval-chain \ --output role-approval-chainOn devnet, prepare generates the member keys, pins their public keys in the
blueprint and renders the project. On a public network, add the reviewed public
member keys to appchain.yaml and run ./yano.sh appchain render instead.
./yano.sh appchain prepare role-approval-chain./yano.sh appchain doctor role-approval-chain --distribution "$PWD"export YANO_HOME="$PWD"role-approval-chain/scripts/startdoctor reports DOCTOR_WARNINGS. APPLICATION_BOOTSTRAPPED stays PENDING
even after the bootstrap below: doctor runs offline and never queries the
chain, so the line is a reminder. Reading every record back is the check.
Bootstrap organizations, actors and policies
Section titled “Bootstrap organizations, actors and policies”bootstrap/role-approvals-plan.yaml is a non-secret plan for organizations,
actors, proof-of-possession, policies, governance and verification. Fill it
with public values only; actor private keys belong in the actor’s application,
KMS, HSM or vault.
Organizations, actors and policies are created through member governance. Each record is proposed by one member, which counts as its first approval, approved by further members up to the chain’s genesis threshold, then activated. Bootstrap is fail-closed and idempotent:
- Query the committed record and verify its proof.
- If the exact revision already exists, record the proof and skip it.
- If it is absent, submit
PROPOSE, collect member approvals, thenACTIVATE. - If an existing revision differs, stop; never replace it silently.
1. Create each actor’s key and its proof of possession. On devnet you can make disposable seeds; in a real deployment each actor does this in its own wallet or HSM and sends you only the public key and the proof:
mkdir -p actorsfor actor in proposer-a reviewer-a; do (umask 077; openssl rand -hex 32 > "actors/$actor.seed")donePROPOSER_KEY=$(./yano.sh appchain role public-key --seed-file actors/proposer-a.seed)REVIEWER_KEY=$(./yano.sh appchain role public-key --seed-file actors/reviewer-a.seed)
./yano.sh appchain role key-proof --chain role-approval-chain \ --actor proposer-a --actor-revision 1 --key proposer-key-v1 \ --public-key "$PROPOSER_KEY" --valid-from-height 1 --valid-until-height 0 \ --seed-file actors/proposer-a.seed > actors/proposer-a.proof./yano.sh appchain role key-proof --chain role-approval-chain \ --actor reviewer-a --actor-revision 1 --key reviewer-key-v1 \ --public-key "$REVIEWER_KEY" --valid-from-height 1 --valid-until-height 0 \ --seed-file actors/reviewer-a.seed > actors/reviewer-a.proof2. Fill in the plan. Replace every REPLACE_* value: here, empty metadata
commitments and the two public keys, in the order the plan lists the actors:
python3 - role-approval-chain/bootstrap/role-approvals-plan.yaml \ role-approvals-plan.yaml "$PROPOSER_KEY" "$REVIEWER_KEY" <<'PY'import syssource, target, proposer, reviewer = sys.argv[1:]text = open(source).read().replace("REPLACE_64_HEX_OR_EMPTY", '""')text = text.replace("REPLACE_64_HEX", proposer, 1).replace("REPLACE_64_HEX", reviewer, 1)open(target, "w").write(text)PY3. Encode the governance commands. role bootstrap turns the plan into one
step per record, in dependency order, with the mutation, its hash, and the
encoded propose, approve and activate commands for that record’s topic. It
refuses a plan with placeholders left, and an actor key without a valid proof:
API0=http://127.0.0.1:7070/api/v1/app-chain/chains/role-approval-chainAPI1=http://127.0.0.1:7071/api/v1/app-chain/chains/role-approval-chainHEIGHT=$(curl -s "$API0/status" | jq -r .tipHeight)
./yano.sh appchain role bootstrap --plan role-approvals-plan.yaml \ --expiry-height $((HEIGHT + 500)) \ --key-proof actors/proposer-a.proof --key-proof actors/reviewer-a.proof \ > bootstrap.jsonjq '[.steps[] | {record, id, topic}]' bootstrap.jsonThe expiry must be above the current height and at most
machines.composite.roles.maximum-mutation-lifetime-blocks (1000 by default)
beyond it. One expiry covers every step, so leave room for all of them: a
propose that arrives after it is ignored. Organizations go to
actors.command.v1 before the actors that belong to them; policies go to
role-approvals.command.v1. If you created the project without
--http-port-base, its members listen on 8080 and up: adjust API0 and
API1.
4. Submit each step. A member’s approval is the envelope it relays, so send the approve through a different member than the propose. With the generated threshold of 2, one extra approval is enough:
submit() { # <member API> <topic> <command hex>; waits until final local id attempt id=$(curl -sf -X POST "$1/messages" -H 'Content-Type: application/json' \ -d "$(jq -nc --arg t "$2" --arg b "$3" '{topic:$t, bodyHex:$b}')" \ | jq -er .messageId) || { echo "submission refused by $1" >&2; return 1; } for attempt in $(seq 1 120); do curl -sf "$1/messages/$id" | jq -e .height >/dev/null && return 0 sleep 1 done echo "message $id not final after 120 s" >&2 return 1}
jq -c '.steps[]' bootstrap.json | while read -r step; do topic=$(jq -r .topic <<<"$step") submit "$API0" "$topic" "$(jq -r .propose <<<"$step")" submit "$API1" "$topic" "$(jq -r .approve <<<"$step")" submit "$API0" "$topic" "$(jq -r .activate <<<"$step")" echo "activated $(jq -r '.record + " " + .id' <<<"$step")"doneA final activate is not proof that the record exists. An activate below the threshold does nothing, and a later one can still succeed. An activate that reaches the threshold but cannot apply, for example an actor whose organization is not active yet, marks that attempt failed for good; so does any approve or activate after the expiry. Read each record back, from any member:
BUNDLE=http://127.0.0.1:7072/api/v1/plugins/org.yanoproject.x.role-workflowfor record in organizations/organization-a organizations/organization-b \ actors/proposer-a actors/reviewer-a policies/application-approval; do curl -s "$BUNDLE/$record?chain=role-approval-chain" | jq -c '.record'done✓ You should see five records with
"revision":1; the organizations and actors also show"status":"ACTIVE".
If a record is missing after its activate, the attempt expired or failed and
its mutation id is spent. Fix the cause, then run role bootstrap again with
--attempt 2 (then 3, and so on) and a fresh expiry, and resubmit only the
steps whose records are missing.
To change a record later, raise its revision in a copy of the plan that holds
only that record, and run the same steps. For an actor, the new revision must
keep every existing key with the same public key and validFromHeight; mark
each kept key proofOfPossession: OPTIONAL and pass --key-proof only for the
keys the revision adds, signed with --actor-revision set to the new revision.
Sign and submit a decision
Section titled “Sign and submit a decision”Hash the exact canonical application bytes before actors sign them. The payload
domain names that byte contract, for example com.example.release.v1. Each
statement binds the proposal, the policy revision, the payload hash and a
deadline height:
printf 'release-7' > release-7.binPAYLOAD_HASH=$(openssl dgst -sha256 -binary release-7.bin | xxd -p -c 256)DEADLINE=$(( $(curl -s "$API0/status" | jq -r .tipHeight) + 200 ))
sign() { ./yano.sh appchain role sign --chain role-approval-chain \ --proposal release-7 --policy application-approval --policy-revision 1 \ --payload-domain com.example.release.v1 --payload-hash "$PAYLOAD_HASH" \ --deadline-height "$DEADLINE" --actor-revision 1 "$@"}
submit "$API0" role-approvals.command.v1 "$(sign --action propose \ --actor proposer-a --key proposer-key-v1 --seed-file actors/proposer-a.seed)"submit "$API0" role-approvals.command.v1 "$(sign --action approve \ --actor reviewer-a --key reviewer-key-v1 --clause reviewers \ --seed-file actors/reviewer-a.seed)"Any member can relay an actor statement; the actor’s signature, not the member, is what counts.
When API authentication is enabled (yano.app-chain.api.auth.enabled=true),
add -H "X-API-Key: $API_KEY" to every request. A generated project keeps its
local key as YANO_APPCHAIN_API_KEYS in secrets/node0.env.
202 means queued, not approved. submit waits for finality; then read the
proposal below. The reviewers clause needs one reviewer, counted once per
organization, so reviewer-a’s approval makes the status "APPROVED".
Query and verify
Section titled “Query and verify”The bundle exposes read-only routes:
BASE=http://127.0.0.1:7070/api/v1BUNDLE=org.yanoproject.x.role-workflow
curl -sS "$BASE/plugins/$BUNDLE/proposals/release-7?chain=role-approval-chain" | jq .record.statuscurl -sS "$BASE/plugins/$BUNDLE/stats?chain=role-approval-chain" | jq .Organizations, actors and policies follow the same pattern:
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. A route that resolves the current revision also returns the
current-pointer key and value. Verify both proofs at the same height and root:
the record proof shows that a revision exists, and the pointer proof shows it
was current. The physical keys are namespaced by the composite profile; always
prove the returned proofKey.
Submit from Java
Section titled “Submit from Java”implementation "org.yanoproject.x:yano-x-client:${yanoXVersion}"implementation "org.yanoproject.x:yano-x-role-workflow-contracts:${yanoXVersion}"var statement = new ActorStatementV1( ActorStatementV1.Action.APPROVE, "role-approval-chain", "release-7", "application-approval", 1, "com.example.release.v1", payloadHash, deadlineHeight, "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") .build();
var submitted = client.submit("role-approvals.command.v1", command);In production, sign ActorStatementV1.signingPreimage() in a KMS or HSM rather
than loading a raw seed into application memory.
Advanced
Section titled “Advanced”Limits
Section titled “Limits”- Roles are normalized strings, not a fixed enum.
- Policies have proposer roles, at most 16 clauses, minimum counts, distinctness by actor or organization, a rejection mode and a maximum lifetime.
- Actor and policy revisions are immutable; current pointers are governed.
- An actor keeps at most 16 key epochs.
- Pending proposals are bounded in total and per actor, policy and deadline.
Acting on an approval
Section titled “Acting on an approval”The profile emits no effect, because an approved hash is not executable bytes. An application can:
- query the terminal proposal and act idempotently off the ledger;
- submit a separately validated command bound to the proposal; or
- install a reviewed composite that consumes the approval atomically, as
authenticated-mapdoes forapprovalcollections.
Do not attach an arbitrary executor and assume it knows which bytes were approved.
Operations and recovery
Section titled “Operations and recovery”- Monitor pending, rejected and expired proposal counts.
- Rotate a key by governing the actor’s next revision with proof-of-possession.
- Suspend or revoke a compromised actor, then govern cancellation of its pending proposals.
- Approved decisions stay immutable after rotation or revocation.
- A changed profile digest, component order, route or state identity is a consensus upgrade: it needs governed activation or a new chain.
The full wire, governance and recovery model is in Domain Actors and Role-Aware Approvals, the portable contracts, and the implementation guide.