Tenzro Testnet is live —request testnet TNZO

Multi-Party Workflows

The tenzro-workflow crate is a Canton-native multi-party workflow engine that turns a Tenzro chain transaction into a structured, multi-stakeholder process: a typed lifecycle, an obligations table per counterparty, an approvals graph governed by a small policy DSL, an optional fee route, an optional privacy domain, a kill-switch path, and a hash-chained audit receipt — all mirrored into Canton's Daml runtime through the same participant node used by the Canton bridge.

Object model

A workflow is a Workflow value with the following fields:

  • idWorkflowId derived from SHA-256("tenzro/workflow/id" || initiator || nonce_le || template_id)
  • template_id — reference to the workflow template (e.g. AutonomousProcurement, CantonTradeSettler)
  • initiator_did — TDIP DID of the workflow originator
  • counterparties — ordered set of TDIP DIDs that must sign or act on the workflow
  • obligations — per-counterparty Obligation records: { counterparty_did, action, deadline, status }
  • approvalsApproval records gated by policy-DSL expressions
  • fee_route_id — optional reference to a FeeRoute used for settlement payouts
  • privacy_domain_id — optional reference to a PrivacyDomain
  • state — typed state machine (see below)
  • signatures — map of did → Signature accumulating multi-party consent

Lifecycle

Draft  Active  AwaitingSignatures  Executing  Completed
                                              
                                                Cancelled / Disputed / Failed / Suspended  (terminal)

Transitions are validated by a fixed transition table; an invalid edge returns a typed WorkflowError rather than being silently coerced.

Privileged-VM selectors

Every state-changing operation flows through a signed transaction dispatched by the Native VM, not a privileged RPC. This makes the chain's block history the canonical workflow log and lets the workflow be replayed deterministically from any node.

  • 0x01000040CreateWorkflow
  • 0x01000041SubmitSignature
  • 0x01000042CompleteObligation
  • 0x01000043RecordApproval
  • 0x01000044TransitionState
  • 0x01000045RegisterFeeRoute
  • 0x01000046RegisterPrivacyDomain
  • 0x01000047MirrorToCanton
  • 0x01000048KillSwitchSuspend
  • 0x01000049KillSwitchCancel
  • 0x0100004AOpenDispute
  • 0x0100004BResolveDispute

All selectors enforce signer-vs-counterparty authorization at execution; an unauthorized signer returns a typed WorkflowError::Unauthorized instead of producing a partially-applied state.

Receipts

WorkflowReceipt {
  id: Hash,                          // SHA-256(canonical receipt bytes)
  workflow_id: WorkflowId,
  state_before: WorkflowState,
  state_after: WorkflowState,
  signer: Did,
  block_height: BlockHeight,
  prev_receipt: Hash,                // hash-chain link; Hash::default() at genesis
  payload_envelope: ReceiptEnvelope, // inline summary or DA pointer
}

Receipts are persisted under wf_receipt:<id> in CF_SETTLEMENTS; the chain head is stored in the workflow's WorkflowMeta.last_receipt. The full receipt history is recovered by walking prev_receipt backwards from the head until Hash::default(). The RPC tenzro_listWorkflowReceipts walks up to max entries on demand; receipts are not held in memory.

Canton mirror

When a workflow is mirrored to Canton (selector 0x01000047), the same receipt is projected into a Tenzro.Workflow.Receipt Daml template via the co-located participant's Ledger API. The Daml template carries the ReceiptEnvelope payload either inline (defaults: settlement, kill-switch, lifecycle, governance) or as a DaPointer reference (defaults: settlement-channel, inference, agent-message), and the originating Tenzro chain's block_height + receipt_idis recorded as the cross-ledger anchor. Canton's sub-transaction privacy ensures only the workflow's stakeholders observe the mirrored receipt.

Privacy domains

A PrivacyDomain is a named ACL of TDIP DIDs that gates encrypted payloads. Workflows that opt into a domain seal their payload and event payloads with a domain key shared among the ACL. The seal/open round-trip is symmetric AES-256-GCM with the domain key derived per the standard envelope-encryption flow.

  • Members in the ACL can seal and open payloads addressed to the domain.
  • Auditors (a subset of the ACL) can open payloads they were never explicit recipients of.
  • Frozen domains refuse new sealings while permitting existing payloads to continue being opened — preserving the data-retention contract across governance-driven freezes.

Policy DSL

Approvals are gated by a small DSL evaluated against a PolicyContext containing { now, signer, counterparties, accumulated_amount_today, kyc_tiers, ... }. Expressions evaluate to PolicyOutcome::{ Allow, Deny, RequireApproval(approver_did) }. Combinators:

  • amount_lte(N) / daily_amount_lte(N) — numeric ceilings
  • counterparty_kyc_tier_gte(tier) — KYC gating
  • time_window(start, end) — wraps around midnight; supports business-hours
  • and(left, right) — short-circuits on Deny; RequireApproval propagates if either branch requires
  • or(left, right) — short-circuits on Allow; collapses to RequireApproval when no branch allows but at least one branch requires
  • not(inner) — flips Allow ⇄ Deny; RequireApproval is a no-op under negation

The DSL is tree-shaped, terminating, and pure — no side effects, no I/O — which makes it safe to evaluate inside the Native VM during selector dispatch.

Fee routing

FeeRoute {
  id: FeeRouteId,
  recipients: Vec<FeeRouteRecipient {
    recipient_did: Did,
    label: String,
    share_bps: u16,                  // basis points; sum of all = 10_000
  }>,
}

compute_fee_route_payouts(route, gross_wei: u128) returns per-recipient payouts using basis-point splits with truncation; any rounding remainder is added to the last recipient so the sum of payouts equals the gross. The RPC tenzro_computeFeeRoutePayouts exposes this as a read-only preview; actual settlement payouts move through the consensus-mediated escrow primitive.

Kill switch

  • Suspend (0x01000048) moves the workflow into Suspended. Callable by the initiator at any time and by governance-bound DIDs at any time.
  • Cancel (0x01000049) moves a Suspended workflow into terminal Cancelled. Callable only by the initiator after suspension.

A suspended workflow rejects all writes except KillSwitchCancel and dispute selectors. The pair removes the only condition under which an autonomous agent could be trapped in a non-responsive multi-party flow it initiated.

Operational metrics

WorkflowRuntime::operational_metrics() returns an OperationalMetrics snapshot computed by walking the in-memory workflow / obligation / approval indices once and partitioning by status. The snapshot is rendered to the standard Prometheus text format with # HELP / # TYPE headers per metric and BTreeMap ordering for deterministic output. It is exposed at /metrics and graphed by deploy/monitoring/grafana-workflow-dashboard.json (UID tenzro-workflow):

  • tenzro_workflow_workflows_total (gauge, label status)
  • tenzro_workflow_obligations_total (gauge, label status)
  • tenzro_workflow_approvals_total (gauge, label status)
  • tenzro_workflow_signatures_collected_total (counter)
  • tenzro_workflow_canton_mirrored_total (counter)
  • tenzro_workflow_fee_routes_total (gauge)
  • tenzro_workflow_privacy_domains_total (gauge)

Surfaces

Read-only access is mirrored across all three external surfaces. Writes never occur through these surfaces — every state-changing operation is a privileged-VM selector dispatched by a signed transaction submitted via tenzro_signAndSendTransaction or eth_sendRawTransaction.

  • JSON-RPC (port 8545): tenzro_getWorkflow, tenzro_getWorkflowLifecycle, tenzro_listWorkflowsByCreator, tenzro_listWorkflowsByParticipant, tenzro_listWorkflowsByStatus, tenzro_getObligation, tenzro_getApproval, tenzro_getWorkflowReceipt, tenzro_listWorkflowReceipts, tenzro_getFeeRoute, tenzro_listFeeRoutes, tenzro_computeFeeRoutePayouts, tenzro_getPrivacyDomain, tenzro_listPrivacyDomainsForDid, tenzro_getWorkflowOperationalMetrics.
  • MCP (port 3001): the same surface mirrored as #[tool]-defined methods on the main MCP server.
  • A2A (port 3002): the workflow skill on the Tenzro Agent Card surfaces all of the above through natural-language utterances.

Reference templates

Five reference workflow templates ship under crates/tenzro-workflow/reference_workflows/, each paired with a *_daml_map.json describing the Canton DAML projection:

  • autonomous_procurement — Buyer/seller/auditor procurement on Canton with DvP
  • autonomous_treasury — Multi-sig treasury operations with policy-gated approvals
  • dvp_settlement — Delivery-vs-payment settlement on a Canton synchronizer
  • environmental_mrv — Environmental measurement / reporting / verification with auditor sign-off
  • supply_chain_dpp — Supply-chain digital product passport with multi-party attestations

Each template defines its WorkflowSpec (counterparty roles, obligation set, approvals graph, fee route, privacy domain) and is instantiated at runtime via tenzro-agent-kit's spawner. The agent-kit's separate reference_templates/ directory carries agent templates (inference marketplace, RWA custodian, bridge arbitrage scanner, etc.) that may originate workflows but are not themselves workflow specs.

See also