@agenttrust-sdk/mcp 0.2.6 → 0.3.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.
Files changed (49) hide show
  1. package/dist/embedded-data/devnet-smoke.json +4 -4
  2. package/dist/embedded-docs/architecture.mdx +174 -0
  3. package/dist/embedded-docs/getting-started/quickstart.mdx +79 -56
  4. package/dist/embedded-docs/index.mdx +54 -37
  5. package/dist/embedded-docs/integration-guides/capability-namespaces.mdx +135 -8
  6. package/dist/embedded-docs/integration-guides/custom-attestor.mdx +169 -8
  7. package/dist/embedded-docs/integration-guides/dexter-adapter.mdx +76 -0
  8. package/dist/embedded-docs/integration-guides/facilitator-adapters.mdx +85 -41
  9. package/dist/embedded-docs/integration-guides/pay-sh-adapter.mdx +90 -54
  10. package/dist/embedded-docs/integration-guides/x402-facilitator.mdx +55 -24
  11. package/dist/embedded-docs/mcp/hosted-endpoint.mdx +197 -0
  12. package/dist/embedded-docs/mcp/index.mdx +108 -0
  13. package/dist/embedded-docs/mcp/install.mdx +183 -0
  14. package/dist/embedded-docs/mcp/prompts.mdx +90 -0
  15. package/dist/embedded-docs/mcp/resources.mdx +115 -0
  16. package/dist/embedded-docs/mcp/tools.mdx +156 -0
  17. package/dist/embedded-docs/programs/policy-vault/composer.mdx +117 -0
  18. package/dist/embedded-docs/programs/policy-vault/counterparty-tier-policy.mdx +81 -9
  19. package/dist/embedded-docs/programs/policy-vault/index.mdx +77 -47
  20. package/dist/embedded-docs/programs/policy-vault/kill-switch-policy.mdx +65 -8
  21. package/dist/embedded-docs/programs/policy-vault/require-validation-policy.mdx +76 -8
  22. package/dist/embedded-docs/programs/policy-vault/spending-policy.mdx +83 -8
  23. package/dist/embedded-docs/programs/policy-vault/velocity-policy.mdx +85 -8
  24. package/dist/embedded-docs/programs/trustgate.mdx +112 -30
  25. package/dist/embedded-docs/programs/validation-registry.mdx +139 -32
  26. package/dist/embedded-docs/reference/byte-offset-reference.mdx +102 -13
  27. package/dist/embedded-docs/reference/capability-namespaces.mdx +56 -0
  28. package/dist/embedded-docs/reference/changelog.mdx +230 -13
  29. package/dist/embedded-docs/reference/deny-reason-codes.mdx +86 -0
  30. package/dist/embedded-docs/reference/devnet-program-ids.mdx +50 -8
  31. package/dist/embedded-docs/reference/discriminator-constants.mdx +104 -10
  32. package/dist/embedded-docs/reference/mainnet-program-ids.mdx +89 -5
  33. package/dist/embedded-docs/reference/quantu-agent-registry.mdx +104 -9
  34. package/dist/embedded-docs/sdk/exports-reference.mdx +239 -0
  35. package/dist/embedded-docs/sdk/gate-payment.mdx +99 -14
  36. package/dist/embedded-docs/sdk/index.mdx +141 -40
  37. package/dist/embedded-docs/sdk/mount-trustgate.mdx +178 -8
  38. package/dist/embedded-docs/verification/adversarial-harness.mdx +88 -0
  39. package/dist/embedded-docs/verification/atomic-tx-invariant.mdx +141 -0
  40. package/dist/embedded-docs/verification/chained-validation.mdx +87 -0
  41. package/dist/embedded-docs/verification/devnet-smoke.mdx +85 -0
  42. package/dist/embedded-docs/verification/index.mdx +31 -0
  43. package/dist/embedded-docs/verification/kani-proofs.mdx +144 -0
  44. package/dist/embedded-docs/verification/live-evidence.mdx +180 -0
  45. package/dist/tools/write/emit-feedback.d.ts +6 -0
  46. package/dist/tools/write/emit-feedback.js +12 -1
  47. package/dist/tools/write/emit-feedback.js.map +1 -1
  48. package/package.json +16 -15
  49. package/scripts/install-claude-desktop.sh +0 -0
@@ -1,73 +1,174 @@
1
1
  ---
2
2
  title: "@agenttrust-sdk/trustgate"
3
- description: TypeScript client helpers, Express middleware, and atomicity guard.
3
+ description: TypeScript surface for AgentTrust on Solana — Express middleware, client helpers, atomicity guard, PDA derivers, and the full ValidationRegistry instruction builder set.
4
4
  ---
5
5
 
6
- The SDK is the facilitator-facing TypeScript package. The server adapter layer currently carries the concrete Pay.sh integration and the demo path; the package API remains the smaller client / Express surface for apps that call AgentTrust directly.
6
+ [`@agenttrust-sdk/trustgate@0.2.0`](https://www.npmjs.com/package/@agenttrust-sdk/trustgate) is the TypeScript package every facilitator pulls in. Express middleware, client helpers for direct calls, the atomicity guard, PDA derivers + Anchor loaders for all three programs, and instruction builders for ValidationRegistry's full lifecycle.
7
+
8
+ Source: [`trustgate/sdk/`](https://github.com/agenttrust-labs/agenttrust/tree/main/trustgate/sdk). License: MIT.
9
+
10
+ ## Install
7
11
 
8
12
  ```bash
9
13
  pnpm add @agenttrust-sdk/trustgate
14
+ # or: npm install @agenttrust-sdk/trustgate
15
+ # or: yarn add @agenttrust-sdk/trustgate
10
16
  ```
11
17
 
12
- ## Imports
18
+ Peer dep: `express ^4.21` (only required if you mount the middleware). Node `>=20`.
19
+
20
+ ## Three import surfaces
13
21
 
14
22
  ```ts
15
- import { mountTrustGate } from "@agenttrust-sdk/trustgate/express";
16
- import { gatePayment, settle, dispute } from "@agenttrust-sdk/trustgate/client";
17
- import {
18
- AtomicityNotEnforcedError,
19
- DEFAULT_DEVNET_PROGRAM_IDS,
20
- derivePolicyPda,
21
- } from "@agenttrust-sdk/trustgate";
23
+ import { mountTrustGate } from "@agenttrust-sdk/trustgate/express";
24
+ import { gatePayment, settle, dispute } from "@agenttrust-sdk/trustgate/client";
25
+ import { composeAtomicSettleTx } from "@agenttrust-sdk/trustgate";
22
26
  ```
23
27
 
24
- ## gatePayment
28
+ The root namespace re-exports every helper grouped by concern — atomicity guard, PDA derivers, Anchor loaders, Quantu helpers, ValidationRegistry instruction builders, SPL helpers, x402 header constants. Full list: [Exports reference](/sdk/exports-reference).
29
+
30
+ The `/express` and `/client` subpaths are tree-shake-friendly entry points; the root namespace doesn't re-export them so a client-only consumer doesn't pull Express into the bundle.
25
31
 
26
- `gatePayment()` is a read-only decision call.
32
+ ## Quick start Express
27
33
 
28
34
  ```ts
29
- type GateDecision =
30
- | { kind: "Allow" }
31
- | { kind: "Deny"; reasonCode: number; reasonName: string }
32
- | { kind: "RequireValidation"; capabilityHash: number[] };
35
+ import express from "express";
36
+ import { Keypair } from "@solana/web3.js";
37
+ import { mountTrustGate } from "@agenttrust-sdk/trustgate/express";
38
+
39
+ const app = express();
40
+ app.use(express.json());
41
+
42
+ await mountTrustGate(app, {
43
+ rpcUrl: "https://api.devnet.solana.com",
44
+ facilitatorKeypair: Keypair.fromSecretKey(/* facilitator key */),
45
+ network: "solana-devnet",
46
+ atomicityEnforced: true, // literal `true`
47
+ });
48
+
49
+ app.listen(3000);
33
50
  ```
34
51
 
35
- The helper simulates the Anchor instruction and parses the return-data channel into the TypeScript union.
52
+ Mounts four routes:
53
+
54
+ | Route | Purpose |
55
+ |---|---|
56
+ | `POST /verify` | Read-only `gate_payment` simulation. Returns 200/Allow, 402/Deny + reason headers, or 402/RequireValidation + capability hash. |
57
+ | `GET /receipt/:paymentIdHashHex` | `FeedbackEmissionLog` lookup. Returns `{ exists: false }` until settlement. |
58
+ | `POST /settle` | Atomic settle (`gate_payment_strict + transferChecked + emit_feedback`). |
59
+ | `POST /dispute` | Dispute path (`dispute_payment` ix). |
36
60
 
37
- ## mountTrustGate
61
+ Full route reference: [mountTrustGate](/sdk/mount-trustgate).
38
62
 
39
- `mountTrustGate(app, config)` adds x402 routes to an Express service in fewer than 50 lines.
63
+ ## Quick start client
40
64
 
41
65
  ```ts
42
- await mountTrustGate(app, {
43
- rpcUrl: "https://api.devnet.solana.com",
44
- facilitatorKeypair,
45
- defaultPolicyId: 1,
46
- network: "solana-devnet",
47
- atomicityEnforced: true,
66
+ import { Keypair, PublicKey } from "@solana/web3.js";
67
+ import { gatePayment } from "@agenttrust-sdk/trustgate/client";
68
+
69
+ const decision = await gatePayment({
70
+ rpcUrl: "https://api.devnet.solana.com",
71
+ caller: facilitatorKeypair,
72
+ payerAgentAsset: new PublicKey("…"),
73
+ payeeAgentAsset: new PublicKey("…"),
74
+ amount: 1_000_000n,
75
+ mint: new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"), // USDC devnet
76
+ policyId: 1,
48
77
  });
78
+
79
+ switch (decision.kind) {
80
+ case "Allow": /* proceed */ break;
81
+ case "Deny": console.log(decision.reasonName); break;
82
+ case "RequireValidation": /* route to attestation flow with decision.capabilityHash */ break;
83
+ }
84
+ ```
85
+
86
+ Full reference: [gatePayment](/sdk/gate-payment).
87
+
88
+ ## Atomic-tx invariant
89
+
90
+ `gate_payment + SPL transfer + emit_feedback` MUST execute as one Solana transaction. Splitting them silently corrupts `VelocityLedger` when a Token-2022 mint's `TransferHook` extension reverts the transfer.
91
+
92
+ The SDK enforces atomicity at three layers:
93
+
94
+ ```ts
95
+ import {
96
+ AtomicityEnforced,
97
+ AtomicityNotEnforcedError,
98
+ composeAtomicSettleTx,
99
+ } from "@agenttrust-sdk/trustgate";
100
+
101
+ // 1. Compile-time literal-type guard:
102
+ // `AtomicityEnforced` is `{ atomicityEnforced: true }` (literal `true`).
103
+ // TS rejects `false`, missing, or `boolean` widening.
104
+ //
105
+ // 2. Runtime guard:
106
+ // `assertAtomicityEnforced` throws `AtomicityNotEnforcedError` for any
107
+ // value that isn't strictly `=== true`. Catches `as any` cast bypasses.
108
+ //
109
+ // 3. Composer structure:
110
+ // `composeAtomicSettleTx` returns one `Transaction` with exactly three
111
+ // instructions in canonical order (gate, transfer, feedback).
49
112
  ```
50
113
 
51
- ## Atomicity guard
114
+ Plus the on-chain Kani proof `gate_payment_strict_correctness` pins the strict handler's contract — `Ok(())` if and only if the composer returned `Allow`. Full proof: [Verification → Atomic-tx invariant](/verification/atomic-tx-invariant).
52
115
 
53
- `settle`, `dispute`, and middleware config require:
116
+ ## Breaking changes (0.2.0)
117
+
118
+ `ProgramIds.trustgate` (lowercase) renamed to `ProgramIds.trustGate` (camelCase, matches `policyVault`). New `validationRegistry` field populated by default with the deployed devnet ID `Cx4RFa6ysw3qXYhugPkF8pFSWBkmKq59h2dWgF2tKhtv`.
54
119
 
55
120
  ```ts
56
- { atomicityEnforced: true }
121
+ // 0.1.x
122
+ DEFAULT_DEVNET_PROGRAM_IDS.trustgate.toBase58();
123
+
124
+ // 0.2.0
125
+ DEFAULT_DEVNET_PROGRAM_IDS.trustGate.toBase58();
126
+ DEFAULT_DEVNET_PROGRAM_IDS.validationRegistry.toBase58(); // new
57
127
  ```
58
128
 
59
- The marker is literal `true`, not `boolean`, and the runtime guard throws if a caller bypasses TypeScript with a cast. The server adapter path extends this into proof validation and feedback emission.
129
+ One-line migration: search-and-replace `.trustgate` `.trustGate` for every `programIds.*` field access. Full changelog: [Reference Changelog](/reference/changelog).
60
130
 
61
- ## Current surface
131
+ ## On-chain programs (devnet)
62
132
 
63
- | API | Status |
64
- | --- | --- |
65
- | `gatePayment` | implemented |
66
- | `mountTrustGate` `/verify` | implemented |
67
- | `mountTrustGate` `/receipt` | implemented |
68
- | Pay.sh adapter | implemented in `@agenttrust/trustgate-server` workspace package |
69
- | Pay.sh demo | implemented in `examples/pay-sh-demo` |
70
- | `settle` | guarded SDK surface; server adapter validation path is active |
71
- | `dispute` | guarded SDK surface; negative feedback route remains narrower |
133
+ <ProgramIdsTable />
134
+
135
+ `loadPolicyVault` / `loadTrustGate` / `loadValidationRegistry` fetch each IDL from chain by default. Pass an explicit `idl` argument to use a bundled snapshot — useful when avoiding an extra RPC hop in latency-sensitive paths or when running against a freshly redeployed program before `anchor idl upgrade` runs.
136
+
137
+ ```bash
138
+ anchor idl fetch <programId> --provider.cluster devnet
139
+ ```
140
+
141
+ ## Test coverage
142
+
143
+ | Suite | Count | Where |
144
+ |---|---:|---|
145
+ | Atomicity unit tests | 6 (literal-type guard + runtime + composer structure) | `trustgate/sdk/test/atomicity.test.ts` |
146
+ | PDA derivation tests | 24 | `trustgate/sdk/test/chain.test.ts` |
147
+ | Quantu helpers | 12 | `trustgate/sdk/test/quantu.test.ts` |
148
+ | ValidationRegistry instruction builders | 14 | `trustgate/sdk/test/validation-registry.test.ts` |
149
+ | All SDK unit tests | 56 (+ 16 INTEGRATION-gated devnet round-trips) | `pnpm --filter ./trustgate/sdk run test` |
150
+
151
+ Run via:
152
+
153
+ ```bash
154
+ cd trustgate/sdk
155
+ pnpm test
156
+ INTEGRATION=1 pnpm test:integration # devnet round-trip
157
+ ```
72
158
 
73
- Source: `trustgate/sdk/src`.
159
+ ## Read next
160
+
161
+ <Cards>
162
+ <Card title="gatePayment" href="/sdk/gate-payment">
163
+ Read-only policy decision call — type signature, config fields, decision union, errors.
164
+ </Card>
165
+ <Card title="mountTrustGate" href="/sdk/mount-trustgate">
166
+ Express middleware reference — every route, every header, the atomicity guard contract.
167
+ </Card>
168
+ <Card title="Exports reference" href="/sdk/exports-reference">
169
+ One-screen list of every public export grouped by concern.
170
+ </Card>
171
+ <Card title="Atomic-tx invariant" href="/verification/atomic-tx-invariant">
172
+ Three layers of proof + the on-chain Kani strict-correctness binding.
173
+ </Card>
174
+ </Cards>
@@ -1,15 +1,185 @@
1
1
  ---
2
2
  title: mountTrustGate
3
- description: Express middleware that mounts AgentTrust x402 routes.
3
+ description: Drop-in Express middleware. Adds the four x402 routes to any app — verify, receipt, settle, dispute — with the atomicity guard enforced at compile-time and runtime.
4
4
  ---
5
5
 
6
- `In progress`
6
+ `mountTrustGate(app, config)` adds four endpoints to any Express app. The atomicity guard runs at the top of the call so a missing or `false` `atomicityEnforced` flag fails synchronously before any route binds.
7
7
 
8
- `mountTrustGate(app, config)` adds `/verify`, `/receipt`, `/settle`, and `/dispute` handlers to an Express service. The SDK enforces the atomicity config before binding routes.
8
+ Source: [`trustgate/sdk/src/express.ts`](https://github.com/agenttrust-labs/agenttrust/blob/main/trustgate/sdk/src/express.ts).
9
9
 
10
- | Source | Path |
11
- | --- | --- |
12
- | Express middleware | [`trustgate/sdk/src/express.ts`](https://github.com/agenttrust-labs/agenttrust/blob/main/trustgate/sdk/src/express.ts) |
13
- | x402 helpers | [`trustgate/sdk/src/x402.ts`](https://github.com/agenttrust-labs/agenttrust/blob/main/trustgate/sdk/src/x402.ts) |
14
- | atomicity tests | [`trustgate/sdk/test/atomicity.test.ts`](https://github.com/agenttrust-labs/agenttrust/blob/main/trustgate/sdk/test/atomicity.test.ts) |
10
+ ## Signature
15
11
 
12
+ ```ts
13
+ import { mountTrustGate } from "@agenttrust-sdk/trustgate/express";
14
+
15
+ export function mountTrustGate(
16
+ app: Application,
17
+ config: MountTrustGateConfig,
18
+ ): Promise<void>;
19
+ ```
20
+
21
+ ## `MountTrustGateConfig`
22
+
23
+ ```ts
24
+ interface MountTrustGateConfig extends AtomicityEnforced {
25
+ rpcUrl: string; // Solana RPC URL
26
+ facilitatorKeypair: Keypair; // signs emit_feedback CPIs + tx fees
27
+ defaultPolicyId?: number; // fallback if /verify body omits policyId
28
+ programIds?: ProgramIds; // defaults to DEFAULT_DEVNET_PROGRAM_IDS
29
+ network?: string; // x402 header label, e.g. "solana-devnet"
30
+ atomicityEnforced: true; // literal `true` — TS compile error on `false`
31
+ }
32
+ ```
33
+
34
+ The promise resolves once IDLs are loaded from the cluster and routes are bound. If the cluster doesn't have published IDLs, `mountTrustGate` rejects — call `anchor idl init` once per program before mounting.
35
+
36
+ ## Routes
37
+
38
+ ### `POST /verify`
39
+
40
+ Read-only `gate_payment` simulation. Same semantics as [`gatePayment()`](/sdk/gate-payment).
41
+
42
+ ```http
43
+ POST /verify
44
+ Content-Type: application/json
45
+
46
+ {
47
+ "payerAgentAsset": "<base58 pubkey>",
48
+ "payeeAgentAsset": "<base58 pubkey>",
49
+ "amount": "1000000",
50
+ "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
51
+ "policyId": 1
52
+ }
53
+ ```
54
+
55
+ Responses:
56
+
57
+ ```http
58
+ HTTP/1.1 200 OK
59
+ X-Agent-Trust-Decision: Allow
60
+ X-Payment-Network: solana-devnet
61
+ ```
62
+
63
+ ```http
64
+ HTTP/1.1 402 Payment Required
65
+ X-Agent-Trust-Decision: Deny
66
+ X-Payment-Required: denied
67
+ X-Payment-Reason-Code: 6
68
+ X-Payment-Reason-Name: CounterpartyTierBelowMin
69
+ X-Payment-Network: solana-devnet
70
+ ```
71
+
72
+ ```http
73
+ HTTP/1.1 402 Payment Required
74
+ X-Agent-Trust-Decision: RequireValidation
75
+ X-Payment-Required: validation
76
+ X-Capability-Required: 366c075140aa69746625d4b733b55e267fc5c28387fd6d1c24901976ee3ddc42
77
+ X-Payment-Network: solana-devnet
78
+ ```
79
+
80
+ Headers built via `buildHeadersForDecision` in [`trustgate/sdk/src/x402.ts`](https://github.com/agenttrust-labs/agenttrust/blob/main/trustgate/sdk/src/x402.ts).
81
+
82
+ ### `GET /receipt/:paymentIdHashHex`
83
+
84
+ `FeedbackEmissionLog` PDA lookup. Returns `{ exists: false }` until the payment settles.
85
+
86
+ ```http
87
+ GET /receipt/6984738594e493bfd4314866840427a11e8e53677bc0ff4b98ae8aa39ce0c859
88
+
89
+ HTTP/1.1 200 OK
90
+ Content-Type: application/json
91
+
92
+ {
93
+ "exists": true,
94
+ "score": 100,
95
+ "isDispute": false,
96
+ "emittedAtSlot": 460466788,
97
+ "feedbackEmissionLog": "HB4BBi9jaD3VPcZkQQaH3DxukSqBiXfW8RejtaLa8bF3"
98
+ }
99
+ ```
100
+
101
+ The `paymentIdHashHex` parameter is the lowercase hex encoding of `SHA-256(payment_id_string)`. The SDK exports `deriveFeedbackLogPda(programId, paymentIdHashBytes)` for callers that derive the PDA themselves.
102
+
103
+ ### `POST /settle`
104
+
105
+ Atomic settle path. The handler accepts the verify-time payment context plus the `tag1`/`tag2`/`endpoint`/`feedback_uri` metadata, builds the three-instruction transaction via `composeAtomicSettleTx`, and submits.
106
+
107
+ ```http
108
+ POST /settle
109
+ Content-Type: application/json
110
+
111
+ {
112
+ "payerAgentAsset": "<base58>",
113
+ "payeeAgentAsset": "<base58>",
114
+ "amount": "1000000",
115
+ "mint": "<base58>",
116
+ "policyId": 1,
117
+ "paymentIdHash": "6984738594e493bfd4314866840427a11e8e53677bc0ff4b98ae8aa39ce0c859",
118
+ "feedbackUri": "ipfs://…",
119
+ "score": 100,
120
+ "tag1": "successful",
121
+ "tag2": "x402",
122
+ "endpoint": "/protected"
123
+ }
124
+ ```
125
+
126
+ The route requires `payer` keypair access (the SPL transfer authority). The default Express handler reads it from `req.body.payerKey` for testing; production deploys typically wrap this route with their own auth + signature scheme. See [Pay.sh adapter](/integration-guides/pay-sh-adapter) for the canonical wrapping pattern.
127
+
128
+ ### `POST /dispute`
129
+
130
+ Same shape as `/settle`, but invokes `dispute_payment` (negative-score feedback) instead. Requires `dispute_reason_hash` in the body. The same `FeedbackEmissionLog` idempotency rules apply: a second dispute for the same `payment_id_hash` fails account-already-in-use.
131
+
132
+ ## Atomicity guard
133
+
134
+ `mountTrustGate` is itself a `MountTrustGateConfig extends AtomicityEnforced` consumer:
135
+
136
+ ```ts
137
+ export interface MountTrustGateConfig extends AtomicityEnforced {
138
+ // … the literal `atomicityEnforced: true` is required
139
+ }
140
+ ```
141
+
142
+ ```ts
143
+ export async function mountTrustGate(app, config) {
144
+ assertAtomicityEnforced(config, "mountTrustGate");
145
+ // …
146
+ }
147
+ ```
148
+
149
+ Compile-time: `await mountTrustGate(app, { …, atomicityEnforced: false });` is a TS error. Runtime: `await mountTrustGate(app, { …, atomicityEnforced: true as any });` works because TS would have already let it through, BUT `await mountTrustGate(app, JSON.parse(unsafeUserConfig));` still fails the runtime guard.
150
+
151
+ Six SDK unit tests cover both layers in [`trustgate/sdk/test/atomicity.test.ts`](https://github.com/agenttrust-labs/agenttrust/blob/main/trustgate/sdk/test/atomicity.test.ts) — including the `as any` cast bypass and the `composeAtomicSettleTx` three-instruction structure check.
152
+
153
+ ## Example
154
+
155
+ ```ts
156
+ import express from "express";
157
+ import { Keypair } from "@solana/web3.js";
158
+ import { mountTrustGate } from "@agenttrust-sdk/trustgate/express";
159
+
160
+ const app = express();
161
+ app.use(express.json());
162
+
163
+ const facilitatorKeypair = Keypair.fromSecretKey(
164
+ Uint8Array.from(JSON.parse(process.env.FACILITATOR_KEYPAIR!)),
165
+ );
166
+
167
+ await mountTrustGate(app, {
168
+ rpcUrl: process.env.SOLANA_RPC_URL ?? "https://api.devnet.solana.com",
169
+ facilitatorKeypair,
170
+ defaultPolicyId: 1,
171
+ network: "solana-devnet",
172
+ atomicityEnforced: true,
173
+ });
174
+
175
+ app.get("/", (_req, res) => res.send("AgentTrust facilitator up"));
176
+ app.listen(3000, () => console.log("listening on :3000"));
177
+ ```
178
+
179
+ The hosted facilitator at [`api.agenttrust.tech`](https://api.agenttrust.tech) runs exactly this code path. Health: `GET /healthz`.
180
+
181
+ ## Source
182
+
183
+ - Express middleware: [`trustgate/sdk/src/express.ts`](https://github.com/agenttrust-labs/agenttrust/blob/main/trustgate/sdk/src/express.ts)
184
+ - x402 helpers: [`trustgate/sdk/src/x402.ts`](https://github.com/agenttrust-labs/agenttrust/blob/main/trustgate/sdk/src/x402.ts)
185
+ - Atomicity tests: [`trustgate/sdk/test/atomicity.test.ts`](https://github.com/agenttrust-labs/agenttrust/blob/main/trustgate/sdk/test/atomicity.test.ts)
@@ -0,0 +1,88 @@
1
+ ---
2
+ title: Adversarial harness
3
+ description: Fourteen hostile-scenario assertions that exercise the gate against malformed PDAs, replayed proofs, and corrupted state.
4
+ ---
5
+
6
+ The adversarial harness sits alongside the Anchor end-to-end suite. While the e2e tests cover the happy + edge paths a normal integration would hit, the adversarial harness exercises the failure modes a hostile caller would try.
7
+
8
+ Source: [`tests/adversarial.spec.ts`](https://github.com/agenttrust-labs/agenttrust/blob/main/tests/adversarial.spec.ts). Run via `anchor test --skip-deploy --provider.cluster localnet`.
9
+
10
+ ## The fourteen scenarios
11
+
12
+ Each scenario constructs a hostile input and asserts the program rejects it with a typed error rather than producing an `Allow` or silently mutating state.
13
+
14
+ | # | Scenario | Expected behaviour |
15
+ |---:|---|---|
16
+ | 1 | Forged `AtomStats` — wrong owner program | `Deny(AtomStatsWrongOwner)` (code 9) |
17
+ | 2 | Forged `AtomStats` — schema-version canary mismatch | `Deny(AtomStatsSchemaMismatch)` (code 10) |
18
+ | 3 | Forged `AtomStats` — tier byte > `ATOM_TIER_MAX = 4` | `Deny(AtomStatsSchemaMismatch)` (code 10) |
19
+ | 4 | Forged `AtomStats` — undersized buffer | `Deny(AtomStatsSchemaMismatch)` (code 10) |
20
+ | 5 | Replayed `payment_id_hash` | `account-already-in-use` on `FeedbackEmissionLog` init; whole tx reverts |
21
+ | 6 | Self-pay attempt — facilitator signing as both payer and payee | `Deny` from facilitator-signer-mismatch check |
22
+ | 7 | Wrong attestor — attestation issued by a key not in `accepted_attestors[]` | `Deny(AttestationAttestorRejected)` (code 14) |
23
+ | 8 | Stale attestation — `expires_at <= now_slot` (and != 0) | `Deny(AttestationExpired)` (code 12) |
24
+ | 9 | Revoked attestation — `revoked == true` | `Deny(AttestationRevoked)` (code 13) |
25
+ | 10 | Wrong subject — attestation's `subject_asset` doesn't match the payee | `Deny(AttestationMissing)` (code 11) |
26
+ | 11 | Wrong capability — attestation's `capability_hash` doesn't match `required_capability_hash` | `Deny(AttestationMissing)` (code 11) |
27
+ | 12 | Multisig threshold bypass attempt — fewer distinct signers than `threshold` | `ThresholdNotMet` (Anchor error) |
28
+ | 13 | Multisig duplicate-signer attempt — same key signing twice | counted as one (pubkey-dedup); `ThresholdNotMet` if remaining distinct count is below threshold |
29
+ | 14 | Velocity counter manipulation — `now_slot < window_start_slot` (clock-skew or replay) | `saturating_sub` clamps elapsed to 0; window NOT expired; cumulative correctly preserved |
30
+
31
+ ## What the harness covers that Kani doesn't
32
+
33
+ Kani's bounded model checker proves the pure-Rust composer's safety invariants over symbolic state. The adversarial harness covers the on-chain Anchor wrapper that Kani doesn't:
34
+
35
+ - account-validation checks (owner mismatch, size mismatch, schema-version canary)
36
+ - the `init`-only `FeedbackEmissionLog` + replay mechanism (Anchor's `account-already-in-use` semantics)
37
+ - the Anchor-handler's signer constraints (facilitator self-pay, attestor key validation)
38
+ - end-to-end on-chain tx behaviour the bounded composer can't see (idempotency, atomic revert)
39
+
40
+ The two together cover both the in-program decision logic (Kani) and the surrounding Anchor wrapper (adversarial harness). Either alone leaves gaps.
41
+
42
+ ## Why localnet, not devnet
43
+
44
+ Adversarial scenarios sometimes require constructing accounts the real Quantu programs would never produce (forged `AtomStats` with bad schema versions, forged `ValidationAttestation` PDAs with wrong owner). On localnet, the test fixture writes those raw bytes directly. On devnet, the only way to construct such an account is to compromise the real program — which we obviously can't do in CI.
45
+
46
+ Localnet also produces deterministic timing for slot-based assertions (clock-skew test #14). Devnet cluster-time variance would make that test flaky.
47
+
48
+ ## Reproduce
49
+
50
+ ```bash
51
+ git clone https://github.com/agenttrust-labs/agenttrust && cd agenttrust
52
+ pnpm install
53
+ anchor build
54
+ anchor test --provider.cluster localnet --validator legacy --skip-build \
55
+ --grep "adversarial"
56
+ ```
57
+
58
+ Expected: 14 / 14 passing in ~30 s.
59
+
60
+ ## Where it complements other layers
61
+
62
+ - **PolicyVault unit tests (113 cases).** Cover normal paths through every policy module.
63
+ - **Anchor TS e2e suite (50 cases).** Cover the Anchor wrapper happy paths against real (or cloned) Quantu state.
64
+ - **Kani proofs (6 / 635 sub-checks).** Cover the pure-Rust composer's safety invariants over symbolic state.
65
+ - **Adversarial harness (14 cases).** Cover the failure modes a hostile caller would try.
66
+ - **MCP protocol conformance (21 cases).** Cover the MCP wire protocol shape.
67
+ - **Adapter contract conformance (per-adapter test suites).** Cover the `FacilitatorAdapter` contract.
68
+
69
+ The combination — 113 + 50 + 635 sub-checks + 14 + 21 + per-adapter — is what underwrites the v1 safety claim.
70
+
71
+ ## Source
72
+
73
+ - Test file: [`tests/adversarial.spec.ts`](https://github.com/agenttrust-labs/agenttrust/blob/main/tests/adversarial.spec.ts)
74
+ - Helper fixtures: [`tests/helpers/`](https://github.com/agenttrust-labs/agenttrust/tree/main/tests/helpers)
75
+
76
+ ## Read next
77
+
78
+ <Cards>
79
+ <Card title="Kani proofs" href="/verification/kani-proofs">
80
+ The other side of the invariant proof — pure-Rust over symbolic state.
81
+ </Card>
82
+ <Card title="Atomic-tx invariant" href="/verification/atomic-tx-invariant">
83
+ The biggest scenario the harness covers — split-tx state corruption.
84
+ </Card>
85
+ <Card title="DenyReason codes" href="/reference/deny-reason-codes">
86
+ The 15 reason codes the harness asserts against.
87
+ </Card>
88
+ </Cards>
@@ -0,0 +1,141 @@
1
+ ---
2
+ title: Atomic-tx invariant
3
+ description: gate_payment + transfer + emit_feedback execute as one Solana transaction or none. Three SDK layers + the on-chain Kani anchor + localnet runtime proof.
4
+ ---
5
+
6
+ The load-bearing safety property of the SDK:
7
+
8
+ > `gate_payment` + SPL `transferChecked` + `emit_feedback` MUST execute as one Solana transaction. Splitting them silently corrupts state when a Token-2022 mint's `TransferHook` extension reverts the transfer.
9
+
10
+ Background writeup: [`docs/proofs/transfer-hook-atomicity.md`](https://github.com/agenttrust-labs/agenttrust/blob/main/docs/proofs/transfer-hook-atomicity.md). The proof is structured as five layers; all five must hold.
11
+
12
+ ## The corruption vector
13
+
14
+ `gate_payment_strict` mutates state on `Allow`:
15
+
16
+ - `PolicyAccount.spending_today_used`, `spending_today_anchor`, `spending_week_used`, `spending_week_anchor`
17
+ - `VelocityLedger.cumulative_amount`, `last_commit_slot`, `window_start_slot`
18
+
19
+ If a downstream SPL transfer reverts after that mutation — and the two instructions are in *different* Solana transactions — the gate's state is durable while no money has moved. Subsequent `gate_payment` calls read the inflated counters and may `Deny` a legitimate payment. In the inverse, counters can drift such that velocity caps appear hit when nothing real has happened.
20
+
21
+ A Token-2022 `TransferHook` extension is the canonical instance of this fault model. The hook program runs synchronously inside the SPL `transferChecked` execution; if it returns `Err`, the transfer aborts. The specifics — compliance check, allowlist denial, freeze on suspicious counterparty — are domain-specific. From the runtime's perspective, a `TransferHook` revert is indistinguishable from any other inner-ix revert (NonTransferable mint, DefaultAccountState=Frozen, MissingRequiredSignature, out-of-funds). The atomicity guarantee is mint-extension-agnostic.
22
+
23
+ ## Five layers of proof
24
+
25
+ | Layer | What it proves | Evidence |
26
+ |---|---|---|
27
+ | A.1 — compile-time guard | `AtomicityEnforced` is a literal `true` marker. TS rejects `false`, missing, and `boolean` widening at compile time. | [`trustgate/sdk/src/atomicity.ts`](https://github.com/agenttrust-labs/agenttrust/blob/main/trustgate/sdk/src/atomicity.ts) lines 31-33 + [`test/atomicity.test.ts`](https://github.com/agenttrust-labs/agenttrust/blob/main/trustgate/sdk/test/atomicity.test.ts) `describe("AtomicityEnforced literal-type guard")` |
28
+ | A.2 — runtime guard | `assertAtomicityEnforced` throws `AtomicityNotEnforcedError` for any value that isn't strictly `=== true`. Catches `as any` casts. | [`trustgate/sdk/src/atomicity.ts`](https://github.com/agenttrust-labs/agenttrust/blob/main/trustgate/sdk/src/atomicity.ts) lines 51-58 + 6-case test suite |
29
+ | A.3 — composer structure | `composeAtomicSettleTx` returns ONE `Transaction` with EXACTLY 3 instructions in canonical order — gate (PolicyVault) + `transferChecked` (Token / Token-2022) + `emit_feedback` (TrustGate). | [`trustgate/sdk/test/atomicity.test.ts`](https://github.com/agenttrust-labs/agenttrust/blob/main/trustgate/sdk/test/atomicity.test.ts) `describe("composeAtomicSettleTx")` (5 cases including Token-2022 program-id propagation) |
30
+ | B — single-tx atomic revert | When the bundled tx contains a failing inner ix, Solana reverts the entire bundle; `gate_payment_strict`'s state mutation rolls back. PolicyAccount + VelocityLedger stay clean. | [`tests/atomic-tx-invariant.spec.ts`](https://github.com/agenttrust-labs/agenttrust/blob/main/tests/atomic-tx-invariant.spec.ts) `describe("B. single-tx atomic revert")` |
31
+ | C — split-tx corruption (anti-pattern) | If a caller splits the bundle into two txs, gate_payment_strict commits in tx1, the failing transfer reverts in tx2, and VelocityLedger is left dirty (counted a payment that never moved). This is the corruption vector A + B prevent. | [`tests/atomic-tx-invariant.spec.ts`](https://github.com/agenttrust-labs/agenttrust/blob/main/tests/atomic-tx-invariant.spec.ts) `describe("C. split-tx anti-pattern corrupts state")` |
32
+ | On-chain anchor | `gate_payment_strict_correctness` (Kani #6, 258 sub-checks) — strict handler returns `Ok(())` if and only if the lazy composer returns `Allow`. | [`programs/policy-vault/src/proofs/inv_gate_payment_strict_correctness.rs`](https://github.com/agenttrust-labs/agenttrust/blob/main/programs/policy-vault/src/proofs/inv_gate_payment_strict_correctness.rs) |
33
+
34
+ ## A — SDK type + runtime guards
35
+
36
+ ```ts
37
+ export interface AtomicityEnforced {
38
+ readonly atomicityEnforced: true; // literal true, not boolean
39
+ }
40
+
41
+ export class AtomicityNotEnforcedError extends Error {
42
+ constructor(siteName: string) {
43
+ super(`[${siteName}] atomicityEnforced=true is required …`);
44
+ this.name = "AtomicityNotEnforcedError";
45
+ }
46
+ }
47
+
48
+ export function assertAtomicityEnforced(
49
+ cfg: { atomicityEnforced?: unknown },
50
+ siteName: string,
51
+ ): void {
52
+ if ((cfg as any).atomicityEnforced !== true) {
53
+ throw new AtomicityNotEnforcedError(siteName);
54
+ }
55
+ }
56
+ ```
57
+
58
+ Compile-time: `composeAtomicSettleTx({ …, atomicityEnforced: false })` is a TS error. Runtime: `composeAtomicSettleTx({ …, atomicityEnforced: 1 } as any)` throws `AtomicityNotEnforcedError("composeAtomicSettleTx")`. Both layers required: skipping either re-opens the corruption vector.
59
+
60
+ ## A.3 — Composer structure
61
+
62
+ `composeAtomicSettleTx` returns:
63
+
64
+ ```ts
65
+ interface ComposedAtomicSettle {
66
+ readonly tx: Transaction;
67
+ readonly instructions: ReadonlyArray<TransactionInstruction>; // exactly 3
68
+ readonly accounts: { … };
69
+ }
70
+ ```
71
+
72
+ The three instructions, in order:
73
+
74
+ 1. `gate_payment_strict` — `Err` on `Deny` / `RequireValidation`. Reverts the whole bundle if the gate denies.
75
+ 2. SPL `transferChecked` (legacy Token or Token-2022 — `tokenProgram` arg switches).
76
+ 3. `emit_feedback` — PDA-signed CPI into Quantu via TrustGate.
77
+
78
+ Five test cases pin this contract — including a Token-2022 program-id propagation case that ensures the SPL ix uses `TOKEN_2022_PROGRAM_ID` when the caller passes it.
79
+
80
+ ## B+C — Localnet runtime proofs
81
+
82
+ The Anchor TS test [`tests/atomic-tx-invariant.spec.ts`](https://github.com/agenttrust-labs/agenttrust/blob/main/tests/atomic-tx-invariant.spec.ts) splits into two `describe` blocks:
83
+
84
+ - **`B. single-tx atomic revert`.** Builds the bundled tx with an intentionally-failing System.transfer (source keypair never signs → guaranteed `MissingRequiredSignature`) and asserts the entire bundle reverts; PolicyAccount + VelocityLedger remain in their pre-tx state.
85
+ - **`C. split-tx anti-pattern corrupts state`.** Splits the same flow into two transactions. Tx 1 commits the gate's velocity update. Tx 2 reverts on the failing transfer. The ledger now reflects a payment that never moved — the corruption vector that B prevents.
86
+
87
+ ### Why System.transfer instead of TransferHook
88
+
89
+ The localnet tests deliberately use a System.transfer with a never-signed source as the failing inner instruction. This:
90
+
91
+ 1. Exercises the same Solana atomicity semantics a `TransferHook` revert would — the runtime aborts the bundle, all child mutations roll back.
92
+ 2. Requires no Token-2022 mint setup or custom hook program on the localnet validator, keeping the test focused on the property under proof rather than mint-init plumbing.
93
+ 3. Documents the corruption vector at the layer where it actually matters: any failing inner ix corrupts split-tx state. `TransferHook` is one specific failure mode, not the property itself.
94
+
95
+ The Token-2022 angle is fully covered at the SDK layer in `composeAtomicSettleTx` test cases.
96
+
97
+ ## On-chain Kani anchor
98
+
99
+ The strict handler's contract is pinned by Kani `gate_payment_strict_correctness`:
100
+
101
+ - `strict_returns_ok_iff_allow` — `Ok(())` ⇔ `Allow`. 131 sub-checks.
102
+ - `gate_decision_is_one_of_three_disjoint_variants` — three arms pairwise disjoint; a fourth variant cannot silently slip past. 127 sub-checks.
103
+
104
+ Combined: 258 sub-checks, ~0.9 s. Sub-check 0 failed. If a future change re-routes the `Deny` arm to an `Ok` return, both proofs fail loud.
105
+
106
+ The on-chain anchor + the SDK guards together close the corruption vector at three layers: compile, runtime, and on-chain.
107
+
108
+ ## Reproduce
109
+
110
+ ```bash
111
+ git clone https://github.com/agenttrust-labs/agenttrust && cd agenttrust
112
+ pnpm install
113
+
114
+ # A — SDK layers
115
+ cd trustgate/sdk
116
+ pnpm test # 6 atomicity-specific cases + 50 others
117
+
118
+ # B + C — localnet
119
+ cd ../..
120
+ anchor build
121
+ anchor test --provider.cluster localnet --validator legacy --skip-build
122
+
123
+ # On-chain anchor (Kani)
124
+ cd programs/policy-vault
125
+ cargo kani --harness strict_returns_ok_iff_allow
126
+ cargo kani --harness gate_decision_is_one_of_three_disjoint_variants
127
+ ```
128
+
129
+ ## Read next
130
+
131
+ <Cards>
132
+ <Card title="Kani proofs" href="/verification/kani-proofs">
133
+ Per-harness deep-dive on all 6 invariants.
134
+ </Card>
135
+ <Card title="Composer" href="/programs/policy-vault/composer">
136
+ The pure-Rust orchestration the strict handler wraps.
137
+ </Card>
138
+ <Card title="mountTrustGate" href="/sdk/mount-trustgate">
139
+ Where the SDK consumer plugs into the atomicity guard.
140
+ </Card>
141
+ </Cards>