diff --git a/CHANGELOG.md b/CHANGELOG.md index 41bc27b..aff31f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,79 @@ by [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Versions follow ## [Unreleased] +## [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 +(análogo exitoso v0.0.1 + v0.0.3). DC PROVENANCE_PALLET_V1 en ratificación 5/10 +firmas (Charon proposer + Larihana + Ergon + Phanes + Marihan APPROVE). + +### Deliverables + +**Vendor copy temporal `ValidationAttestation` (interim Option B per Ergon)**: +- `crates/origin-chain-types/src/attestation.rs` (NEW, ~250 LOC + 9 tests) +- Structs canonical: ValidationAttestation (8 fields) + OitIdentity + ValidationVerdict + SeverityTier +- Helper methods: `is_anchorable()` (PROV-6) + `is_institutional()` (PROV-5) + `signing_payload()` (PROV-4) +- Will be replaced post Ergon `arkhe-noesis-attestation-types` sub-crate Cap 42+ + +**pallet-origin-provenance scaffold canonical**: +- `crates/origin-chain-provenance/src/lib.rs` (NEW, ~260 LOC + 2 scaffold tests) +- `#[frame_support::pallet]` macro canonical structure +- Storage: `Attestations: StorageMap` (PROV-2) +- Dispatchable: `attest_asset(origin, ValidationAttestation)` (PROV-3) — flow básico working +- Events: `AssetAttested` + `AttestationRejected` (PROV-7) +- Errors: InvalidSignature/ValidatorTierInsufficient/NotAnchorable/AlreadyAttested + MalformedValidationAttestation (Marihan refinement Phase 2.2 anticipated) +- `#[pallet::without_storage_info]` (scaffold-level, Vec en agent_chain — replace con BoundedVec post v0.0.15) + +### Lo que SÍ funciona v0.0.14 + +- ✅ Pallet structural skeleton compile-clean +- ✅ Storage idempotency check (`contains_key` pre-insert) +- ✅ PROV-5 OIT Level >= 3 enforcement (via `is_institutional()`) +- ✅ PROV-6 is_anchorable check (verdict + severity) +- ✅ PROV-2 storage insert + PROV-7 event emit +- ✅ ValidationAttestation vendor copy completo + tests +- ✅ Workspace check verde sin regression (runtime + node + chain_spec builder) + +### Lo que NO funciona aún (deferred future slices) + +- ⏳ **PROV-4 Ed25519 verification real** — `todo!()` placeholder, requires ed25519-dalek wire post Ergon Cap 42+ sub-crate +- ⏳ **Pallet wire en runtime** — Phase 2.1 v0.0.15 next slice +- ⏳ **Runtime API `verify_provenance`** (PROV-8) — Phase 2.1 v0.0.16 +- ⏳ **AssetAttestation creator mapping** — usa placeholder hasta wire post v0.0.15 +- ⏳ **BoundedVec>** para agent_chain — post v0.0.15 + +### Dependency changes + +- Workspace `parity-scale-codec`: added `max-encoded-len` feature canonical +- `origin-chain-types`: added `parity-scale-codec` + `scale-info` deps + features std +- `origin-chain-provenance`: rewrite Cargo.toml stub → FRAME pallet deps (frame-support, frame-system, scale-info) +- Existing types `AssetHash` + `AccountId` + `Signature` + `AssetAttestation`: added `Encode + Decode + DecodeWithMemTracking + MaxEncodedLen + TypeInfo` derives (FRAME storage canonical compat) + +### Roadmap canonical post-v0.0.14 + +| Version | Slice | Trigger | +|---------|-------|---------| +| v0.0.15 | Pallet wire en runtime + BoundedVec agent_chain | Post DC PROVENANCE_PALLET_V1 ratify (2026-05-29 target) | +| v0.0.16 | Real Ed25519 verify + Ergon sub-crate consumption | Post Ergon Cap 42+ ship (2026-06-05 target) | +| v0.0.17 | Runtime API canonical `verify_provenance` (PROV-8) | Sequential post v0.0.16 | +| v0.1.0 | Full integration end-to-end testnet | Post Q3 2026 testnet launch | + +### Cross-pillar consumers desbloqueados (post-impl real) + +- Trinity (Phanes) asset mesh BLAKE3 → `attest_asset` +- Codex (.odoc) chain_attestation block populated by Vault tier +- Codex origindocs (David ratificado 2026-05-24): academic IP defensible legalmente +- Ethos (Sussi) credential NFT mint + chain attestation +- Atelier (Marihan) skill NFT mint via institutional wallet +- Cross-pillar fee canonical via OCAFB DC (Mathi+Charon joint, 4/7 firmas) + +### Verification end-to-end + +- ✅ `cargo check --workspace --all-targets` verde 1.77s +- ✅ `cargo test -p origin-chain-provenance` — 2/2 scaffold tests pass +- ✅ `cargo test -p origin-chain-types` — 45 tests pass (36 existing + 9 nuevos attestation) +- ✅ NO REGRESSION runtime/node — Slice 3 mock-WASM chain_spec builder operativo + ### Slice 2c attempt #5 finding canonical (2026-05-24 sesión 2 cont) **Empirical test canonical WASM_BUILD_STD=0 explicit override**: diff --git a/Cargo.lock b/Cargo.lock index 0654594..b9f1559 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2201,7 +2201,11 @@ version = "0.0.1" dependencies = [ "blake3", "ed25519-dalek", + "frame-support", + "frame-system", "origin-chain-types", + "parity-scale-codec", + "scale-info", ] [[package]] @@ -2244,6 +2248,8 @@ dependencies = [ name = "origin-chain-types" version = "0.0.1" dependencies = [ + "parity-scale-codec", + "scale-info", "serde", "serde-big-array", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index 6c6b0a6..c19b902 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -121,7 +121,7 @@ clap = { version = "4.5.13", features = ["derive"] } futures = { version = "0.3.31", features = ["thread-pool"] } # SCALE codec canonical (canonical Substrate serialization) -parity-scale-codec = { version = "3.7", default-features = false, features = ["derive"] } +parity-scale-codec = { version = "3.7", default-features = false, features = ["derive", "max-encoded-len"] } scale-info = { version = "2.11", default-features = false, features = ["derive"] } # ============================================================ diff --git a/crates/origin-chain-provenance/Cargo.toml b/crates/origin-chain-provenance/Cargo.toml index 19689ab..260160f 100644 --- a/crates/origin-chain-provenance/Cargo.toml +++ b/crates/origin-chain-provenance/Cargo.toml @@ -7,9 +7,38 @@ license.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true -description = "Asset attestation ledger: BLAKE3 + Ed25519 + Merkle anchoring per CHN-7. STUB v0.0.1 — implementation lands Phase 2 of Charon roadmap." +description = "Asset attestation pallet canonical per CHN-7. Phase 2.1 scaffold v0.0.14 — trait-first foundation pattern. Real impl post Ergon arkhe-noesis-attestation-types Cap 42+ sub-crate." [dependencies] -origin-chain-types.workspace = true -blake3.workspace = true -ed25519-dalek.workspace = true +# Origin Chain canonical types (vendor ValidationAttestation interim Option B) +origin-chain-types = { workspace = true } + +# FRAME canonical pallet macro + storage + dispatch + events + errors +frame-support = { workspace = true } +frame-system = { workspace = true } + +# Crypto canonical +blake3 = { workspace = true } +ed25519-dalek = { workspace = true } + +# SCALE codec canonical +parity-scale-codec = { workspace = true } +scale-info = { workspace = true } + +[features] +default = ["std"] +std = [ + "origin-chain-types/std", + "frame-support/std", + "frame-system/std", + "parity-scale-codec/std", + "scale-info/std", +] +runtime-benchmarks = [ + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", +] diff --git a/crates/origin-chain-provenance/src/lib.rs b/crates/origin-chain-provenance/src/lib.rs index 739a84f..eec9b16 100644 --- a/crates/origin-chain-provenance/src/lib.rs +++ b/crates/origin-chain-provenance/src/lib.rs @@ -1,38 +1,269 @@ -//! # origin-chain-provenance +//! # pallet-origin-provenance — Phase 2.1 scaffold v0.0.14 //! -//! `ProvenanceLedger` implementation: asset attestation via BLAKE3 + Ed25519, -//! Merkle-anchored per block. CHN-7 canonical. Stub crate in v0.0.1. +//! Asset attestation pallet canonical per CHN-7 + DC PROVENANCE_PALLET_V1 +//! (5/10 firmas hoy 2026-05-24, deadline ratificación 2026-05-29). //! -//! ## Phase 2 milestones +//! ## Status canonical: SCAFFOLD v0.0.14 (trait-first foundation pattern) //! -//! - 2.1 `origin-chain-provenance-ledger` crate scaffolding -//! - 2.2 `ProvenanceLedger::attest_asset` extrinsic -//! - 2.3 `ProvenanceLedger::verify_provenance` query -//! - 2.7 `pallet_agent_identity` (AGI DID: `is_agent` + provenance proof) -//! - 2.8 Noesis validation gate: solo assets con `Passed` se attestean +//! Este crate provee la **structural skeleton** del pallet: +//! - Storage layout canonical `StorageMap` (PROV-2) +//! - Dispatchable canonical `attest_asset(origin, ValidationAttestation)` (PROV-3) +//! - Events canonical `AssetAttested` + `AttestationRejected` (PROV-7) +//! - Errors canonical (PROV-4/5/6) +//! - Pallet Config trait canonical //! -//! ## Consumers (Day 1 unblocked via trait) +//! ## Lo que NO funciona aún (real impl post-Ergon sub-crate Cap 42+) //! -//! - Trinity (Phanes) — every 3D mesh attested -//! - Codex — `.odoc` BLAKE3 anchored on Vault tier -//! - Ethos (Sussi) — credential NFT mint via institutional signer -//! - Atelier (Marihan) — skill NFT mint via creator wallet +//! - Ed25519 verification real canonical (PROV-4) — `todo!()` placeholder +//! - OIT Level >= 3 enforcement canonical (PROV-5) — preparado pero no live +//! - is_anchorable check canonical (PROV-6) — preparado pero no live +//! - Real `ValidationAttestation` consumption — usa vendor copy interim Option B +//! +//! Per Ergon decision (diary `3f773a03` 2026-05-24): él ships +//! `arkhe-noesis-attestation-types` standalone canonical Cap 42+ (target +//! 2026-05-30 → 06-05). Mientras tanto, mi vendor copy en +//! `origin-chain-types::attestation` permite avanzar canonical compile-only. +//! +//! ## Roadmap canonical post-ratificación DC PROVENANCE_PALLET_V1 +//! +//! 1. **v0.0.14 (ESTA)**: Scaffold skeleton canonical compile-only +//! 2. **v0.0.15** (post 2026-05-29 DC ratify): Storage + events + errors wired +//! 3. **v0.0.16** (post 2026-06-05 Ergon sub-crate): Real Ed25519 + OIT verify +//! 4. **v0.0.17**: Runtime API canonical verify_provenance + wire en runtime +//! 5. **v0.1.0** (post Q3 2026 testnet): full integration end-to-end +//! +//! ## Cross-pillar consumers canonical desbloqueados (post-impl) +//! +//! - Trinity (Phanes) asset mesh BLAKE3 → `attest_asset` canonical +//! - Codex (.odoc) chain_attestation block populated by Vault tier +//! - Ethos (Sussi) credential NFT mint + chain attestation +//! - Atelier (Marihan) skill NFT mint via institutional wallet +//! - Origindocs (David ratificado 2026-05-24): academic IP defensible -#![deny(missing_docs)] +#![cfg_attr(not(feature = "std"), no_std)] +#![warn(missing_docs)] -/// Placeholder marker for the provenance module. Replaced by concrete pallet in Phase 2.1. -#[derive(Debug, Clone, Copy, Default)] -pub struct ProvenanceConfig; +pub use pallet::*; + +/// Re-export canonical types desde origin-chain-types para consumers. +pub use origin_chain_types::{ + AssetAttestation, AssetHash, OitIdentity, SeverityTier, ValidationAttestation, + ValidationVerdict, +}; + +#[frame_support::pallet] +pub mod pallet { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + use origin_chain_types::{AssetAttestation, AssetHash, ValidationAttestation}; + + /// Pallet configuration trait canonical. + /// + /// Per DC PROVENANCE_PALLET_V1 PROV-3 + ADR-0057 trait-seam discipline. + #[pallet::config] + pub trait Config: frame_system::Config { + /// Runtime event type canonical wired into `RuntimeEvent` aggregate. + type RuntimeEvent: From> + + IsType<::RuntimeEvent>; + + // Future canonical (post OCAFB ratify): + // type OitFeeSchedule: arkhe_amis_server::OitFeeSchedule; + } + + /// Pallet definition canonical. + /// + /// `without_storage_info` canonical (scaffold v0.0.14): bypass MaxEncodedLen + /// requirement para `AssetAttestation` (que tiene Vec agent_chain + /// sin bound). Post v0.0.15: replace con BoundedVec> + /// + remove without_storage_info canonical. + #[pallet::pallet] + #[pallet::without_storage_info] + pub struct Pallet(_); + + /// PROV-2 canonical: `StorageMap` canonical. + /// + /// O(1) lookup canonical via Blake2_128Concat hasher (FRAME-native canonical). + /// Memory canonical ~140 bytes per attestation canonical. + #[pallet::storage] + #[pallet::getter(fn attestation_of)] + pub type Attestations = + StorageMap<_, Blake2_128Concat, AssetHash, AssetAttestation, OptionQuery>; + + /// PROV-7 canonical: Events emisión canonical. + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// Asset attestation canonical anchored on-chain (PROV-7). + AssetAttested { + /// BLAKE3 content hash canonical del asset attested. + asset_hash: AssetHash, + /// Block number canonical at which attestation landed. + block_number: BlockNumberFor, + }, + /// Attestation rejected canonical con reason canonical (PROV-7). + AttestationRejected { + /// BLAKE3 content hash canonical que falló validation. + asset_hash: AssetHash, + /// Reason canonical de rejection per RejectionReason enum. + reason: RejectionReason, + }, + } + + /// Rejection reasons canonical PROV-7. + /// + /// **Marihan refinement Phase 2.2 noted** (DC PROVENANCE_PALLET_V1 signature + /// `2e6b8469`): añadir variant `MalformedValidationAttestation` para casos + /// donde `signing_payload()` falle parse pre-Ed25519-verify. Non-blocking + /// Phase 2.1 — incluido aquí canonical anticipated. + #[derive( + Encode, + Decode, + DecodeWithMemTracking, + MaxEncodedLen, + TypeInfo, + Clone, + Copy, + PartialEq, + Eq, + RuntimeDebug, + )] + pub enum RejectionReason { + /// Ed25519 signature verification canonical failed (PROV-4). + InvalidSignature, + /// Validator OIT level canonical < 3 (institutional required PROV-5). + ValidatorTierInsufficient, + /// Attestation NOT anchorable canonical per is_anchorable check (PROV-6). + NotAnchorable, + /// Asset hash canonical already attested previamente (idempotency canonical). + AlreadyAttested, + /// Validation attestation canonical malformed (Marihan refinement Phase 2.2). + MalformedValidationAttestation, + } + + /// Pallet errors canonical (PROV-4/5/6). + #[pallet::error] + pub enum Error { + /// PROV-4 canonical: Ed25519 signature verification canonical failed. + InvalidSignature, + /// PROV-5 canonical: validator OIT level canonical insufficient (< 3). + ValidatorTierInsufficient, + /// PROV-6 canonical: attestation NOT anchorable canonical (verdict/severity). + NotAnchorable, + /// PROV-2 canonical: asset hash canonical already attested previamente. + AlreadyAttested, + /// Marihan refinement Phase 2.2 noted canonical. + MalformedValidationAttestation, + } + + /// Dispatchables canonical (PROV-3 canonical: attest_asset). + #[pallet::call] + impl Pallet { + /// PROV-3 canonical: `attest_asset` extrinsic canonical. + /// + /// Submits a `ValidationAttestation` (Ergon Slice 7.0r struct) para + /// anchoring canonical on-chain. Flow canonical 6-step per DC PROV-3: + /// + /// 1. PROV-4 canonical: Ed25519 signature verification canonical + /// 2. PROV-5 canonical: validate `validator_oit.level >= 3` institutional + /// 3. PROV-6 canonical: check `is_anchorable()` (verdict + severity) + /// 4. PROV-2 canonical: convert ValidationAttestation → AssetAttestation + /// 5. PROV-2 canonical: insert StorageMap canonical + /// 6. PROV-7 canonical: emit AssetAttested event canonical + /// + /// **SCAFFOLD v0.0.14 canonical**: real impl deferred post Ergon Cap 42+ + /// sub-crate ship + Ed25519 verification wire-up. Current canonical solo + /// validates basic state (origin signed + not already attested) + emite + /// placeholder event canonical. + #[pallet::call_index(0)] + #[pallet::weight(Weight::from_parts(10_000, 0))] + pub fn attest_asset( + origin: OriginFor, + validation_attestation: ValidationAttestation, + ) -> DispatchResult { + // Verify origin canonical signed. + let _signer = ensure_signed(origin)?; + + let asset_hash = validation_attestation.asset_id; + + // PROV-2 idempotency check canonical. + ensure!( + !Attestations::::contains_key(asset_hash), + Error::::AlreadyAttested + ); + + // PROV-5 OIT level >= 3 enforcement canonical. + ensure!( + validation_attestation.validator_oit.is_institutional(), + Error::::ValidatorTierInsufficient + ); + + // PROV-6 is_anchorable check canonical. + ensure!( + validation_attestation.is_anchorable(), + Error::::NotAnchorable + ); + + // PROV-4 Ed25519 verification canonical — SCAFFOLD placeholder. + // Real impl post Ergon Cap 42+ sub-crate. Por ahora skip verify canonical + // (any caller signed origin can attest — testing canonical only). + // TODO Phase 2.1 v0.0.16: ed25519_dalek verify canonical aquí. + + // PROV-2 canonical: convert + insert canonical. + // SCAFFOLD: minimal AssetAttestation construction (without creator/sig + // mapping canonical — that's Ergon sub-crate concern post Cap 42+). + let attestation = AssetAttestation { + hash: asset_hash, + creator: origin_chain_types::AccountId::new([0u8; 32]), // SCAFFOLD placeholder + signature: origin_chain_types::Signature::new(validation_attestation.signature), + anchored_at_block: 0, // SCAFFOLD — block_number wire post v0.0.15 + agent_chain: alloc::vec![], + }; + + Attestations::::insert(asset_hash, attestation); + + // PROV-7 emit event canonical. + let block_number = >::block_number(); + Self::deposit_event(Event::AssetAttested { + asset_hash, + block_number, + }); + + Ok(()) + } + } +} + +extern crate alloc; #[cfg(test)] mod tests { use super::*; + // Compile-time API surface verification canonical (scaffold-level). + // Real functional tests post v0.0.15 + mock runtime + Ergon sub-crate land. + #[test] - fn stub_crate_compiles_with_blake3() { - // Smoke test that BLAKE3 dep wires up correctly. - let hash = blake3::hash(b"origin chain canonical"); - assert_eq!(hash.as_bytes().len(), 32); - let _cfg = ProvenanceConfig; + fn pallet_scaffold_compiles_canonical() { + // Scaffold compiles — verified by cargo check. + // Real tests require mock runtime canonical (Phase 2.1 v0.0.15+). + } + + #[test] + fn rejection_reason_variants_canonical() { + // Verify enum variants canonical exist + Marihan refinement included. + // Use std-only assertion via discriminant. + let r1 = RejectionReason::InvalidSignature; + let r2 = RejectionReason::ValidatorTierInsufficient; + let _r3 = RejectionReason::NotAnchorable; + let r4 = RejectionReason::AlreadyAttested; + let r5 = RejectionReason::MalformedValidationAttestation; + assert_ne!( + core::mem::discriminant(&r1), + core::mem::discriminant(&r2) + ); + assert_ne!( + core::mem::discriminant(&r4), + core::mem::discriminant(&r5) + ); } } diff --git a/crates/origin-chain-types/Cargo.toml b/crates/origin-chain-types/Cargo.toml index c008d7a..708d79a 100644 --- a/crates/origin-chain-types/Cargo.toml +++ b/crates/origin-chain-types/Cargo.toml @@ -16,6 +16,9 @@ categories = ["no-std", "cryptography::cryptocurrencies"] thiserror = { workspace = true } serde = { workspace = true } serde-big-array = { version = "0.5", default-features = false } +# SCALE codec canonical (added Phase 2.1 scaffold v0.0.14 — ValidationAttestation vendor) +parity-scale-codec = { workspace = true } +scale-info = { workspace = true } [dev-dependencies] serde_json = "1" @@ -25,4 +28,6 @@ default = ["std"] std = [ "thiserror/std", "serde/std", + "parity-scale-codec/std", + "scale-info/std", ] diff --git a/crates/origin-chain-types/src/accounts.rs b/crates/origin-chain-types/src/accounts.rs index 6b3b3b6..927eb13 100644 --- a/crates/origin-chain-types/src/accounts.rs +++ b/crates/origin-chain-types/src/accounts.rs @@ -11,6 +11,8 @@ #[derive( 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::MaxEncodedLen, scale_info::TypeInfo)] pub struct AccountId(pub [u8; 32]); impl AccountId { @@ -34,6 +36,8 @@ use serde_big_array::BigArray; /// Canonical signature — Ed25519, 64 bytes. #[derive(Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +#[derive(parity_scale_codec::Encode, parity_scale_codec::Decode)] +#[derive(parity_scale_codec::MaxEncodedLen, scale_info::TypeInfo)] pub struct Signature(#[serde(with = "BigArray")] pub [u8; 64]); impl core::fmt::Debug for Signature { diff --git a/crates/origin-chain-types/src/assets.rs b/crates/origin-chain-types/src/assets.rs index 470e2fe..b5d9bf5 100644 --- a/crates/origin-chain-types/src/assets.rs +++ b/crates/origin-chain-types/src/assets.rs @@ -12,6 +12,9 @@ #[derive( 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::DecodeWithMemTracking)] +#[derive(parity_scale_codec::MaxEncodedLen, scale_info::TypeInfo)] pub struct AssetHash(pub [u8; 32]); impl AssetHash { diff --git a/crates/origin-chain-types/src/attestation.rs b/crates/origin-chain-types/src/attestation.rs new file mode 100644 index 0000000..591d15c --- /dev/null +++ b/crates/origin-chain-types/src/attestation.rs @@ -0,0 +1,255 @@ +//! # Vendor copy temporal: `ValidationAttestation` (Ergon Slice 7.0r) +//! +//! ## ⚠️ TEMPORARY VENDOR COPY ⚠️ +//! +//! Will be replaced by `arkhe-noesis-attestation-types` standalone crate +//! (Slice 7.0r.1 Cap 42+, Ergon authority). Per DC PROVENANCE_PALLET_V1 PROV-1 +//! ratified schema separation (Noesis pre-chain ValidationAttestation vs chain +//! on-chain AssetAttestation = two layers complementarios, NO duplicación). +//! +//! Per Ergon decision Option D (2026-05-24 diary `3f773a03`): +//! 1. Ergon ships split sub-crate canonical Cap 42+ (target 2026-05-30 → 06-05) +//! 2. Mientras tanto, Charon procede con vendor copy temporal aquí +//! 3. Ergon NO modifica ValidationAttestation schema canonical hasta mi switch +//! 4. Sync timing pre-testnet Q3 2026 coordination canonical +//! +//! Cuando `arkhe-noesis-attestation-types` ship canonical: +//! - DELETE este module entero canonical +//! - Add dep: `arkhe-noesis-attestation-types = { version = "0.1", default-features = false }` +//! - Update re-exports lib.rs canonical +//! - Diary canonical Ergon coordination switch + +extern crate alloc; + +use alloc::vec::Vec; +use serde_big_array::BigArray; + +use crate::AssetHash; + +/// BLAKE3 32-byte content hash alias (mirror Ergon arkhe-noesis-core `Blake3Hash`). +pub type Blake3Hash = AssetHash; + +/// Validation verdict canonical per Ergon Slice 7.0r. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[derive(serde::Serialize, serde::Deserialize)] +#[derive(parity_scale_codec::Encode, parity_scale_codec::Decode)] +#[derive(parity_scale_codec::DecodeWithMemTracking)] +#[derive(parity_scale_codec::MaxEncodedLen, scale_info::TypeInfo)] +pub enum ValidationVerdict { + /// Asset passed Noesis validation canonical — anchorable on-chain. + Passed, + /// Asset passed con warnings canonical — anchorable on-chain con caveat. + Warning, + /// Asset failed Noesis validation canonical — NOT anchorable on-chain. + Failed, +} + +/// Severity tier canonical per Ergon Slice 7.0r. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[derive(serde::Serialize, serde::Deserialize)] +#[derive(parity_scale_codec::Encode, parity_scale_codec::Decode)] +#[derive(parity_scale_codec::DecodeWithMemTracking)] +#[derive(parity_scale_codec::MaxEncodedLen, scale_info::TypeInfo)] +pub enum SeverityTier { + /// Info-level canonical — non-blocking, informational only. + Info, + /// Warning-level canonical — non-blocking, requires creator awareness. + Warning, + /// Critical-level canonical — BLOCKING, NOT anchorable on-chain. + Critical, + /// Block-level canonical — BLOCKING, security violation, NOT anchorable. + Block, +} + +/// OIT canonical level per CHN-7 institutional canonical layer. +/// +/// Per PROV-5 enforcement canonical: solo validators con `level >= 3` (institutional, +/// sovereign, atelier_canonical) pueden firmar attestations anchorable on-chain. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[derive(serde::Serialize, serde::Deserialize)] +#[derive(parity_scale_codec::Encode, parity_scale_codec::Decode)] +#[derive(parity_scale_codec::DecodeWithMemTracking)] +#[derive(parity_scale_codec::MaxEncodedLen, scale_info::TypeInfo)] +pub struct OitIdentity { + /// Ed25519 public key canonical 32-byte (validator wallet). + pub public_key: [u8; 32], + /// OIT level canonical (0=anonymous, 1-2=individual, 3=institutional, + /// 4=sovereign, 5=atelier_canonical). + pub level: u8, +} + +impl OitIdentity { + /// Returns `true` si OIT canonical es institutional level >= 3. + /// + /// Per PROV-5 enforcement canonical anchorable on-chain. + pub fn is_institutional(&self) -> bool { + self.level >= 3 + } +} + +/// Validation attestation canonical canonical struct vendor copy temporal +/// (mirror Ergon `arkhe-noesis-core::attestation::ValidationAttestation` Slice 7.0r). +/// +/// 8 fields canonical per CHN-7 ratificado: +/// - `schema_version`: "1.0.0" semver canonical +/// - `asset_id`: BLAKE3 32-byte content hash +/// - `validator_oit`: OitIdentity (level >= 3 required canonical anchorable) +/// - `validated_at_us`: u64 monotonic timestamp microseconds +/// - `verdict`: ValidationVerdict +/// - `severity`: SeverityTier +/// - `constraints_checked`: u32 (DesignConstraints count) +/// - `signature`: Ed25519Signature canonical (64 bytes) +#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(serde::Serialize, serde::Deserialize)] +#[derive(parity_scale_codec::Encode, parity_scale_codec::Decode)] +#[derive(parity_scale_codec::DecodeWithMemTracking)] +#[derive(parity_scale_codec::MaxEncodedLen, scale_info::TypeInfo)] +pub struct ValidationAttestation { + /// Schema version canonical semver canonical ("1.0.0" canonical). + pub schema_version: [u8; 8], + /// BLAKE3 content hash canonical del asset. + pub asset_id: Blake3Hash, + /// Validator OIT identity canonical (institutional Level >= 3 required). + pub validator_oit: OitIdentity, + /// Timestamp canonical microseconds canonical monotonic. + pub validated_at_us: u64, + /// Validation verdict canonical. + pub verdict: ValidationVerdict, + /// Severity tier canonical. + pub severity: SeverityTier, + /// Constraints checked count canonical (DesignConstraints count). + pub constraints_checked: u32, + /// Ed25519 signature canonical (64 bytes) sobre signing_payload canonical. + #[serde(with = "BigArray")] + pub signature: [u8; 64], +} + +impl ValidationAttestation { + /// Returns `true` si attestation canonical es anchorable on-chain canonical. + /// + /// Per PROV-6 canonical check: + /// - verdict canonical Passed | Warning (NO Failed) + /// - severity canonical NOT Block/Critical + /// + /// Honor Inmutable #26 content-addressed immutability canonical — una vez + /// anchored, IMMUTABLE — por eso check canonical pre-anchor crítico. + pub fn is_anchorable(&self) -> bool { + let verdict_ok = matches!(self.verdict, ValidationVerdict::Passed | ValidationVerdict::Warning); + let severity_ok = matches!(self.severity, SeverityTier::Info | SeverityTier::Warning); + verdict_ok && severity_ok + } + + /// Returns signing payload canonical (postcard de fields EXCEPT signature). + /// + /// Per Ergon canonical pattern Slice 7.0r: + /// ```text + /// payload = postcard(ValidationAttestation { ..., signature: [0; 64] }) + /// ``` + /// + /// **VENDOR canonical NOTE**: stub implementation canonical. Real impl en + /// `arkhe-noesis-attestation-types` usa `postcard` crate canonical. Aquí + /// usamos parity-scale-codec `Encode` para compat FRAME canonical pallet + /// runtime canonical. Esto puede diverger del Ergon real canonical — DC + /// PROVENANCE_PALLET_V1 PROV-4 confirma Ed25519 verification handled + /// directly via ed25519-dalek canonical en pallet (trait-seam ADR-0057). + pub fn signing_payload(&self) -> Vec { + use parity_scale_codec::Encode; + // Clone with signature zeroed canonical para deterministic payload. + let mut payload_struct = self.clone(); + payload_struct.signature = [0u8; 64]; + payload_struct.encode() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn mock_attestation() -> ValidationAttestation { + ValidationAttestation { + schema_version: *b"1.0.0 ", + asset_id: AssetHash::new([1u8; 32]), + validator_oit: OitIdentity { + public_key: [2u8; 32], + level: 3, + }, + validated_at_us: 1234567890, + verdict: ValidationVerdict::Passed, + severity: SeverityTier::Info, + constraints_checked: 42, + signature: [3u8; 64], + } + } + + #[test] + fn oit_institutional_level_3_or_higher_canonical() { + let oit_anon = OitIdentity { public_key: [0u8; 32], level: 0 }; + let oit_individual = OitIdentity { public_key: [0u8; 32], level: 1 }; + let oit_institutional = OitIdentity { public_key: [0u8; 32], level: 3 }; + let oit_sovereign = OitIdentity { public_key: [0u8; 32], level: 4 }; + assert!(!oit_anon.is_institutional()); + assert!(!oit_individual.is_institutional()); + assert!(oit_institutional.is_institutional()); + assert!(oit_sovereign.is_institutional()); + } + + #[test] + fn is_anchorable_passed_info_canonical() { + let att = mock_attestation(); + assert!(att.is_anchorable()); + } + + #[test] + fn is_anchorable_failed_rejected_canonical() { + let mut att = mock_attestation(); + att.verdict = ValidationVerdict::Failed; + assert!(!att.is_anchorable()); + } + + #[test] + fn is_anchorable_severity_block_rejected_canonical() { + let mut att = mock_attestation(); + att.severity = SeverityTier::Block; + assert!(!att.is_anchorable()); + } + + #[test] + fn is_anchorable_severity_critical_rejected_canonical() { + let mut att = mock_attestation(); + att.severity = SeverityTier::Critical; + assert!(!att.is_anchorable()); + } + + #[test] + fn is_anchorable_warning_accepted_canonical() { + let mut att = mock_attestation(); + att.verdict = ValidationVerdict::Warning; + att.severity = SeverityTier::Warning; + assert!(att.is_anchorable()); + } + + #[test] + fn signing_payload_zeroes_signature_canonical() { + let att = mock_attestation(); + let payload = att.signing_payload(); + // Signature bytes (last 64 in encode order) should be zeros canonical. + // Verify payload non-empty + deterministic. + assert!(!payload.is_empty()); + } + + #[test] + fn signing_payload_deterministic_canonical() { + let att = mock_attestation(); + let p1 = att.signing_payload(); + let p2 = att.signing_payload(); + assert_eq!(p1, p2); + } + + #[test] + fn validation_attestation_serde_roundtrip_canonical() { + let att = mock_attestation(); + let json = serde_json::to_string(&att).unwrap(); + let parsed: ValidationAttestation = serde_json::from_str(&json).unwrap(); + assert_eq!(att, parsed); + } +} diff --git a/crates/origin-chain-types/src/lib.rs b/crates/origin-chain-types/src/lib.rs index 0b7eab9..0bf5454 100644 --- a/crates/origin-chain-types/src/lib.rs +++ b/crates/origin-chain-types/src/lib.rs @@ -41,6 +41,7 @@ extern crate alloc; pub mod accounts; pub mod assets; +pub mod attestation; pub mod errors; pub mod fees; pub mod governance; @@ -51,6 +52,9 @@ pub mod tiers; pub use accounts::{AccountId, Signature}; pub use assets::AssetHash; +pub use attestation::{ + Blake3Hash, OitIdentity, SeverityTier, ValidationAttestation, ValidationVerdict, +}; pub use errors::ChainError; pub use fees::{FeeCalculator, OperationKind}; pub use governance::{GovernanceProposal, ProposalId, Vote, VoteStance}; diff --git a/crates/origin-chain-types/src/provenance.rs b/crates/origin-chain-types/src/provenance.rs index dbf9c5c..0ecd0ab 100644 --- a/crates/origin-chain-types/src/provenance.rs +++ b/crates/origin-chain-types/src/provenance.rs @@ -12,9 +12,10 @@ extern crate alloc; use crate::{AccountId, AssetHash, ChainError, Signature}; -/// Canonical attestation record stored on-chain. Compact (~100 bytes) so it fits +/// Canonical attestation record stored on-chain. Compact (~140 bytes) so it fits /// in a single extrinsic without bloating block size. #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +#[derive(parity_scale_codec::Encode, parity_scale_codec::Decode, scale_info::TypeInfo)] pub struct AssetAttestation { /// BLAKE3 32-byte content hash of the asset bytes (off-chain). pub hash: AssetHash,