capacity-attest 0.1.0 → 0.1.1

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
@@ -2,26 +2,26 @@
2
2
 
3
3
  MCP-server voor leverings-attestaties bij x402-capaciteitshandel tussen AI-agents.
4
4
 
5
- > Status: MVP, **nog niet gepubliceerd** (`"private": true` in `package.json`). Structuur is publicatie-klaar (zie `server.json`), maar er is bewust nog geen `npm publish` of `git push` naar een remote gedaan.
5
+ > Status: MVP, gepubliceerd op npm (`npm install capacity-attest`) en in het officiële MCP-register (`io.github.holistis/capacity-attest`).
6
6
 
7
7
  ## Waarom dit bestaat
8
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.
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
10
 
11
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
12
 
13
- Geen oordeel. Geen reputatiescore. Geen "vonnis" puur een ondertekende bon-plus-claim, net zoals een afleverbon bij een fysieke levering.
13
+ Geen oordeel. Geen reputatiescore. Geen "vonnis", puur een ondertekende bon-plus-claim, net zoals een afleverbon bij een fysieke levering.
14
14
 
15
15
  ## Wat dit NIET is
16
16
 
17
17
  Dit is bewust en hardcoded **niet**:
18
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.
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
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.
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
23
 
24
- Dit is een sharia-getoetste (halal) 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.
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
25
 
26
26
  ## Hoe het werkt
27
27
 
@@ -32,25 +32,25 @@ De betalende agent (de koper) roept dit aan **na** een x402-afwikkeling, zodra b
32
32
  | Veld | Betekenis |
33
33
  | --- | --- |
34
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 |
35
+ | `buyerAddress` | 0x-adres van de betalende agent, moet overeenkomen met het adres dat uit `signature` wordt teruggerekend |
36
36
  | `assetType` | `gpu-hours` \| `storage` \| `api-credits` \| `bandwidth` |
37
- | `promisedSpec` | Wat er beloofd was vrije tekst of een gestructureerd object |
37
+ | `promisedSpec` | Wat er beloofd was: vrije tekst of een gestructureerd object |
38
38
  | `delivered` | `yes` \| `no` \| `partial` |
39
- | `evidenceHash` | sha256-hex van bewijsmateriaal (logs, response-payload, ...) het bewijs zelf wordt niet opgeslagen |
39
+ | `evidenceHash` | sha256-hex van bewijsmateriaal (logs, response-payload, ...), het bewijs zelf wordt niet opgeslagen |
40
40
  | `settlementRef` | x402-payment-ref of on-chain tx-hash van de onderliggende betaling |
41
41
  | `timestamp` | ISO-8601 tijdstip |
42
- | `claimId` | content-addressed sha256-hash van alle velden hierboven zie `computeClaimId()` in `src/schema.ts` |
42
+ | `claimId` | content-addressed sha256-hash van alle velden hierboven, zie `computeClaimId()` in `src/schema.ts` |
43
43
  | `signature` | EIP-191 personal-sign handtekening van de koper over `claimId` |
44
44
 
45
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
46
 
47
47
  ### 2. `get_delivery_history`
48
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.
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
50
 
51
51
  ## Ondertekening
52
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.
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
54
 
55
55
  ## Lokaal draaien
56
56
 
@@ -63,7 +63,7 @@ npm run demo # end-to-end lokale demo met TEST-sleutels, geen live infra
63
63
  npm start # start de MCP-server over stdio (bijv. voor Claude Desktop/Code als lokale MCP-server)
64
64
  ```
65
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.
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
67
 
68
68
  ## Architectuur
69
69
 
@@ -82,4 +82,4 @@ examples/demo.ts end-to-end lokaal voorbeeld met TEST-sleutels
82
82
 
83
83
  ## Relatie tot x402
84
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).
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 +1 @@
1
- {"version":3,"file":"ledger.d.ts","sourceRoot":"","sources":["../src/ledger.ts"],"names":[],"mappings":"AAYA,OAAO,EAAuB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAuBtE;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,aAAa,GAAG,aAAa,CAQ/D;AAED,4DAA4D;AAC5D,wBAAgB,eAAe,CAAC,aAAa,EAAE,MAAM,GAAG,aAAa,EAAE,CAItE;AAED,mFAAmF;AACnF,wBAAgB,SAAS,IAAI,aAAa,EAAE,CAE3C"}
1
+ {"version":3,"file":"ledger.d.ts","sourceRoot":"","sources":["../src/ledger.ts"],"names":[],"mappings":"AAYA,OAAO,EAAuB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAqFtE;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,aAAa,GAAG,aAAa,CAc/D;AAED,4DAA4D;AAC5D,wBAAgB,eAAe,CAAC,aAAa,EAAE,MAAM,GAAG,aAAa,EAAE,CAItE;AAED,mFAAmF;AACnF,wBAAgB,SAAS,IAAI,aAAa,EAAE,CAE3C"}
package/dist/ledger.js CHANGED
@@ -6,13 +6,75 @@
6
6
  // history. A second claim with the same content-addressed claimId (i.e. an
7
7
  // attempt to re-record the exact same claim) is rejected rather than
8
8
  // silently duplicated.
9
- import { existsSync, readFileSync, appendFileSync } from "node:fs";
9
+ import { existsSync, readFileSync, appendFileSync, openSync, closeSync, unlinkSync, statSync } from "node:fs";
10
10
  import { join } from "node:path";
11
11
  import { dataDir, ensureDataDir } from "./config.js";
12
12
  import { DeliveryClaimSchema } from "./schema.js";
13
13
  function claimsFile() {
14
14
  return join(dataDir(), "claims.jsonl");
15
15
  }
16
+ // A concurrency benchmark (bench/ledger-concurrency.mjs) found that
17
+ // appendClaim()'s original read-then-check-then-write sequence was a real,
18
+ // reproducible TOCTOU race: with separate OS processes sharing one
19
+ // CAPACITY_ATTEST_DATA_DIR (exactly the multi-agent deployment this ledger
20
+ // exists for), 3 of 5 runs at N=8 concurrent submissions of the SAME claim
21
+ // produced actual duplicate lines in claims.jsonl — the ledger's one
22
+ // documented guarantee, broken 60% of the time under realistic load.
23
+ //
24
+ // Fix: a lockfile-based mutex around the whole read+check+append sequence,
25
+ // using O_EXCL ("wx") as the atomic primitive — only one process can
26
+ // successfully create the lock file at a time, so the race window closes.
27
+ // This module is deliberately fully synchronous throughout (see the header
28
+ // comment above), so the wait loop is a bounded synchronous retry rather
29
+ // than an async queue.
30
+ const LOCK_RETRY_DELAY_MS = 5;
31
+ const LOCK_TIMEOUT_MS = 5_000;
32
+ // If a process crashes while holding the lock, the lock file would
33
+ // otherwise block every future write forever. Treat a lock older than this
34
+ // as abandoned and reclaim it.
35
+ const STALE_LOCK_MS = 30_000;
36
+ function lockFile() {
37
+ return claimsFile() + ".lock";
38
+ }
39
+ function acquireLock(path) {
40
+ const deadline = Date.now() + LOCK_TIMEOUT_MS;
41
+ for (;;) {
42
+ try {
43
+ closeSync(openSync(path, "wx"));
44
+ return;
45
+ }
46
+ catch (e) {
47
+ if (e.code !== "EEXIST")
48
+ throw e;
49
+ try {
50
+ if (Date.now() - statSync(path).mtimeMs > STALE_LOCK_MS) {
51
+ unlinkSync(path);
52
+ continue;
53
+ }
54
+ }
55
+ catch {
56
+ // Lock file vanished between our failed open() and this stat/unlink
57
+ // — another process released it. Just retry the open immediately.
58
+ continue;
59
+ }
60
+ if (Date.now() > deadline) {
61
+ throw new Error(`ledger_lock_timeout: could not acquire lock at ${path}`);
62
+ }
63
+ const until = Date.now() + LOCK_RETRY_DELAY_MS;
64
+ while (Date.now() < until) {
65
+ /* bounded synchronous backoff */
66
+ }
67
+ }
68
+ }
69
+ }
70
+ function releaseLock(path) {
71
+ try {
72
+ unlinkSync(path);
73
+ }
74
+ catch {
75
+ // Already gone (e.g. reclaimed as stale by another process) — fine.
76
+ }
77
+ }
16
78
  function readClaims() {
17
79
  const file = claimsFile();
18
80
  if (!existsSync(file))
@@ -36,13 +98,20 @@ function readClaims() {
36
98
  * the same claimId was already recorded.
37
99
  */
38
100
  export function appendClaim(claim) {
39
- const existing = readClaims();
40
- if (existing.some((c) => c.claimId.toLowerCase() === claim.claimId.toLowerCase())) {
41
- throw new Error(`claim_already_recorded: ${claim.claimId}`);
42
- }
43
101
  ensureDataDir();
44
- appendFileSync(claimsFile(), JSON.stringify(claim) + "\n");
45
- return claim;
102
+ const lock = lockFile();
103
+ acquireLock(lock);
104
+ try {
105
+ const existing = readClaims();
106
+ if (existing.some((c) => c.claimId.toLowerCase() === claim.claimId.toLowerCase())) {
107
+ throw new Error(`claim_already_recorded: ${claim.claimId}`);
108
+ }
109
+ appendFileSync(claimsFile(), JSON.stringify(claim) + "\n");
110
+ return claim;
111
+ }
112
+ finally {
113
+ releaseLock(lock);
114
+ }
46
115
  }
47
116
  /** All claims recorded against one seller, oldest first. */
48
117
  export function claimsForSeller(sellerAddress) {
@@ -1 +1 @@
1
- {"version":3,"file":"ledger.js","sourceRoot":"","sources":["../src/ledger.ts"],"names":[],"mappings":"AAAA,6DAA6D;AAC7D,EAAE;AACF,4EAA4E;AAC5E,qEAAqE;AACrE,yEAAyE;AACzE,2EAA2E;AAC3E,qEAAqE;AACrE,uBAAuB;AAEvB,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AACnE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,mBAAmB,EAAsB,MAAM,aAAa,CAAC;AAEtE,SAAS,UAAU;IACjB,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,cAAc,CAAC,CAAC;AACzC,CAAC;AAED,SAAS,UAAU;IACjB,MAAM,IAAI,GAAG,UAAU,EAAE,CAAC;IAC1B,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,CAAC;IACjC,OAAO,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC;SAC9B,KAAK,CAAC,IAAI,CAAC;SACX,MAAM,CAAC,OAAO,CAAC;SACf,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE;QAChB,IAAI,CAAC;YACH,OAAO,CAAC,mBAAmB,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACvD,CAAC;QAAC,MAAM,CAAC;YACP,qEAAqE;YACrE,mCAAmC;YACnC,OAAO,EAAE,CAAC;QACZ,CAAC;IACH,CAAC,CAAC,CAAC;AACP,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,KAAoB;IAC9C,MAAM,QAAQ,GAAG,UAAU,EAAE,CAAC;IAC9B,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,KAAK,KAAK,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC;QAClF,MAAM,IAAI,KAAK,CAAC,2BAA2B,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;IAC9D,CAAC;IACD,aAAa,EAAE,CAAC;IAChB,cAAc,CAAC,UAAU,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;IAC3D,OAAO,KAAK,CAAC;AACf,CAAC;AAED,4DAA4D;AAC5D,MAAM,UAAU,eAAe,CAAC,aAAqB;IACnD,OAAO,UAAU,EAAE;SAChB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,WAAW,EAAE,KAAK,aAAa,CAAC,WAAW,EAAE,CAAC;SAC5E,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;AACvE,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,SAAS;IACvB,OAAO,UAAU,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;AACxF,CAAC"}
1
+ {"version":3,"file":"ledger.js","sourceRoot":"","sources":["../src/ledger.ts"],"names":[],"mappings":"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"}
package/dist/schema.d.ts CHANGED
@@ -3,14 +3,9 @@ export declare const ASSET_TYPES: readonly ["gpu-hours", "storage", "api-credits
3
3
  export type AssetType = (typeof ASSET_TYPES)[number];
4
4
  export declare const DELIVERED_VALUES: readonly ["yes", "no", "partial"];
5
5
  export type Delivered = (typeof DELIVERED_VALUES)[number];
6
- /**
7
- * The part of a claim that is hashed to produce claimId and that gets
8
- * signed. Everything in here is chosen by the buyer at claim-creation time;
9
- * claimId and signature (in DeliveryClaimSchema below) are derived from it.
10
- */
11
6
  export declare const ClaimContentSchema: z.ZodObject<{
12
- sellerAddress: z.ZodString;
13
- buyerAddress: z.ZodString;
7
+ sellerAddress: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
8
+ buyerAddress: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
14
9
  assetType: z.ZodEnum<{
15
10
  "gpu-hours": "gpu-hours";
16
11
  storage: "storage";
@@ -30,8 +25,8 @@ export declare const ClaimContentSchema: z.ZodObject<{
30
25
  export type ClaimContent = z.infer<typeof ClaimContentSchema>;
31
26
  /** The full, stored, signed claim — ClaimContent plus its content-address and signature. */
32
27
  export declare const DeliveryClaimSchema: z.ZodObject<{
33
- sellerAddress: z.ZodString;
34
- buyerAddress: z.ZodString;
28
+ sellerAddress: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
29
+ buyerAddress: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
35
30
  assetType: z.ZodEnum<{
36
31
  "gpu-hours": "gpu-hours";
37
32
  storage: "storage";
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,CAAC,MAAM,QAAQ,CAAC;AAG5B,eAAO,MAAM,WAAW,+DAAgE,CAAC;AACzF,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC;AAErD,eAAO,MAAM,gBAAgB,mCAAoC,CAAC;AAClE,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AAS1D;;;;GAIG;AACH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;iBA0B7B,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D,4FAA4F;AAC5F,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;iBAS9B,CAAC;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEhE;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAEnD;AAeD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,CAI5D"}
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,CAAC,MAAM,QAAQ,CAAC;AAG5B,eAAO,MAAM,WAAW,+DAAgE,CAAC;AACzF,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC;AAErD,eAAO,MAAM,gBAAgB,mCAAoC,CAAC;AAClE,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC;AAoD1D,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;iBA6C7B,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D,4FAA4F;AAC5F,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;iBAS9B,CAAC;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEhE;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAEnD;AA+BD;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,YAAY,GAAG,MAAM,CAI5D"}
package/dist/schema.js CHANGED
@@ -5,11 +5,11 @@
5
5
  // actually arrived." It is deliberately NOT a score, rating, or judgment —
6
6
  // see README.md "Wat dit NIET is".
7
7
  //
8
- // GUARDRAIL (halal): assetType is a closed enum of physical/compute capacity
9
- // kinds. Do not add anything that looks like a financial instrument (credit,
10
- // loan, yield, interest-bearing balance, invoice-financing) to this file —
11
- // see the "HALAL GUARDRAILS" section of the project brief. If a field or enum
12
- // value even smells like that, leave it out.
8
+ // GUARDRAIL (design boundary): assetType is a closed enum of physical/compute
9
+ // capacity kinds. Do not add anything that looks like a financial instrument
10
+ // (credit, loan, yield, interest-bearing balance, invoice-financing) to this
11
+ // file — see the "DESIGN GUARDRAILS" section of the project brief. If a field
12
+ // or enum value even smells like that, leave it out.
13
13
  import * as z from "zod/v4";
14
14
  import { createHash } from "node:crypto";
15
15
  export const ASSET_TYPES = ["gpu-hours", "storage", "api-credits", "bandwidth"];
@@ -25,18 +25,76 @@ const SIGNATURE_RE = /^0x[0-9a-fA-F]{130}$/;
25
25
  * signed. Everything in here is chosen by the buyer at claim-creation time;
26
26
  * claimId and signature (in DeliveryClaimSchema below) are derived from it.
27
27
  */
28
+ // Guardrails against two verified bugs (2026-08-31 adversarial audit):
29
+ // - MAX_PROMISED_SPEC_STRING_LENGTH / MAX_SETTLEMENT_REF_LENGTH: without a
30
+ // bound, a multi-megabyte string was accepted in ~6ms, letting any caller
31
+ // grow the append-only ledger and every future read's memory footprint
32
+ // without limit.
33
+ // - MAX_PROMISED_SPEC_JSON_BYTES: the object branch of promisedSpec has no
34
+ // own length check, so it needs an explicit serialized-size cap instead.
35
+ const MAX_PROMISED_SPEC_STRING_LENGTH = 4_000;
36
+ const MAX_SETTLEMENT_REF_LENGTH = 512;
37
+ const MAX_PROMISED_SPEC_JSON_BYTES = 8_000;
38
+ // Shared with sortKeysDeep() below: how deep promisedSpec may nest.
39
+ const MAX_DEPTH = 32;
40
+ // Iterative (non-recursive) depth check for the object branch of
41
+ // promisedSpec, used at schema-parse time. This deliberately does NOT use
42
+ // recursion, and deliberately runs BEFORE any JSON.stringify of the raw
43
+ // input: an earlier version of this guard called JSON.stringify(v) directly
44
+ // inside the refine below to enforce MAX_PROMISED_SPEC_JSON_BYTES, and that
45
+ // call is itself a recursive walk that blew the call stack on the exact
46
+ // pathologically-deep input it was meant to reject (caught by this
47
+ // package's own fuzz tests). Checking depth first, with an explicit stack
48
+ // instead of language-level recursion, closes that gap for good.
49
+ function exceedsMaxDepth(value, maxDepth) {
50
+ const stack = [{ v: value, d: 0 }];
51
+ while (stack.length > 0) {
52
+ const { v, d } = stack.pop();
53
+ if (d > maxDepth)
54
+ return true;
55
+ if (Array.isArray(v)) {
56
+ for (const item of v)
57
+ stack.push({ v: item, d: d + 1 });
58
+ }
59
+ else if (v !== null && typeof v === "object") {
60
+ for (const key of Object.keys(v)) {
61
+ stack.push({ v: v[key], d: d + 1 });
62
+ }
63
+ }
64
+ }
65
+ return false;
66
+ }
28
67
  export const ClaimContentSchema = z.object({
68
+ // .transform(toLowerCase): a fuzz-and-benchmark audit found that
69
+ // computeClaimId() hashed addresses exactly as submitted while every other
70
+ // address comparison in this codebase (signature recovery, seller
71
+ // filtering) was already case-insensitive. That let the same real claim be
72
+ // resubmitted under a different claimId by only re-casing a hex letter,
73
+ // defeating appendClaim()'s claimId-based duplicate rejection. Normalizing
74
+ // here, once, before anything is hashed or compared, closes that gap for
75
+ // every consumer of this schema.
29
76
  sellerAddress: z
30
77
  .string()
31
78
  .regex(ETH_ADDRESS_RE, "sellerAddress must be a 0x-prefixed 20-byte address")
79
+ .transform((v) => v.toLowerCase())
32
80
  .describe("0x address of the agent/service that was paid and was supposed to deliver"),
33
81
  buyerAddress: z
34
82
  .string()
35
83
  .regex(ETH_ADDRESS_RE, "buyerAddress must be a 0x-prefixed 20-byte address")
84
+ .transform((v) => v.toLowerCase())
36
85
  .describe("0x address of the paying agent — must match the address recovered from `signature`"),
37
86
  assetType: z.enum(ASSET_TYPES).describe("What kind of capacity this claim is about"),
38
87
  promisedSpec: z
39
- .union([z.string().min(1), z.record(z.string(), z.unknown())])
88
+ .union([
89
+ z.string().min(1).max(MAX_PROMISED_SPEC_STRING_LENGTH),
90
+ z.record(z.string(), z.unknown()).refine((v) => {
91
+ if (exceedsMaxDepth(v, MAX_DEPTH))
92
+ return false;
93
+ // Only safe to JSON.stringify for the size check once depth is
94
+ // bounded — see exceedsMaxDepth's comment above.
95
+ return Buffer.byteLength(JSON.stringify(v), "utf8") <= MAX_PROMISED_SPEC_JSON_BYTES;
96
+ }, `promisedSpec object exceeds the allowed nesting depth (${MAX_DEPTH}) or size (${MAX_PROMISED_SPEC_JSON_BYTES} bytes)`),
97
+ ])
40
98
  .describe("What the seller promised to deliver — free text or a structured object"),
41
99
  delivered: z.enum(DELIVERED_VALUES).describe("Whether what was promised actually arrived"),
42
100
  evidenceHash: z
@@ -46,6 +104,7 @@ export const ClaimContentSchema = z.object({
46
104
  settlementRef: z
47
105
  .string()
48
106
  .min(1, "settlementRef is required (x402 payment reference or on-chain tx hash)")
107
+ .max(MAX_SETTLEMENT_REF_LENGTH)
49
108
  .describe("x402 payment reference or on-chain tx hash for the settlement this claim is about"),
50
109
  timestamp: z
51
110
  .string()
@@ -70,16 +129,32 @@ export const DeliveryClaimSchema = ClaimContentSchema.extend({
70
129
  * sorted.
71
130
  */
72
131
  export function canonicalize(value) {
73
- return JSON.stringify(sortKeysDeep(value));
132
+ return JSON.stringify(sortKeysDeep(value, 0));
74
133
  }
75
- function sortKeysDeep(value) {
134
+ // A fuzz audit found that a promisedSpec nested a few thousand levels deep
135
+ // (no valid signature required — this runs before signature verification)
136
+ // crashes the process with an uncaught RangeError instead of the normal
137
+ // {ok:false, reason} response every other invalid claim gets. MAX_DEPTH
138
+ // (declared above, shared with the schema-level guard on promisedSpec)
139
+ // turns that crash into a clean, catchable Error well before the engine's
140
+ // own call-stack limit, however it's shaped (objects or arrays) — kept here
141
+ // too as defense-in-depth in case computeClaimId is ever called on content
142
+ // that bypassed schema validation.
143
+ function sortKeysDeep(value, depth) {
144
+ if (depth > MAX_DEPTH) {
145
+ throw new Error(`promisedSpec nesting exceeds max depth of ${MAX_DEPTH}`);
146
+ }
76
147
  if (Array.isArray(value))
77
- return value.map(sortKeysDeep);
148
+ return value.map((v) => sortKeysDeep(v, depth + 1));
78
149
  if (value !== null && typeof value === "object") {
79
150
  const input = value;
80
- const out = {};
151
+ // Object.create(null) has no inherited `__proto__` accessor, so a key
152
+ // literally named "__proto__" becomes a normal own property instead of
153
+ // silently reassigning out's prototype (and vanishing from the hashed
154
+ // output) the way `out["__proto__"] = ...` would on a plain {} object.
155
+ const out = Object.create(null);
81
156
  for (const key of Object.keys(input).sort()) {
82
- out[key] = sortKeysDeep(input[key]);
157
+ out[key] = sortKeysDeep(input[key], depth + 1);
83
158
  }
84
159
  return out;
85
160
  }
@@ -1 +1 @@
1
- {"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAMA,OAAO,EAAuB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAItE,MAAM,MAAM,oBAAoB,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjG;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,oBAAoB,CAmBnE;AAED,MAAM,WAAW,qBAAqB;IACpC,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,aAAa,EAAE,CAAC;CACzB;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,aAAa,EAAE,MAAM,GAAG,qBAAqB,CAG/E"}
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAMA,OAAO,EAAuB,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAItE,MAAM,MAAM,oBAAoB,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjG;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,oBAAoB,CA4BnE;AAED,MAAM,WAAW,qBAAqB;IACpC,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,aAAa,EAAE,CAAC;CACzB;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,aAAa,EAAE,MAAM,GAAG,qBAAqB,CAG/E"}
package/dist/tools.js CHANGED
@@ -17,7 +17,17 @@ export function recordDelivery(input) {
17
17
  return { ok: false, reason: `invalid_claim: ${parsed.error.issues.map((i) => i.message).join("; ")}` };
18
18
  }
19
19
  const claim = parsed.data;
20
- const verdict = verifyClaim(claim);
20
+ let verdict;
21
+ try {
22
+ verdict = verifyClaim(claim);
23
+ }
24
+ catch (e) {
25
+ // computeClaimId() (called inside verifyClaim, before any signature is
26
+ // even checked) can throw on pathological input, e.g. promisedSpec
27
+ // nested past schema.ts's MAX_DEPTH. That must degrade to the tool's
28
+ // normal error contract, not an uncaught exception.
29
+ return { ok: false, reason: `invalid_claim: ${e.message}` };
30
+ }
21
31
  if (!verdict.ok) {
22
32
  return { ok: false, reason: `signature_invalid: ${verdict.reason}` };
23
33
  }
package/dist/tools.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"tools.js","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,uEAAuE;AACvE,yEAAyE;AACzE,0EAA0E;AAC1E,4BAA4B;AAE5B,OAAO,EAAE,mBAAmB,EAAsB,MAAM,aAAa,CAAC;AACtE,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAI3D;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,KAAc;IAC3C,MAAM,MAAM,GAAG,mBAAmB,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IACpD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,kBAAkB,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;IACzG,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC;IAC1B,MAAM,OAAO,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;IACnC,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC;QAChB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,sBAAsB,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;IACvE,CAAC;IAED,IAAI,CAAC;QACH,WAAW,CAAC,KAAK,CAAC,CAAC;IACrB,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAG,CAAW,CAAC,OAAO,EAAE,CAAC;IACrD,CAAC;IAED,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC;AAC9C,CAAC;AAQD;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,aAAqB;IACtD,MAAM,MAAM,GAAG,eAAe,CAAC,aAAa,CAAC,CAAC;IAC9C,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC;AACzD,CAAC"}
1
+ {"version":3,"file":"tools.js","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,uEAAuE;AACvE,yEAAyE;AACzE,0EAA0E;AAC1E,4BAA4B;AAE5B,OAAO,EAAE,mBAAmB,EAAsB,MAAM,aAAa,CAAC;AACtE,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAI3D;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAAC,KAAc;IAC3C,MAAM,MAAM,GAAG,mBAAmB,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IACpD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,kBAAkB,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;IACzG,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC;IAC1B,IAAI,OAAuC,CAAC;IAC5C,IAAI,CAAC;QACH,OAAO,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;IAC/B,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,uEAAuE;QACvE,mEAAmE;QACnE,qEAAqE;QACrE,oDAAoD;QACpD,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,kBAAmB,CAAW,CAAC,OAAO,EAAE,EAAE,CAAC;IACzE,CAAC;IACD,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC;QAChB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,sBAAsB,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;IACvE,CAAC;IAED,IAAI,CAAC;QACH,WAAW,CAAC,KAAK,CAAC,CAAC;IACrB,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAG,CAAW,CAAC,OAAO,EAAE,CAAC;IACrD,CAAC;IAED,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC;AAC9C,CAAC;AAQD;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,aAAqB;IACtD,MAAM,MAAM,GAAG,eAAe,CAAC,aAAa,CAAC,CAAC;IAC9C,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC;AACzD,CAAC"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "capacity-attest",
3
- "version": "0.1.0",
4
- "description": "MCP server for capacity-delivery attestations: after an x402 payment for compute capacity (GPU-hours, storage, API credits, bandwidth), the paying agent leaves a signed, factual claim of what was actually delivered so other agents can check a seller's delivery history before paying. No reputation score, no judgment, no financial instrument.",
3
+ "version": "0.1.1",
4
+ "description": "MCP server for capacity-delivery attestations: after an x402 payment for compute capacity (GPU-hours, storage, API credits, bandwidth), the paying agent leaves a signed, factual claim of what was actually delivered, so other agents can check a seller's delivery history before paying. No reputation score, no judgment, no financial instrument.",
5
5
  "keywords": [
6
6
  "mcp",
7
7
  "model-context-protocol",
@@ -18,14 +18,14 @@
18
18
  "homepage": "https://tokenizen.nl",
19
19
  "repository": {
20
20
  "type": "git",
21
- "url": "https://github.com/holistis/tokenizen.git",
21
+ "url": "git+https://github.com/holistis/tokenizen.git",
22
22
  "directory": "packages/capacity-attest"
23
23
  },
24
24
  "mcpName": "io.github.holistis/capacity-attest",
25
25
  "type": "module",
26
26
  "main": "./dist/index.js",
27
27
  "bin": {
28
- "capacity-attest": "./dist/index.js"
28
+ "capacity-attest": "dist/index.js"
29
29
  },
30
30
  "files": [
31
31
  "dist",
@@ -50,6 +50,6 @@
50
50
  "@types/node": "^22.10.5",
51
51
  "tsx": "^4.19.2",
52
52
  "typescript": "^5.7.3",
53
- "vitest": "^2.1.8"
53
+ "vitest": "^4.1.11"
54
54
  }
55
55
  }