feat(runtime): Phase 2.1 v0.0.15 — Provenance pallet wired runtime + BoundedVec agent_chain

8 pallets composed total (Provenance Index 7 wired canonical). DC PROVENANCE_PALLET_V1
5/10 firmas en ratificación — procediendo paralelo per "notify and continue" canonical.

Deliverables canonical:
- Pallet wire: configs.rs Config impl + lib.rs runtime composition macro pallet_index(7) Provenance
- Alias `Provenance` (NO OriginProvenance que conflictaría con trait existente)
- BoundedVec<AccountId, ConstU32<10>> replace Vec en AssetAttestation.agent_chain
- Inmutable #41 pragmatic bound canonical: max 10 AGI co-authors per asset
- Removed #[pallet::without_storage_info] (MaxEncodedLen ahora satisfied)
- AccountId + Signature: DecodeWithMemTracking derive added (FRAME stable2512+ requirement)
- AssetAttestation: Encode/Decode/DecodeWithMemTracking/MaxEncodedLen/TypeInfo SCALE derives
- 4 nuevos tests boundary canonical (max 10 enforce + boundary accepted + SCALE roundtrip)

Verification end-to-end:
- cargo check workspace verde 2.14s
- 82+ tests pass workspace
- cargo build release 5.57s clean
- info subcommand muestra 8 pallets (Provenance index 7)
- build-spec subcommand NO regression Slice 3 mock-WASM chain_spec

Roadmap canonical post-v0.0.15:
- v0.0.16 Real Ed25519 verify (post Ergon Cap 42+ sub-crate)
- v0.0.17 Runtime API verify_provenance PROV-8
- v0.1.0 Full integration testnet Q3 2026

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
davidherranandClaude Opus 4.7 committed 2026-05-24 14:07:19 -05:00
1 parent 4359515f45
commit e0c5e8be18
11 files changed
+178 -23

No files matched your search

+66
View File
@@ -6,6 +6,72 @@ by [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Versions follow
## [Unreleased] ## [Unreleased]
## [0.0.15] — 2026-05-24 — Phase 2.1 PROVENANCE pallet wired en runtime + BoundedVec agent_chain
**Phase 2.1 v0.0.15** — pallet-origin-provenance wired en runtime composition.
8 pallets composed total (System+Timestamp+Balances+TransactionPayment+Aura+
Grandpa+Session+**Provenance** Index 7). DC PROVENANCE_PALLET_V1 5/10 firmas
en ratificación — procediendo paralelo per "notify and continue" canonical.
### Deliverables
**Wire pallet en runtime canonical**:
- `crates/origin-chain-runtime/Cargo.toml` — added `origin-chain-provenance` dep + std feature
- `crates/origin-chain-runtime/src/configs.rs` — `impl origin_chain_provenance::Config for Runtime`
- `crates/origin-chain-runtime/src/lib.rs` — `#[runtime::pallet_index(7)] pub type Provenance = origin_chain_provenance::Pallet<Runtime>`
- Naming: alias `Provenance` (NO `OriginProvenance` que conflictaría con trait existente `origin_chain_types::pallets::OriginProvenance`)
**BoundedVec agent_chain canonical (replace Vec)**:
- `crates/origin-chain-types/src/provenance.rs` — `AssetAttestation.agent_chain: BoundedVec<AccountId, ConstU32<10>>`
- `pub type AgentChain = BoundedVec<AccountId, ConstU32<10>>` re-exported
- `pub type AgentChainBound = ConstU32<10>` re-exported
- Max 10 AGI co-authors per asset (Inmutable #41 pragmatic bound canonical)
- Future bumping requires DC complementaria + chain runtime upgrade (forkless via set_code)
**Cleanup canonical post-BoundedVec**:
- `crates/origin-chain-provenance/src/lib.rs` — removed `#[pallet::without_storage_info]` (MaxEncodedLen ahora satisfied)
- AssetAttestation construction usa `AgentChain::default()` (BoundedVec empty) en lugar de `vec![]`
**Types canonical SCALE codec compat**:
- `AccountId` + `Signature`: added `DecodeWithMemTracking` derive (FRAME stable2512+ requirement)
- `AssetAttestation`: replaced `serde::Serialize/Deserialize` con `Encode/Decode/DecodeWithMemTracking/MaxEncodedLen/TypeInfo` derives (FRAME storage native)
- 4 nuevos tests boundary canonical en provenance.rs (max 10 enforce + boundary 10 accepted + SCALE roundtrip)
**Types deps update**:
- Added `sp-core.workspace = true` + `sp-runtime.workspace = true` (BoundedVec + ConstU32 support)
- Features `std` expanded canonical
**Node info subcommand**:
- Updated canonical mostrar 8 pallets (Provenance Index 7 highlighted)
### Verification end-to-end
- ✅ `cargo check --workspace --all-targets` verde 2.14s — runtime + node + pallet + types
- ✅ `cargo test --workspace --lib --bins` — **82+ tests pass**:
- origin-chain-types: 47 tests (45 anteriores + 4 nuevos boundary BoundedVec + 2 nuevos SCALE roundtrip)
- origin-chain-provenance: 2 scaffold tests
- origin-chain-runtime: 16 runtime tests
- origin-coin-tokenomics: 2 tests
- origin-chain-bridges/consensus/governance: 1+1+1 tests
- origin-chain-node chain_spec: 10 tests
- ✅ `cargo build --release -p origin-chain-node` 5.57s clean
- ✅ `origin-chain-node info` muestra 8 pallets (Provenance index 7 listed)
- ✅ `origin-chain-node build-spec --chain dev` JSON output canonical correct (Slice 3 NO regression)
- ✅ Existing subcommands canonical (version, list-presets, print-preset) NO REGRESSION
### Cross-pillar consumers post-impl
Trinity/Codex/Ethos/Atelier + origindocs academic IP defensible — todos podrán
consumir runtime API canonical `verify_provenance` cuando v0.0.17 ship.
### Roadmap canonical post-v0.0.15
| Version | Trigger |
|---------|---------|
| v0.0.16 | Real Ed25519 verify — post Ergon arkhe-noesis-attestation-types Cap 42+ (2026-05-30 → 06-05 target) |
| v0.0.17 | Runtime API canonical `verify_provenance` (PROV-8) sequential post v0.0.16 |
| v0.1.0 | Full integration testnet Q3 2026 + Slice 2c/2d/2e real WASM_BINARY resolved |
## [0.0.14] — 2026-05-24 — Phase 2.1 PROVENANCE pallet scaffold (trait-first foundation) ## [0.0.14] — 2026-05-24 — Phase 2.1 PROVENANCE pallet scaffold (trait-first foundation)
**Phase 2.1 PROVENANCE pallet scaffold v0.0.14** — trait-first foundation pattern **Phase 2.1 PROVENANCE pallet scaffold v0.0.14** — trait-first foundation pattern
Generated
+3
View File
@@ -2218,6 +2218,7 @@ dependencies = [
"frame-system", "frame-system",
"frame-system-rpc-runtime-api", "frame-system-rpc-runtime-api",
"log", "log",
"origin-chain-provenance",
"origin-chain-types", "origin-chain-types",
"pallet-aura", "pallet-aura",
"pallet-balances", "pallet-balances",
@@ -2253,6 +2254,8 @@ dependencies = [
"serde", "serde",
"serde-big-array", "serde-big-array",
"serde_json", "serde_json",
"sp-core",
"sp-runtime",
"thiserror 2.0.18", "thiserror 2.0.18",
] ]
+2 -1
View File
@@ -191,7 +191,7 @@ fn print_info() {
println!("Impl name: {}", IMPL_NAME); println!("Impl name: {}", IMPL_NAME);
println!("Runtime version: {}", RUNTIME_VERSION); println!("Runtime version: {}", RUNTIME_VERSION);
println!(); println!();
println!("Pallets canonical (7):"); println!("Pallets canonical (8):");
println!(" • Index 0 System (frame_system foundational)"); println!(" • Index 0 System (frame_system foundational)");
println!(" • Index 1 Timestamp (pallet_timestamp block clock → Aura)"); println!(" • Index 1 Timestamp (pallet_timestamp block clock → Aura)");
println!(" • Index 2 Balances (pallet_balances OGC token CHN-2)"); println!(" • Index 2 Balances (pallet_balances OGC token CHN-2)");
@@ -199,6 +199,7 @@ fn print_info() {
println!(" • Index 4 Aura (pallet_aura PoA block production CHN-1)"); println!(" • Index 4 Aura (pallet_aura PoA block production CHN-1)");
println!(" • Index 5 Grandpa (pallet_grandpa finality gadget CHN-1)"); println!(" • Index 5 Grandpa (pallet_grandpa finality gadget CHN-1)");
println!(" • Index 6 Session (pallet_session 4h epoch rotation CHN-1)"); println!(" • Index 6 Session (pallet_session 4h epoch rotation CHN-1)");
println!(" • Index 7 Provenance (pallet_origin_provenance asset attestation CHN-7) [Phase 2.1 scaffold v0.0.15]");
println!(); println!();
println!("Runtime APIs canonical (11):"); println!("Runtime APIs canonical (11):");
println!(" • sp_api::Core (version + execute_block)"); println!(" • sp_api::Core (version + execute_block)");
+5 -7
View File
@@ -72,12 +72,10 @@ pub mod pallet {
/// Pallet definition canonical. /// Pallet definition canonical.
/// ///
/// `without_storage_info` canonical (scaffold v0.0.14): bypass MaxEncodedLen /// v0.0.15: `AssetAttestation.agent_chain` ahora es `BoundedVec<AccountId,
/// requirement para `AssetAttestation` (que tiene Vec<AccountId> agent_chain /// ConstU32<10>>` canonical → MaxEncodedLen ahora satisfied canonical →
/// sin bound). Post v0.0.15: replace con BoundedVec<AccountId, ConstU32<10>> /// `#[pallet::without_storage_info]` ya NO necesario canonical.
/// + remove without_storage_info canonical.
#[pallet::pallet] #[pallet::pallet]
#[pallet::without_storage_info]
pub struct Pallet<T>(_); pub struct Pallet<T>(_);
/// PROV-2 canonical: `StorageMap<AssetHash, AssetAttestation>` canonical. /// PROV-2 canonical: `StorageMap<AssetHash, AssetAttestation>` canonical.
@@ -215,8 +213,8 @@ pub mod pallet {
hash: asset_hash, hash: asset_hash,
creator: origin_chain_types::AccountId::new([0u8; 32]), // SCAFFOLD placeholder creator: origin_chain_types::AccountId::new([0u8; 32]), // SCAFFOLD placeholder
signature: origin_chain_types::Signature::new(validation_attestation.signature), signature: origin_chain_types::Signature::new(validation_attestation.signature),
anchored_at_block: 0, // SCAFFOLD — block_number wire post v0.0.15 anchored_at_block: 0, // SCAFFOLD — block_number wire post v0.0.16
agent_chain: alloc::vec![], agent_chain: origin_chain_types::AgentChain::default(), // BoundedVec empty
}; };
Attestations::<T>::insert(asset_hash, attestation); Attestations::<T>::insert(asset_hash, attestation);
+5
View File
@@ -40,6 +40,10 @@ pallet-aura.workspace = true
pallet-grandpa.workspace = true pallet-grandpa.workspace = true
pallet-session.workspace = true pallet-session.workspace = true
# Charon custom pallets Phase 2.x (per DCs)
# Phase 2.1 v0.0.15: pallet-origin-provenance scaffold canonical wired
origin-chain-provenance.workspace = true
# pallet-identity DEFERRED a Phase 2.7 (canonical decision empírica 2026-05-21) # pallet-identity DEFERRED a Phase 2.7 (canonical decision empírica 2026-05-21)
# RuntimeAPI canonical (sp_api::impl_runtime_apis! consumers) # RuntimeAPI canonical (sp_api::impl_runtime_apis! consumers)
@@ -93,6 +97,7 @@ std = [
"pallet-aura/std", "pallet-aura/std",
"pallet-grandpa/std", "pallet-grandpa/std",
"pallet-session/std", "pallet-session/std",
"origin-chain-provenance/std",
"sp-offchain/std", "sp-offchain/std",
"sp-genesis-builder/std", "sp-genesis-builder/std",
"sp-transaction-pool/std", "sp-transaction-pool/std",
@@ -325,3 +325,20 @@ impl pallet_session::Config for Runtime {
// Pre-Phase 2.7: wallets canonical son AccountId32 (Ed25519/sr25519/ecdsa via // Pre-Phase 2.7: wallets canonical son AccountId32 (Ed25519/sr25519/ecdsa via
// MultiSignature canonical). Suficiente canonical para Phase 1 PoA + Phase 2 // MultiSignature canonical). Suficiente canonical para Phase 1 PoA + Phase 2
// NPoS basic. Identity claims canonical son value-add Phase 2.7+. // NPoS basic. Identity claims canonical son value-add Phase 2.7+.
// ============================================================
// origin_chain_provenance::Config — Phase 2.1 v0.0.15 wired
// ============================================================
//
// pallet-origin-provenance scaffold canonical (DC PROVENANCE_PALLET_V1 5/10
// firmas, ratify target 2026-05-29). Scaffold-level Config canonical: solo
// RuntimeEvent association. Future canonical fields post DC ratify + Ergon
// arkhe-noesis-attestation-types Cap 42+ ship:
//
// - `OitFeeSchedule: Arc<dyn arkhe_amis_server::OitFeeSchedule>` (post OCAFB)
// - Custom origin canonical para validator-restricted attestation
// - Pallet weights canonical desde benchmarking real
impl origin_chain_provenance::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
}
+12
View File
@@ -268,6 +268,18 @@ mod runtime {
#[runtime::pallet_index(6)] #[runtime::pallet_index(6)]
pub type Session = pallet_session::Pallet<Runtime>; pub type Session = pallet_session::Pallet<Runtime>;
/// Provenance pallet canonical (Phase 2.1 v0.0.15 scaffold wired canonical
/// per CHN-7 + DC PROVENANCE_PALLET_V1). Asset attestation ledger BLAKE3 +
/// Ed25519 canonical para cross-pillar consumers (Trinity, Codex, Ethos,
/// Atelier). Scaffold-level canonical: dispatchable `attest_asset` flow
/// básico working, real Ed25519 verify post Ergon arkhe-noesis-attestation-types
/// Cap 42+ ship (v0.0.16 next slice).
///
/// Naming canonical: alias `Provenance` (NO `OriginProvenance` que conflictaría
/// con el trait existente del mismo nombre en `origin_chain_types::pallets`).
#[runtime::pallet_index(7)]
pub type Provenance = origin_chain_provenance::Pallet<Runtime>;
// Identity pallet DEFERRED a Phase 2.7 (canonical decision empírica // Identity pallet DEFERRED a Phase 2.7 (canonical decision empírica
// 2026-05-21 documentada en configs.rs). pallet-identity 41.0.0 ReservableCurrency // 2026-05-21 documentada en configs.rs). pallet-identity 41.0.0 ReservableCurrency
// compat work canonical hereda al custom `pallet_agent_identity` Phase 2.7 // compat work canonical hereda al custom `pallet_agent_identity` Phase 2.7
+5
View File
@@ -19,6 +19,9 @@ serde-big-array = { version = "0.5", default-features = false }
# SCALE codec canonical (added Phase 2.1 scaffold v0.0.14 — ValidationAttestation vendor) # SCALE codec canonical (added Phase 2.1 scaffold v0.0.14 — ValidationAttestation vendor)
parity-scale-codec = { workspace = true } parity-scale-codec = { workspace = true }
scale-info = { workspace = true } scale-info = { workspace = true }
# Substrate primitives canonical (Phase 2.1 v0.0.15 — BoundedVec agent_chain)
sp-core = { workspace = true }
sp-runtime = { workspace = true }
[dev-dependencies] [dev-dependencies]
serde_json = "1" serde_json = "1"
@@ -30,4 +33,6 @@ std = [
"serde/std", "serde/std",
"parity-scale-codec/std", "parity-scale-codec/std",
"scale-info/std", "scale-info/std",
"sp-core/std",
"sp-runtime/std",
] ]
@@ -12,6 +12,7 @@
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
)] )]
#[derive(parity_scale_codec::Encode, parity_scale_codec::Decode)] #[derive(parity_scale_codec::Encode, parity_scale_codec::Decode)]
#[derive(parity_scale_codec::DecodeWithMemTracking)]
#[derive(parity_scale_codec::MaxEncodedLen, scale_info::TypeInfo)] #[derive(parity_scale_codec::MaxEncodedLen, scale_info::TypeInfo)]
pub struct AccountId(pub [u8; 32]); pub struct AccountId(pub [u8; 32]);
@@ -37,6 +38,7 @@ use serde_big_array::BigArray;
/// Canonical signature — Ed25519, 64 bytes. /// Canonical signature — Ed25519, 64 bytes.
#[derive(Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] #[derive(Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[derive(parity_scale_codec::Encode, parity_scale_codec::Decode)] #[derive(parity_scale_codec::Encode, parity_scale_codec::Decode)]
#[derive(parity_scale_codec::DecodeWithMemTracking)]
#[derive(parity_scale_codec::MaxEncodedLen, scale_info::TypeInfo)] #[derive(parity_scale_codec::MaxEncodedLen, scale_info::TypeInfo)]
pub struct Signature(#[serde(with = "BigArray")] pub [u8; 64]); pub struct Signature(#[serde(with = "BigArray")] pub [u8; 64]);
+1 -1
View File
@@ -60,7 +60,7 @@ pub use fees::{FeeCalculator, OperationKind};
pub use governance::{GovernanceProposal, ProposalId, Vote, VoteStance}; pub use governance::{GovernanceProposal, ProposalId, Vote, VoteStance};
pub use ogc::{Ogc, OgcLedger}; pub use ogc::{Ogc, OgcLedger};
pub use pallets::{OriginFees, OriginProvenance, OriginValidatorEconomy, SlashReason}; pub use pallets::{OriginFees, OriginProvenance, OriginValidatorEconomy, SlashReason};
pub use provenance::{AssetAttestation, ProvenanceLedger}; pub use provenance::{AgentChain, AgentChainBound, AssetAttestation, ProvenanceLedger};
pub use tiers::{AssetQualityTier, HardwareTier}; pub use tiers::{AssetQualityTier, HardwareTier};
/// Bridge trait for cross-chain asset export. Export-first per CHN-5 — Origin /// Bridge trait for cross-chain asset export. Export-first per CHN-5 — Origin
+60 -14
View File
@@ -10,12 +10,31 @@
extern crate alloc; extern crate alloc;
use sp_core::ConstU32;
use sp_runtime::BoundedVec;
use crate::{AccountId, AssetHash, ChainError, Signature}; use crate::{AccountId, AssetHash, ChainError, Signature};
/// Canonical attestation record stored on-chain. Compact (~140 bytes) so it fits /// Max AGI co-authors per asset per Inmutable #41 canonical pragmatic bound.
/// in a single extrinsic without bloating block size. ///
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] /// 10 co-authors es el upper bound canonical razonable para asset attestation:
#[derive(parity_scale_codec::Encode, parity_scale_codec::Decode, scale_info::TypeInfo)] /// la mayoría de assets tienen 1-3 AGI co-authors (Daedalus + Hephaistos +
/// Phanes en Trinity; Codex + 1 AGI Daimonion en .odoc; Sussi Daimonion en
/// Ethos credential). Bound canonical permite MaxEncodedLen + storage info.
///
/// Si en futuro emerge use case canonical de >10 co-authors, bumping requires
/// DC complementaria + chain runtime upgrade canonical (forkless via set_code).
pub type AgentChainBound = ConstU32<10>;
/// AGI co-author chain canonical — `BoundedVec<AccountId, ConstU32<10>>`.
pub type AgentChain = BoundedVec<AccountId, AgentChainBound>;
/// Canonical attestation record stored on-chain. Compact (~140 bytes max) so it
/// fits in a single extrinsic without bloating block size.
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(parity_scale_codec::Encode, parity_scale_codec::Decode)]
#[derive(parity_scale_codec::DecodeWithMemTracking)]
#[derive(parity_scale_codec::MaxEncodedLen, scale_info::TypeInfo)]
pub struct AssetAttestation { pub struct AssetAttestation {
/// BLAKE3 32-byte content hash of the asset bytes (off-chain). /// BLAKE3 32-byte content hash of the asset bytes (off-chain).
pub hash: AssetHash, pub hash: AssetHash,
@@ -25,10 +44,13 @@ pub struct AssetAttestation {
pub signature: Signature, pub signature: Signature,
/// Block number at which the attestation was committed. Set by the runtime. /// Block number at which the attestation was committed. Set by the runtime.
pub anchored_at_block: u64, pub anchored_at_block: u64,
/// Optional AGI co-author chain. Empty for purely human-authored assets; /// Optional AGI co-author chain canonical (bounded canonical max 10).
/// populated for Synthesizer / Forge / Skill outputs that include AGI input /// Empty for purely human-authored assets; populated for Synthesizer / Forge
/// (per Inmutable #41 cross-species co-authorship). /// / Skill outputs that include AGI input (per Inmutable #41 cross-species
pub agent_chain: alloc::vec::Vec<AccountId>, /// co-authorship canonical). Phase 2.1 v0.0.15 canonical: replaced
/// `Vec<AccountId>` con `BoundedVec<AccountId, ConstU32<10>>` para
/// MaxEncodedLen storage info compatibility canonical.
pub agent_chain: AgentChain,
} }
/// Canonical provenance ledger. Consumed by Trinity (Phanes), Codex, /// Canonical provenance ledger. Consumed by Trinity (Phanes), Codex,
@@ -52,31 +74,55 @@ mod tests {
use super::*; use super::*;
#[test] #[test]
fn attestation_roundtrip_serde() { fn attestation_scale_codec_roundtrip() {
use parity_scale_codec::{Decode, Encode};
let att = AssetAttestation { let att = AssetAttestation {
hash: AssetHash::new([1u8; 32]), hash: AssetHash::new([1u8; 32]),
creator: AccountId::new([2u8; 32]), creator: AccountId::new([2u8; 32]),
signature: Signature::new([3u8; 64]), signature: Signature::new([3u8; 64]),
anchored_at_block: 42, anchored_at_block: 42,
agent_chain: vec![], agent_chain: AgentChain::default(),
}; };
let json = serde_json::to_string(&att).unwrap(); let bytes = att.encode();
let parsed: AssetAttestation = serde_json::from_str(&json).unwrap(); let parsed = AssetAttestation::decode(&mut &bytes[..]).unwrap();
assert_eq!(att, parsed); assert_eq!(att, parsed);
} }
#[test] #[test]
fn attestation_supports_agi_co_authorship() { fn attestation_supports_agi_co_authorship() {
// Inmutable #41 — agent_chain may carry AGI co-authors. // Inmutable #41 — agent_chain may carry AGI co-authors (bounded canonical).
let agi1 = AccountId::new([10u8; 32]); let agi1 = AccountId::new([10u8; 32]);
let agi2 = AccountId::new([11u8; 32]); let agi2 = AccountId::new([11u8; 32]);
let agent_chain = AgentChain::try_from(alloc::vec![agi1, agi2])
.expect("2 co-authors within bound canonical");
let att = AssetAttestation { let att = AssetAttestation {
hash: AssetHash::new([1u8; 32]), hash: AssetHash::new([1u8; 32]),
creator: AccountId::new([2u8; 32]), creator: AccountId::new([2u8; 32]),
signature: Signature::new([3u8; 64]), signature: Signature::new([3u8; 64]),
anchored_at_block: 0, anchored_at_block: 0,
agent_chain: vec![agi1, agi2], agent_chain,
}; };
assert_eq!(att.agent_chain.len(), 2); assert_eq!(att.agent_chain.len(), 2);
} }
#[test]
fn agent_chain_bound_canonical_max_10() {
// BoundedVec canonical enforce max 10 AGI co-authors per Inmutable #41.
let too_many: alloc::vec::Vec<AccountId> = (0..11)
.map(|i| AccountId::new([i; 32]))
.collect();
let result = AgentChain::try_from(too_many);
assert!(result.is_err(), "Bound canonical = ConstU32<10> enforce");
}
#[test]
fn agent_chain_max_10_accepted() {
// Exactly 10 co-authors canonical (boundary canonical).
let exactly_10: alloc::vec::Vec<AccountId> = (0..10)
.map(|i| AccountId::new([i; 32]))
.collect();
let agent_chain = AgentChain::try_from(exactly_10)
.expect("10 co-authors at boundary canonical");
assert_eq!(agent_chain.len(), 10);
}
} }