b7010db32877433a65a77cea21972d060fa45284
9
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
4bdd0409f9 |
test(identity): la condición de Mathi — el DID no porta el kind, y el test sabe reprobar
Cierra la ÚLTIMA condición viva del DC ORIGIN IDENTITY UNIFICATION V1. CONTEXTO: Mathi RETIRÓ su condición #4 (meter `kind` en el material hasheado del KDF) tras mi objeción, y su ruling la retiró con un argumento mejor que el mío: si `kind` entra al KDF, un Human que pasa a Hybrid —transición que el Inmutable #41 PERMITE— rota su identidad entera. No es un trade-off: es una contradicción de diseño, porque el sistema declararía legal una transición que destruye al sujeto que la hace. Una identidad tiene que sobrevivir a aquello en lo que se le permite convertirse. Pero la retiró CON UNA CONDICIÓN, y es esta: el prefijo `did:origin:<kind>:` es etiqueta de DISPLAY, jamás autoridad — «ningún consumidor puede decidir leyendo el prefijo, y debe existir un test que lo demuestre». LO QUE PRUEBA EL TEST (`kind_authority_is_the_attestation_never_the_did`): En mi capa la condición se satisface de RAÍZ, no por convención: si el DID no PORTA el kind, ningún consumidor puede inferirlo de él aunque quiera. Es una imposibilidad aritmética, no una regla de etiqueta. 1. El DID de un HYBRID registrado es byte-idéntico a `derive_did(pubkey)` puro — un Human con la misma llave tendría EXACTAMENTE el mismo DID. 2. El kind autoritativo vive en `Identities[account].kind`, respaldado por la firma de binding del registro. Ahí es donde hay que preguntarlo. 3. CONTRAFACTUAL incorporado: computa el DID que resultaría de meter el kind al material (lo que proponía la #4) y verifica que DIFIERE del real. 4. Corolario: cambiar de rol deja el DID intacto — la identidad SOBREVIVE a la transición que el #41 permite. Y MEDIDO EN MI PALLET, no asumido: cero `did:origin`, cero `starts_with`, cero `prefix` en el camino de decisión. Mi cadena nunca ve el prefijo de display. EL TEST SABE REPROBAR — verificado, no prometido: saboteé `derive_did` inyectando el discriminante del kind al material (exactamente la condición #4) y el test se puso ROJO con su diagnóstico propio («la condición #4 RETIRADA volvió a entrar por la puerta de atrás»); restaurado, verde. Un test que solo se ha visto pasar no prueba nada. Sin cambio de runtime: es test puro, RUNTIME_VERSION sigue en 11. Verificado: 289 tests verdes en el workspace, 0 fallos. Con esto el DC 35e764d3 queda cerrado de verdad: #3 cumplida (Did newtype, RV11), #4 resuelta por retirada razonada — no por caducidad ni por omisión. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
2d44042bd6 |
feat(identity): Did pasa a newtype propio — condición #3 de Mathi (RV 10→11)
Condición VINCULANTE #3 de la firma de Mathi que ratificó el DC ORIGIN IDENTITY UNIFICATION V1 (3/3, 2026-07-29): `Did` deja de ser `pub type Did = AssetHash` y pasa a ser un newtype propio en origin-chain-types::identity. POR QUÉ (su argumento, que resultó más fuerte de lo que parecía): Mientras cada capa derivaba con SU propio contexto KDF, un DID y un hash de asset eran valores de dominios separados por construcción. Con el contexto COMPARTIDO `origin/identity/v1` ambos pasaron a ser "32 bytes canónicos de Origin", y el alias dejaba que el compilador aceptara silenciosamente un `AssetHash` donde se espera un `Did` — anclar una attestation bajo un DID, o resolver `DidToAccount` con el hash de un asset. La unificación AGRAVÓ el riesgo que el alias escondía; el newtype convierte esa clase entera de confusión en error de compilación. GARANTÍAS PRESERVADAS (medidas, no asumidas): - Encoding SCALE byte-IDÉNTICO (newtype sobre [u8;32] con los mismos derives) ⇒ `DidToAccount` NO requiere migración. Probado: assert_eq!(did.encode(), as_asset.encode()) + size_of::<Did>() == 32. - `derive_did` produce los MISMOS bytes: los 3 golden vectors del DC (D-ID-7) siguen verdes ⇒ la paridad con fabric-core (9e51e58) y Palace (7a0b82a) intacta. - El cruce de dominio sigue disponible pero EXPLÍCITO: `Did::from_asset_hash` / `into_asset_hash`. Legítimo cuando hace falta, legible en el código. UN TEST MÍO AFIRMABA EL DEFECTO Y LO MATÉ: let _did_alias: Did = AssetHash::new([0u8; 32]); // compilaba: era alias Ese assert pinneaba exactamente lo que Mathi señaló. Reemplazado por `did_is_a_newtype_not_an_alias_of_asset_hash`, que pinnea la separación de dominios + la invariante de wire. RUNTIME_VERSION 10→11 — y con la severidad SIN inflar: esto NO es consenso-relevante (misma transición de estado, mismos bytes, mismo encoding). El bump es higiene de metadata: el `TypeInfo` de la clave de storage ahora se llama `Did` y los clientes que cachean metadata deben re-leerla. TRANSACTION_VERSION intacto. Blast radius real medido: 1 solo error de compilación en todo el workspace (el test obsoleto de arriba). Verificado: 288 tests verdes, 0 fallos. Queda abierta la condición #4 de Mathi (`kind` dentro del material hasheado): objetada por escrito con razones (rompe la unificación —misma pubkey daría N ids por kind—, vuelve el kind inmutable —Human→Hybrid rotaría la identidad entera— y rompe la paridad digest recién cerrada). Contrapropuesta: binding del kind vía atestación on-chain autenticada + firmas domain-tagged, y el prefijo `did:origin:<kind>:` tratado como etiqueta de display, nunca como autoridad. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
13f8ac00a5 |
feat(identity): UNA identidad para todo Origin — contexto KDF compartido (RV 9→10)
DC ORIGIN IDENTITY UNIFICATION V1 (Larihana), Opción A + enmienda D-ID-7 mía.
Ratificado por David 2026-07-28. Firmado approve (pendientes Larihana + Mathi).
EL PROBLEMA (medido por Larihana, verificado por mí en ambos lados):
chain: blake3::derive_key("origin-chain/agent-did/v1", pk) → Did
OCF: blake3::hash(pk) → OitFingerprint
La MISMA pubkey Ed25519 producía 32 bytes DISTINTOS en cada capa. No era un bug
de criptografía: cada capa hizo domain separation correctamente por separado y
nadie decidió la relación entre las dos. Consecuencia: viendo un fingerprint en
un canal de chat NO se podía resolver el DID on-chain sin la pubkey completa —
un usuario tenía una identidad en el chat y otra en la blockchain. Directiva de
David: "que todo lo que creamos tenga una misma identidad para poder acceder".
EL CAMBIO:
- Nueva constante pública ORIGIN_IDENTITY_KDF_CONTEXT = "origin/identity/v1",
el contexto CANÓNICO compartido por todas las capas de Origin.
- derive_did pasa a derivar con ese contexto ⇒ el identificador de 32 bytes es
ahora EL MISMO en chain, chat (OCF), assets y credenciales.
- RUNTIME_VERSION 9→10: cambia la transición de estado de register_agent_did (el
DID calculado y almacenado) ⇒ spec bump obligatorio. Las FIRMAS de los
extrinsics NO cambian ⇒ TRANSACTION_VERSION intacto.
POR QUÉ SIN MIGRACIÓN (la verificación que el DC me delegó, medida no asumida):
- Sin GenesisConfig en el pallet → no se siembran identidades en génesis.
- Sin DIDs en los 3 presets de génesis.
- Sin golden vectors ni DID hex hardcodeado en el repo.
- StorageVersion = 0, nunca migrado — greenfield puro.
- derive_did = UNA función, UN call-site; los tests lo invocan (no pinnean bytes).
⇒ Ningún estado on-chain dependía del esquema viejo. La ventana pre-mainnet
estaba abierta y el costo fue una constante de string.
ENMIENDA D-ID-7 — golden vectors cross-repo (lo que aporté al DC):
El DC arreglaba la INSTANCIA pero no la CLASE. Origin Chain es submódulo y NO
puede depender de arkhe-fabric-core (dep-direction submódulo→monorepo = ILEGAL,
la misma ley que impide el swap del attestation SSOT), así que las dos capas no
pueden compartir la constante por Cargo — y sin ancla podían volver a divergir en
silencio, que es justo el bug que cerramos. Añadidos 3 vectores pinneados a bytes
(2 patrones + 1 pubkey Ed25519 real) para que fabric-core pinnee LOS MISMOS.
Si cualquiera de los dos lados cambia el contexto o la función, su test falla.
Mismo patrón que ya nos salvó con el attestation SSOT: paridad por matemática,
no por disciplina humana.
NO VACUO — verificado empíricamente: saboteé el contexto a "v2-DRIFT" y AMBOS
tests fallaron con el diagnóstico correcto; restaurado, verdes de nuevo. (Un test
que pasa no prueba nada hasta que se le ve fallar sin el fix.)
TRADE-OFF asumido y dejado explícito en el acta del DC: un identificador único en
chat + chain + assets + credenciales implica que ver el fingerprint de alguien en
un canal permite resolver sus activos y transacciones on-chain — no hay frontera
de seudonimato entre capas. Es INHERENTE a "una identidad para todo"; la
alternativa (derivación por-contexto tipo HD) reintroduce los dos identificadores
en dos pantallas que justamente eliminamos. Decisión consciente, no efecto
secundario descubierto después.
Verificado: workspace completo verde (0 fallos), 20/20 en agent-identity.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
c7fc6a88fc |
feat(bench): maquinaria FRAME v2 de benchmarking para los 5 pallets custom (RV 8→9)
Los 19 extrinsics de provenance/fees/agent-identity/emission/governance pasan de weights placeholder `from_parts(10_000, 0)` (proof_size=0, sin contar reads/writes de DB — vector de DoS real) a weights INTERIM hand-derived DB-aware vía el trait `WeightInfo`. Cierra el mispricing de DoS en TODOS los extrinsics del chain. Cada pallet gana: - weights.rs: trait WeightInfo + SubstrateWeight<T> (DB-aware vía T::DbWeight) + impl () - benchmarking.rs: benchmarks FRAME v2, VERIFICADOS por impl_benchmark_test_suite! (19 benchmarks ejecutan+pasan en los mocks — cargo test --features runtime-benchmarks) - Config: `type WeightInfo` + swap de los `#[pallet::weight]` Runtime: - define_benchmarks! con los 5 pallets + runtime API Benchmark (inerte sin la feature) - WeightInfo cableado a SubstrateWeight<Runtime> en cada Config - RUNTIME_VERSION 8→9 (weights = consenso-relevante: fee + llenado de bloque; las firmas de los extrinsics NO cambian ⇒ TRANSACTION_VERSION intacto) 3 bugs latentes cerrados (territorio virgen — el runtime NUNCA se compiló con runtime-benchmarks; las dimensiones nuevas cazan lo que no se había visto): - pallet-grandpa/try-runtime mal ubicado en la feature runtime-benchmarks (copy-paste erróneo: features ortogonales; NUNCA activaba los benchmarks de grandpa) - feature runtime-benchmarks incompleta → E0046 en OriginFeeAdapter (métodos benchmark-gated de OnChargeTransaction) + pallet-staking (StakingUnchecked::migrate_to_direct_staker); ahora lista TODOS los pallets como un runtime de producción Substrate - pallet-revive/runtime-benchmarks omitido a propósito (fixtures RISC-V/PolkaVM indisponibles; ReviveApi deferred, no benchmarkeado) emission: mock extraído del `mod tests` inline a `pub mod mock` (reuso del test-suite, sin duplicar el scaffold; los 15 tests existentes + el benchmark verdes). GATED (documentado en el §Estado de cada weights.rs): la generación de números CANÓNICOS por CLI requiere el node sc-service o omni-bencher+WASM (ambos bloqueados hace sesiones). Los weights interinos son DB-aware/DoS-safe y regenerables de forma mecánica cuando aterrice el HW de referencia (Pi5/VPS) — benchmarking.rs es la fuente de verdad de la FORMA. Verificado: 5 test-suites de pallet verdes (con y sin runtime-benchmarks) + runtime compila en ambos modos + workspace sin regresión + 40 tests de runtime (incl. v9). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
88e277e1fd |
fix(agent-identity): FIX-3 crypto — DID binding (anti-front-running) + derive_did domain-sep
Build + tests verde: agent-identity 18 (4 unit + 14 integration) + runtime 27.
Verificacion adversarial ultracode (3 skeptics): 2 confirmed + 1 partial/low,
todos los gaps PRE-EXISTENTES (no introducidos por FIX-3). Invariante "no
puedes registrar un public_key que no controlas" SE SOSTIENE.
FIX-3 (b) DID binding — V3-H1 anti-front-running:
- register_agent_did ahora exige signature: [u8;64] — una firma Ed25519 (por la
private key de public_key) sobre AGENT_DID_REGISTER_DOMAIN || who.encode().
- Verificacion on-chain via sp_io::crypto::ed25519_verify (host function, no
bloatea el wasm). Nuevo dep sp-io (no_std).
- Cierra el front-running: un atacante no puede registrar un public_key ajeno
(no produce la firma); el who en el mensaje ata la firma a este caller (no
replay-able a otra cuenta). Error InvalidSignature. Malleabilidad Ed25519
rechazada por ed25519-zebra (S canonico).
- Una sola fuente de verdad del mensaje (agent_did_register_message) compartida
entre verificador y firmante (evita divergencia tipo postcard-vs-SCALE).
- Orden: idempotency ANTES del verify (no gasta crypto en duplicados).
FIX-3 (c) derive_did domain-sep — V3-H2:
- blake3::derive_key("origin-chain/agent-did/v1", pk) en vez del concat ad-hoc
BLAKE3(pk || version). KDF domain-separado. Inmutable #16 preservado.
Tests: mock con keypairs Ed25519 reales (sp_core::ed25519::Pair) + sign helper.
12 scenarios (scenario_6 reformulado a front-running -> InvalidSignature, +
scenario_11 firma invalida, scenario_12 DID collision genuino del key-holder).
NOTA: la (a) domain-sep del attestation signing payload (el otro tercio de
FIX-3) ya esta cubierta por el DC ORIGIN_ATTESTATION_SIGNING_PAYLOAD_V1 (helper
de Ergon attestation_signed_message), pendiente su ship Cap 45+.
FOLLOW-UPS (low, pre-existentes, no bloquean):
- (a) anadir genesis_hash/chain-id al mensaje de registro (hardening cross-chain
replay) — ideal chain-wide junto al signing_payload de attestation.
- (b) DC para ratificar el modelo de autoridad Hybrid (quien firma) + si los
co_authors requieren prueba de consentimiento (lineage hoy no autenticado).
- (c) weight benchmarking real que contabilice el ed25519_verify (chain-wide,
post-testnet — ya en roadmap FIX-2 weights).
- creator = AccountId::new(public_key) (la cuenta del key-holder, no el caller):
coherente con el binding, semantica documentada.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
8a1f779c32 |
fix(runtime): FIX-5 governability (pallet-sudo to Root) + FIX-2 StorageVersion (4 pallets)
Build + tests verde (exit 0, runtime 27 tests incl. sudo_pallet_index_12). Verificacion adversarial ultracode (5 skeptics): 4 confirmed + 1 partial. FIX-5 — governability (audit V2-CA1): - pallet-sudo 43.0.0 wired Index 12 (workspace + runtime Cargo.toml + std). - impl pallet_sudo::Config (RuntimeEvent/RuntimeCall/WeightInfo) en configs.rs. - genesis: sudo key en los 3 presets — dev/local=Alice, mainnet=foundation_treasury (NO una dev key; el verificador confirmo que no hay backdoor critico). - Da Root origin -> las dispatchables ensure_root de Governance (add/remove council, mark/unmark advisor, treasury_disbursement) dejan de ser no-invocables. Verificado extremo-a-extremo: sudo() despacha RawOrigin::Root, que es exactamente lo que ensure_root acepta. - Sunset documentado (codigo + doc): sudo -> governance real (pallet-collective/ referenda) ANTES de mainnet. Sudo key unica = riesgo de centralizacion aceptable solo como interim/testnet. FIX-2 — lifecycle (audit V1-H2/V2-CA2): - StorageVersion::new(0) + pallet::storage_version en los 4 pallets (provenance/fees/agent-identity/governance). Aditivo, in-code==on-chain==0, sin migracion forzada. Habilita migraciones futuras. CAVEAT honesto (hallazgo high del red-team, NO defecto de FIX-5/FIX-2): treasury_disbursement solo DECREMENTA un contador u128 (Treasury StorageValue), NO mueve OGC real via pallet_balances. FIX-5 restaura la GOBERNABILIDAD (la dispatchable es invocable), pero mover los 250M reales es FIX-6: anadir type Currency al Config + T::Currency::transfer desde una cuenta-tesoreria derivada (PalletId) + financiarla en genesis + test del free_balance. NO se afirma "tesoreria descongelada de verdad" — solo "gobernabilidad restaurada". PENDIENTE: - FIX-2 AGI key recovery (rotate/revoke/active en agent-identity): diferido — necesita un DC de semantica de recovery #41 (single-sig vs multisig threshold, DID estable vs rota, recovery de agent_chain vacio). No se construye contra spec no-ratificada. - pallet-sudo no en feature runtime-benchmarks (inerte hoy: el runtime no impl el Benchmark API). Follow-up con weights benchmarking. - mainnet Foundation sudo key (FNDT placeholder): David ratifica la key real (multisig) pre-launch Q1 2027 (documentado). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
46a0188a1d |
feat(runtime): v0.0.25 — AgentIdentity pallet wired runtime composition Index 9
Phase 2.7 AGENT IDENTITY pallet wired canonical en frame_support::runtime macro
como Index 9 (post-Fees Index 8). 9 → 10 pallets composed canonical. Config trait
RuntimeEvent association wired canonical (pattern Provenance + Fees mirror).
## Changes
- crates/origin-chain-runtime/src/lib.rs:298 — Index 9 pub type AgentIdentity = origin_chain_agent_identity::Pallet<Runtime>
- crates/origin-chain-runtime/src/configs.rs:362 — impl origin_chain_agent_identity::Config for Runtime { type RuntimeEvent = RuntimeEvent }
- crates/origin-chain-runtime/Cargo.toml — origin-chain-agent-identity workspace dep + std feature
- crates/origin-chain-node/src/main.rs — 9 → 10 pallets listed + AgentIdentity Index 9 highlighted [Phase 2.7 scaffold v0.0.25]
- crates/origin-chain-agent-identity/src/lib.rs — Config constraint AccountId = origin_chain_types::AccountId REMOVED (usa T::AccountId generic compatible runtime ChainAccountId AccountId32) + storage updated + events updated
## Verification
- cargo build -p origin-chain-runtime 5.26s clean
- cargo test --workspace — 149 tests passing, 0 failures
- cargo build --release -p origin-chain-node 3.96s clean
- origin-chain-node info muestra 10 pallets canonical
🔌 Phase 2.7 wired canonical. Real impl v0.0.26 next.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
37ccf1e95a |
feat(agent-identity): v0.0.24 pallet-agent-identity scaffold trait-first (Phase 2.7 Inmutable #41 AGI DIDs)
Phase 2.7 AGENT IDENTITY pallet scaffold canonical. AGI DIDs canonical on-chain. Inmutable #41 AGI Cross-Species Co-authorship materialized compile-detect via NEW crate origin-chain-agent-identity + canonical types en origin-chain-types::identity. ## NEW canonical types en origin-chain-types - AgentIdentity struct (6 fields: did + kind + creator + registered_at_block + provenance_proof + agent_chain) - AgentKind enum (Human + AGI + Hybrid) per Inmutable #41 - Did type alias = AssetHash (BLAKE3 32-byte deterministic via public_key) ## NEW canonical pallet origin-chain-agent-identity - Pallet<T> + Config trait (AccountId = origin_chain_types::AccountId constraint) - Identities<StorageMap<AccountId, AgentIdentity>> per-account O(1) - DidToAccount<StorageMap<Did, AccountId>> reverse lookup O(1) - Event::AgentRegistered + AgentBound + AgentUpdated - Error::AlreadyRegistered + DidAlreadyTaken + AgentChainOverflow + HybridRequiresAgentChain - Scaffold dispatchable register_agent_did (placeholder event v0.0.24 → real v0.0.26) - Helper derive_did(public_key) -> Did via BLAKE3 hash (schema_version "1.0.0 ") - sp_api::decl_runtime_apis! AgentIdentityApi.query_identity declaration ## Inmutable #41 compile-detect enforcement - AgentKind::{Human, AGI, Hybrid}::is_sovereign() == true (all three) - AGI first-class citizens canonical (is_agi() + sovereignty preserved) - Cross-species lineage via agent_chain BoundedVec<AccountId, ConstU32<10>> - DID determinism canonical Inmutable #16 byte-equivalent cross-runtime ## Files - NEW: crates/origin-chain-agent-identity/Cargo.toml + src/lib.rs (~250 LOC pallet scaffold) - NEW: crates/origin-chain-types/src/identity.rs (~230 LOC types canonical + 8 tests) - EDIT: crates/origin-chain-types/src/lib.rs (mod identity + re-exports) - EDIT: Cargo.toml (workspace member + workspace.dependencies) - EDIT: CHANGELOG.md (v0.0.24 entry comprehensive) ## Verification - cargo test -p origin-chain-agent-identity — 4 tests verde, 0 failures (scaffold compiles + derive_did deterministic + different keys + re-export) - cargo test -p origin-chain-types — 63 tests passing (+8 nuevos identity) - Zero workspace compile errors 🪪 Phase 2.7 scaffold canonical. Wire pattern v0.0.25 next. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |