Execution Bridge Specification
Status: Authoritative Design Document Last Updated: 2026-02-17 Purpose: Specifies how finalized governance decisions become executed economic actions. Audience: Implementors wiring the governance-to-economics pipeline Depends On: economics/model-validation.md, governance/model-validation.md
1. Overview
The execution bridge converts governance decisions into economic state changes. The provenance chain is:
GovernanceDecisionReceipt (icn-governance)
├─ decision_hash: [u8; 32] (canonical, cross-node)
│
▼
AllocationReceipt (icn-kernel-api/src/receipts.rs)
├─ decision_hash (links back to governance)
├─ intents: Vec<SettlementIntent>
│
▼
SettlementIntent (icn-kernel-api/src/economics.rs)
├─ decision_hash (same anchor)
├─ from, to, amount, unit
├─ asset: AssetType
│
▼
JournalEntry (icn-ledger/src/types.rs)
├─ accounts: Vec<AccountDelta> (balanced double-entry)
├─ parent_hash (Merkle-DAG link)
Two parallel paths currently exist:
| Path | Trigger | Executor | Status |
|---|---|---|---|
| Effect path | Governance tally closes | KernelGovernanceExecutor |
WIRED (treasury spend, protocol params, federation, membership, control) |
| Receipt path | Compute task completes | SettlementEngine |
SCHEMA (engine tested, not called from runtime) |
2. Effect Path (Governance Decisions)
2.1 Event Source
Governance proposals that pass tally emit KernelEffect variants:
KernelEffect::Treasury(TreasuryEffect)
KernelEffect::Protocol(ProtocolEffect)
KernelEffect::Federation(FederationEffect)
KernelEffect::Control(ControlEffect)
KernelEffect::Membership(MembershipEffect)
KernelEffect::NoOp { reason }
File: icn-kernel-api/src/effects.rs
Executor: icn-core/src/supervisor/governance_executor.rs
2.2 Treasury Effect Flow
TreasuryEffect::Spend { treasury_did, recipient_did, amount, currency, memo, decision_hash }
│
▼ treasury_effect_to_operation() [governance_executor.rs:722]
│
TreasuryOperation { treasury_id, operation_type, amount, currency, recipient, memo, decision_hash }
│
▼ KernelTreasuryExecutor::execute_treasury_operation() [governance_executor.rs:234]
│
├── IF ledger service configured AND decision_hash present:
│ TreasuryEntryRequest → LedgerService::submit_treasury_entry()
│ → JournalEntry created in ledger
│
└── ELSE: placeholder success (no ledger mutation)
Current state: The LedgerService path is wired. When the governance executor has a ledger service injected (production mode), treasury spend effects create real journal entries.
2.3 Supported Treasury Effects
| Effect | Maps To | Wired? |
|---|---|---|
TreasuryEffect::Spend |
Spend operation |
YES - creates journal entry via LedgerService |
TreasuryEffect::CreateBudget |
Allocate operation |
YES - creates journal entry |
TreasuryEffect::Allocate |
Allocate operation |
YES (no decision_hash provenance) |
TreasuryEffect::Transfer |
Spend operation |
YES (no decision_hash provenance) |
| Other variants | Reserve placeholder |
NO - returns unmapped placeholder |
2.4 Protocol Effect Flow
ProtocolEffect::SetParameter { parameter_name, old_value_hash, new_value_json, effective_at }
│
▼ protocol_effect_to_change() [governance_executor.rs:804]
│
ProtocolChange { parameter_name, old_value, new_value, effective_at }
│
▼ KernelProtocolExecutor::apply_protocol_change() [governance_executor.rs:370]
│
├── Read current value from ProtocolParameterStore
├── Optimistic concurrency check (old_value matches current)
├── Parse new_value to match parameter type
├── Write updated parameter (durable via Sled)
└── Return state_change_hash for provenance
Concurrency guard: If the parameter changed between proposal creation and tally, execution fails with "has changed since proposal". This prevents stale writes.
2.5 Unsupported Effects (Pilot V1)
| Effect | Reason | Alternative |
|---|---|---|
ProtocolEffect::Upgrade |
Requires migration coordination | Not available |
ProtocolEffect::SetSchedulingPolicy |
Works via EventBus side-channel | EventBus subscription |
FederationOperationType::LeaveFederation |
Not implemented with durable state | Manual |
FederationOperationType::EstablishClearing |
Not implemented with durable state | Manual |
3. Receipt Path (Compute Settlement)
3.1 SettlementEngine
File: icn-ledger/src/settlement.rs
Converts verified ExecutionReceipt (from compute layer) into JournalEntry:
SettlementRequest {
receipt_hash: [u8; 32], // opaque compute-layer hash
executor: Did, // earns credit
submitter: Did, // pays debit
attester: Option<Did>, // governance/audit
scope: ScopeLevel,
amount: i64,
currency: String,
executor_verified: bool, // MUST be true
}
3.2 Invariants
- Verification required:
executor_verifiedmust betrue(caller verifies signatures) - Scope routing: Local/Cell/Org settle directly. Federation/Commons MUST use clearing.
- Positive amounts:
amount > 0 - No self-dealing:
executor != submitter - Dedup:
sha256("icn-ledger:settlement:v1:" || receipt_hash)tracked inHashSet
3.3 Current Gap
The SettlementEngine is fully tested (14 tests) but never called from the runtime.
No code path converts an ExecutionReceipt into a SettlementRequest and invokes settle_receipt().
4. Allocation Receipt Path (Budget Decisions)
4.1 AllocationReceipt
File: icn-kernel-api/src/receipts.rs
Links governance budget decisions to settlement intents:
AllocationReceipt {
decision_hash: Hash, // links to GovernanceDecisionReceipt
intents: Vec<SettlementIntent>, // one or more economic actions
scope: ScopeLevel,
provenance: ProvenanceAnchors, // upstream hash chain
}
4.2 SettlementIntent
File: icn-kernel-api/src/economics.rs
SettlementIntent {
decision_receipt_id: String,
decision_hash: Hash,
asset: AssetType, // Fungible, Consumable, Service, Claim, etc.
from: String, // source DID
to: String, // destination DID
amount: u64,
unit: String,
scope: ScopeLevel,
memo: Option<String>, // excluded from canonical hash
provenance: ProvenanceAnchors,
}
4.3 Builder
File: icn-ledger/src/allocations.rs
create_budget_allocation(
decision_hash, proposal_id, treasury_did, recipient_did,
amount, currency, purpose,
) -> AllocationReceipt
4.4 Current Gap
create_budget_allocation() produces an AllocationReceipt with SettlementIntent,
but nothing converts the intent's (from, to, amount, unit) into a JournalEntry
and appends it to the ledger. The conversion function does not exist yet.
5. Idempotency Rules
5.1 Effect Path
| Layer | Idempotency Mechanism |
|---|---|
| Governance tally | Each proposal executes effects exactly once on tally close |
| Protocol parameters | Optimistic concurrency (old_value check) prevents stale writes |
| Treasury entries | decision_receipt_id included in journal entry metadata |
Gap: No dedup guard on TreasuryEntryRequest. If the same decision is replayed (e.g., gossip redelivery), the same journal entry could be created twice.
5.2 Receipt Path
| Layer | Idempotency Mechanism |
|---|---|
| SettlementEngine | sha256(DEDUP_PREFIX || receipt_hash) in HashSet<[u8; 32]> |
Gap: Dedup set is in-memory only. Node restart loses dedup state. Must persist to sled.
5.3 Allocation Path
No idempotency mechanism exists. The AllocationReceipt.canonical_hash() could serve as a dedup key, but no code checks it.
6. Canonical Hash Rules
All receipts implement CanonicalReceipt trait:
trait CanonicalReceipt {
fn canonical_hash(&self) -> Hash; // deterministic, cross-node equal
fn receipt_id(&self) -> &ReceiptId; // node-local, excluded from hash
fn provenance(&self) -> &ProvenanceAnchors;
}
Hash computation
- Serialization:
bincode::serialize()(deterministic byte order) - Hash function:
blake3::hash()->[u8; 32] - Helper:
compute_canonical_hash<T: Serialize>(value: &T) -> Hash
Excluded from canonical hash
| Type | Excluded Fields |
|---|---|
SettlementIntent |
intent_id (node-local), memo |
AllocationReceipt |
receipt_id, signature |
Order independence
AllocationReceipt::canonical_hash() sorts intent hashes before hashing.
Two nodes adding the same intents in different order produce the same canonical hash.
7. Failure Modes and Recovery
7.1 Effect Execution Failures
| Failure | Handling | Recovery |
|---|---|---|
| Parameter concurrent modification | ExecutionOutcome::Failed with reason |
Re-propose with current value |
| Treasury ledger submission error | anyhow::Error propagated |
Governance records failure in tally |
| Federation service not configured | ExecutionOutcome::Failed |
Configure service, re-execute |
| Membership service not configured | ExecutionOutcome::Failed |
Configure service, re-execute |
| Unknown treasury effect variant | Maps to placeholder Reserve(0) | Add explicit mapping |
7.2 Settlement Failures
| Failure | Error Type | Recovery |
|---|---|---|
| Unverified receipt | LedgerError::InvalidEntry |
Caller must verify signatures first |
| Federation/Commons scope | LedgerError::InvalidEntry |
Route to clearing manager |
| Non-positive amount | LedgerError::InvalidEntry |
Fix upstream |
| Self-dealing | LedgerError::InvalidEntry |
Different executor/submitter required |
| Duplicate receipt | LedgerError::DuplicateEntry |
Already settled, safe to ignore |
| Lock poisoned | LedgerError::Internal |
Node restart |
7.3 Retry Policy
- Effect path: No automatic retry. Failed effects are recorded in governance tally results. Re-execution requires a new governance proposal.
- Receipt path: Caller may retry after transient failures (lock contention). Dedup prevents double-settlement on retry.
- Allocation path: Not yet implemented. Should follow receipt path pattern (dedup on canonical_hash).
8. Golden-Path Test Inventory
8.1 Effect Path Tests
| Test | File | What It Proves |
|---|---|---|
test_treasury_executor_placeholder |
governance_executor.rs:1459 |
Treasury effect produces Success outcome |
test_protocol_executor_apply_change |
governance_executor.rs:1486 |
Protocol param update writes to store |
test_protocol_executor_concurrent_modification |
governance_executor.rs:1519 |
Stale write detected and rejected |
test_protocol_upgrade_returns_explicit_failure |
governance_executor.rs:1663 |
Unsupported effects fail honestly |
test_protocol_set_scheduling_policy_returns_explicit_failure |
governance_executor.rs:1694 |
Pilot V1 limitations documented in tests |
8.2 Settlement Path Tests
| Test | File | What It Proves |
|---|---|---|
test_settle_creates_balanced_entry |
settlement.rs:263 |
Debit submitter + credit executor, balanced |
test_settle_deduplication |
settlement.rs:301 |
Same receipt_hash rejected on second attempt |
test_settle_federation_scope_rejected |
settlement.rs:320 |
Federation scope routed to clearing |
test_settle_self_dealing_rejected |
settlement.rs:396 |
executor == submitter blocked |
test_dedup_key_domain_separated |
settlement.rs:476 |
Dedup prefix prevents cross-feature collision |
8.3 Allocation Receipt Tests
| Test | File | What It Proves |
|---|---|---|
test_creates_valid_receipt_with_one_intent |
allocations.rs:80 |
Builder produces valid receipt |
test_decision_hash_propagates_to_intent |
allocations.rs:89 |
Provenance chain integrity |
test_canonical_hash_is_stable |
allocations.rs:122 |
Deterministic hashing |
test_allocation_receipt_canonical_hash_order_independent |
receipts.rs:586 |
Intent order does not affect hash |
8.4 Missing Golden-Path Tests
| Scenario | Why It Matters |
|---|---|
| End-to-end governance proposal -> treasury journal entry | Proves the full pipeline works |
| AllocationReceipt -> JournalEntry conversion | Proves budget decisions become ledger mutations |
| SettlementEngine called from supervisor/gateway | Proves compute receipts settle |
| Dedup survives node restart | Proves persistence of settlement dedup |
| Concurrent settlement of same receipt from two gossip paths | Proves dedup under concurrency |
9. Minimum Viable Fixes for Pilot
Fix 1: Settlement dedup persistence
Problem: SettlementEngine.settled is RwLock<HashSet<[u8; 32]>> (in-memory).
Fix: Add sled persistence with key prefix settlement:dedup:.
Effort: ~30 lines. Read settled set on startup, write on each settle.
File: icn-ledger/src/settlement.rs
Fix 2: AllocationReceipt -> JournalEntry conversion
Problem: No function converts SettlementIntent fields into JournalEntryBuilder calls.
Fix: Add settle_allocation(receipt: &AllocationReceipt, ledger: &mut Ledger) that iterates intents, builds entries, and appends to ledger with dedup on intent.canonical_hash().
Effort: ~50 lines.
File: New function in icn-ledger/src/settlement.rs
Fix 3: Treasury effect dedup guard
Problem: submit_treasury_entry() has no idempotency check. Replayed governance decisions could create duplicate entries.
Fix: Track decision_receipt_id in a dedup set (same pattern as SettlementEngine).
Effort: ~20 lines.
File: icn-core/src/supervisor/governance_executor.rs or the LedgerService implementation
Fix 4: Wire SettlementEngine into compute receipt handler
Problem: No runtime code converts ExecutionReceipt to SettlementRequest.
Fix: Add handler in compute receipt processing that calls settle_receipt().
Effort: ~40 lines.
Files: icn-core/src/supervisor/ (new compute settlement handler)
Fix 5: End-to-end integration test
Problem: No test proves governance decision -> ledger entry across the full stack.
Fix: Integration test using TestNode that creates a treasury, submits a governance proposal, tallies it, and verifies the resulting journal entry.
Effort: ~100 lines.
File: icn-core/tests/ (new integration test)
10. Decision Executor (New Component)
The Decision Executor is the missing layer that wraps the EffectDispatcher with persistent idempotency, saga state tracking, and execution receipts.
10.1 Architecture
┌─────────────────────────────────────────────────────────────────────┐
│ APP LAYER (icn-governance) │
│ 1. ProposalState::Accepted │
│ 2. GovernanceDecisionReceipt generated │
│ 3. translate_payload_to_effects(payload) -> Vec<KernelEffect> │
│ 4. decision_hash computed from (proposal_id, payload, tally) │
└───────────────────────────────┬─────────────────────────────────────┘
│ Vec<KernelEffect> + decision_hash
▼
┌─────────────────────────────────────────────────────────────────────┐
│ DECISION EXECUTOR (new, in icn-core supervisor) │
│ 5. Check execution_log[decision_hash] │
│ ├── Confirmed → SKIP (idempotent) │
│ ├── Failed (retries < max) → RETRY │
│ └── Not found → CREATE (status=Pending) │
│ 6. Set status = Executing │
│ 7. EffectDispatcher.execute_effects(effects, receipt_id) │
│ 8. Collect Vec<EffectResult> │
│ ├── All success → status=Confirmed, store entry IDs │
│ └── Any failure → status=Failed, store error, increment retry │
│ 9. Generate ExecutionReceipt │
└───────────────────────────────┬─────────────────────────────────────┘
│ KernelEffect per effect
▼
┌─────────────────────────────────────────────────────────────────────┐
│ KERNEL LAYER (existing KernelGovernanceExecutor) │
│ 10. Execute: Treasury→LedgerService, Protocol→ParamStore, etc. │
│ 11. Return EffectResult { success, message, state_change_hash } │
└─────────────────────────────────────────────────────────────────────┘
10.2 ExecutionRecord Schema
pub struct ExecutionRecord {
/// SHA-256 of (proposal_id || payload_bytes || tally_bytes).
/// ONLY idempotency key. Survives retries, rebuilds, federation replay.
pub decision_hash: [u8; 32],
/// Governance proposal ID (human reference, not dedup).
pub proposal_id: String,
/// Current execution status.
pub status: ExecutionStatus,
/// When execution was first attempted.
pub started_at: u64,
/// When execution reached terminal state.
pub finished_at: Option<u64>,
/// Ledger entry IDs produced (empty until Confirmed).
pub ledger_entry_ids: Vec<String>,
/// State change hashes from each effect.
pub state_change_hashes: Vec<String>,
/// Error if Failed.
pub error: Option<String>,
/// Retry count.
pub retries: u32,
/// Links to GovernanceDecisionReceipt.
pub decision_receipt_id: String,
}
pub enum ExecutionStatus {
Pending, // Record created, not yet started
Executing, // Effects being applied
Confirmed, // All succeeded. INVARIANT: never re-execute.
Failed, // May retry (retries < max)
PermanentlyFailed, // Max retries exceeded. Manual intervention.
}
10.3 Idempotency Key: decision_hash
Why decision_hash, not proposal_id: Proposals can be re-submitted. Federation nodes may assign different local IDs. The decision_hash captures the exact combination of proposal + payload + vote outcome.
Why not timestamp: Non-deterministic. Two nodes processing the same decision must agree on the key.
Computation:
fn decision_hash(proposal_id: &str, payload: &[u8], tally: &[u8]) -> [u8; 32] {
sha256(b"icn:decision:v1:" || proposal_id || b":" || payload || b":" || tally)
}
10.4 Retry Policy
| Category | Example | Retry? | Max |
|---|---|---|---|
| Transient | sled timeout, lock contention | Yes | 3 |
| Business logic | Insufficient funds | No | - |
| Invariant | Already confirmed | No (skip) | - |
| Config | Service not wired | No | - |
Backoff: 1s, 2s, 4s (exponential, base 1s).
10.5 Component Placement
| Component | Crate | Rationale |
|---|---|---|
ExecutionRecord, ExecutionStatus |
icn-kernel-api |
Shared types |
ExecutionLog trait + sled impl |
icn-core |
Persistence near supervisor |
DecisionExecutor |
icn-core |
Supervisor background task |
| Effect translation | icn-governance |
Meaning firewall: app translates |
10.6 Receipt Chain
GovernanceDecisionReceipt (existing, icn-governance)
├── decision_hash ─────────────┐
├── vote_tally │
└── attestations │ SAME KEY
│
ExecutionReceipt │ (new, execution bridge)
├── decision_hash ─────────────┘
├── execution_hash
├── ledger_entry_ids ──────────┐
└── executed_at │ BACK-LINK
│
JournalEntry │ (existing, icn-ledger)
├── id ────────────────────────┘
├── decision_receipt_id
└── decision_hash
11. Implementation Sequence
Phase 1: Skeleton (no behavior)
Branch: feat/execution-bridge-skeleton
ExecutionRecord+ExecutionStatustypes inicn-kernel-apiExecutionLogtrait + sled impl inicn-coreDecisionExecutorstruct withprocess_decision()stub- Wire into supervisor lifecycle (runs, does nothing)
- Unit tests for record serialization + log CRUD
Phase 2: Escrow Release Slice
Branch: feat/executor-escrow-release
- Idempotency check in
process_decision() - Status transitions: Pending -> Executing -> Confirmed/Failed
- Wire
TreasuryEffect::Spendthrough decision executor ExecutionReceiptgeneration- Three integration tests:
- Golden path: governance approve -> ledger entry created -> receipt generated
- Failure path: insufficient funds -> escrow stays locked -> no partial state
- Idempotency: replayed decision -> no double-disbursement
Phase 3: Budget Enforcement Slice
Branch: feat/executor-budget-enforcement
- New
TreasuryEffect::CreateBudgetvariant (or extend existing) - Wire to
BudgetManager::create_budget() - Budget validation in
Ledger::append_entry() - Integration test: approved budget -> spending constrained
Phase 4: Generalize
- Effect translation for remaining
ProposalPayloadvariants SurplusAllocation,ShareRedemption,DisputeResolutioneffects- Federation effect execution beyond logging
12. Sequence Diagram: Full Golden Path
Governance Effect Treasury Ledger
Tally Executor Executor Service
│ │ │ │
│ KernelEffect:: │ │ │
│ Treasury(Spend) │ │ │
├─────────────────>│ │ │
│ │ TreasuryOp │ │
│ ├───────────────>│ │
│ │ │ TreasuryEntry │
│ │ │ Request │
│ │ ├──────────────>│
│ │ │ │ build JournalEntry
│ │ │ │ append to ledger
│ │ │ │ dedup check
│ │ │ entry_hash │
│ │ │<──────────────┤
│ │ Success │ │
│ │<───────────────┤ │
│ EffectResult │ │ │
│<─────────────────┤ │ │
│ (success=true) │ │ │
Document History
- 2026-02-17: Initial creation from codebase audit and architecture analysis