capacity-attest 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.nl.md ADDED
@@ -0,0 +1,204 @@
1
+ # capacity-attest
2
+
3
+ MCP-server voor leverings-attestaties bij x402-capaciteitshandel tussen AI-agents.
4
+
5
+ > Status: MVP, gepubliceerd op npm (`npm install capacity-attest`) en in het officiële MCP-register (`io.github.holistis/capacity-attest`).
6
+
7
+ ## Onafhankelijk gecontroleerd, niet alleen beweerd
8
+
9
+ Beweringen over dit project zijn hieronder allemaal aanklikbaar en zelf na te trekken, niet op ons woord te geloven.
10
+
11
+ | Wat | Door wie | Status |
12
+ |---|---|---|
13
+ | Gebruikt `capacity-attest@0.2.0` als echte dependency, verifieert claim-digest/claimId/handtekening via onze eigen code | [YE-YI7/asm-spec#18](https://github.com/YE-YI7/asm-spec/pull/18) | Gemerged |
14
+ | BSV-rail-adapter op hetzelfde content-geadresseerde claim-formaat | [YE-YI7/asm-spec#19](https://github.com/YE-YI7/asm-spec/pull/19) (auteur EmbryoSpace) | Gemerged |
15
+ | Onze grensuitspraken ("vindbaarheid ≠ volledigheid") zelf op de keten geverifieerd door een derde, geen woord aangenomen | [x402-foundation/x402#3379](https://github.com/x402-foundation/x402/issues/3379) | Publiek, doorlopend |
16
+ | Live delivery-claims als on-chain attestaties op Base mainnet, door iedereen te decoderen | [delivered=yes](https://base.easscan.org/attestation/view/0x81a55d54452b2cf8bdda7918f63a27bf9ff79e5025b485f7316aae6259288ccc) · [delivered=no](https://base.easscan.org/attestation/view/0xe736b005cbcb54f8f196ac64ef09d75d939c8a18c0d5d9670b5c5025c07398c4) | Live |
17
+ | Hetzelfde, live op een tweede en derde chain (WP2), bewijst dat de EAS-bron over meerdere deployments werkt, niet alleen Base | [Optimism](https://optimism.easscan.org/attestation/view/0x46148283cb005aa43387fb62b2e1ccd0b001ff82dc9310ea885237fd8dea8832) · [Ethereum mainnet](https://easscan.org/attestation/view/0x40da382231eeb6186b7daf231a430488627769feb64039ae3ac671a14f7a8137) | Live |
18
+ | Echte `giveFeedback()`-aanroep op de ERC-8004 Reputation Registry, Base mainnet | [tx 0x2217...efc0](https://basescan.org/tx/0x221797800d5941dff62e87022083e7c6dfba3e07b35c84e56b10fdca8967efc0) | Live |
19
+ | Voorgesteld als koperszijde-aanvulling op een andermans agent-spec | [omworldprotocol/om-world#18](https://github.com/omworldprotocol/om-world/pull/18) | In review, nog niet gemerged |
20
+ | Zelfstandig herbouwde, stdlib-only Python-verifier (eigen secp256k1/EIP-191/canonical-JSON-implementatie, geen gedeelde code), 7/7 match op onze eigen testvectoren inclusief beide negatieve controles (vervalste handtekening, geknoeide claim), nu permanent als regressietest in een ander, apart gepubliceerd project | [x402-foundation/x402#2887 (comment)](https://github.com/x402-foundation/x402/issues/2887#issuecomment-5687398497), auteur goun7, geintegreerd in Tamga (`pip install tamga-protocol`) | Permanent geintegreerd in andermans project |
21
+
22
+ **Eerlijk apart gehouden van bovenstaande tabel, want dit is geen derde-partij-controle:** voor 0.6.0 (de eerste keer dat dit pakket echt naar de blockchain schrijft) hebben we zelf een adversariële security review uitgevoerd. Tien bevindingen, allemaal gefixt met een eigen regressietest, geen onafhankelijke audit door een externe partij. Volledig, controleerbaar verslag: [docs/SECURITY-REVIEW-2026-09-11.md](https://github.com/holistis/tokenizen/blob/main/packages/capacity-attest/docs/SECURITY-REVIEW-2026-09-11.md).
23
+
24
+ ## Waarom dit bestaat
25
+
26
+ Wanneer een AI-agent via het [x402-protocol](https://www.x402.org/) betaalt voor capaciteit (GPU-uren, opslag, API/inference-credits, bandbreedte) bij een andere agent of dienst, is er na de betaling geen bewijs dat het beloofde ook echt geleverd is. De kopende agent weet het zelf (hij zag de output, of zag hem niet), maar die kennis gaat verloren zodra de sessie eindigt. De volgende agent die met dezelfde verkoper zaken wil doen, begint weer blind.
27
+
28
+ `capacity-attest` lost dat specifieke gat op: na afwikkeling laat de **betalende** agent een cryptografisch ondertekende, feitelijke claim achter (`delivered: yes/no/partial` + een hash van het bewijsmateriaal). Andere agents kunnen die geschiedenis opvragen **voordat** ze zelf met die verkoper in zee gaan.
29
+
30
+ Geen oordeel. Geen reputatiescore. Geen "vonnis", puur een ondertekende bon-plus-claim, net zoals een afleverbon bij een fysieke levering.
31
+
32
+ ## Wat dit NIET is
33
+
34
+ Dit is bewust en hardcoded **niet**:
35
+
36
+ - **Geen reputatiescore of rating.** `get_delivery_history` retourneert de ruwe, chronologische lijst van claims, geen gemiddelde, geen percentage, geen "trust score". Het samenvatten tot één getal is impliciet een oordeel, en dat is expliciet afgewezen tijdens de besluitvorming voor dit project.
37
+ - **Geen financieel product.** Geen rente, geen tijd-disconto op betalingen, geen yield op het ledger-saldo (er ís geen saldo, dit is geen escrow), geen lening, geen onderpand, geen invoice-financing/factoring. `assetType` is een gesloten enum van capaciteitssoorten (`gpu-hours`, `storage`, `api-credits`, `bandwidth`) en bevat bewust niets dat op een financieel instrument lijkt.
38
+ - **Geen eigen token of munt.** Betalingen lopen via x402/USDC zoals gebruikelijk; dit project registreert alleen de *bon* van een afwikkeling die al ergens anders heeft plaatsgevonden.
39
+ - **Geen krediet-verlening.** Een claim wordt pas gemaakt **na** een voltooide betaling. Dit project financiert niets, het documenteert een reeds afgeronde ijara (verhuur/dienst)-transactie.
40
+ - **Geen eigen identity-, autoriteits- of geschillenlaag.** `externalRefs` (zie hieronder) is puur een citaat naar een systeem van een ander (ERC-8004, AP2, Legal Context Protocol, ...). Dit project resolvet, verifieert of beoordeelt die verwijzing zelf nooit. Zie [DECISIONS.md](https://github.com/holistis/tokenizen/blob/main/packages/capacity-attest/DECISIONS.md) D-007 t/m D-013 voor waarom dit bewust geen eigen protocol is geworden.
41
+
42
+ Dit is een bewuste, formeel getoetste ontwerpkeuze, niet een toevallige scope-beperking. Zie de guardrails-sectie in het project-brief als je overweegt hier iets aan toe te voegen: bij twijfel of een veld/functie hiertegenaan schuurt, laat het weg.
43
+
44
+ Bewust uitgestelde features (verankering, tussentijdse status, formele conformance-vectoren), inclusief de precieze voorwaarde waaronder we ze alsnog zouden bouwen: zie [DECISIONS.md](https://github.com/holistis/tokenizen/blob/main/packages/capacity-attest/DECISIONS.md).
45
+
46
+ Verder lezen: [negen manieren om een leverings-claim te vervalsen, en waarom geen enkele volledig werkte](https://tokenizen.nl/en/notes/nine-ways-to-fake-a-delivery-claim), de publieke versie van D-014/D-018 hierboven.
47
+
48
+ ## Hoe het werkt
49
+
50
+ ### 1. `record_delivery`
51
+
52
+ De betalende agent (de koper) roept dit aan **na** een x402-afwikkeling, zodra bekend is of het beloofde is aangekomen. De claim bevat:
53
+
54
+ | Veld | Betekenis |
55
+ | --- | --- |
56
+ | `sellerAddress` | 0x-adres van de partij die betaald werd |
57
+ | `buyerAddress` | 0x-adres van de betalende agent, moet overeenkomen met het adres dat uit `signature` wordt teruggerekend |
58
+ | `assetType` | `gpu-hours` \| `storage` \| `api-credits` \| `bandwidth` |
59
+ | `promisedSpec` | Wat er beloofd was: vrije tekst of een gestructureerd object |
60
+ | `delivered` | `yes` \| `no` \| `partial` |
61
+ | `evidenceHash` | sha256-hex van bewijsmateriaal (logs, response-payload, ...), het bewijs zelf wordt niet opgeslagen |
62
+ | `settlementRef` | x402-payment-ref of on-chain tx-hash van de onderliggende betaling |
63
+ | `timestamp` | ISO-8601 tijdstip |
64
+ | `claimId` | content-addressed sha256-hash van alle velden hierboven, zie `computeClaimId()` in `src/schema.ts` |
65
+ | `signature` | EIP-191 personal-sign handtekening van de koper over `claimId` |
66
+ | `externalRefs` | *(optioneel, sinds 0.3.0)* ongeverifieerde verwijzingen naar andere agent-economie-infrastructuur: `sellerAgentRef`/`buyerAgentRef` (bv. een ERC-8004-agent-id of DID), `mandateRef`+`mandateIssuerDid` (een extern uitgegeven AP2/AAE-mandaat), `intentRef` (een extern AP2 IntentMandate), `disputeContext` (`protocol`+`termsHash`+optioneel `resolutionRef`, bv. een Legal Context Protocol-verwijzing). Zie [DECISIONS.md](https://github.com/holistis/tokenizen/blob/main/packages/capacity-attest/DECISIONS.md) D-007 t/m D-013 |
67
+ | `priorClaimId` | *(optioneel, sinds 0.4.0)* de `claimId` van jouw vorige claim over dezelfde `sellerAddress`, zodat jouw claims over die verkoper een ketting vormen. Weggelaten bij je eerste claim over een verkoper. Zit in de ondertekende inhoud, dus een host kan het niet weghalen. Laat een lezer een host betrappen die een middelste claim verbergt. Zie [DECISIONS.md](https://github.com/holistis/tokenizen/blob/main/packages/capacity-attest/DECISIONS.md) D-006 |
68
+
69
+ De server valideert eerst het schema, dan of `claimId` echt de hash van de inhoud is, en dan of `signature` echt terugrekent naar `buyerAddress`. Alleen dan wordt de claim toegevoegd aan de append-only ledger (`data/claims.jsonl`). Een ongeldige handtekening of een claim die al eerder is opgeslagen (zelfde `claimId`) wordt geweigerd.
70
+
71
+ ### 2. `get_delivery_history`
72
+
73
+ Gegeven een `sellerAddress`, retourneert dit alle bekende claims tegen die verkoper op déze installatie, chronologisch (oudst eerst). Puur feitelijk, geen samengevat getal. Een kopende agent roept dit aan **vóórdat** hij betaalt, om de ruwe leveringsgeschiedenis van een potentiële verkoper te zien en zelf te beoordelen.
74
+
75
+ Het antwoord bevat naast `sellerAddress`, `count` en `claims` ook `scope` (altijd `"local-ledger"`) en `note`: een vaste, feitelijke tekst die uitlegt dat dit resultaat alleen de lokale ledger van déze installatie weerspiegelt. Een lege of korte geschiedenis betekent niet dat de verkoper een schone staat van dienst heeft, het kan ook betekenen dat er hier simpelweg nog geen claims zijn vastgelegd. Zie [DECISIONS.md](https://github.com/holistis/tokenizen/blob/main/packages/capacity-attest/DECISIONS.md) (D-005) voor de bredere architectuurvraag hierachter: hoe vindt een koper claims die op een ándere installatie zijn vastgelegd.
76
+
77
+ Sinds 0.4.0 bevat het antwoord ook `completeness`: een analyse van de per-koper ketens (`priorClaimId`) in precies deze uitkomst. Als een getoonde claim terugverwijst naar een claim die NIET in de uitkomst zit, komt die in `possibleOmissions` te staan. Dat is een concreet, controleerbaar signaal dat de host mogelijk een middelste claim verbergt, in plaats van een vaag vermoeden.
78
+
79
+ Let op, dit is het belangrijkste punt: dat `completeness`-veld wordt berekend door dezelfde server die de claims teruggeeft. Vertrouw je die server niet, vertrouw dan ook het veld niet, want een oneerlijke host kan er gewoon "alles compleet" in zetten. De echte zekerheid zit in de ondertekende `priorClaimId` in de claims zelf, die een host niet kan vervalsen of weghalen. Reken de controle dus zelf opnieuw uit over de teruggekregen claims:
80
+
81
+ ```js
82
+ // recompute-completeness.mjs
83
+ import { verifyClaim } from "capacity-attest/dist/signing.js";
84
+ import { analyzeCompleteness } from "capacity-attest/dist/completeness.js";
85
+
86
+ // `claims` = de array uit het get_delivery_history-antwoord.
87
+ const allSigned = claims.every((c) => verifyClaim(c).ok); // elke claim echt?
88
+ const report = analyzeCompleteness(claims); // zelf herrekenen, niet het host-veld geloven
89
+ console.log({ allSigned, chainConsistent: report.chainConsistent, possibleOmissions: report.possibleOmissions });
90
+ ```
91
+
92
+ Eerlijke grens: ook zelf-herrekenen betrapt geen verborgen laatste claim en geen verborgen hele koper, want daar valt geen schakel over te struikelen. En een losse terugverwijzing hoeft geen bedrog te zijn: de eerdere claim kan ook gewoon op een andere installatie zijn vastgelegd (het D-005-geval). Voor echte zekerheid blijven de externe getuigen nodig: je eigen bewaarde kopie hierboven, en de betaling op de keten via `settlementRef`. Zie [DECISIONS.md](https://github.com/holistis/tokenizen/blob/main/packages/capacity-attest/DECISIONS.md) D-006.
93
+
94
+ Een openbaar, zelf-controleerbaar voorbeeld met een nagebootste verbergende host en expres-kapotte testgevallen staat in [docs/COMPLETENESS-FIXTURE.md](https://github.com/holistis/tokenizen/blob/main/packages/capacity-attest/docs/COMPLETENESS-FIXTURE.md). Draai het met `npm run fixture`; dezelfde controles draaien bij elke push als test. Zo kun je onze claim zelf natellen in plaats van ons op ons woord te geloven.
95
+
96
+ ## Claims van andere installaties vinden (D-005)
97
+
98
+ `get_delivery_history` is per definitie lokaal: koper B ziet niet wat koper A op een andere installatie vastlegde over dezelfde verkoper. Omdat elke claim zelf-verifieerbaar is, heeft vindbaarheid geen vertrouwde index nodig. `discoverDeliveryHistory(seller, sources)` (zie `src/discovery.ts`) leest een verkopers claims uit meerdere onafhankelijke, ONvertrouwde bronnen (je lokale ledger plus elk host-onafhankelijk substraat dat je wilt lezen), ontdubbelt, herverifieert elke claim, filtert andere verkopers eruit, en draait de completeness-check over het geheel. Een bron die nep injecteert wordt geweigerd; een bron die weglaat is het D-006-probleem, meegenomen maar niet magisch opgelost.
99
+
100
+ De productie-onderlaag is live, op alle drie de chains die dit project ondersteunt: EAS-attestaties bestaan op Base mainnet, Optimism mainnet en Ethereum mainnet, elk zelfstandig te controleren via een openbare block-explorer-link, niet alleen beschreven. Een openbaar, draaibaar voorbeeld met twee nagebootste installaties staat in [docs/DISCOVERY-FIXTURE.md](https://github.com/holistis/tokenizen/blob/main/packages/capacity-attest/docs/DISCOVERY-FIXTURE.md), draai het met `npm run discovery-fixture`. Zie [DECISIONS.md](https://github.com/holistis/tokenizen/blob/main/packages/capacity-attest/DECISIONS.md) D-005.
101
+
102
+ Een tweede, niet-EAS productieklare `ClaimSource` bestaat ook: `httpClaimSource` (`src/http-source.ts`), een simpel HTTP-eindpunt volgens de conventie `GET <url>?seller=<address>` -> een JSON-array van claims. `examples/reputation-lookup.mjs` is een draaibare CLI die de lokale ledger met beide productiebronnen combineert in één echte opzoeking. [docs/INTEGRATION-GUIDE.md](https://github.com/holistis/tokenizen/blob/main/packages/capacity-attest/docs/INTEGRATION-GUIDE.md) loopt door alle drie brontypen heen met echte code, plus twee onafhankelijke, echte integraties gebouwd door andere projecten.
103
+
104
+ ### 3. `resolve_agent_identity` *(sinds 0.3.0)*
105
+
106
+ Read-only opzoeking tegen een ERC-8004 Identity Registry: wie bezit `agentId` (`ownerOf`) en waar staat zijn registratiebestand (`tokenURI`). Alleen de standaard ERC-721-interface wordt aangeroepen, niets ERC-8004-specifieks. Vereist van de aanroeper zowel `agentRegistryRef` (`"eip155:<chainId>:<registryAddress>"`) als een `rpcUrl` voor die chain: dit project bundelt bewust geen eigen RPC-provider en geen canoniek registry-adres, want ERC-8004 heeft onafhankelijke deployments per chain en de EIP-tekst zelf noemt geen vast adres. Haalt bewust NOOIT op wat `tokenURI` aanwijst (dat blijft een pointer die de aanroeper zelf desgewenst opvraagt); dat zou een SSRF-vormig risico zijn op aanroeper-gecontroleerde on-chain data.
107
+
108
+ Getest tegen een injecteerbare `ContractFactory` (`src/erc8004.test.ts`, geen netwerkafhankelijkheid) én live tegen de echte, gedeployde registry op Base mainnet (`examples/verify-erc8004-live.mjs`, `npm run build && node examples/verify-erc8004-live.mjs`). Zie [DECISIONS.md](https://github.com/holistis/tokenizen/blob/main/packages/capacity-attest/DECISIONS.md) D-007 voor de volledige achtergrond.
109
+
110
+ ### 4. `publishReputationFeedback` *(sinds 0.6.0, library-functie, geen MCP-tool)*
111
+
112
+ **Let op (adversariele review 2026-09-11): op het moment van schrijven staat op npm nog versie 0.5.0 gepubliceerd, zonder deze functie.** Wie via GitHub leest en meteen `npm install capacity-attest` doet zoals verderop in dit document beschreven, krijgt dus nog geen `publishReputationFeedback`, check `npm view capacity-attest version` voor de daadwerkelijk gepubliceerde versie voor je dit importeert. Alles hieronder beschrijft de code zoals hij op de `main`-branch staat.
113
+
114
+ Publiceert het `delivered`-feit van een al ondertekende claim naar een ERC-8004 Reputation Registry se `giveFeedback()`, dezelfde plek waar ~500k geregistreerde agents al naar reputatiesignalen kunnen kijken, in plaats van alleen naar deze installatie se eigen ledger of EAS. Het contract vereist een numeriek `value`+`valueDecimals`-veld; dit pakket verzint daar bewust geen eigen beoordelingsschaal voor. `value` is een letterlijke, mechanische spiegel van `delivered` (yes=1.0, partial=0.5, no=0.0), nooit een nieuw oordeel, en capacity-attest leest of toont dat getal zelf nergens terug. Herverifieert de claim se handtekening voordat er iets on-chain geschreven wordt.
115
+
116
+ Vereist van de aanroeper `reputationRegistryRef` (`"eip155:<chainId>:<registryAddress>"`, de Reputation Registry, niet de Identity Registry), `agentRegistryRef` (dezelfde chain, maar de Identity Registry) en een `rpcUrl`, zelfde caller-levert-alles-postuur als `resolve_agent_identity`. `agentId` (de verkoper se ERC-8004-agent) moet al een geldig geregistreerde Identity-Registry-agent zijn; het contract weigert zelf feedback van de agent se eigen eigenaar ("Self-feedback not allowed"). Sinds de adversariele review van 2026-09-11 wordt `agentId` se geregistreerde eigenaar (via `agentRegistryRef`) ook altijd tegen `claim.sellerAddress` gecontroleerd voor er iets on-chain geschreven wordt, zonder die controle kon een aanroeper een echte, geldig ondertekende claim aan een willekeurig ander agentId hangen.
117
+
118
+ **Bewust GEEN MCP-tool**, om dezelfde reden als EAS se `publishClaim`: dit is een schrijf-actie die een echte, gefinancierde signer en gas vereist, en deze server bundelt of bewaart bewust geen eigen private key. Beschikbaar als directe import (`src/erc8004-reputation.ts`) voor wie zelf een signer beheert.
119
+
120
+ Getest tegen een injecteerbare `ReputationContractFactory` (`src/erc8004-reputation.test.ts`, 19 tests, geen netwerkafhankelijkheid, inclusief een expliciete test dat `value` uitsluitend van `delivered` afhangt, en drie tests voor de agentId-eigenaarschapscontrole) én live tegen de echte, gedeployde registry op Base mainnet (`examples/erc8004-reputation-live-demo.ts`, `npm run erc8004-reputation-demo`): op 2026-09-10 bevestigd via een eigen, wegwerpbare test-agent (agentId 85888) en een echte `giveFeedback()`-aanroep, [tx 0x221797800d5941dff62e87022083e7c6dfba3e07b35c84e56b10fdca8967efc0](https://basescan.org/tx/0x221797800d5941dff62e87022083e7c6dfba3e07b35c84e56b10fdca8967efc0), onafhankelijk teruggecontroleerd via een losse `eth_getTransactionReceipt`-aanroep. Zie [DECISIONS.md](https://github.com/holistis/tokenizen/blob/main/packages/capacity-attest/DECISIONS.md) D-016 voor de volledige achtergrond, inclusief waarom dit ondanks D-005's eigen trigger-criterium toch vandaag gebouwd is.
121
+
122
+ ## Ondertekening
123
+
124
+ De claim wordt ondertekend door de **koper** (de partij die betaalde en dus weet wat er wel/niet aankwam), niet door de verkoper. Dit is bewust eenvoudige EIP-191 `personal_sign` over `claimId` (via `ethers.Signer#signMessage`), geen EIP-712 typed data. Dat houdt het crypto-oppervlak van deze MVP klein en makkelijk te controleren. Een latere upgrade naar EIP-712 (zoals in `mcp-paywall/src/x402.mjs`) is additief mogelijk zonder bestaande claims ongeldig te maken.
125
+
126
+ ## Een claim onafhankelijk verifiëren
127
+
128
+ Elke claim in de ledger is met alleen het npm-package en de rauwe claim-bytes na te rekenen, zonder toegang tot dit project of een netwerkoproep naar ons. Geen account, geen hosted call.
129
+
130
+ ```bash
131
+ npm install capacity-attest
132
+ ```
133
+
134
+ ```js
135
+ // verify.mjs, als ES module draaien (top-level await)
136
+ import { verifyClaim } from "capacity-attest/dist/signing.js";
137
+
138
+ const claim = JSON.parse(await (await fetch("<url naar een claim.jsonl-regel>")).text());
139
+ console.log(verifyClaim(claim));
140
+ // { ok: true } als claimId echt de hash van de inhoud is EN signature echt naar buyerAddress terugrekent
141
+ ```
142
+
143
+ Let op: importeer `capacity-attest/dist/signing.js` rechtstreeks, niet het package-root. De root (`dist/index.js`) start bij het importeren meteen de MCP-server over stdio, wat een los verificatie-script laat hangen.
144
+
145
+ `verifyClaim()` controleert precies twee dingen: dat `claimId` de content-addressed hash van de claim-velden is, en dat `signature` (EIP-191) terugrekent naar `buyerAddress`. Het controleert niet of de onderliggende afwikkeling (`settlementRef`) echt on-chain klopt, dat is een losse, aparte check tegen de betreffende chain, en het controleert niet of `delivered` waar is of of `evidenceHash` een echt bewijsstuk dekt, dat blijft de eigen verklaring van de kopende agent.
146
+
147
+ Een werkend, extern gereproduceerd voorbeeld van deze exacte stappen staat in [github.com/YE-YI7/asm-spec, PR #18](https://github.com/YE-YI7/asm-spec/pull/18): een onafhankelijk project dat dit tegen een echte, live geregistreerde claim heeft gedraaid.
148
+
149
+ ## Je eigen ingediende claims delen, los van een host (D-006)
150
+
151
+ `get_delivery_history` vertrouwt op de eerlijkheid van wie de MCP-server bedient: zie de `note` in dat tool-antwoord en [DECISIONS.md](https://github.com/holistis/tokenizen/blob/main/packages/capacity-attest/DECISIONS.md) (D-006). Elke getoonde claim is wel degelijk echt (ondertekening wordt sinds 2026-09-06 ook bij het lezen opnieuw gecontroleerd, niet alleen bij het schrijven), maar niets bewijst dat de host de VOLLEDIGE set laat zien die hij daadwerkelijk heeft.
152
+
153
+ Als jij zelf de koper bent die een claim indiende, hoef je op die host niet te wachten: jij hebt die claim zelf al ondertekend, dus jij kan 'm rechtstreeks aan een wantrouwende tegenpartij laten zien, buiten elke host om.
154
+
155
+ ```js
156
+ // export-my-claims.mjs
157
+ import { claimsForSeller } from "capacity-attest/dist/ledger.js";
158
+
159
+ const myAddress = "0x..."; // jouw buyerAddress
160
+ const seller = "0x..."; // de verkoper waar het over gaat
161
+
162
+ const mine = (await claimsForSeller(seller)).filter(
163
+ (c) => c.buyerAddress.toLowerCase() === myAddress.toLowerCase(),
164
+ );
165
+ console.log(JSON.stringify(mine, null, 2));
166
+ ```
167
+
168
+ Elke claim in die lijst is zelfstandig verifieerbaar met `verifyClaim()` (zie hierboven), zonder dat de ontvanger jouw installatie of enige host hoeft te vertrouwen. Dit lost geen vindbaarheid op (D-005: hoe vindt iemand anders jouw claim zonder dat jij 'm deelt) en geen volledigheid over ALLE kopers samen (D-006: dit bewijst alleen wat JIJ indiende, niet wat een host verder mogelijk verzwijgt van andere kopers), maar het geeft een concrete, kosteloze manier om één specifiek geschil te bewijzen zonder een host te hoeven vertrouwen.
169
+
170
+ ## Lokaal draaien
171
+
172
+ ```bash
173
+ npm install
174
+ npm run build # tsc -> dist/
175
+ npm run typecheck # tsc --noEmit
176
+ npm test # vitest run
177
+ npm run demo # end-to-end lokale demo met TEST-sleutels, geen live infra
178
+ npm start # start de MCP-server over stdio (bijv. voor Claude Desktop/Code als lokale MCP-server)
179
+ ```
180
+
181
+ De ledger-locatie is instelbaar via `CAPACITY_ATTEST_DATA_DIR` (default: `./data` in dit package). Tests en de demo gebruiken altijd een eigen, wegwerpbare tijdelijke map, nooit de echte `data/` map.
182
+
183
+ ## Architectuur
184
+
185
+ ```text
186
+ src/
187
+ schema.ts DeliveryClaim zod-schema + content-addressing (computeClaimId, canonicalize)
188
+ signing.ts sign/verify van een claim (ethers, EIP-191 personal-sign)
189
+ ledger.ts append-only JSONL-opslag (data/claims.jsonl), nooit muteerbaar
190
+ tools.ts de daadwerkelijke logica achter beide MCP-tools, transport-onafhankelijk
191
+ config.ts waar de ledger-map leeft, lazy zodat tests 'm kunnen overriden
192
+ index.ts MCP-server wiring (registreert record_delivery + get_delivery_history)
193
+ examples/demo.ts end-to-end lokaal voorbeeld met TEST-sleutels
194
+ ```
195
+
196
+ `tools.ts` bevat de eigenlijke business-logica; `index.ts` vertaalt dat alleen naar MCP tool-calls. Zo kunnen tests en de demo dezelfde logica direct aanroepen zonder een stdio-transport op te tuigen.
197
+
198
+ ## Relatie tot x402
199
+
200
+ Dit project verifieert of settelt zelf géén x402-betalingen, dat gebeurt al bij de betaalstap zelf (zie bijvoorbeeld `mcp-paywall/src/x402.mjs` in dit ecosysteem voor een volledige EIP-3009-verify/settle-implementatie). `settlementRef` verwijst simpelweg naar die reeds-voltooide afwikkeling. Dat betekent ook dat de MVP-koppeling met een echte x402-facilitator eenvoudig kan blijven: `settlementRef` is vrije tekst, met als aanname dat de koper 'm eerlijk invult. Een latere versie kan dat veld optioneel verifiëren tegen een echte facilitator (TODO, niet in deze MVP).
201
+
202
+ ## Relatie tot AWS Bedrock AgentCore Payments
203
+
204
+ Geen overlap, geen concurrentie: verschillende stap in de keten. Bedrock AgentCore Payments (Amazon, sinds 2026) regelt de betaalstap zelf, tot en met het moment dat "the merchant verifies the payment proof... [and] returns the requested content" ([officiële AWS-documentatie](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/payments-how-it-works.html)). Dat bewijs is een bewijs van **betaling**, niet van **levering**: er staat nergens vastgelegd of de agent na die stap ook echt kreeg wat beloofd was. Precies daar begint `capacity-attest`. Net als bij x402 hierboven: dit project settelt geen betalingen en concurreert niet met de betaalrail, het legt vast wat er ná de betaling wel of niet daadwerkelijk aankwam, ongeacht welke rail (x402 of anders) die betaling afhandelde.
@@ -0,0 +1,12 @@
1
+ import type { DeliveryClaim } from "./schema.js";
2
+ declare const SELLER = "0x00000000000000000000000000000000000000aa";
3
+ /**
4
+ * Generate `count` distinct, genuinely-signed claims for SELLER, spread
5
+ * across a small wallet pool (not one fresh wallet per claim). Each claim is
6
+ * still a real, independently verifiable signature; only wallet reuse is a
7
+ * shortcut, and reusing a buyer wallet across claims is realistic (a real
8
+ * buyer transacts with the same seller more than once).
9
+ */
10
+ export declare function generateClaims(count: number): Promise<DeliveryClaim[]>;
11
+ export { SELLER as BENCH_SELLER };
12
+ //# sourceMappingURL=bench-helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bench-helpers.d.ts","sourceRoot":"","sources":["../src/bench-helpers.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAgB,aAAa,EAAE,MAAM,aAAa,CAAC;AAE/D,QAAA,MAAM,MAAM,+CAA+C,CAAC;AAS5D;;;;;;GAMG;AACH,wBAAsB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAmB5E;AAED,OAAO,EAAE,MAAM,IAAI,YAAY,EAAE,CAAC"}
@@ -0,0 +1,51 @@
1
+ // bench-helpers.ts — shared fixture generation for *.bench.ts files.
2
+ //
3
+ // Not part of the published surface (same convention as test-helpers.ts).
4
+ // Separate from test-helpers.ts because benchmark fixtures need to generate
5
+ // large volumes fast: real signing per claim (buildSignedClaim) is correct
6
+ // but costs real ECDSA work per call, so generating tens of thousands of
7
+ // claims with a FRESH random wallet each time would make fixture setup
8
+ // itself the bottleneck, not the code being measured. This reuses a small,
9
+ // fixed pool of wallets (signing cost scales with claim count either way;
10
+ // wallet CREATION does not need to) and skips the schema round-trip test
11
+ // suites don't need for a benchmark.
12
+ import { ethers } from "ethers";
13
+ import { createHash } from "node:crypto";
14
+ import { signClaim } from "./signing.js";
15
+ const SELLER = "0x00000000000000000000000000000000000000aa";
16
+ const WALLET_POOL_SIZE = 50;
17
+ let pool = null;
18
+ function walletPool() {
19
+ if (!pool)
20
+ pool = Array.from({ length: WALLET_POOL_SIZE }, () => ethers.Wallet.createRandom());
21
+ return pool;
22
+ }
23
+ /**
24
+ * Generate `count` distinct, genuinely-signed claims for SELLER, spread
25
+ * across a small wallet pool (not one fresh wallet per claim). Each claim is
26
+ * still a real, independently verifiable signature; only wallet reuse is a
27
+ * shortcut, and reusing a buyer wallet across claims is realistic (a real
28
+ * buyer transacts with the same seller more than once).
29
+ */
30
+ export async function generateClaims(count) {
31
+ const wallets = walletPool();
32
+ const claims = [];
33
+ for (let i = 0; i < count; i++) {
34
+ const wallet = wallets[i % wallets.length];
35
+ const content = {
36
+ sellerAddress: SELLER,
37
+ buyerAddress: wallet.address,
38
+ assetType: "gpu-hours",
39
+ promisedSpec: `bench claim ${i}`,
40
+ delivered: i % 5 === 0 ? "no" : "yes",
41
+ evidenceHash: createHash("sha256").update(`evidence-${i}`).digest("hex"),
42
+ settlementRef: "0x" + i.toString(16).padStart(64, "0"),
43
+ timestamp: new Date(Date.UTC(2026, 0, 1) + i * 1000).toISOString(),
44
+ };
45
+ const { claimId, signature } = await signClaim(wallet, content);
46
+ claims.push({ ...content, claimId, signature });
47
+ }
48
+ return claims;
49
+ }
50
+ export { SELLER as BENCH_SELLER };
51
+ //# sourceMappingURL=bench-helpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bench-helpers.js","sourceRoot":"","sources":["../src/bench-helpers.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,EAAE;AACF,0EAA0E;AAC1E,4EAA4E;AAC5E,2EAA2E;AAC3E,yEAAyE;AACzE,uEAAuE;AACvE,2EAA2E;AAC3E,0EAA0E;AAC1E,yEAAyE;AACzE,qCAAqC;AAErC,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAGzC,MAAM,MAAM,GAAG,4CAA4C,CAAC;AAC5D,MAAM,gBAAgB,GAAG,EAAE,CAAC;AAE5B,IAAI,IAAI,GAAiC,IAAI,CAAC;AAC9C,SAAS,UAAU;IACjB,IAAI,CAAC,IAAI;QAAE,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,gBAAgB,EAAE,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC,CAAC;IAC/F,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,KAAa;IAChD,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;IAC7B,MAAM,MAAM,GAAoB,EAAE,CAAC;IACnC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC;QAC/B,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,OAAO,CAAC,MAAM,CAAE,CAAC;QAC5C,MAAM,OAAO,GAAiB;YAC5B,aAAa,EAAE,MAAM;YACrB,YAAY,EAAE,MAAM,CAAC,OAAO;YAC5B,SAAS,EAAE,WAAW;YACtB,YAAY,EAAE,eAAe,CAAC,EAAE;YAChC,SAAS,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK;YACrC,YAAY,EAAE,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;YACxE,aAAa,EAAE,IAAI,GAAG,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE,EAAE,GAAG,CAAC;YACtD,SAAS,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC,WAAW,EAAE;SACnE,CAAC;QACF,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,GAAG,MAAM,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAChE,MAAM,CAAC,IAAI,CAAC,EAAE,GAAG,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC;IAClD,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,OAAO,EAAE,MAAM,IAAI,YAAY,EAAE,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=discovery.bench.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discovery.bench.d.ts","sourceRoot":"","sources":["../src/discovery.bench.ts"],"names":[],"mappings":""}
@@ -0,0 +1,30 @@
1
+ // discovery.bench.ts — real wall-clock numbers for discoverDeliveryHistory at
2
+ // the volumes the NLnet proposal's WP1 explicitly names: "a source returning
3
+ // tens of thousands of claims" and multi-source aggregation. Run with:
4
+ // npx vitest bench src/discovery.bench.ts
5
+ //
6
+ // This does not replace discovery.fuzz.test.ts (adversarial correctness); it
7
+ // answers a different question: given genuinely valid, well-formed claims at
8
+ // real scale, how fast is aggregation, and does it stay roughly linear as
9
+ // volume grows, or does it degrade unexpectedly.
10
+ import { bench, describe } from "vitest";
11
+ import { discoverDeliveryHistory, staticSource } from "./discovery.js";
12
+ import { generateClaims, BENCH_SELLER } from "./bench-helpers.js";
13
+ // Pre-generate once per size, outside the timed bench() body, so the
14
+ // benchmark measures discoverDeliveryHistory itself, not fixture setup.
15
+ const small = await generateClaims(1_000);
16
+ const large = await generateClaims(40_000); // close to MAX_TOTAL_CLAIMS (50,000) without exceeding it
17
+ const perSourceForFanOut = await generateClaims(4_000);
18
+ describe("discoverDeliveryHistory volume", () => {
19
+ bench("single source, 1,000 claims", async () => {
20
+ await discoverDeliveryHistory(BENCH_SELLER, [staticSource("s1", small)]);
21
+ });
22
+ bench("single source, 40,000 claims (near MAX_TOTAL_CLAIMS)", async () => {
23
+ await discoverDeliveryHistory(BENCH_SELLER, [staticSource("s1", large)]);
24
+ });
25
+ bench("ten independent sources, 4,000 claims each (40,000 total, realistic multi-source fan-in)", async () => {
26
+ const sources = Array.from({ length: 10 }, (_, i) => staticSource(`s${i}`, perSourceForFanOut));
27
+ await discoverDeliveryHistory(BENCH_SELLER, sources);
28
+ });
29
+ });
30
+ //# sourceMappingURL=discovery.bench.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discovery.bench.js","sourceRoot":"","sources":["../src/discovery.bench.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,6EAA6E;AAC7E,uEAAuE;AACvE,4CAA4C;AAC5C,EAAE;AACF,6EAA6E;AAC7E,6EAA6E;AAC7E,0EAA0E;AAC1E,iDAAiD;AAEjD,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AACzC,OAAO,EAAE,uBAAuB,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACvE,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElE,qEAAqE;AACrE,wEAAwE;AACxE,MAAM,KAAK,GAAG,MAAM,cAAc,CAAC,KAAK,CAAC,CAAC;AAC1C,MAAM,KAAK,GAAG,MAAM,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,0DAA0D;AACtG,MAAM,kBAAkB,GAAG,MAAM,cAAc,CAAC,KAAK,CAAC,CAAC;AAEvD,QAAQ,CAAC,gCAAgC,EAAE,GAAG,EAAE;IAC9C,KAAK,CAAC,6BAA6B,EAAE,KAAK,IAAI,EAAE;QAC9C,MAAM,uBAAuB,CAAC,YAAY,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IAC3E,CAAC,CAAC,CAAC;IAEH,KAAK,CAAC,sDAAsD,EAAE,KAAK,IAAI,EAAE;QACvE,MAAM,uBAAuB,CAAC,YAAY,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IAC3E,CAAC,CAAC,CAAC;IAEH,KAAK,CAAC,0FAA0F,EAAE,KAAK,IAAI,EAAE;QAC3G,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,EAAE,kBAAkB,CAAC,CAAC,CAAC;QAChG,MAAM,uBAAuB,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;IACvD,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -35,6 +35,8 @@ export interface SourceReport {
35
35
  export interface DiscoverOptions {
36
36
  maxClaimsPerSource?: number;
37
37
  maxTotalClaims?: number;
38
+ /** Max time to wait on a single source's fetchForSeller before treating it as a per-source error and moving on. */
39
+ sourceTimeoutMs?: number;
38
40
  }
39
41
  export interface AggregatedHistory {
40
42
  sellerAddress: string;
@@ -1 +1 @@
1
- {"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../src/discovery.ts"],"names":[],"mappings":"AAqCA,OAAO,EAAuB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAGtE,OAAO,EAAuB,KAAK,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AASjF;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IAC1B,2DAA2D;IAC3D,IAAI,EAAE,MAAM,CAAC;IACb,sFAAsF;IACtF,cAAc,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAAC;CACjE;AAED,yFAAyF;AACzF,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,OAAO,EAAE,MAAM,CAAC;IAChB,uGAAuG;IACvG,QAAQ,EAAE,MAAM,CAAC;IACjB,uFAAuF;IACvF,QAAQ,EAAE,MAAM,CAAC;IACjB,sGAAsG;IACtG,WAAW,EAAE,MAAM,CAAC;IACpB,oFAAoF;IACpF,UAAU,EAAE,MAAM,CAAC;IACnB,2GAA2G;IAC3G,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,8GAA8G;IAC9G,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,mHAAmH;AACnH,MAAM,WAAW,eAAe;IAC9B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,iBAAiB;IAChC,aAAa,EAAE,MAAM,CAAC;IACtB,kFAAkF;IAClF,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB,0CAA0C;IAC1C,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,sFAAsF;IACtF,YAAY,EAAE,kBAAkB,CAAC;IACjC,8EAA8E;IAC9E,IAAI,EAAE,MAAM,CAAC;CACd;AAoBD;;;;;;;;;;;;GAYG;AACH,wBAAsB,uBAAuB,CAC3C,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,WAAW,EAAE,EACtB,IAAI,GAAE,eAAoB,GACzB,OAAO,CAAC,iBAAiB,CAAC,CAiG5B;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,SAAiB,GAAG,WAAW,CAKpE;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,GAAG,WAAW,CAK/E"}
1
+ {"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../src/discovery.ts"],"names":[],"mappings":"AAqCA,OAAO,EAAuC,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAGtF,OAAO,EAAuB,KAAK,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AASjF;;;;;;GAMG;AACH,MAAM,WAAW,WAAW;IAC1B,2DAA2D;IAC3D,IAAI,EAAE,MAAM,CAAC;IACb,sFAAsF;IACtF,cAAc,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAAC;CACjE;AAED,yFAAyF;AACzF,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,6DAA6D;IAC7D,OAAO,EAAE,MAAM,CAAC;IAChB,uGAAuG;IACvG,QAAQ,EAAE,MAAM,CAAC;IACjB,uFAAuF;IACvF,QAAQ,EAAE,MAAM,CAAC;IACjB,sGAAsG;IACtG,WAAW,EAAE,MAAM,CAAC;IACpB,oFAAoF;IACpF,UAAU,EAAE,MAAM,CAAC;IACnB,2GAA2G;IAC3G,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,8GAA8G;IAC9G,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAUD,mHAAmH;AACnH,MAAM,WAAW,eAAe;IAC9B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,mHAAmH;IACnH,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AA0BD,MAAM,WAAW,iBAAiB;IAChC,aAAa,EAAE,MAAM,CAAC;IACtB,kFAAkF;IAClF,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB,0CAA0C;IAC1C,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,sFAAsF;IACtF,YAAY,EAAE,kBAAkB,CAAC;IACjC,8EAA8E;IAC9E,IAAI,EAAE,MAAM,CAAC;CACd;AAoBD;;;;;;;;;;;;GAYG;AACH,wBAAsB,uBAAuB,CAC3C,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,WAAW,EAAE,EACtB,IAAI,GAAE,eAAoB,GACzB,OAAO,CAAC,iBAAiB,CAAC,CA6H5B;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,SAAiB,GAAG,WAAW,CAKpE;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,GAAG,WAAW,CAK/E"}
package/dist/discovery.js CHANGED
@@ -34,7 +34,7 @@
34
34
  // wired here. That needs gas, a live chain, and a real integrator; see
35
35
  // DECISIONS.md D-005/D-012. This module is the substrate-agnostic seam
36
36
  // plus a reference in-memory/static source for simulation and tests.
37
- import { DeliveryClaimSchema } from "./schema.js";
37
+ import { DeliveryClaimSchema, computeClaimId } from "./schema.js";
38
38
  import { verifyClaim } from "./signing.js";
39
39
  import { claimsForSeller } from "./ledger.js";
40
40
  import { analyzeCompleteness } from "./completeness.js";
@@ -44,6 +44,32 @@ import { analyzeCompleteness } from "./completeness.js";
44
44
  // Overridable per call via DiscoverOptions.
45
45
  const MAX_CLAIMS_PER_SOURCE = 10_000;
46
46
  const MAX_TOTAL_CLAIMS = 50_000;
47
+ // A source that never resolves (a hung connection, a slow/overloaded
48
+ // endpoint, a hostile one deliberately stalling) would otherwise block
49
+ // discoverDeliveryHistory indefinitely, since sources are awaited in order:
50
+ // one stuck source censors every source after it, the same failure mode the
51
+ // non-array guard above already exists to prevent for a different case. This
52
+ // bounds how long a single source is allowed to take.
53
+ const DEFAULT_SOURCE_TIMEOUT_MS = 30_000;
54
+ class SourceTimeoutError extends Error {
55
+ constructor(ms) {
56
+ super(`source did not respond within ${ms}ms`);
57
+ this.name = "SourceTimeoutError";
58
+ }
59
+ }
60
+ /** Race a source's fetch against a timeout. The source's own promise is not cancelled (fetchForSeller has no cancellation contract); this only stops WAITING on it, so a late resolution after timeout is simply ignored. */
61
+ function withTimeout(promise, ms) {
62
+ return new Promise((resolve, reject) => {
63
+ const timer = setTimeout(() => reject(new SourceTimeoutError(ms)), ms);
64
+ promise.then((v) => {
65
+ clearTimeout(timer);
66
+ resolve(v);
67
+ }, (e) => {
68
+ clearTimeout(timer);
69
+ reject(e);
70
+ });
71
+ });
72
+ }
47
73
  const DISCOVERY_NOTE = "Aggregated from independent, UNTRUSTED sources. Every claim shown was re-verified locally (claimId recomputed and " +
48
74
  "signature recovered to buyerAddress), so a source cannot inject a forged claim. What verification proves is narrow: " +
49
75
  "it proves AUTHORSHIP of the content, NOT that a real payment or delivery happened (settlementRef is not checked " +
@@ -74,12 +100,13 @@ function timestampMs(claim) {
74
100
  export async function discoverDeliveryHistory(sellerAddress, sources, opts = {}) {
75
101
  const maxPerSource = opts.maxClaimsPerSource ?? MAX_CLAIMS_PER_SOURCE;
76
102
  const maxTotal = opts.maxTotalClaims ?? MAX_TOTAL_CLAIMS;
103
+ const sourceTimeoutMs = opts.sourceTimeoutMs ?? DEFAULT_SOURCE_TIMEOUT_MS;
77
104
  const byId = new Map();
78
105
  const sourceReports = [];
79
106
  for (const source of sources) {
80
107
  let fetched;
81
108
  try {
82
- fetched = await source.fetchForSeller(sellerAddress);
109
+ fetched = await withTimeout(source.fetchForSeller(sellerAddress), sourceTimeoutMs);
83
110
  }
84
111
  catch (e) {
85
112
  sourceReports.push({ name: source.name, fetched: 0, accepted: 0, rejected: 0, wrongSeller: 0, duplicates: 0, error: e.message });
@@ -110,6 +137,34 @@ export async function discoverDeliveryHistory(sellerAddress, sources, opts = {})
110
137
  if (byId.size >= maxTotal)
111
138
  break;
112
139
  const claim = fetched[i];
140
+ // Cheap pre-check BEFORE paying for a full cryptographic verification.
141
+ // Deliberately does NOT trust the raw, self-reported claim.claimId
142
+ // field for this (an earlier version of this optimization did, and a
143
+ // test caught why that is wrong: a tampered claim can carry a stale,
144
+ // untouched claimId copied from the genuine one it was mutated from).
145
+ // Instead this RECOMPUTES claimId from the claim's own content fields
146
+ // via computeClaimId(), the same pure sha256-over-canonical-JSON hash
147
+ // verifyClaim itself uses first (see signing.ts). That recomputation is
148
+ // cheap: no elliptic-curve math, unlike the signature RECOVERY
149
+ // (ethers.verifyMessage) verifyClaim also does, which is the actually
150
+ // expensive step this skips. If the recomputed hash matches an id
151
+ // already in byId, the content is proven identical (a hash match IS a
152
+ // content match) to something already fully verified, so signature
153
+ // recovery is redundant and safely skipped. If computeClaimId itself
154
+ // throws (pathological/malformed content), that is not this branch's
155
+ // problem to handle: fall through to the normal path below, which
156
+ // already has its own try/catch around verifyClaim for exactly that.
157
+ try {
158
+ const recomputed = lower(computeClaimId(claim));
159
+ if (byId.has(recomputed)) {
160
+ duplicates++;
161
+ continue;
162
+ }
163
+ }
164
+ catch {
165
+ // Malformed content: let the normal verifyClaim path below handle
166
+ // and count it, do not duplicate that error handling here.
167
+ }
113
168
  // Re-verify EVERY claim, from EVERY source, no exceptions: this is the
114
169
  // entire trust model. A source is just bytes until verifyClaim passes.
115
170
  let verdict;
@@ -1 +1 @@
1
- {"version":3,"file":"discovery.js","sourceRoot":"","sources":["../src/discovery.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"discovery.js","sourceRoot":"","sources":["../src/discovery.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=eas.bench.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eas.bench.d.ts","sourceRoot":"","sources":["../src/eas.bench.ts"],"names":[],"mappings":""}
@@ -0,0 +1,40 @@
1
+ // eas.bench.ts — quantifies the concurrency fix from PR #15: how much faster
2
+ // is easSource.fetchForSeller at the new default (concurrency=10) versus the
3
+ // old, fully-sequential behavior (concurrency=1), under a fixed per-call
4
+ // network latency. Run with:
5
+ // npx vitest bench src/eas.bench.ts
6
+ //
7
+ // A fake AttestationReader simulates realistic RPC latency (a fixed delay per
8
+ // dataForUid call) without touching a real network, so the number measured
9
+ // is the fan-out strategy's own overhead/parallelism, not real-world RPC
10
+ // jitter, which this benchmark cannot control anyway.
11
+ import { bench, describe } from "vitest";
12
+ import { easSource, encodeClaimData } from "./eas.js";
13
+ import { generateClaims, BENCH_SELLER } from "./bench-helpers.js";
14
+ const UID_COUNT = 100;
15
+ const SIMULATED_LATENCY_MS = 5; // a fast, optimistic RPC round-trip
16
+ const claims = await generateClaims(UID_COUNT);
17
+ const store = new Map(claims.map((c, i) => [`0xuid-${i}`, encodeClaimData(c)]));
18
+ const uids = [...store.keys()];
19
+ function readerWithConcurrency(concurrency) {
20
+ return {
21
+ uidsForSeller: async () => uids,
22
+ concurrency,
23
+ dataForUid: async (uid) => {
24
+ await new Promise((resolve) => setTimeout(resolve, SIMULATED_LATENCY_MS));
25
+ return store.get(uid);
26
+ },
27
+ };
28
+ }
29
+ describe(`easSource.fetchForSeller, ${UID_COUNT} attestations, ${SIMULATED_LATENCY_MS}ms simulated latency each`, () => {
30
+ bench("concurrency=1 (old, fully sequential behavior before PR #15)", async () => {
31
+ await easSource(readerWithConcurrency(1)).fetchForSeller(BENCH_SELLER);
32
+ });
33
+ bench("concurrency=10 (new default)", async () => {
34
+ await easSource(readerWithConcurrency(10)).fetchForSeller(BENCH_SELLER);
35
+ });
36
+ bench("concurrency=25 (higher, for comparison)", async () => {
37
+ await easSource(readerWithConcurrency(25)).fetchForSeller(BENCH_SELLER);
38
+ });
39
+ });
40
+ //# sourceMappingURL=eas.bench.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eas.bench.js","sourceRoot":"","sources":["../src/eas.bench.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,6EAA6E;AAC7E,yEAAyE;AACzE,6BAA6B;AAC7B,sCAAsC;AACtC,EAAE;AACF,8EAA8E;AAC9E,2EAA2E;AAC3E,yEAAyE;AACzE,sDAAsD;AAEtD,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,eAAe,EAA0B,MAAM,UAAU,CAAC;AAC9E,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElE,MAAM,SAAS,GAAG,GAAG,CAAC;AACtB,MAAM,oBAAoB,GAAG,CAAC,CAAC,CAAC,oCAAoC;AAEpE,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,SAAS,CAAC,CAAC;AAC/C,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,EAAE,EAAE,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAChF,MAAM,IAAI,GAAG,CAAC,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;AAE/B,SAAS,qBAAqB,CAAC,WAAmB;IAChD,OAAO;QACL,aAAa,EAAE,KAAK,IAAI,EAAE,CAAC,IAAI;QAC/B,WAAW;QACX,UAAU,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;YACxB,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,oBAAoB,CAAC,CAAC,CAAC;YAC1E,OAAO,KAAK,CAAC,GAAG,CAAC,GAAG,CAAE,CAAC;QACzB,CAAC;KACF,CAAC;AACJ,CAAC;AAED,QAAQ,CAAC,6BAA6B,SAAS,kBAAkB,oBAAoB,2BAA2B,EAAE,GAAG,EAAE;IACrH,KAAK,CAAC,8DAA8D,EAAE,KAAK,IAAI,EAAE;QAC/E,MAAM,SAAS,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,YAAY,CAAC,CAAC;IACzE,CAAC,CAAC,CAAC;IAEH,KAAK,CAAC,8BAA8B,EAAE,KAAK,IAAI,EAAE;QAC/C,MAAM,SAAS,CAAC,qBAAqB,CAAC,EAAE,CAAC,CAAC,CAAC,cAAc,CAAC,YAAY,CAAC,CAAC;IAC1E,CAAC,CAAC,CAAC;IAEH,KAAK,CAAC,yCAAyC,EAAE,KAAK,IAAI,EAAE;QAC1D,MAAM,SAAS,CAAC,qBAAqB,CAAC,EAAE,CAAC,CAAC,CAAC,cAAc,CAAC,YAAY,CAAC,CAAC;IAC1E,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}