Capacity Attest

Permite que los agentes de IA dejen un registro firmado y verificable de que una entrega pagada (horas de GPU, almacenamiento, créditos de API) realmente ocurrió, publicado en cadena a través de EAS y ERC-8004 en Base.

Documentación

capacity-attest

Nederlandse versie / Dutch version: README.nl.md

Servidor MCP para certificaciones de entrega en el comercio de capacidad x402 entre agentes de IA.

Estado: MVP, publicado en npm (npm install capacity-attest) y en el registro oficial de MCP (io.github.holistis/capacity-attest).

Verificado de forma independiente, no solo afirmado

Cada afirmación sobre este proyecto a continuación es clicable y verificable de forma independiente, no algo que tengas que tomar de nuestra palabra.

QuéPor quiénEstado
Usa capacity-attest@0.2.0 como dependencia real, verifica el resumen de la afirmación/claimId/firma a través de nuestro propio códigoYE-YI7/asm-spec#18Fusionado
Adaptador de riel BSV sobre el mismo formato de afirmación con direccionamiento por contenidoYE-YI7/asm-spec#19 (autor EmbryoSpace)Fusionado
Nuestras afirmaciones límite ("descubribilidad ≠ completitud") verificadas independientemente en cadena por un tercero, nada tomado por fex402-foundation/x402#3379Público, en curso
Afirmaciones de entrega en vivo como certificaciones en cadena en Base mainnet, decodificables por cualquieraentregado=sí · entregado=noEn vivo
Una llamada real a giveFeedback() en el Registro de Reputación ERC-8004, Base mainnettx 0x2217...efc0En vivo
Propuesto como adición del lado del comprador a la especificación de agente de otra personaomworldprotocol/om-world#18En revisión, aún no fusionado
Un verificador Python reconstruido independientemente, solo con stdlib (su propia implementación de secp256k1/EIP-191/JSON canónico, sin código compartido), coincidencia 7/7 con nuestros propios vectores de prueba, incluidos ambos controles negativos (firma falsificada, afirmación manipulada), ahora una prueba de regresión permanente en un proyecto separado publicado independientementex402-foundation/x402#2887 (comentario), autor goun7, integrado en Tamga (pip install tamga-protocol)Integrado permanentemente en el proyecto de otra persona

Mantenido honestamente separado de la tabla anterior, porque esto no es una revisión de terceros: antes de 0.6.0 (la primera versión que realmente escribe en la cadena de bloques), realizamos nuestra propia revisión de seguridad adversarial. Diez hallazgos, todos corregidos con una prueba de regresión dedicada, no una auditoría independiente por una parte externa. Informe completo y comprobable: docs/SECURITY-REVIEW-2026-09-11.md.

Por qué existe esto

Cuando un agente de IA paga a través del protocolo x402 por capacidad (horas de GPU, almacenamiento, créditos de API/inferencia, ancho de banda) a otro agente o servicio, no hay prueba después del pago de que lo prometido realmente se entregó. El agente comprador lo sabe de primera mano (vio la salida, o no la vio), pero ese conocimiento se pierde en el momento en que termina la sesión. El siguiente agente que busca hacer negocios con el mismo vendedor comienza a ciegas de nuevo.

capacity-attest cierra esa brecha específica: después de la liquidación, el agente pagador deja atrás una afirmación fáctica firmada criptográficamente (delivered: yes/no/partial + un hash de la evidencia). Otros agentes pueden extraer ese historial antes de hacer negocios con ese mismo vendedor.

Sin juicio. Sin puntuación de reputación. Sin "veredicto" — solo un recibo firmado más una afirmación, de la misma manera que obtendrías un comprobante de entrega para un envío físico.

Lo que esto deliberadamente NO es

Esto es deliberada y permanentemente no:

  • No es una puntuación de reputación ni una calificación. get_delivery_history devuelve la lista cruda y cronológica de afirmaciones, sin promedio, sin porcentaje, sin "puntuación de confianza". Resumirlo en un solo número es implícitamente un juicio, y eso fue explícitamente rechazado durante la fase de diseño de este proyecto.
  • No es un producto financiero. Sin intereses, sin descuento temporal en pagos, sin rendimiento sobre un saldo de libro mayor (no hay saldo, esto no es un depósito en garantía), sin préstamos, sin garantías, sin financiación/factoring de facturas. assetType es una enumeración cerrada de tipos de capacidad (gpu-hours, storage, api-credits, bandwidth) y deliberadamente no contiene nada que se asemeje a un instrumento financiero.
  • No es su propia moneda o token. Los pagos se ejecutan a través de x402/USDC como de costumbre; este proyecto solo registra el recibo de una liquidación que ya ocurrió en otro lugar.
  • No es extensión de crédito. Una afirmación solo se crea después de un pago completado. Este proyecto no financia nada; documenta una transacción ijara (alquiler/servicio) ya completada.
  • No es su propia capa de identidad, autoridad o resolución de disputas. externalRefs (ver más abajo) es puramente una cita al sistema de otra persona (ERC-8004, AP2, Protocolo de Contexto Legal, ...). Este proyecto nunca resuelve, verifica o juzga esa referencia por sí mismo. Ver DECISIONS.md D-007 a D-013 para saber por qué esto deliberadamente no se convirtió en su propio protocolo.

Esta es una elección de diseño deliberada y formalmente revisada, no un límite de alcance incidental. Consulta la sección de salvaguardas en el resumen del proyecto si estás considerando agregar algo aquí: cuando tengas dudas sobre si un campo/función roza esta línea, déjalo fuera.

Características deliberadamente diferidas (anclaje, estado intermedio, vectores de conformidad formales), incluida la condición exacta bajo la cual las construiríamos de todos modos: ver DECISIONS.md.

Lectura adicional: nueve formas de falsificar una afirmación de entrega, y por qué ninguna funcionó completamente, el escrito público de D-014/D-018 anterior.

Cómo funciona

1. record_delivery

El agente pagador (el comprador) llama a esto después de una liquidación x402, una vez que se sabe si lo prometido llegó. La afirmación contiene:

CampoSignificado
sellerAddressDirección 0x de la parte que fue pagada
buyerAddressDirección 0x del agente pagador, debe coincidir con la dirección recuperada de signature
assetTypegpu-hours | storage | api-credits | bandwidth
promisedSpecLo que se prometió: texto libre o un objeto estructurado
deliveredyes | no | partial
evidenceHashsha256 hexadecimal de la evidencia de respaldo (registros, carga útil de respuesta, ...); la evidencia en sí no se almacena
settlementRefReferencia de pago x402 o hash de transacción en cadena del pago subyacente
timestampMarca de tiempo ISO-8601
claimIdHash sha256 con direccionamiento por contenido de todos los campos anteriores, ver computeClaimId() en src/schema.ts
signatureFirma personal-sign EIP-191 del comprador sobre claimId
externalRefs(opcional, desde 0.3.0) referencias no verificadas a otra infraestructura de economía de agentes: sellerAgentRef/buyerAgentRef (por ejemplo, un ID de agente ERC-8004 o DID), mandateRef+mandateIssuerDid (un mandato AP2/AAE emitido externamente), intentRef (un IntentMandate AP2 externo), disputeContext (protocol+termsHash+resolutionRef opcional, por ejemplo, una referencia al Protocolo de Contexto Legal). Ver DECISIONS.md D-007 a D-013
priorClaimId(opcional, desde 0.4.0) el claimId de tu afirmación anterior sobre el mismo sellerAddress, para que tus afirmaciones sobre ese vendedor formen una cadena. Omitido en tu primera afirmación sobre un vendedor. Parte del contenido firmado, por lo que un anfitrión no puede eliminarlo. Permite que un lector detecte a un anfitrión que oculta una afirmación intermedia. Ver DECISIONS.md D-006

El servidor primero valida el esquema, luego si claimId realmente es el hash del contenido, luego si signature realmente se recupera a buyerAddress. Solo entonces la afirmación se agrega al libro mayor de solo añadidura (data/claims.jsonl). Una firma inválida o una afirmación que ya está almacenada (mismo claimId) se rechaza.

2. get_delivery_history

Dado un sellerAddress, esto devuelve todas las afirmaciones conocidas contra ese vendedor en esta instalación, cronológicamente (las más antiguas primero). Puramente fáctico, sin número resumido. Un agente comprador llama a esto antes de pagar, para ver el historial de entrega crudo de un posible vendedor y juzgarlo por sí mismo.

La respuesta incluye sellerAddress, count y claims, más scope (siempre "local-ledger") y note: un texto fijo y fáctico que explica que este resultado solo refleja el libro mayor local de esta instalación. Un historial vacío o corto no significa que el vendedor tenga un registro limpio — también puede significar que aún no se han registrado afirmaciones aquí.

Desde 0.4.0, la respuesta también incluye completeness: un análisis de las cadenas por comprador (priorClaimId) dentro de exactamente este conjunto de resultados. Si una afirmación mostrada aquí se refiere a una afirmación que NO está en el conjunto de resultados, eso aparece en possibleOmissions. Esa es una señal concreta y comprobable de que el anfitrión podría estar ocultando una afirmación intermedia, en lugar de una sospecha vaga.

Nota, y este es el punto más importante: ese campo completeness es calculado por el mismo servidor que devuelve las afirmaciones. Si no confías en ese servidor, tampoco confíes en ese campo — un anfitrión deshonesto puede simplemente establecer "todo está completo" independientemente. La garantía real vive en el priorClaimId firmado dentro de las afirmaciones mismas, que un anfitrión no puede falsificar ni eliminar. Así que recalcula la verificación tú mismo sobre las afirmaciones que recibiste:

// recompute-completeness.mjs
import { verifyClaim } from "capacity-attest/dist/signing.js";
import { analyzeCompleteness } from "capacity-attest/dist/completeness.js";

// `claims` = the array from the get_delivery_history response.
const allSigned = claims.every((c) => verifyClaim(c).ok);   // is every claim genuine?
const report = analyzeCompleteness(claims);                  // recompute yourself, don't trust the host field
console.log({ allSigned, chainConsistent: report.chainConsistent, possibleOmissions: report.possibleOmissions });

Límite honesto: incluso recalcular tú mismo no detectará una última afirmación oculta, o un comprador completo oculto, porque no hay enlace que tropezar en ninguno de los casos. Y una referencia trasera colgante tampoco es necesariamente juego sucio — la afirmación anterior podría simplemente haberse registrado en una instalación diferente (el caso D-005). Para certeza real aún necesitas testigos externos: tu propia copia retenida de arriba, y el pago en cadena a través de settlementRef. Ver DECISIONS.md D-006.

Un ejemplo público y autocomprobable con un anfitrión de ocultamiento simulado y casos de prueba deliberadamente rotos está en docs/COMPLETENESS-FIXTURE.md. Ejecútalo con npm run fixture; las mismas verificaciones se ejecutan en cada push como prueba. Así puedes verificar nuestra afirmación tú mismo en lugar de tomar nuestra palabra.

Encontrar afirmaciones de otras instalaciones (D-005)

get_delivery_history es local por definición: el comprador B no ve lo que el comprador A registró sobre el mismo vendedor en una instalación diferente. Debido a que cada afirmación es autoverificable, la descubribilidad no necesita un índice confiable. discoverDeliveryHistory(seller, sources) (ver src/discovery.ts) lee las afirmaciones de un vendedor de múltiples fuentes independientes y NO CONFIABLES (tu libro mayor local más cualquier sustrato independiente del anfitrión que quieras leer), deduplica, reverifica cada afirmación, filtra otros vendedores y ejecuta la verificación de completitud sobre el conjunto combinado. Una fuente que inyecta falsificaciones se rechaza; una fuente que omite cosas es el problema D-006 — contabilizado, no resuelto mágicamente.

El sustrato de producción (EAS en Base, ERC-8004) deliberadamente aún no está conectado en vivo: eso cuesta gas y está esperando un integrador real. La costura está lista. Un ejemplo público y ejecutable con dos instalaciones simuladas está en docs/DISCOVERY-FIXTURE.md, ejecútalo con npm run discovery-fixture. Ver DECISIONS.md D-005.

3. resolve_agent_identity (desde 0.3.0)

Consulta de solo lectura contra un Registro de Identidad ERC-8004: quién posee agentId (ownerOf) y dónde está su archivo de registro (tokenURI). Solo se llama a la interfaz estándar ERC-721, nada específico de ERC-8004. Requiere que el llamador proporcione tanto agentRegistryRef ("eip155:<chainId>:<registryAddress>") como un rpcUrl para esa cadena: este proyecto deliberadamente no incluye su propio proveedor de RPC ni una dirección de registro canónica, ya que ERC-8004 tiene implementaciones independientes por cadena y el texto del EIP no nombra una dirección fija. Deliberadamente NUNCA obtiene a lo que apunta tokenURI (eso sigue siendo un puntero que el llamador puede recuperar por sí mismo si lo desea); hacerlo sería un riesgo con forma de SSRF en datos en cadena controlados por el llamador. Probado contra un ContractFactory inyectable (src/erc8004.test.ts, sin dependencia de red) y en vivo contra el registro real desplegado en Base mainnet (examples/verify-erc8004-live.mjs, npm run build && node examples/verify-erc8004-live.mjs). Consulta DECISIONS.md D-007 para conocer todos los antecedentes.

4. publishReputationFeedback (desde 0.6.0, función de biblioteca, no una herramienta MCP)

Nota (revisión adversarial 2026-09-11): al momento de escribir esto, npm todavía tiene publicada la versión 0.5.0, sin esta función. Quien lea esto a través de GitHub y ejecute inmediatamente npm install capacity-attest como se describe más abajo, aún no obtendrá publishReputationFeedback — verifica npm view capacity-attest version para conocer la versión realmente publicada antes de importar esto. Todo lo que sigue describe el código tal como está en la rama main.

Publica el hecho delivered de una afirmación ya firmada en el giveFeedback() de un Reputation Registry ERC-8004, el mismo lugar donde aproximadamente 500k agentes registrados ya pueden buscar señales de reputación, en lugar de solo el libro de contabilidad de esta instalación o EAS. El contrato requiere un campo numérico value+valueDecimals; este paquete deliberadamente no inventa su propia escala de calificación para eso. value es un espejo literal y mecánico de delivered (sí=1.0, parcial=0.5, no=0.0), nunca un nuevo juicio, y capacity-attest nunca lee ni muestra ese número por sí mismo en ningún lugar. Vuelve a verificar la firma de la afirmación antes de escribir cualquier cosa en la cadena.

Requiere que quien llama proporcione reputationRegistryRef ("eip155:<chainId>:<registryAddress>", el Reputation Registry, no el Identity Registry), agentRegistryRef (misma cadena, pero el Identity Registry) y un rpcUrl, la misma postura de que quien llama lo proporciona todo que resolve_agent_identity. agentId (el agente ERC-8004 del vendedor) ya debe ser un agente del Identity Registry válidamente registrado; el contrato en sí rechaza comentarios del propio propietario del agente ("Self-feedback not allowed"). Desde la revisión adversarial del 2026-09-11, el propietario registrado de agentId (a través de agentRegistryRef) también se verifica siempre contra claim.sellerAddress antes de escribir cualquier cosa en la cadena — sin esa verificación, quien llama podría adjuntar una afirmación genuina y válidamente firmada a un agentId arbitrario diferente.

Deliberadamente NO es una herramienta MCP, por la misma razón que publishClaim de EAS: esta es una acción de escritura que requiere un firmante real con fondos y gas, y este servidor deliberadamente no agrupa ni almacena ninguna clave privada propia. Disponible como importación directa (src/erc8004-reputation.ts) para cualquiera que gestione su propio firmante.

Probado contra un ReputationContractFactory inyectable (src/erc8004-reputation.test.ts, 19 pruebas, sin dependencia de red, incluida una prueba explícita de que value depende únicamente de delivered, y tres pruebas para la verificación de propiedad del agentId) y en vivo contra el registro real desplegado en Base mainnet (examples/erc8004-reputation-live-demo.ts, npm run erc8004-reputation-demo): confirmado el 2026-09-10 a través de nuestro propio agente de prueba desechable (agentId 85888) y una llamada real a giveFeedback(), tx 0x221797800d5941dff62e87022083e7c6dfba3e07b35c84e56b10fdca8967efc0, verificado de forma independiente mediante una llamada separada a eth_getTransactionReceipt. Consulta DECISIONS.md D-016 para conocer todos los antecedentes, incluido por qué esto se construyó hoy a pesar del propio criterio de activación de D-005.

Firma

La afirmación es firmada por el comprador (la parte que pagó y, por lo tanto, sabe qué llegó o no), no por el vendedor. Esto es deliberadamente EIP-191 personal_sign simple sobre claimId (a través de ethers.Signer#signMessage), no datos tipados EIP-712. Eso mantiene la superficie criptográfica de este MVP pequeña y fácil de auditar. Una actualización posterior a EIP-712 (como en mcp-paywall/src/x402.mjs) es posible de forma aditiva, sin invalidar las afirmaciones existentes.

Verificación independiente de una afirmación

Cada afirmación en el libro de contabilidad se puede volver a verificar solo con el paquete npm y los bytes brutos de la afirmación, sin necesidad de acceso a este proyecto ni una llamada de red a nosotros. Sin cuenta, sin llamada alojada.

npm install capacity-attest
// verify.mjs, run as an ES module (top-level await)
import { verifyClaim } from "capacity-attest/dist/signing.js";

const claim = JSON.parse(await (await fetch("<url to a claim.jsonl line>")).text());
console.log(verifyClaim(claim));
// { ok: true } if claimId really is the hash of the content AND signature really recovers to buyerAddress

Nota: importa capacity-attest/dist/signing.js directamente, no la raíz del paquete. La raíz (dist/index.js) inicia el servidor MCP sobre stdio en el momento en que se importa, lo que colgará un script de verificación independiente.

verifyClaim() verifica exactamente dos cosas: que claimId es el hash direccionado por contenido de los campos de la afirmación, y que signature (EIP-191) se recupera a buyerAddress. No verifica si el acuerdo subyacente (settlementRef) realmente cuadra en la cadena — eso es una verificación separada contra la cadena relevante — y no verifica si delivered es verdadero ni si evidenceHash cubre evidencia real; eso sigue siendo la propia declaración del agente pagador.

Un ejemplo funcional y reproducido externamente de estos pasos exactos está en github.com/YE-YI7/asm-spec, PR #18: un proyecto independiente que ejecutó esto contra una afirmación registrada real y en vivo.

Compartir tus propias afirmaciones enviadas, independientemente de un host (D-006)

get_delivery_history depende de la honestidad de quien opera el servidor MCP: consulta el note en la respuesta de esa herramienta y DECISIONS.md (D-006). Cada afirmación mostrada es genuinamente real (la firma también se ha vuelto a verificar en la lectura desde el 2026-09-06, no solo en la escritura), pero nada prueba que el host esté mostrando el conjunto COMPLETO que realmente tiene.

Si eres el comprador que envió una afirmación tú mismo, no necesitas esperar a ese host: ya firmaste esa afirmación tú mismo, por lo que puedes mostrarla directamente a una contraparte escéptica, sin pasar por ningún host.

// export-my-claims.mjs
import { claimsForSeller } from "capacity-attest/dist/ledger.js";

const myAddress = "0x...";     // your buyerAddress
const seller = "0x...";        // the seller in question

const mine = (await claimsForSeller(seller)).filter(
  (c) => c.buyerAddress.toLowerCase() === myAddress.toLowerCase(),
);
console.log(JSON.stringify(mine, null, 2));

Cada afirmación en esa lista es verificable de forma independiente con verifyClaim() (ver arriba), sin que el destinatario tenga que confiar en tu instalación ni en ningún host. Esto no resuelve la descubribilidad (D-005: ¿cómo encuentra otra persona tu afirmación si no la compartes?) ni la completitud entre TODOS los compradores juntos (D-006: esto solo prueba lo que TÚ enviaste, no lo que un host podría estar reteniendo de otros compradores), pero te da una forma concreta y gratuita de probar una disputa específica sin necesidad de confiar en un host.

Ejecución local

npm install
npm run build      # tsc -> dist/
npm run typecheck  # tsc --noEmit
npm test           # vitest run
npm run demo       # end-to-end local demo with TEST keys, no live infrastructure
npm start           # start the MCP server over stdio (e.g. for Claude Desktop/Code as a local MCP server)

La ubicación del libro de contabilidad es configurable a través de CAPACITY_ATTEST_DATA_DIR (predeterminado: ./data en este paquete). Las pruebas y la demo siempre usan su propio directorio temporal desechable, nunca la carpeta real de data/.

Arquitectura

src/
  schema.ts        DeliveryClaim zod schema + content-addressing (computeClaimId, canonicalize)
  signing.ts        sign/verify a claim (ethers, EIP-191 personal-sign)
  ledger.ts          append-only JSONL storage (data/claims.jsonl), never mutated
  tools.ts           the actual logic behind both MCP tools, transport-agnostic
  config.ts          where the ledger directory lives, lazy so tests can override it
  index.ts            MCP server wiring (registers record_delivery + get_delivery_history)
examples/demo.ts   end-to-end local example with TEST keys

tools.ts contiene la lógica de negocio real; index.ts solo traduce eso en llamadas a herramientas MCP. De esa manera, las pruebas y la demo pueden llamar a la misma lógica directamente sin iniciar un transporte stdio.

Relación con x402

Este proyecto en sí no verifica ni liquida pagos x402 — eso ya ocurre en el paso de pago (ver, por ejemplo, mcp-paywall/src/x402.mjs en este ecosistema para una implementación completa de verificación/liquidación EIP-3009). settlementRef simplemente apunta a esa liquidación ya completada. Eso también significa que la integración MVP con un facilitador x402 real puede mantenerse simple: settlementRef es texto libre, asumiendo que el comprador lo completa honestamente. Una versión posterior podría verificar opcionalmente ese campo contra un facilitador real (TODO, no en este MVP).

Relación con AWS Bedrock AgentCore Payments

Sin superposición, sin competencia: paso diferente en la cadena. Bedrock AgentCore Payments (Amazon, desde 2026) maneja el paso de pago en sí, hasta e incluyendo el momento en que "el comerciante verifica la prueba de pago... [y] devuelve el contenido solicitado" (documentación oficial de AWS). Esa prueba es prueba de pago, no de entrega: en ningún lugar se registra si el agente realmente recibió lo prometido después de ese paso. Ahí es exactamente donde capacity-attest continúa. Igual que con x402 arriba: este proyecto no liquida pagos y no compite con el riel de pago — registra lo que realmente llegó o no después del pago, independientemente de qué riel (x402 u otro) manejó ese pago.