capacity-attest 0.2.0 → 0.4.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 +161 -85
- package/dist/completeness-fixture.d.ts +35 -0
- package/dist/completeness-fixture.d.ts.map +1 -0
- package/dist/completeness-fixture.js +150 -0
- package/dist/completeness-fixture.js.map +1 -0
- package/dist/completeness.d.ts +56 -0
- package/dist/completeness.d.ts.map +1 -0
- package/dist/completeness.js +113 -0
- package/dist/completeness.js.map +1 -0
- 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 +53 -6
- 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 +98 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +154 -1
- package/dist/schema.js.map +1 -1
- package/dist/test-helpers.d.ts +1 -0
- package/dist/test-helpers.d.ts.map +1 -1
- package/dist/test-helpers.js +3 -0
- package/dist/test-helpers.js.map +1 -1
- package/dist/tools.d.ts +31 -4
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +25 -5
- package/dist/tools.js.map +1 -1
- package/package.json +56 -55
package/README.md
CHANGED
|
@@ -1,85 +1,161 @@
|
|
|
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
|
+
| `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](./DECISIONS.md) D-006 |
|
|
49
|
+
|
|
50
|
+
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.
|
|
51
|
+
|
|
52
|
+
### 2. `get_delivery_history`
|
|
53
|
+
|
|
54
|
+
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.
|
|
55
|
+
|
|
56
|
+
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.
|
|
57
|
+
|
|
58
|
+
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.
|
|
59
|
+
|
|
60
|
+
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:
|
|
61
|
+
|
|
62
|
+
```js
|
|
63
|
+
// recompute-completeness.mjs
|
|
64
|
+
import { verifyClaim } from "capacity-attest/dist/signing.js";
|
|
65
|
+
import { analyzeCompleteness } from "capacity-attest/dist/completeness.js";
|
|
66
|
+
|
|
67
|
+
// `claims` = de array uit het get_delivery_history-antwoord.
|
|
68
|
+
const allSigned = claims.every((c) => verifyClaim(c).ok); // elke claim echt?
|
|
69
|
+
const report = analyzeCompleteness(claims); // zelf herrekenen, niet het host-veld geloven
|
|
70
|
+
console.log({ allSigned, chainConsistent: report.chainConsistent, possibleOmissions: report.possibleOmissions });
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
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](./DECISIONS.md) D-006.
|
|
74
|
+
|
|
75
|
+
Een openbaar, zelf-controleerbaar voorbeeld met een nagebootste verbergende host en expres-kapotte testgevallen staat in [docs/COMPLETENESS-FIXTURE.md](./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.
|
|
76
|
+
|
|
77
|
+
### 3. `resolve_agent_identity` *(sinds 0.3.0)*
|
|
78
|
+
|
|
79
|
+
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.
|
|
80
|
+
|
|
81
|
+
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.
|
|
82
|
+
|
|
83
|
+
## Ondertekening
|
|
84
|
+
|
|
85
|
+
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.
|
|
86
|
+
|
|
87
|
+
## Een claim onafhankelijk verifiëren
|
|
88
|
+
|
|
89
|
+
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.
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
npm install capacity-attest@0.2.0
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
```js
|
|
96
|
+
// verify.mjs, als ES module draaien (top-level await)
|
|
97
|
+
import { verifyClaim } from "capacity-attest/dist/signing.js";
|
|
98
|
+
|
|
99
|
+
const claim = JSON.parse(await (await fetch("<url naar een claim.jsonl-regel>")).text());
|
|
100
|
+
console.log(verifyClaim(claim));
|
|
101
|
+
// { ok: true } als claimId echt de hash van de inhoud is EN signature echt naar buyerAddress terugrekent
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
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.
|
|
105
|
+
|
|
106
|
+
`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.
|
|
107
|
+
|
|
108
|
+
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.
|
|
109
|
+
|
|
110
|
+
## Je eigen ingediende claims delen, los van een host (D-006)
|
|
111
|
+
|
|
112
|
+
`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.
|
|
113
|
+
|
|
114
|
+
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.
|
|
115
|
+
|
|
116
|
+
```js
|
|
117
|
+
// export-my-claims.mjs
|
|
118
|
+
import { claimsForSeller } from "capacity-attest/dist/ledger.js";
|
|
119
|
+
|
|
120
|
+
const myAddress = "0x..."; // jouw buyerAddress
|
|
121
|
+
const seller = "0x..."; // de verkoper waar het over gaat
|
|
122
|
+
|
|
123
|
+
const mine = (await claimsForSeller(seller)).filter(
|
|
124
|
+
(c) => c.buyerAddress.toLowerCase() === myAddress.toLowerCase(),
|
|
125
|
+
);
|
|
126
|
+
console.log(JSON.stringify(mine, null, 2));
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
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.
|
|
130
|
+
|
|
131
|
+
## Lokaal draaien
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
npm install
|
|
135
|
+
npm run build # tsc -> dist/
|
|
136
|
+
npm run typecheck # tsc --noEmit
|
|
137
|
+
npm test # vitest run
|
|
138
|
+
npm run demo # end-to-end lokale demo met TEST-sleutels, geen live infra
|
|
139
|
+
npm start # start de MCP-server over stdio (bijv. voor Claude Desktop/Code als lokale MCP-server)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
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.
|
|
143
|
+
|
|
144
|
+
## Architectuur
|
|
145
|
+
|
|
146
|
+
```text
|
|
147
|
+
src/
|
|
148
|
+
schema.ts DeliveryClaim zod-schema + content-addressing (computeClaimId, canonicalize)
|
|
149
|
+
signing.ts sign/verify van een claim (ethers, EIP-191 personal-sign)
|
|
150
|
+
ledger.ts append-only JSONL-opslag (data/claims.jsonl), nooit muteerbaar
|
|
151
|
+
tools.ts de daadwerkelijke logica achter beide MCP-tools, transport-onafhankelijk
|
|
152
|
+
config.ts waar de ledger-map leeft, lazy zodat tests 'm kunnen overriden
|
|
153
|
+
index.ts MCP-server wiring (registreert record_delivery + get_delivery_history)
|
|
154
|
+
examples/demo.ts end-to-end lokaal voorbeeld met TEST-sleutels
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
`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.
|
|
158
|
+
|
|
159
|
+
## Relatie tot x402
|
|
160
|
+
|
|
161
|
+
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,35 @@
|
|
|
1
|
+
import { ethers } from "ethers";
|
|
2
|
+
import { type DeliveryClaim } from "./schema.js";
|
|
3
|
+
export declare const FIXTURE_BUYER: ethers.Wallet;
|
|
4
|
+
export declare const FIXTURE_SELLER = "0x00000000000000000000000000000000000000aa";
|
|
5
|
+
export interface Fixture {
|
|
6
|
+
/** The buyer's three real, signed claims about one seller, chained oldest->newest. */
|
|
7
|
+
c1: DeliveryClaim;
|
|
8
|
+
c2: DeliveryClaim;
|
|
9
|
+
c3: DeliveryClaim;
|
|
10
|
+
/** What an honest host returns: the full, ordered chain. */
|
|
11
|
+
honestView: DeliveryClaim[];
|
|
12
|
+
/** What a dishonest host returns: the negative middle claim (c2) silently dropped. */
|
|
13
|
+
dishonestView: DeliveryClaim[];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Build the fixture: three real, signed, chained claims from one buyer about
|
|
17
|
+
* one seller. Deterministic given the fixed TEST key above.
|
|
18
|
+
*/
|
|
19
|
+
export declare function buildFixture(): Promise<Fixture>;
|
|
20
|
+
export type ControlKind = "GREEN" | "RED";
|
|
21
|
+
export interface Control {
|
|
22
|
+
id: string;
|
|
23
|
+
kind: ControlKind;
|
|
24
|
+
what: string;
|
|
25
|
+
expected: string;
|
|
26
|
+
actual: string;
|
|
27
|
+
pass: boolean;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Run every control over the fixture and return the results. A caller (the
|
|
31
|
+
* example runner, or the CI test) decides what to do with them; this function
|
|
32
|
+
* itself never throws on a failed control, it just reports pass:false.
|
|
33
|
+
*/
|
|
34
|
+
export declare function runControls(): Promise<Control[]>;
|
|
35
|
+
//# sourceMappingURL=completeness-fixture.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"completeness-fixture.d.ts","sourceRoot":"","sources":["../src/completeness-fixture.ts"],"names":[],"mappings":"AAuBA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,OAAO,EAAqB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAMpE,eAAO,MAAM,aAAa,eAAsC,CAAC;AAGjE,eAAO,MAAM,cAAc,+CAA+C,CAAC;AAW3E,MAAM,WAAW,OAAO;IACtB,sFAAsF;IACtF,EAAE,EAAE,aAAa,CAAC;IAClB,EAAE,EAAE,aAAa,CAAC;IAClB,EAAE,EAAE,aAAa,CAAC;IAClB,4DAA4D;IAC5D,UAAU,EAAE,aAAa,EAAE,CAAC;IAC5B,sFAAsF;IACtF,aAAa,EAAE,aAAa,EAAE,CAAC;CAChC;AAED;;;GAGG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC,OAAO,CAAC,CAuCrD;AAED,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,KAAK,CAAC;AAE1C,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,WAAW,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,OAAO,CAAC;CACf;AAMD;;;;GAIG;AACH,wBAAsB,WAAW,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC,CAkFtD"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
// completeness-fixture.ts — a public, reproducible fixture proving what the
|
|
2
|
+
// per-buyer claim chain (priorClaimId) + analyzeCompleteness() actually
|
|
3
|
+
// detect, and, just as importantly, what they do NOT.
|
|
4
|
+
//
|
|
5
|
+
// Modeled on the transparency discipline goun7 published for Tamga Protocol
|
|
6
|
+
// (docs/PAIRING-FIXTURE.md): every value is labeled by its `source`, the
|
|
7
|
+
// scenario is regenerable from a fixed TEST key so a skeptic can rebuild it,
|
|
8
|
+
// and it ships explicit TAMPER NEGATIVES that MUST be rejected. The point is
|
|
9
|
+
// that nobody has to trust our prose: they run this and check the outcomes
|
|
10
|
+
// themselves.
|
|
11
|
+
//
|
|
12
|
+
// SOURCE LABELS used below (same idea as the Tamga fixture):
|
|
13
|
+
// simulated — a stand-in value, no real-world counterpart is claimed
|
|
14
|
+
// (e.g. settlementRef: no real x402 payment was made here).
|
|
15
|
+
// derived — computed deterministically from other fields (claimId, the
|
|
16
|
+
// chain links, the completeness report).
|
|
17
|
+
// observed — a real, reproducible output of this package's own code (the
|
|
18
|
+
// EIP-191 signatures, produced by the actual signClaim()).
|
|
19
|
+
//
|
|
20
|
+
// This module is pure and offline: no ledger, no network, no clock-dependence
|
|
21
|
+
// (timestamps are fixed literals). It is consumed by examples/completeness-
|
|
22
|
+
// fixture.ts (human-readable run) and src/completeness-fixture.test.ts (CI).
|
|
23
|
+
import { ethers } from "ethers";
|
|
24
|
+
import { signClaim, verifyClaim } from "./signing.js";
|
|
25
|
+
import { analyzeCompleteness } from "./completeness.js";
|
|
26
|
+
// A FIXED, well-known TEST key. Deliberately not a real identity: its whole
|
|
27
|
+
// job is to make this fixture deterministic and regenerable by anyone. source: simulated
|
|
28
|
+
const TEST_PRIVATE_KEY = "0x" + "a1".repeat(32);
|
|
29
|
+
export const FIXTURE_BUYER = new ethers.Wallet(TEST_PRIVATE_KEY);
|
|
30
|
+
// A fixed TEST seller address. source: simulated
|
|
31
|
+
export const FIXTURE_SELLER = "0x00000000000000000000000000000000000000aa";
|
|
32
|
+
// sha256 hex of some evidence bytes — shape only, no real artifact. source: simulated
|
|
33
|
+
const EVIDENCE_HASH = "a".repeat(64);
|
|
34
|
+
async function sign(content) {
|
|
35
|
+
// signature: observed (real EIP-191 output of signClaim); claimId: derived.
|
|
36
|
+
const { claimId, signature } = await signClaim(FIXTURE_BUYER, content);
|
|
37
|
+
return { ...content, claimId, signature };
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Build the fixture: three real, signed, chained claims from one buyer about
|
|
41
|
+
* one seller. Deterministic given the fixed TEST key above.
|
|
42
|
+
*/
|
|
43
|
+
export async function buildFixture() {
|
|
44
|
+
const base = {
|
|
45
|
+
sellerAddress: FIXTURE_SELLER,
|
|
46
|
+
buyerAddress: FIXTURE_BUYER.address,
|
|
47
|
+
assetType: "gpu-hours",
|
|
48
|
+
evidenceHash: EVIDENCE_HASH,
|
|
49
|
+
};
|
|
50
|
+
// c1 — genesis: no priorClaimId. source of scalar fields: simulated.
|
|
51
|
+
const c1 = await sign({
|
|
52
|
+
...base,
|
|
53
|
+
promisedSpec: "1x A100, 4 hours",
|
|
54
|
+
delivered: "yes",
|
|
55
|
+
settlementRef: "0x" + "01".repeat(32),
|
|
56
|
+
timestamp: "2026-01-01T00:00:00.000Z",
|
|
57
|
+
});
|
|
58
|
+
// c2 — the buyer's SECOND claim about this seller, and it is NEGATIVE. This
|
|
59
|
+
// is exactly the claim a seller-friendly host is tempted to hide. Its
|
|
60
|
+
// priorClaimId links it to c1. source: derived (link), simulated (rest).
|
|
61
|
+
const c2 = await sign({
|
|
62
|
+
...base,
|
|
63
|
+
promisedSpec: "1x A100, 8 hours",
|
|
64
|
+
delivered: "no",
|
|
65
|
+
settlementRef: "0x" + "02".repeat(32),
|
|
66
|
+
timestamp: "2026-02-01T00:00:00.000Z",
|
|
67
|
+
priorClaimId: c1.claimId,
|
|
68
|
+
});
|
|
69
|
+
// c3 — the buyer's THIRD claim, linked to c2. Because this link is signed,
|
|
70
|
+
// a host cannot show c3 while hiding c2 without leaving c3 pointing at a
|
|
71
|
+
// claim that is not in the response. source: derived (link), simulated (rest).
|
|
72
|
+
const c3 = await sign({
|
|
73
|
+
...base,
|
|
74
|
+
promisedSpec: "1x A100, 2 hours",
|
|
75
|
+
delivered: "yes",
|
|
76
|
+
settlementRef: "0x" + "03".repeat(32),
|
|
77
|
+
timestamp: "2026-03-01T00:00:00.000Z",
|
|
78
|
+
priorClaimId: c2.claimId,
|
|
79
|
+
});
|
|
80
|
+
return { c1, c2, c3, honestView: [c1, c2, c3], dishonestView: [c1, c3] };
|
|
81
|
+
}
|
|
82
|
+
function control(id, kind, what, expected, actual) {
|
|
83
|
+
return { id, kind, what, expected, actual, pass: expected === actual };
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Run every control over the fixture and return the results. A caller (the
|
|
87
|
+
* example runner, or the CI test) decides what to do with them; this function
|
|
88
|
+
* itself never throws on a failed control, it just reports pass:false.
|
|
89
|
+
*/
|
|
90
|
+
export async function runControls() {
|
|
91
|
+
const { c1, c2, c3, honestView, dishonestView } = await buildFixture();
|
|
92
|
+
const controls = [];
|
|
93
|
+
// --- GREEN: good-path properties that must hold ---
|
|
94
|
+
// G1: every claim in the honest view is individually authentic.
|
|
95
|
+
const honestAllVerify = honestView.every((c) => verifyClaim(c).ok);
|
|
96
|
+
controls.push(control("G1", "GREEN", "Every claim in the honest view verifies (claimId + signature)", "true", String(honestAllVerify)));
|
|
97
|
+
// G2: the honest, complete view has a consistent chain and zero omissions.
|
|
98
|
+
const honestReport = analyzeCompleteness(honestView);
|
|
99
|
+
controls.push(control("G2", "GREEN", "Honest full view: chainConsistent with no possibleOmissions", "consistent=true omissions=0", `consistent=${honestReport.chainConsistent} omissions=${honestReport.possibleOmissions.length}`));
|
|
100
|
+
// G3: KEY PROPERTY — under a dishonest host that hid c2, the shown claims
|
|
101
|
+
// STILL individually verify. Authenticity of what is shown is intact;
|
|
102
|
+
// that is exactly why omission (not forgery) is the real threat.
|
|
103
|
+
const dishonestAllVerify = dishonestView.every((c) => verifyClaim(c).ok);
|
|
104
|
+
controls.push(control("G3", "GREEN", "Under hiding, each shown claim still verifies (authenticity intact)", "true", String(dishonestAllVerify)));
|
|
105
|
+
// G4: DETECTION — recomputing analyzeCompleteness() locally over the
|
|
106
|
+
// dishonest view flags exactly one omission, pointing at the hidden c2.
|
|
107
|
+
const dishonestReport = analyzeCompleteness(dishonestView);
|
|
108
|
+
const flaggedC2 = dishonestReport.chainConsistent === false &&
|
|
109
|
+
dishonestReport.possibleOmissions.length === 1 &&
|
|
110
|
+
dishonestReport.possibleOmissions[0]?.missingPriorClaimId.toLowerCase() === c2.claimId.toLowerCase() &&
|
|
111
|
+
dishonestReport.possibleOmissions[0]?.referencedBy.toLowerCase() === c3.claimId.toLowerCase();
|
|
112
|
+
controls.push(control("G4", "GREEN", "Local recompute over hidden view flags exactly the missing c2", "true", String(flaggedC2)));
|
|
113
|
+
// G5: DON'T TRUST THE HOST'S FIELD — a host can return a rosy completeness
|
|
114
|
+
// field while hiding c2. Trusting it misses the omission; recomputing
|
|
115
|
+
// locally catches it. This control proves the two disagree.
|
|
116
|
+
const hostClaimedRosy = { chainConsistent: true, possibleOmissions: [], forks: [], note: "host says all good" };
|
|
117
|
+
const trustingHostWouldMiss = hostClaimedRosy.chainConsistent === true && dishonestReport.chainConsistent === false;
|
|
118
|
+
controls.push(control("G5", "GREEN", "A rosy host-supplied completeness field is contradicted by local recompute", "true", String(trustingHostWouldMiss)));
|
|
119
|
+
// --- RED: tampered inputs that MUST be rejected by verifyClaim() ---
|
|
120
|
+
// R1: flip c2's delivered byte (no -> yes) without recomputing claimId.
|
|
121
|
+
const flippedDelivery = { ...c2, delivered: "yes" };
|
|
122
|
+
controls.push(control("R1", "RED", "Flipped delivered byte (no->yes), claimId unchanged", "rejected", verifyClaim(flippedDelivery).ok ? "ACCEPTED" : "rejected"));
|
|
123
|
+
// R2: strip c3's priorClaimId but keep its signature — the exact "host tries
|
|
124
|
+
// to remove the chain link" attack. Must break claimId.
|
|
125
|
+
const strippedLink = { ...c3 };
|
|
126
|
+
delete strippedLink.priorClaimId;
|
|
127
|
+
controls.push(control("R2", "RED", "Stripped priorClaimId from c3, signature kept (host cannot remove the link)", "rejected", verifyClaim(strippedLink).ok ? "ACCEPTED" : "rejected"));
|
|
128
|
+
// R3: repoint c3's priorClaimId to a different claim, signature kept.
|
|
129
|
+
const forgedLink = { ...c3, priorClaimId: c1.claimId };
|
|
130
|
+
controls.push(control("R3", "RED", "Repointed c3.priorClaimId to c1, signature kept", "rejected", verifyClaim(forgedLink).ok ? "ACCEPTED" : "rejected"));
|
|
131
|
+
// R4: sign c2's content with a DIFFERENT wallet while still claiming
|
|
132
|
+
// buyerAddress = the fixture buyer. Signature must not recover to buyer.
|
|
133
|
+
const impostor = new ethers.Wallet("0x" + "b2".repeat(32));
|
|
134
|
+
const impostorContent = {
|
|
135
|
+
sellerAddress: FIXTURE_SELLER,
|
|
136
|
+
buyerAddress: FIXTURE_BUYER.address,
|
|
137
|
+
assetType: "gpu-hours",
|
|
138
|
+
promisedSpec: "1x A100, 8 hours",
|
|
139
|
+
delivered: "no",
|
|
140
|
+
evidenceHash: EVIDENCE_HASH,
|
|
141
|
+
settlementRef: "0x" + "02".repeat(32),
|
|
142
|
+
timestamp: "2026-02-01T00:00:00.000Z",
|
|
143
|
+
priorClaimId: c1.claimId,
|
|
144
|
+
};
|
|
145
|
+
const impostorSigned = await signClaim(impostor, impostorContent);
|
|
146
|
+
const impostorClaim = { ...impostorContent, ...impostorSigned };
|
|
147
|
+
controls.push(control("R4", "RED", "c2 signed by a different wallet, buyerAddress unchanged", "rejected", verifyClaim(impostorClaim).ok ? "ACCEPTED" : "rejected"));
|
|
148
|
+
return controls;
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=completeness-fixture.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"completeness-fixture.js","sourceRoot":"","sources":["../src/completeness-fixture.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { DeliveryClaim } from "./schema.js";
|
|
2
|
+
/** A shown claim whose priorClaimId points to a claim absent from the result. */
|
|
3
|
+
export interface CompletenessGap {
|
|
4
|
+
/** The buyer whose chain has the gap (lower-cased). */
|
|
5
|
+
buyerAddress: string;
|
|
6
|
+
/** The claimId that was referenced as a prior link but is not in the result. */
|
|
7
|
+
missingPriorClaimId: string;
|
|
8
|
+
/** The claimId of the shown claim that points back to the missing one. */
|
|
9
|
+
referencedBy: string;
|
|
10
|
+
}
|
|
11
|
+
/** A priorClaimId referenced by more than one shown claim from the same buyer. */
|
|
12
|
+
export interface CompletenessFork {
|
|
13
|
+
buyerAddress: string;
|
|
14
|
+
priorClaimId: string;
|
|
15
|
+
/** The claimIds of the shown claims that all point back to the same prior. */
|
|
16
|
+
claimIds: string[];
|
|
17
|
+
}
|
|
18
|
+
export interface CompletenessReport {
|
|
19
|
+
/**
|
|
20
|
+
* true only when every shown claim's priorClaimId resolves to another shown
|
|
21
|
+
* claim (no dangling back-references) AND no prior is referenced twice (no
|
|
22
|
+
* forks). false means the shown set is not a clean, complete view of every
|
|
23
|
+
* chain it contains: a middle claim is missing (possibleOmissions) OR a
|
|
24
|
+
* buyer forked their own chain (forks). Those two are different things,
|
|
25
|
+
* deliberately collapsed into one boolean only as a quick "all clear" flag,
|
|
26
|
+
* so ALWAYS read possibleOmissions vs forks to know which fired: a fork is a
|
|
27
|
+
* BUYER-side anomaly and says nothing about the host hiding anything.
|
|
28
|
+
* NOTE: true does NOT mean "you were shown everything" — a hidden tail or a
|
|
29
|
+
* hidden whole-buyer chain still passes. And this whole report is only
|
|
30
|
+
* trustworthy if YOU computed it; see this module's header. See D-006.
|
|
31
|
+
*/
|
|
32
|
+
chainConsistent: boolean;
|
|
33
|
+
/** Concrete "a claim may be hidden here" signals: a link points at a claim not shown. */
|
|
34
|
+
possibleOmissions: CompletenessGap[];
|
|
35
|
+
/** Forks: an honest single-writer chain never has two claims sharing one prior. */
|
|
36
|
+
forks: CompletenessFork[];
|
|
37
|
+
/** A fixed, factual sentence explaining what this report does and does not prove. */
|
|
38
|
+
note: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Analyze the per-buyer, per-seller claim chains in a set of claims (already
|
|
42
|
+
* scoped to one seller by getDeliveryHistory) for hidden-middle-claim and
|
|
43
|
+
* fork signals. Pure and offline: no network, no ledger access, operates only
|
|
44
|
+
* on the claims handed in. Safe on an empty set (reports consistent).
|
|
45
|
+
*
|
|
46
|
+
* ASSUMES its inputs are already signature-verified and de-duplicated (which
|
|
47
|
+
* is true for everything getDeliveryHistory feeds it: the read path in
|
|
48
|
+
* ledger.ts re-verifies every claim and appendClaim rejects duplicate
|
|
49
|
+
* claimIds). Because claimId is a content hash, a self-referencing or cyclic
|
|
50
|
+
* priorClaimId is cryptographically unconstructible and cross-buyer claimId
|
|
51
|
+
* collisions are impossible, so those degenerate shapes are not defended
|
|
52
|
+
* against here. If you ever call this on RAW, unverified claims, verify them
|
|
53
|
+
* first — this function trusts that claimId really is the content hash.
|
|
54
|
+
*/
|
|
55
|
+
export declare function analyzeCompleteness(claims: DeliveryClaim[]): CompletenessReport;
|
|
56
|
+
//# sourceMappingURL=completeness.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"completeness.d.ts","sourceRoot":"","sources":["../src/completeness.ts"],"names":[],"mappings":"AAgDA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,iFAAiF;AACjF,MAAM,WAAW,eAAe;IAC9B,uDAAuD;IACvD,YAAY,EAAE,MAAM,CAAC;IACrB,gFAAgF;IAChF,mBAAmB,EAAE,MAAM,CAAC;IAC5B,0EAA0E;IAC1E,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,kFAAkF;AAClF,MAAM,WAAW,gBAAgB;IAC/B,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,8EAA8E;IAC9E,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,kBAAkB;IACjC;;;;;;;;;;;;OAYG;IACH,eAAe,EAAE,OAAO,CAAC;IACzB,yFAAyF;IACzF,iBAAiB,EAAE,eAAe,EAAE,CAAC;IACrC,mFAAmF;IACnF,KAAK,EAAE,gBAAgB,EAAE,CAAC;IAC1B,qFAAqF;IACrF,IAAI,EAAE,MAAM,CAAC;CACd;AAeD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,kBAAkB,CAwC/E"}
|