capacity-attest 0.1.2 → 0.3.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.md CHANGED
@@ -1,85 +1,141 @@
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
- ## Waarom dit bestaat
8
-
9
- 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.
10
-
11
- `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.
12
-
13
- Geen oordeel. Geen reputatiescore. Geen "vonnis", puur een ondertekende bon-plus-claim, net zoals een afleverbon bij een fysieke levering.
14
-
15
- ## Wat dit NIET is
16
-
17
- Dit is bewust en hardcoded **niet**:
18
-
19
- - **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.
20
- - **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.
21
- - **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.
22
- - **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.
23
-
24
- 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.
25
-
26
- ## Hoe het werkt
27
-
28
- ### 1. `record_delivery`
29
-
30
- De betalende agent (de koper) roept dit aan **na** een x402-afwikkeling, zodra bekend is of het beloofde is aangekomen. De claim bevat:
31
-
32
- | Veld | Betekenis |
33
- | --- | --- |
34
- | `sellerAddress` | 0x-adres van de partij die betaald werd |
35
- | `buyerAddress` | 0x-adres van de betalende agent, moet overeenkomen met het adres dat uit `signature` wordt teruggerekend |
36
- | `assetType` | `gpu-hours` \| `storage` \| `api-credits` \| `bandwidth` |
37
- | `promisedSpec` | Wat er beloofd was: vrije tekst of een gestructureerd object |
38
- | `delivered` | `yes` \| `no` \| `partial` |
39
- | `evidenceHash` | sha256-hex van bewijsmateriaal (logs, response-payload, ...), het bewijs zelf wordt niet opgeslagen |
40
- | `settlementRef` | x402-payment-ref of on-chain tx-hash van de onderliggende betaling |
41
- | `timestamp` | ISO-8601 tijdstip |
42
- | `claimId` | content-addressed sha256-hash van alle velden hierboven, zie `computeClaimId()` in `src/schema.ts` |
43
- | `signature` | EIP-191 personal-sign handtekening van de koper over `claimId` |
44
-
45
- 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.
46
-
47
- ### 2. `get_delivery_history`
48
-
49
- Gegeven een `sellerAddress`, retourneert dit alle bekende claims tegen die verkoper, 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.
50
-
51
- ## Ondertekening
52
-
53
- 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.
54
-
55
- ## Lokaal draaien
56
-
57
- ```bash
58
- npm install
59
- npm run build # tsc -> dist/
60
- npm run typecheck # tsc --noEmit
61
- npm test # vitest run
62
- npm run demo # end-to-end lokale demo met TEST-sleutels, geen live infra
63
- npm start # start de MCP-server over stdio (bijv. voor Claude Desktop/Code als lokale MCP-server)
64
- ```
65
-
66
- 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.
67
-
68
- ## Architectuur
69
-
70
- ```
71
- src/
72
- schema.ts DeliveryClaim zod-schema + content-addressing (computeClaimId, canonicalize)
73
- signing.ts sign/verify van een claim (ethers, EIP-191 personal-sign)
74
- ledger.ts append-only JSONL-opslag (data/claims.jsonl), nooit muteerbaar
75
- tools.ts de daadwerkelijke logica achter beide MCP-tools, transport-onafhankelijk
76
- config.ts waar de ledger-map leeft, lazy zodat tests 'm kunnen overriden
77
- index.ts MCP-server wiring (registreert record_delivery + get_delivery_history)
78
- examples/demo.ts end-to-end lokaal voorbeeld met TEST-sleutels
79
- ```
80
-
81
- `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.
82
-
83
- ## Relatie tot x402
84
-
85
- 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).
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
+ ## Waarom dit bestaat
8
+
9
+ 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.
10
+
11
+ `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.
12
+
13
+ Geen oordeel. Geen reputatiescore. Geen "vonnis", puur een ondertekende bon-plus-claim, net zoals een afleverbon bij een fysieke levering.
14
+
15
+ ## Wat dit NIET is
16
+
17
+ Dit is bewust en hardcoded **niet**:
18
+
19
+ - **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.
20
+ - **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.
21
+ - **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.
22
+ - **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.
23
+ - **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](./DECISIONS.md) D-007 t/m D-013 voor waarom dit bewust geen eigen protocol is geworden.
24
+
25
+ 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.
26
+
27
+ Bewust uitgestelde features (verankering, tussentijdse status, formele conformance-vectoren), inclusief de precieze voorwaarde waaronder we ze alsnog zouden bouwen: zie [DECISIONS.md](./DECISIONS.md).
28
+
29
+ ## Hoe het werkt
30
+
31
+ ### 1. `record_delivery`
32
+
33
+ De betalende agent (de koper) roept dit aan **na** een x402-afwikkeling, zodra bekend is of het beloofde is aangekomen. De claim bevat:
34
+
35
+ | Veld | Betekenis |
36
+ | --- | --- |
37
+ | `sellerAddress` | 0x-adres van de partij die betaald werd |
38
+ | `buyerAddress` | 0x-adres van de betalende agent, moet overeenkomen met het adres dat uit `signature` wordt teruggerekend |
39
+ | `assetType` | `gpu-hours` \| `storage` \| `api-credits` \| `bandwidth` |
40
+ | `promisedSpec` | Wat er beloofd was: vrije tekst of een gestructureerd object |
41
+ | `delivered` | `yes` \| `no` \| `partial` |
42
+ | `evidenceHash` | sha256-hex van bewijsmateriaal (logs, response-payload, ...), het bewijs zelf wordt niet opgeslagen |
43
+ | `settlementRef` | x402-payment-ref of on-chain tx-hash van de onderliggende betaling |
44
+ | `timestamp` | ISO-8601 tijdstip |
45
+ | `claimId` | content-addressed sha256-hash van alle velden hierboven, zie `computeClaimId()` in `src/schema.ts` |
46
+ | `signature` | EIP-191 personal-sign handtekening van de koper over `claimId` |
47
+ | `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](./DECISIONS.md) D-007 t/m D-013 |
48
+
49
+ 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.
50
+
51
+ ### 2. `get_delivery_history`
52
+
53
+ 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.
54
+
55
+ 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](./DECISIONS.md) (D-005) voor de bredere architectuurvraag hierachter: hoe vindt een koper claims die op een ándere installatie zijn vastgelegd.
56
+
57
+ ### 3. `resolve_agent_identity` *(sinds 0.3.0)*
58
+
59
+ 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.
60
+
61
+ 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](./DECISIONS.md) D-007 voor de volledige achtergrond.
62
+
63
+ ## Ondertekening
64
+
65
+ 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.
66
+
67
+ ## Een claim onafhankelijk verifiëren
68
+
69
+ 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.
70
+
71
+ ```bash
72
+ npm install capacity-attest@0.2.0
73
+ ```
74
+
75
+ ```js
76
+ // verify.mjs, als ES module draaien (top-level await)
77
+ import { verifyClaim } from "capacity-attest/dist/signing.js";
78
+
79
+ const claim = JSON.parse(await (await fetch("<url naar een claim.jsonl-regel>")).text());
80
+ console.log(verifyClaim(claim));
81
+ // { ok: true } als claimId echt de hash van de inhoud is EN signature echt naar buyerAddress terugrekent
82
+ ```
83
+
84
+ 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.
85
+
86
+ `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.
87
+
88
+ 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.
89
+
90
+ ## Je eigen ingediende claims delen, los van een host (D-006)
91
+
92
+ `get_delivery_history` vertrouwt op de eerlijkheid van wie de MCP-server bedient: zie de `note` in dat tool-antwoord en [DECISIONS.md](./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.
93
+
94
+ 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.
95
+
96
+ ```js
97
+ // export-my-claims.mjs
98
+ import { claimsForSeller } from "capacity-attest/dist/ledger.js";
99
+
100
+ const myAddress = "0x..."; // jouw buyerAddress
101
+ const seller = "0x..."; // de verkoper waar het over gaat
102
+
103
+ const mine = (await claimsForSeller(seller)).filter(
104
+ (c) => c.buyerAddress.toLowerCase() === myAddress.toLowerCase(),
105
+ );
106
+ console.log(JSON.stringify(mine, null, 2));
107
+ ```
108
+
109
+ 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.
110
+
111
+ ## Lokaal draaien
112
+
113
+ ```bash
114
+ npm install
115
+ npm run build # tsc -> dist/
116
+ npm run typecheck # tsc --noEmit
117
+ npm test # vitest run
118
+ npm run demo # end-to-end lokale demo met TEST-sleutels, geen live infra
119
+ npm start # start de MCP-server over stdio (bijv. voor Claude Desktop/Code als lokale MCP-server)
120
+ ```
121
+
122
+ 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.
123
+
124
+ ## Architectuur
125
+
126
+ ```text
127
+ src/
128
+ schema.ts DeliveryClaim zod-schema + content-addressing (computeClaimId, canonicalize)
129
+ signing.ts sign/verify van een claim (ethers, EIP-191 personal-sign)
130
+ ledger.ts append-only JSONL-opslag (data/claims.jsonl), nooit muteerbaar
131
+ tools.ts de daadwerkelijke logica achter beide MCP-tools, transport-onafhankelijk
132
+ config.ts waar de ledger-map leeft, lazy zodat tests 'm kunnen overriden
133
+ index.ts MCP-server wiring (registreert record_delivery + get_delivery_history)
134
+ examples/demo.ts end-to-end lokaal voorbeeld met TEST-sleutels
135
+ ```
136
+
137
+ `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.
138
+
139
+ ## Relatie tot x402
140
+
141
+ 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).
@@ -0,0 +1,39 @@
1
+ import * as z from "zod/v4";
2
+ export declare const ResolveAgentIdentityInputSchema: z.ZodObject<{
3
+ agentRegistryRef: z.ZodString;
4
+ agentId: z.ZodString;
5
+ rpcUrl: z.ZodString;
6
+ }, z.core.$strip>;
7
+ export type ResolveAgentIdentityInput = z.infer<typeof ResolveAgentIdentityInputSchema>;
8
+ export type ResolveAgentIdentityResult = {
9
+ ok: true;
10
+ chainId: string;
11
+ registryAddress: string;
12
+ agentId: string;
13
+ owner: string;
14
+ tokenUri: string;
15
+ } | {
16
+ ok: false;
17
+ reason: string;
18
+ };
19
+ /** The minimal read surface this module needs — real ethers.Contract satisfies this shape structurally. */
20
+ export interface Erc721ReadContract {
21
+ ownerOf(agentId: bigint): Promise<string>;
22
+ tokenURI(agentId: bigint): Promise<string>;
23
+ }
24
+ /**
25
+ * Dependency-injection seam so tests can supply a fake contract instead of
26
+ * making a real network call, without needing to fake ethers' own ABI
27
+ * encoding/decoding. Defaults to a real ethers Contract over a real
28
+ * JsonRpcProvider.
29
+ */
30
+ export type ContractFactory = (registryAddress: string, rpcUrl: string) => Erc721ReadContract;
31
+ /**
32
+ * Read-only lookup against an ERC-8004 Identity Registry: who owns
33
+ * `agentId` (`ownerOf`) and where its registration file lives (`tokenURI`).
34
+ * Never resolves, verifies, or trusts the content the returned `tokenUri`
35
+ * points to — see file header. This is the logic behind the
36
+ * `resolve_agent_identity` MCP tool.
37
+ */
38
+ export declare function resolveAgentIdentity(input: ResolveAgentIdentityInput, contractFactory?: ContractFactory): Promise<ResolveAgentIdentityResult>;
39
+ //# sourceMappingURL=erc8004.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"erc8004.d.ts","sourceRoot":"","sources":["../src/erc8004.ts"],"names":[],"mappings":"AAiCA,OAAO,KAAK,CAAC,MAAM,QAAQ,CAAC;AA2B5B,eAAO,MAAM,+BAA+B;;;;iBAM1C,CAAC;AACH,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,+BAA+B,CAAC,CAAC;AAExF,MAAM,MAAM,0BAA0B,GAClC;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GACxG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAwBlC,2GAA2G;AAC3G,MAAM,WAAW,kBAAkB;IACjC,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC1C,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAC5C;AAED;;;;;GAKG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,eAAe,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,kBAAkB,CAAC;AAO9F;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,yBAAyB,EAChC,eAAe,GAAE,eAAwC,GACxD,OAAO,CAAC,0BAA0B,CAAC,CA8BrC"}
@@ -0,0 +1,122 @@
1
+ // erc8004.ts — read-only resolution against an ERC-8004 Identity Registry.
2
+ //
3
+ // ERC-8004 ("Trustless Agents") defines its Identity Registry as a standard
4
+ // ERC-721 contract: an agent's on-chain identity IS an NFT, `agentId` IS the
5
+ // tokenId. There is no bespoke `getAgent()` function in the spec — lookup is
6
+ // exactly `ownerOf(agentId)` (who controls this identity) and
7
+ // `tokenURI(agentId)` (a pointer to the agent's registration file: an IPFS/
8
+ // HTTPS/data URI, per eips.ethereum.org/EIPS/eip-8004, confirmed 2026-09-06
9
+ // against the spec text directly). This module calls only those two
10
+ // standard, universal ERC-721 read functions — nothing ERC-8004-specific.
11
+ //
12
+ // Deliberately NOT done here: fetching or parsing what `tokenURI` points to.
13
+ // That string is caller-controlled data embedded in on-chain state by
14
+ // whoever registered the agent, and auto-fetching it (an ipfs:// gateway
15
+ // call, or a fetch to an arbitrary https:// URL surfaced from someone
16
+ // else's on-chain write) is a real SSRF-shaped risk this project does not
17
+ // need to take on for a read-only identity check. Callers get the raw
18
+ // tokenUri string back and decide for themselves whether and how to fetch
19
+ // it — same "cite, never resolve further" posture as externalRefs
20
+ // (DECISIONS.md D-007).
21
+ //
22
+ // Deliberately NOT hardcoded here: any specific registry contract address
23
+ // or chain RPC endpoint. ERC-8004 is deployed independently on multiple
24
+ // chains (mainnet, Base, and others per the EIP's own multi-chain design),
25
+ // and the EIP text itself lists no canonical deployment address anywhere.
26
+ // Guessing one would risk querying the wrong contract and returning
27
+ // misleading data. Both the registry reference and the RPC endpoint are
28
+ // therefore required, caller-supplied inputs — this module is a thin,
29
+ // chain-agnostic resolver, never an authority on which deployment is "the"
30
+ // one, and never a source of its own RPC credentials for a published
31
+ // open-source package to leak or rate-limit on other people's behalf.
32
+ import { ethers } from "ethers";
33
+ import * as z from "zod/v4";
34
+ // The EIP's own compound reference format: "{namespace}:{chainId}:
35
+ // {identityRegistry}", namespace fixed to "eip155" for EVM chains
36
+ // (CAIP-2/CAIP-10-style). Captures chainId and the registry contract
37
+ // address separately; the address half accepts either case, normalized to
38
+ // lower case in the result, same convention as sellerAddress/buyerAddress
39
+ // in schema.ts.
40
+ const AGENT_REGISTRY_REF_RE = /^eip155:(\d+):(0x[0-9a-fA-F]{40})$/;
41
+ // ERC-721's two standard read functions. This is the ENTIRE interface this
42
+ // module depends on — see the file header for why nothing ERC-8004-specific
43
+ // is needed.
44
+ const ERC721_READ_ABI = ["function ownerOf(uint256 tokenId) view returns (address)", "function tokenURI(uint256 tokenId) view returns (string)"];
45
+ // uint256's maximum value (2^256 - 1) has exactly 78 decimal digits. A
46
+ // longer digit string can never be a valid ERC-721 tokenId; bounding the
47
+ // length before BigInt() parses it avoids handing an unboundedly large
48
+ // digit string to the runtime's bignum parser.
49
+ const MAX_AGENT_ID_DIGITS = 78;
50
+ const CALL_TIMEOUT_MS = 8_000;
51
+ // Mirrors the plain-TS validation below in isValidAgentId/isHttpUrl at the
52
+ // MCP tool-input boundary (index.ts's inputSchema), so a caller gets a
53
+ // schema-shaped rejection before resolveAgentIdentity() is even called.
54
+ // The runtime function still re-checks everything itself, since it is
55
+ // also callable directly (tests, examples), not only through the MCP tool.
56
+ export const ResolveAgentIdentityInputSchema = z.object({
57
+ agentRegistryRef: z
58
+ .string()
59
+ .describe('Compound ERC-8004 registry reference: "eip155:<chainId>:<registryAddress>", e.g. "eip155:1:0x1234567890123456789012345678901234567890"'),
60
+ agentId: z.string().describe("The ERC-721 tokenId / ERC-8004 agentId, as a decimal string"),
61
+ rpcUrl: z.string().describe("JSON-RPC endpoint (http:// or https://) for the chain named in agentRegistryRef — never assumed or defaulted"),
62
+ });
63
+ function isValidAgentId(v) {
64
+ return v.length <= MAX_AGENT_ID_DIGITS && /^(0|[1-9][0-9]*)$/.test(v);
65
+ }
66
+ function isHttpUrl(v) {
67
+ try {
68
+ const protocol = new URL(v).protocol;
69
+ return protocol === "http:" || protocol === "https:";
70
+ }
71
+ catch {
72
+ return false;
73
+ }
74
+ }
75
+ function withTimeout(p, ms) {
76
+ return Promise.race([
77
+ p,
78
+ new Promise((_, reject) => {
79
+ setTimeout(() => reject(new Error(`timed out after ${ms}ms`)), ms);
80
+ }),
81
+ ]);
82
+ }
83
+ const defaultContractFactory = (registryAddress, rpcUrl) => {
84
+ const provider = new ethers.JsonRpcProvider(rpcUrl);
85
+ return new ethers.Contract(registryAddress, ERC721_READ_ABI, provider);
86
+ };
87
+ /**
88
+ * Read-only lookup against an ERC-8004 Identity Registry: who owns
89
+ * `agentId` (`ownerOf`) and where its registration file lives (`tokenURI`).
90
+ * Never resolves, verifies, or trusts the content the returned `tokenUri`
91
+ * points to — see file header. This is the logic behind the
92
+ * `resolve_agent_identity` MCP tool.
93
+ */
94
+ export async function resolveAgentIdentity(input, contractFactory = defaultContractFactory) {
95
+ const match = AGENT_REGISTRY_REF_RE.exec(input.agentRegistryRef);
96
+ if (!match) {
97
+ return {
98
+ ok: false,
99
+ reason: 'agentRegistryRef must match "eip155:<chainId>:<registryAddress>", e.g. "eip155:1:0x1234567890123456789012345678901234567890"',
100
+ };
101
+ }
102
+ const chainId = match[1];
103
+ const registryAddress = match[2];
104
+ if (!isValidAgentId(input.agentId)) {
105
+ return { ok: false, reason: "agentId must be a non-negative decimal integer string (no leading zeros, no sign, at most 78 digits)" };
106
+ }
107
+ if (!isHttpUrl(input.rpcUrl)) {
108
+ return { ok: false, reason: "rpcUrl must be an http:// or https:// URL" };
109
+ }
110
+ let owner;
111
+ let tokenUri;
112
+ try {
113
+ const contract = contractFactory(registryAddress, input.rpcUrl);
114
+ const agentIdBig = BigInt(input.agentId);
115
+ [owner, tokenUri] = await withTimeout(Promise.all([contract.ownerOf(agentIdBig), contract.tokenURI(agentIdBig)]), CALL_TIMEOUT_MS);
116
+ }
117
+ catch (e) {
118
+ return { ok: false, reason: `on-chain lookup failed: ${e.message}` };
119
+ }
120
+ return { ok: true, chainId, registryAddress: registryAddress.toLowerCase(), agentId: input.agentId, owner, tokenUri };
121
+ }
122
+ //# sourceMappingURL=erc8004.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"erc8004.js","sourceRoot":"","sources":["../src/erc8004.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AASA,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAA2C,MAAM,aAAa,CAAC;AAwDrG,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AASA,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAA2C,MAAM,aAAa,CAAC;AAkGrG,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,CAAC"}
package/dist/index.js CHANGED
@@ -7,6 +7,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
7
7
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
8
8
  import { ASSET_TYPES, DELIVERED_VALUES, ClaimContentSchema, DeliveryClaimSchema } from "./schema.js";
9
9
  import { recordDelivery, getDeliveryHistory } from "./tools.js";
10
+ import { resolveAgentIdentity, ResolveAgentIdentityInputSchema } from "./erc8004.js";
10
11
  const server = new McpServer({
11
12
  name: "capacity-attest",
12
13
  version: "0.1.0",
@@ -34,15 +35,52 @@ server.registerTool("record_delivery", {
34
35
  });
35
36
  server.registerTool("get_delivery_history", {
36
37
  title: "Get a seller's delivery history",
37
- description: "Return every known, signature-verified delivery claim recorded against a given sellerAddress, oldest first. " +
38
- "Purely factual — no aggregate score, rating, or reputation judgment is computed. A buying agent can call this " +
39
- "BEFORE paying a seller to see that seller's raw delivery history for gpu-hours, storage, api-credits, and " +
40
- "bandwidth claims.",
38
+ description: "Return every signature-verified delivery claim recorded against a given sellerAddress in THIS installation's " +
39
+ "local ledger, oldest first. Purely factual — no aggregate score, rating, or reputation judgment is computed. " +
40
+ "IMPORTANT, two separate caveats, both repeated in the response's own `note` field: (1) this is scoped to the " +
41
+ "local ledger only — a different installation may hold other claims against the same seller that this call " +
42
+ "cannot see, so an empty or short result does NOT mean the seller has a clean record elsewhere, only that no " +
43
+ "claims have been recorded here; (2) even within this installation, a shown claim's signature is genuinely " +
44
+ "verified, but nothing proves this is the COMPLETE set of claims the operator actually holds — completeness " +
45
+ "depends on the operator's honesty, not cryptography. " +
46
+ "A buying agent can call this BEFORE paying a seller to see that seller's raw delivery history for gpu-hours, " +
47
+ "storage, api-credits, and bandwidth claims.",
41
48
  inputSchema: {
42
49
  sellerAddress: ClaimContentSchema.shape.sellerAddress,
43
50
  },
44
51
  }, async ({ sellerAddress }) => {
45
- return textResult(await getDeliveryHistory(sellerAddress));
52
+ // NINTH FIX (2026-09-06, found by the same adversarial audit as the
53
+ // ledger.ts SEVENTH/EIGHTH fixes): every other handler/entry point in
54
+ // this codebase (record_delivery below, recordDelivery() in tools.ts)
55
+ // is deliberately wrapped so untrusted-input-triggered errors degrade to
56
+ // the tool's normal {ok:false}/errorResult contract instead of an
57
+ // uncaught exception — this handler was the one place that discipline
58
+ // was not applied. A real fs error surfaced from getFreshCache()/
59
+ // resyncFromDisk() (e.g. a transient EACCES/EBUSY, deliberately made
60
+ // non-swallowed by an earlier fix in ledger.ts) would otherwise escape
61
+ // this handler unhandled instead of returning a normal error result.
62
+ try {
63
+ return textResult(await getDeliveryHistory(sellerAddress));
64
+ }
65
+ catch (e) {
66
+ return errorResult(e.message);
67
+ }
68
+ });
69
+ server.registerTool("resolve_agent_identity", {
70
+ title: "Resolve an ERC-8004 agent identity (read-only, on-chain)",
71
+ description: "Look up an agent's on-chain identity registration in an ERC-8004 Identity Registry: who owns the agentId " +
72
+ "(ownerOf) and where its registration file lives (tokenURI). Read-only — never writes anything, never touches " +
73
+ "the delivery ledger. Requires the caller to supply BOTH the registry reference (chain + contract address) AND " +
74
+ "an RPC endpoint for that chain: this tool does not assume, default, or bundle its own RPC provider or a " +
75
+ "canonical registry address, since ERC-8004 has independent deployments on multiple chains. This tool does NOT " +
76
+ "fetch or parse what tokenURI points to — it returns that pointer as-is for the caller to resolve themselves. " +
77
+ "capacity-attest never verifies or endorses what an ERC-8004 registration claims; see DECISIONS.md D-007.",
78
+ inputSchema: ResolveAgentIdentityInputSchema.shape,
79
+ }, async (args) => {
80
+ const result = await resolveAgentIdentity(args);
81
+ if (!result.ok)
82
+ return errorResult(result.reason);
83
+ return textResult(result);
46
84
  });
47
85
  // Re-exported so callers embedding this package can reference the same enums
48
86
  // / schema the tools validate against without duplicating them.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,yEAAyE;AACzE,2EAA2E;AAC3E,oDAAoD;AACpD,uDAAuD;AAEvD,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAEjF,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AACrG,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAEhE,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;IAC3B,IAAI,EAAE,iBAAiB;IACvB,OAAO,EAAE,OAAO;CACjB,CAAC,CAAC;AAEH,SAAS,UAAU,CAAC,KAAc;IAChC,MAAM,IAAI,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAChF,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;AAC/C,CAAC;AAED,SAAS,WAAW,CAAC,OAAe;IAClC,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,OAAO,EAAE,EAAE,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AACnF,CAAC;AAED,MAAM,CAAC,YAAY,CACjB,iBAAiB,EACjB;IACE,KAAK,EAAE,yBAAyB;IAChC,WAAW,EACT,oHAAoH;QACpH,qGAAqG;QACrG,4GAA4G;QAC5G,2GAA2G;QAC3G,4BAA4B;IAC9B,WAAW,EAAE,mBAAmB,CAAC,KAAK;CACvC,EACD,KAAK,EAAE,IAAI,EAAE,EAAE;IACb,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,IAAI,CAAC,CAAC;IAC1C,IAAI,CAAC,MAAM,CAAC,EAAE;QAAE,OAAO,WAAW,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAClD,OAAO,UAAU,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC;AAC3D,CAAC,CACF,CAAC;AAEF,MAAM,CAAC,YAAY,CACjB,sBAAsB,EACtB;IACE,KAAK,EAAE,iCAAiC;IACxC,WAAW,EACT,8GAA8G;QAC9G,gHAAgH;QAChH,4GAA4G;QAC5G,mBAAmB;IACrB,WAAW,EAAE;QACX,aAAa,EAAE,kBAAkB,CAAC,KAAK,CAAC,aAAa;KACtD;CACF,EACD,KAAK,EAAE,EAAE,aAAa,EAAE,EAAE,EAAE;IAC1B,OAAO,UAAU,CAAC,MAAM,kBAAkB,CAAC,aAAa,CAAC,CAAC,CAAC;AAC7D,CAAC,CACF,CAAC;AAEF,6EAA6E;AAC7E,gEAAgE;AAChE,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,CAAC;AAEzC,KAAK,UAAU,IAAI;IACjB,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE;IACjB,OAAO,CAAC,KAAK,CAAC,6CAA6C,EAAE,CAAC,CAAC,CAAC;IAChE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"ledger.d.ts","sourceRoot":"","sources":["../src/ledger.ts"],"names":[],"mappings":"AAYA,OAAO,EAAuB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAoftE;;;GAGG;AACH,wBAAsB,WAAW,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,CA0D9E;AAED,4DAA4D;AAC5D,wBAAsB,eAAe,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAQrF;AAED,mFAAmF;AACnF,wBAAsB,SAAS,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC,CAG1D"}
1
+ {"version":3,"file":"ledger.d.ts","sourceRoot":"","sources":["../src/ledger.ts"],"names":[],"mappings":"AAYA,OAAO,EAAuB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AA0hBtE;;;GAGG;AACH,wBAAsB,WAAW,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,CA0D9E;AAED,4DAA4D;AAC5D,wBAAsB,eAAe,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAQrF;AAED,mFAAmF;AACnF,wBAAsB,SAAS,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC,CAG1D"}
package/dist/ledger.js CHANGED
@@ -10,6 +10,7 @@ import { readFileSync, appendFileSync, openSync, closeSync, unlinkSync, statSync
10
10
  import { join } from "node:path";
11
11
  import { dataDir, ensureDataDir } from "./config.js";
12
12
  import { DeliveryClaimSchema } from "./schema.js";
13
+ import { verifyClaim } from "./signing.js";
13
14
  function claimsFile() {
14
15
  return join(dataDir(), "claims.jsonl");
15
16
  }
@@ -100,7 +101,18 @@ async function acquireLock(path) {
100
101
  continue;
101
102
  }
102
103
  if (Date.now() > deadline) {
103
- throw new Error(`ledger_lock_timeout: could not acquire lock at ${path}`);
104
+ // EIGHTH FIX (2026-09-06, found by the same adversarial audit as the
105
+ // SEVENTH FIX above): this used to interpolate the full resolved
106
+ // `path` (== CAPACITY_ATTEST_DATA_DIR + "/claims.jsonl.lock") into the
107
+ // thrown message. That propagates unchanged through appendClaim() ->
108
+ // recordDelivery()'s `{ok:false, reason: e.message}` -> the MCP
109
+ // tool's error text (index.ts's errorResult) — a caller who simply
110
+ // triggers ordinary lock contention (trivial: a few concurrent
111
+ // record_delivery calls) gets the server's absolute filesystem path
112
+ // handed back, which on a real deployment can reveal the install
113
+ // location and, on Windows, the operating user's name. No caller
114
+ // needs that path to understand or retry the failure.
115
+ throw new Error("ledger_lock_timeout: could not acquire the ledger lock in time");
104
116
  }
105
117
  // Non-blocking backoff: await a real timer instead of spinning, so the
106
118
  // event loop stays free to process other work (timers, other
@@ -387,8 +399,34 @@ async function resyncFromDisk(file) {
387
399
  const line = lines[i];
388
400
  try {
389
401
  const claim = DeliveryClaimSchema.parse(JSON.parse(line));
390
- decorated.push({ claim, ts: timestampMs(claim) });
391
- claimIdSet.add(claim.claimId.toLowerCase());
402
+ // SEVENTH FIX (2026-09-06, found by an adversarial audit): until this
403
+ // fix, a line only had to match DeliveryClaimSchema's SHAPE (claimId a
404
+ // 0x+64-hex string, signature a 0x+130-hex string — see schema.ts's
405
+ // claimIdField/signatureField, both plain regexes) to be accepted into
406
+ // the cache and served back out through get_delivery_history. Neither
407
+ // claimId (does it really hash the claim's own content?) nor signature
408
+ // (does it really recover to buyerAddress?) was ever recomputed on
409
+ // this READ path — only appendClaim()'s caller (recordDelivery() in
410
+ // tools.ts) verifies before writing. That is airtight for claims that
411
+ // only ever arrive via record_delivery, but this ledger's own header
412
+ // comment documents a deployment shape this package is explicitly
413
+ // built for — multiple OS processes sharing one
414
+ // CAPACITY_ATTEST_DATA_DIR — where nothing stops a second writer (or a
415
+ // corrupted/hand-edited file) from appending a well-shaped but entirely
416
+ // fabricated line directly to claims.jsonl, bypassing recordDelivery()
417
+ // and its verifyClaim() gate completely. Before this fix such a line
418
+ // would resync into the cache and get returned as if genuine,
419
+ // contradicting both get_delivery_history's own tool description
420
+ // ("every SIGNATURE-VERIFIED delivery claim") and DECISIONS.md D-006's
421
+ // reasoning, which assumed authenticity of every shown claim was never
422
+ // in question — it was, on this exact path. Fix: reuse the same
423
+ // verifyClaim() the write path already trusts, and treat a claim that
424
+ // fails it exactly like a corrupt/truncated line below — silently
425
+ // excluded, ledger stays readable, no new error surface.
426
+ if (verifyClaim(claim).ok) {
427
+ decorated.push({ claim, ts: timestampMs(claim) });
428
+ claimIdSet.add(claim.claimId.toLowerCase());
429
+ }
392
430
  }
393
431
  catch {
394
432
  // A corrupt/partial line (e.g. a truncated write) must not take down