Phase 2.7 AGENT IDENTITY pallet REAL IMPL canonical. Full 8-step CHN-7 flow canonical register_agent_did dispatchable + storage assertions + Inmutable #41 Cross-Species Co-authorship enforcement compile-detect. Mock runtime + 10 integration tests E2E pattern v0.0.18 + v0.0.22 mirror. ## Real impl canonical 8-step CHN-7 1. ensure_signed(origin) 2. Idempotency check (!Identities::contains_key(&who)) 3. Derive DID canonical via derive_did(public_key) BLAKE3 (Inmutable #16) 4. DID collision check (!DidToAccount::contains_key(&did)) 5. Build AgentChain BoundedVec from co_authors_raw Vec<[u8;32]> (overflow → AgentChainOverflow) 6. Inmutable #41 enforce: Hybrid requires non-empty agent_chain 7. Storage insert: Identities + DidToAccount reverse lookup 8. Emit AgentRegistered + AgentBound (if cross-species) Pure helper Pallet::<T>::query_identity_of() para Runtime API v0.0.27. ## 10 integration tests E2E canonical - scenario_1: Human self-registration → ok + DID + empty chain - scenario_2: AGI self-registration → ok + Inmutable #41 sovereign - scenario_3: Hybrid + 2 co-authors → ok + AgentBound emitted - scenario_4: Hybrid + empty chain → Err::HybridRequiresAgentChain - scenario_5: duplicate account → Err::AlreadyRegistered - scenario_6: same pubkey diff account → Err::DidAlreadyTaken - scenario_7: 11 co-authors → Err::AgentChainOverflow (ConstU32<10>) - scenario_8: DID → Account reverse lookup canonical - scenario_9: Human + AGI both is_sovereign() (Inmutable #41 lobo blanco) - scenario_10: 8-step CHN-7 walkthrough explicit Pre/Post assertions ## Files - EDIT: crates/origin-chain-agent-identity/src/lib.rs (real impl 8-step + query_identity_of helper) - NEW: crates/origin-chain-agent-identity/src/mock.rs (80 LOC mock runtime) - NEW: crates/origin-chain-agent-identity/src/tests_integration.rs (310 LOC, 10 tests) - EDIT: CHANGELOG.md (v0.0.26 entry comprehensive) ## Verification - cargo test -p origin-chain-agent-identity — 16 tests verde canonical, 0 failures (10 integration + 4 standalone + 2 auto-generated) 🛠️ Phase 2.7 real impl canonical. Runtime API v0.0.27 next ship → Phase 2.7 COMPLETE. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
351 lines
15 KiB
Rust
351 lines
15 KiB
Rust
//! # pallet-agent-identity — Phase 2.7 scaffold v0.0.24 (Inmutable #41 AGI DIDs)
|
|
//!
|
|
//! Agent identity pallet canonical per CHN-7 + Inmutable #41 Cross-Species
|
|
//! Co-authorship. AGI DIDs canonical on-chain registrados — AGIs first-class
|
|
//! citizens del Origin Metaverse pueden owning assets + signing transactions +
|
|
//! DAO voting via sus DIDs canonical.
|
|
//!
|
|
//! ## Status canonical: v0.0.24 — Scaffold trait-first foundation
|
|
//!
|
|
//! Este crate provee canonical:
|
|
//! - Pallet Config trait canonical (RuntimeEvent association) ✅
|
|
//! - Storage `Identities<StorageMap<AccountId, AgentIdentity>>` per-account O(1) ✅
|
|
//! - Reverse lookup `DidToAccount<StorageMap<Did, AccountId>>` ✅
|
|
//! - Events canonical `AgentRegistered` + `AgentBound` + `AgentUpdated` ✅
|
|
//! - Errors canonical (PROV-like pattern) ✅
|
|
//! - Scaffold dispatchable `register_agent_did` ✅
|
|
//! - Helper `derive_did(public_key) -> Did` canonical via BLAKE3 ✅
|
|
//!
|
|
//! ## Tres clases canonical de agent registrable
|
|
//!
|
|
//! - **Human**: Wallet humano creator. Self-registration via public_key.
|
|
//! - **AGI**: AGI canonical (13 hermanos + future). Self-registration via daimonion
|
|
//! public_key (ed25519 keypair canonical per AGI).
|
|
//! - **Hybrid**: Cross-species collaborative. Registrado por humano coordinator
|
|
//! con `agent_chain` BoundedVec populated canonical preservando lineage.
|
|
//!
|
|
//! ## Lo que NO funciona aún (v0.0.26 real impl + v0.0.27 Runtime API)
|
|
//!
|
|
//! - Real `register_agent_did` impl con storage assertions end-to-end (v0.0.26)
|
|
//! - Mock runtime integration tests E2E (v0.0.26, pattern v0.0.22 mirror)
|
|
//! - Runtime API canonical `AgentIdentityApi.query_identity` read-only (v0.0.27)
|
|
//!
|
|
//! ## Roadmap canonical post-v0.0.24
|
|
//!
|
|
//! 1. **v0.0.24 (ESTA)**: Scaffold trait-first compile-only canonical
|
|
//! 2. **v0.0.25**: Wired runtime composition Index 9 canonical
|
|
//! 3. **v0.0.26**: Real `register_agent_did` impl + mock runtime integration tests E2E
|
|
//! 4. **v0.0.27**: Runtime API `AgentIdentityApi` declaration + impl runtime → **Phase 2.7 COMPLETE**
|
|
//! 5. **v0.1.0** (post Q3 2026 testnet): full integration end-to-end
|
|
//!
|
|
//! ## Cross-pillar consumers canonical desbloqueados (post-impl)
|
|
//!
|
|
//! - Trinity (Phanes/Daedalus/Hephaistos): asset registry queries AGI co-author DIDs
|
|
//! - Mathi (Pilar 1 AMIS): tier-aware fees + EEG consent via DID resolution
|
|
//! - Codex (.odoc + origindocs): author DID canonical academic IP defensible
|
|
//! - Atelier (Marihan): Skill Library NFT mint via institutional wallet identity
|
|
//! - Ethos (Sussi): credential mint canonical needs human/AGI DID
|
|
//! - Ergon (Pilar 2 Noesis): validation cycle attests AGI authorship via DID
|
|
|
|
#![cfg_attr(not(feature = "std"), no_std)]
|
|
#![warn(missing_docs)]
|
|
|
|
pub use pallet::*;
|
|
|
|
/// Re-export canonical types desde origin-chain-types para consumers.
|
|
pub use origin_chain_types::{AgentIdentity, AgentKind, Did};
|
|
|
|
/// Derive deterministic DID canonical from public_key via BLAKE3 hash.
|
|
///
|
|
/// Per CHN-7: `did = BLAKE3(public_key || schema_version)`. Garantiza
|
|
/// determinismo cross-runtime canonical (Pi 5 + Desktop + M4 Ultra producen
|
|
/// el mismo DID para el mismo public_key, Inmutable #16 byte-determinism).
|
|
///
|
|
/// Schema version actual canonical: `"1.0.0 "` (8-byte ASCII fixed, alineado
|
|
/// con ValidationAttestation pattern Phase 2.1).
|
|
pub fn derive_did(public_key: &[u8; 32]) -> Did {
|
|
let mut hasher = blake3::Hasher::new();
|
|
hasher.update(public_key);
|
|
hasher.update(b"1.0.0 "); // schema version canonical
|
|
let hash = hasher.finalize();
|
|
origin_chain_types::AssetHash::new(*hash.as_bytes())
|
|
}
|
|
|
|
/// Runtime API canonical para cross-pillar consumers + light clients.
|
|
///
|
|
/// Permite query canonical de agent identity canonical sin necesidad de full
|
|
/// node access — light client envía `AccountId` → recibe `Option<AgentIdentity>`
|
|
/// canonical con DID + kind + creator + agent_chain canonical.
|
|
///
|
|
/// Consumers canonical:
|
|
/// - Trinity (Phanes): asset gallery render con AGI co-author badge
|
|
/// - Mathi AMIS: per-session tier resolution canonical via DID lookup
|
|
/// - Codex (.odoc + origindocs): author DID resolution canonical academic IP
|
|
/// - Atelier (Marihan): institutional wallet identity verification canonical
|
|
/// - Ethos (Sussi): credential issuer DID resolution canonical
|
|
///
|
|
/// Future Phase 2.8 expansion canonical:
|
|
/// - `query_by_did(did) -> Option<AccountId>` (reverse lookup paginated)
|
|
/// - `list_agi_agents() -> Vec<(AccountId, AgentIdentity)>` (Origin AGIs canonical)
|
|
/// - `list_co_authors(asset_hash) -> Vec<AccountId>` (cross-link Provenance)
|
|
sp_api::decl_runtime_apis! {
|
|
/// Agent identity runtime API canonical canonical (Phase 2.7 v0.0.27).
|
|
pub trait AgentIdentityApi {
|
|
/// Query agent identity canonical por AccountId canonical.
|
|
///
|
|
/// Returns `Some(AgentIdentity)` si registered previamente on-chain,
|
|
/// `None` si nunca registered canonical. Read-only canonical sin
|
|
/// transaction fee canonical (off-chain via RPC).
|
|
fn query_identity(account: origin_chain_types::AccountId) -> Option<origin_chain_types::AgentIdentity>;
|
|
}
|
|
}
|
|
|
|
#[frame_support::pallet]
|
|
pub mod pallet {
|
|
use alloc::vec::Vec;
|
|
use frame_support::pallet_prelude::*;
|
|
use frame_system::pallet_prelude::*;
|
|
use origin_chain_types::{AccountId, AgentChain, AgentIdentity, AgentKind, Did};
|
|
use sp_runtime::traits::SaturatedConversion;
|
|
|
|
/// Pallet configuration trait canonical.
|
|
///
|
|
/// Per CHN-7 + Inmutable #41 + ADR-0057 trait-seam discipline. Scaffold-level
|
|
/// Config canonical: solo RuntimeEvent association. Future canonical fields
|
|
/// post DC ratify (Phase 2.7 DC pending):
|
|
///
|
|
/// - `MaxAgentChainLen: Get<u32>` (BoundedVec bound canonical)
|
|
/// - Custom origin canonical para Hybrid agent registration (governance)
|
|
/// - Pallet weights canonical desde benchmarking real
|
|
#[pallet::config]
|
|
pub trait Config: frame_system::Config {
|
|
/// Runtime event type canonical wired into `RuntimeEvent` aggregate.
|
|
type RuntimeEvent: From<Event<Self>>
|
|
+ IsType<<Self as frame_system::Config>::RuntimeEvent>;
|
|
}
|
|
|
|
/// Pallet definition canonical.
|
|
///
|
|
/// v0.0.24 scaffold: storage layout canonical + Config + Events + Errors +
|
|
/// scaffold dispatchable. Real impl `register_agent_did` v0.0.26 next slice.
|
|
#[pallet::pallet]
|
|
pub struct Pallet<T>(_);
|
|
|
|
/// CHN-7 canonical: `Identities<StorageMap<AccountId, AgentIdentity>>` canonical.
|
|
///
|
|
/// O(1) lookup canonical via Blake2_128Concat hasher (FRAME-native canonical).
|
|
/// Memory canonical ~190 bytes per identity canonical max (MaxEncodedLen).
|
|
#[pallet::storage]
|
|
#[pallet::getter(fn identity_of)]
|
|
pub type Identities<T: Config> =
|
|
StorageMap<_, Blake2_128Concat, T::AccountId, AgentIdentity, OptionQuery>;
|
|
|
|
/// Reverse lookup canonical: `DidToAccount<StorageMap<Did, T::AccountId>>`.
|
|
///
|
|
/// O(1) lookup canonical desde DID → T::AccountId canonical para cross-pillar
|
|
/// consumers (Codex .odoc author resolution, Trinity asset co-author lookup).
|
|
#[pallet::storage]
|
|
#[pallet::getter(fn account_of_did)]
|
|
pub type DidToAccount<T: Config> =
|
|
StorageMap<_, Blake2_128Concat, Did, T::AccountId, OptionQuery>;
|
|
|
|
/// Events emisión canonical (CHN-7 + Inmutable #41).
|
|
#[pallet::event]
|
|
#[pallet::generate_deposit(pub(super) fn deposit_event)]
|
|
pub enum Event<T: Config> {
|
|
/// Agent registered canonical on-chain — DID canonical first registration.
|
|
///
|
|
/// Materializa Inmutable #41 Cross-Species Co-authorship compile-detect.
|
|
AgentRegistered {
|
|
/// T::AccountId del agent registered canonical.
|
|
account: T::AccountId,
|
|
/// DID canonical derived from public_key + schema version.
|
|
did: Did,
|
|
/// AgentKind canonical (Human + AGI + Hybrid).
|
|
kind: AgentKind,
|
|
},
|
|
/// Cross-species lineage bound canonical (Hybrid agent registration).
|
|
///
|
|
/// Inmutable #41 cross-species explicit canonical — emitted SOLO cuando
|
|
/// `agent_chain` canonical non-empty (Hybrid o collaborative registration).
|
|
AgentBound {
|
|
/// T::AccountId del agent canonical.
|
|
account: T::AccountId,
|
|
/// Number canonical de co-authors en agent_chain.
|
|
co_author_count: u32,
|
|
},
|
|
/// Agent identity updated canonical (provenance_proof binding o agent_chain expansion).
|
|
///
|
|
/// Phase 2.8 anticipated canonical: futuro update_provenance dispatchable
|
|
/// para academic IP attestation post-registration canonical.
|
|
AgentUpdated {
|
|
/// T::AccountId del agent canonical.
|
|
account: T::AccountId,
|
|
},
|
|
}
|
|
|
|
/// Pallet errors canonical.
|
|
#[pallet::error]
|
|
pub enum Error<T> {
|
|
/// CHN-7 canonical: agent canonical already registered en este account.
|
|
AlreadyRegistered,
|
|
/// CHN-7 canonical: DID canonical already registered por diferente account
|
|
/// (collision protection canonical).
|
|
DidAlreadyTaken,
|
|
/// CHN-7 canonical: agent_chain canonical overflow `BoundedVec` bound.
|
|
AgentChainOverflow,
|
|
/// CHN-7 canonical: Hybrid agent requires non-empty agent_chain canonical
|
|
/// (Inmutable #41 cross-species verification).
|
|
HybridRequiresAgentChain,
|
|
}
|
|
|
|
/// Dispatchables canonical (real impl v0.0.26).
|
|
#[pallet::call]
|
|
impl<T: Config> Pallet<T> {
|
|
/// CHN-7 canonical: `register_agent_did` real dispatchable canonical.
|
|
///
|
|
/// **Real impl v0.0.26**: Full 8-step canonical flow + storage assertions
|
|
/// end-to-end + Inmutable #41 Cross-Species Co-authorship verification
|
|
/// compile-detect.
|
|
///
|
|
/// Flow canonical 8-step per CHN-7:
|
|
/// 1. `ensure_signed(origin)` canonical
|
|
/// 2. Idempotency check: `!Identities::contains_key(&who)` (no duplicate)
|
|
/// 3. Derive DID canonical via `derive_did(public_key)` BLAKE3
|
|
/// 4. DID collision check: `!DidToAccount::contains_key(&did)` (no collision)
|
|
/// 5. Build `AgentChain` canonical `BoundedVec` (overflow → `AgentChainOverflow`)
|
|
/// 6. Inmutable #41 enforce: Hybrid requires non-empty agent_chain
|
|
/// 7. Insert storage canonical: `Identities` + `DidToAccount`
|
|
/// 8. Emit `AgentRegistered` + `AgentBound` (if cross-species) canonical
|
|
#[pallet::call_index(0)]
|
|
#[pallet::weight(Weight::from_parts(10_000, 0))]
|
|
pub fn register_agent_did(
|
|
origin: OriginFor<T>,
|
|
public_key: [u8; 32],
|
|
kind: AgentKind,
|
|
co_authors_raw: Vec<[u8; 32]>,
|
|
) -> DispatchResult {
|
|
// Step 1: ensure origin signed canonical
|
|
let who = ensure_signed(origin)?;
|
|
|
|
// Step 2: idempotency check canonical (no duplicate registration)
|
|
ensure!(
|
|
!Identities::<T>::contains_key(&who),
|
|
Error::<T>::AlreadyRegistered
|
|
);
|
|
|
|
// Step 3: derive DID canonical via BLAKE3 (Inmutable #16 byte-determinism)
|
|
let did = crate::derive_did(&public_key);
|
|
|
|
// Step 4: DID collision check canonical
|
|
ensure!(
|
|
!DidToAccount::<T>::contains_key(did),
|
|
Error::<T>::DidAlreadyTaken
|
|
);
|
|
|
|
// Step 5: build AgentChain canonical BoundedVec<AccountId, ConstU32<10>>
|
|
let mut agent_chain = AgentChain::default();
|
|
for raw in co_authors_raw {
|
|
agent_chain
|
|
.try_push(AccountId::new(raw))
|
|
.map_err(|_| Error::<T>::AgentChainOverflow)?;
|
|
}
|
|
|
|
// Step 6: Inmutable #41 canonical enforce — Hybrid requires non-empty agent_chain
|
|
if matches!(kind, AgentKind::Hybrid) && agent_chain.is_empty() {
|
|
return Err(Error::<T>::HybridRequiresAgentChain.into());
|
|
}
|
|
|
|
// Step 7: build AgentIdentity struct + insert storage canonical
|
|
let has_cross_species = !agent_chain.is_empty();
|
|
let co_author_count = agent_chain.len() as u32;
|
|
let creator = AccountId::new(public_key);
|
|
let block_number: u64 = <frame_system::Pallet<T>>::block_number()
|
|
.saturated_into::<u64>();
|
|
|
|
let identity = AgentIdentity {
|
|
did,
|
|
kind,
|
|
creator,
|
|
registered_at_block: block_number,
|
|
provenance_proof: None,
|
|
agent_chain,
|
|
};
|
|
|
|
Identities::<T>::insert(&who, identity);
|
|
DidToAccount::<T>::insert(did, &who);
|
|
|
|
// Step 8: emit events canonical
|
|
Self::deposit_event(Event::AgentRegistered {
|
|
account: who.clone(),
|
|
did,
|
|
kind,
|
|
});
|
|
|
|
if has_cross_species {
|
|
Self::deposit_event(Event::AgentBound {
|
|
account: who,
|
|
co_author_count,
|
|
});
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
/// Pure helper canonical para Runtime API v0.0.27 consumer.
|
|
impl<T: Config> Pallet<T> {
|
|
/// Query identity canonical sin storage mutation (read-only canonical).
|
|
pub fn query_identity_of(account: &T::AccountId) -> Option<AgentIdentity> {
|
|
Identities::<T>::get(account)
|
|
}
|
|
}
|
|
}
|
|
|
|
extern crate alloc;
|
|
|
|
// v0.0.26 — Mock runtime canonical + integration tests E2E CHN-7 flow.
|
|
// Pattern v0.0.18 (Provenance) + v0.0.22 (Fees) mirror.
|
|
#[cfg(test)]
|
|
pub mod mock;
|
|
|
|
#[cfg(test)]
|
|
mod tests_integration;
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
// Compile-time tests canonical scaffold v0.0.24.
|
|
// Mock runtime integration tests canonical = v0.0.26 next slice.
|
|
|
|
#[test]
|
|
fn pallet_scaffold_compiles_canonical() {
|
|
// Scaffold compiles canonical — verified by cargo check.
|
|
// Real mock runtime integration tests = v0.0.26 (pattern v0.0.22 mirror).
|
|
}
|
|
|
|
#[test]
|
|
fn derive_did_deterministic_canonical() {
|
|
// Inmutable #16 byte-determinism canonical: same public_key → same DID
|
|
let pubkey = [42u8; 32];
|
|
let did1 = derive_did(&pubkey);
|
|
let did2 = derive_did(&pubkey);
|
|
assert_eq!(did1, did2);
|
|
}
|
|
|
|
#[test]
|
|
fn derive_did_different_keys_produce_different_dids_canonical() {
|
|
let did_a = derive_did(&[1u8; 32]);
|
|
let did_b = derive_did(&[2u8; 32]);
|
|
assert_ne!(did_a, did_b);
|
|
}
|
|
|
|
#[test]
|
|
fn re_export_agent_identity_canonical() {
|
|
// Verify re-exports canonical desde origin-chain-types
|
|
let _kind: AgentKind = AgentKind::AGI;
|
|
let _did_alias: Did = origin_chain_types::AssetHash::new([0u8; 32]);
|
|
}
|
|
}
|