B3: Allocation Receipt Chain — Implementation Plan
For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
Goal: Wire governance budget decisions to structured AllocationReceipts with verifiable provenance chain, replacing ad-hoc audit JSON in the vertical slice test.
Architecture: Pure conversion function create_budget_allocation() in icn-ledger bridges governance decisions to AllocationReceipt + SettlementIntent types from icn-kernel-api. The vertical slice integration test is updated to produce and verify these receipts end-to-end. No new actors or traits.
Tech Stack: Rust, icn-kernel-api (AllocationReceipt, SettlementIntent, CanonicalReceipt), icn-governance (GovernanceDecisionReceipt), icn-ledger (new allocations module), icn-time (timestamps).
Design Doc: docs/plans/2026-02-15-b3-allocation-receipt-chain-design.md
Pre-flight
Before starting, create the feature branch:
git checkout main && git pull origin main
git checkout -b feat/allocation-receipt-chain
Task 1: Create allocations module with unit tests
Files:
- Create:
icn/crates/icn-ledger/src/allocations.rs - Modify:
icn/crates/icn-ledger/src/lib.rs(add module + re-exports)
Step 1: Write the allocations module with tests
Create icn/crates/icn-ledger/src/allocations.rs:
//! Allocation receipt creation for governance → economics bridge.
//!
//! Converts governance budget decision parameters into structured
//! `AllocationReceipt` objects with `SettlementIntent` entries.
//! These receipts form the provenance chain:
//!
//! ```text
//! GovernanceDecisionReceipt.decision_hash
//! → AllocationReceipt.decision_hash
//! → SettlementIntent.decision_hash
//! → JournalEntry (ledger mutation)
//! ```
use icn_kernel_api::economics::SettlementIntent;
use icn_kernel_api::receipts::{AllocationReceipt, CanonicalReceipt, Hash};
use icn_kernel_api::ScopeLevel;
/// Create an `AllocationReceipt` from a governance budget decision.
///
/// This is the bridge between governance decisions and economic settlement.
/// The receipt links `decision_hash` to a `SettlementIntent` describing
/// the treasury → recipient transfer.
///
/// # Arguments
///
/// * `decision_hash` — Canonical hash of the `GovernanceDecisionReceipt`
/// * `proposal_id` — Governance proposal ID (used as `decision_receipt_id` on the intent)
/// * `treasury_did` — Source account DID (treasury paying out)
/// * `recipient_did` — Destination account DID (receiving the allocation)
/// * `amount` — Transfer amount in smallest unit
/// * `currency` — Currency/unit symbol (e.g. "HOURS")
/// * `purpose` — Human-readable memo (excluded from canonical hash)
pub fn create_budget_allocation(
decision_hash: Hash,
proposal_id: &str,
treasury_did: &str,
recipient_did: &str,
amount: u64,
currency: &str,
purpose: &str,
) -> AllocationReceipt {
let now = icn_time::current_timestamp_secs();
let intent = SettlementIntent::new(
proposal_id,
decision_hash,
treasury_did,
recipient_did,
amount,
currency,
)
.with_memo(purpose)
.with_timestamp(now);
AllocationReceipt::new(decision_hash, ScopeLevel::Org)
.with_timestamp(now)
.add_intent(intent)
}
#[cfg(test)]
mod tests {
use super::*;
const TEST_DECISION_HASH: Hash = [42u8; 32];
fn make_test_allocation() -> AllocationReceipt {
create_budget_allocation(
TEST_DECISION_HASH,
"prop-budget-001",
"did:icn:treasury",
"did:icn:supplier",
30,
"HOURS",
"Purchase drill press",
)
}
#[test]
fn test_creates_valid_receipt_with_one_intent() {
let receipt = make_test_allocation();
assert_eq!(receipt.intents.len(), 1);
assert_eq!(receipt.decision_hash, TEST_DECISION_HASH);
assert_eq!(receipt.scope, ScopeLevel::Org);
assert!(receipt.created_at > 0);
}
#[test]
fn test_decision_hash_propagates_to_intent() {
let receipt = make_test_allocation();
let intent = &receipt.intents[0];
assert_eq!(intent.decision_hash, TEST_DECISION_HASH);
assert_eq!(receipt.decision_hash, intent.decision_hash);
}
#[test]
fn test_intent_fields_match_inputs() {
let receipt = make_test_allocation();
let intent = &receipt.intents[0];
assert_eq!(intent.from, "did:icn:treasury");
assert_eq!(intent.to, "did:icn:supplier");
assert_eq!(intent.amount, 30);
assert_eq!(intent.unit, "HOURS");
assert_eq!(intent.decision_receipt_id, "prop-budget-001");
assert_eq!(intent.memo.as_deref(), Some("Purchase drill press"));
}
#[test]
fn test_provenance_chain_includes_decision_hash() {
let receipt = make_test_allocation();
assert!(
receipt.provenance.upstream_hashes.contains(&TEST_DECISION_HASH),
"provenance must link to decision_hash"
);
}
#[test]
fn test_canonical_hash_is_stable() {
let r1 = create_budget_allocation(
TEST_DECISION_HASH,
"prop-001",
"did:icn:a",
"did:icn:b",
100,
"HOURS",
"test",
);
let r2 = create_budget_allocation(
TEST_DECISION_HASH,
"prop-001",
"did:icn:a",
"did:icn:b",
100,
"HOURS",
"test",
);
// Same inputs within same second → same canonical hash
// (canonical hash excludes memo, so purpose doesn't matter)
assert_eq!(r1.canonical_hash(), r2.canonical_hash());
}
#[test]
fn test_different_amounts_produce_different_hashes() {
let r1 = create_budget_allocation(
TEST_DECISION_HASH,
"prop-001",
"did:icn:a",
"did:icn:b",
100,
"HOURS",
"test",
);
let r2 = create_budget_allocation(
TEST_DECISION_HASH,
"prop-001",
"did:icn:a",
"did:icn:b",
200,
"HOURS",
"test",
);
assert_ne!(r1.canonical_hash(), r2.canonical_hash());
}
}
Step 2: Register module in lib.rs
In icn/crates/icn-ledger/src/lib.rs, add after pub mod asset_types;:
pub mod allocations;
And add to re-exports:
pub use allocations::create_budget_allocation;
Step 3: Run tests to verify
cargo test -p icn-ledger --lib allocations -- --quiet
Expected: 6 tests pass.
Step 4: Run fmt + clippy
cargo fmt -p icn-ledger -- --check
cargo clippy -p icn-ledger -- -D warnings
Expected: Clean.
Step 5: Commit
git add icn/crates/icn-ledger/src/allocations.rs icn/crates/icn-ledger/src/lib.rs
git commit -m "feat(ledger): add allocation receipt creation for budget decisions (#1141)"
Task 2: Update vertical slice test — receipt production in Step 6
Files:
- Modify:
icn/crates/icn-core/tests/vertical_slice_integration.rs
The ProposalAccepted event handler (lines ~204-252) currently creates an ad-hoc JSON audit record. Replace it with structured receipt production.
Step 1: Add imports at top of test file
Add these imports (some may already be present):
use icn_governance::{GovernanceDecisionReceipt, ProofOutcome, VoteTally};
use icn_kernel_api::receipts::CanonicalReceipt;
use icn_ledger::create_budget_allocation;
Step 2: Update the ProposalAccepted event handler
Replace the event handler closure (the event_bus.subscribe(...) block around lines 203-252) so that after creating the JournalEntry it also:
- Creates a
GovernanceDecisionReceiptfrom the proposal outcome - Calls
create_budget_allocation()to produce anAllocationReceipt - Stores both the receipt canonical hash and the serialized receipt in the KV store
The handler should store:
- Key
"receipt:allocation:<hex_hash>"→ serializedAllocationReceipt - Key
"receipt:decision:<proposal_id>"→ serializedGovernanceDecisionReceipt
Step 3: Run the test
cargo test -p icn-core --test vertical_slice_integration -- --quiet
Expected: Pass (receipt production is additive, doesn't change existing behavior).
Step 4: Commit
git add icn/crates/icn-core/tests/vertical_slice_integration.rs
git commit -m "feat(test): produce allocation receipts in vertical slice event handler (#1141)"
Task 3: Update vertical slice test — receipt verification in Step 9
Files:
- Modify:
icn/crates/icn-core/tests/vertical_slice_integration.rs
Step 1: Replace the ad-hoc audit verification in Step 9
The current Step 9 loads gov:audit:<proposal_id> and checks JSON fields. Replace with:
- Load the
AllocationReceiptfrom KV by scanning"receipt:allocation:"prefix - Verify
receipt.decision_hashis non-zero - Verify the
SettlementIntentinside has correct from/to/amount/unit - Verify provenance chain:
receipt.provenance.upstream_hashescontains the decision hash - Load the
GovernanceDecisionReceiptand verify itsdecision_hashmatches
Keep the existing cooperative state and member count assertions — those are still valid.
Step 2: Run the full test
cargo test -p icn-core --test vertical_slice_integration -- --nocapture
Expected: All 9 steps pass with receipt chain verification logged.
Step 3: Commit
git add icn/crates/icn-core/tests/vertical_slice_integration.rs
git commit -m "feat(test): verify allocation receipt chain end-to-end in vertical slice (#1141)"
Task 4: Final verification and cleanup
Step 1: Run full workspace lib tests
cargo test --workspace --lib -- --quiet 2>&1 | tail -5
Expected: All pass (4400+ tests).
Step 2: Run the specific integration test
cargo test -p icn-core --test vertical_slice_integration -- --nocapture
Expected: 9-step vertical slice passes with receipt chain output.
Step 3: Run fmt + clippy on workspace
cargo fmt --all -- --check
cargo clippy --all-targets -- -D warnings
Expected: Clean.
Step 4: Commit any remaining changes and push
Use /push skill to push the branch.
Step 5: Create PR
Use /pr-create skill targeting main. PR title: feat(ledger): allocation receipt chain (#1141)
Verification Checklist
-
cargo test -p icn-ledger --lib allocations— 6+ unit tests pass -
cargo test -p icn-core --test vertical_slice_integration— full chain verified -
cargo fmt --all -- --check— clean -
cargo clippy --all-targets -- -D warnings— clean - Provenance chain: decision_hash propagates through AllocationReceipt → SettlementIntent
- No new dependencies added (uses existing icn-kernel-api types)
- No panics in non-test code (all Result returns)