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 +141 -85
- package/dist/erc8004.d.ts +39 -0
- package/dist/erc8004.d.ts.map +1 -0
- package/dist/erc8004.js +122 -0
- package/dist/erc8004.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +43 -5
- package/dist/index.js.map +1 -1
- package/dist/ledger.d.ts.map +1 -1
- package/dist/ledger.js +41 -3
- package/dist/ledger.js.map +1 -1
- package/dist/schema.d.ts +417 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +589 -13
- package/dist/schema.js.map +1 -1
- package/dist/tools.d.ts +29 -4
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +38 -7
- package/dist/tools.js.map +1 -1
- package/package.json +55 -55
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
|
36
|
-
|
|
|
37
|
-
| `
|
|
38
|
-
| `
|
|
39
|
-
| `
|
|
40
|
-
| `
|
|
41
|
-
| `
|
|
42
|
-
| `
|
|
43
|
-
| `
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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"}
|
package/dist/erc8004.js
ADDED
|
@@ -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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"}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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;
|
|
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
|
|
38
|
-
"Purely factual — no aggregate score, rating, or reputation judgment is computed.
|
|
39
|
-
"
|
|
40
|
-
"
|
|
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
|
-
|
|
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;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/ledger.d.ts.map
CHANGED
|
@@ -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;
|
|
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
|
-
|
|
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
|
-
|
|
391
|
-
|
|
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
|