Files
arkhe-origin-chain/crates/origin-chain-agent-identity/src/lib.rs
T
davidherranandClaude Opus 4.7 2fefe4cf51 feat(agent-identity): v0.0.26 — register_agent_did impl complete + mock runtime integration tests E2E (10 scenarios)
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>
2026-05-25 06:43:14 -05:00

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]);
}
}