@payclaw/badge 0.7.1 â 0.8.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 +68 -2
- package/dist/api/client.js +1 -1
- package/dist/api/client.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +24 -11
- package/dist/index.js.map +1 -1
- package/dist/lib/device-auth.js +11 -3
- package/dist/lib/device-auth.js.map +1 -1
- package/dist/lib/parse-outcome.js +1 -0
- package/dist/lib/parse-outcome.js.map +1 -1
- package/dist/lib/report-badge-presented-handler.d.ts +7 -1
- package/dist/lib/report-badge-presented-handler.js +13 -2
- package/dist/lib/report-badge-presented-handler.js.map +1 -1
- package/dist/lib/report-badge.d.ts +1 -1
- package/dist/lib/report-badge.js +3 -1
- package/dist/lib/report-badge.js.map +1 -1
- package/dist/lib/storage.d.ts +5 -0
- package/dist/lib/storage.js +28 -0
- package/dist/lib/storage.js.map +1 -1
- package/dist/lib/ucp-manifest.d.ts +32 -0
- package/dist/lib/ucp-manifest.js +117 -0
- package/dist/lib/ucp-manifest.js.map +1 -0
- package/dist/lib/ucp-manifest.test.d.ts +1 -0
- package/dist/lib/ucp-manifest.test.js +92 -0
- package/dist/lib/ucp-manifest.test.js.map +1 -0
- package/dist/sampling.d.ts +4 -0
- package/dist/sampling.js +43 -6
- package/dist/sampling.js.map +1 -1
- package/dist/sampling.test.d.ts +1 -0
- package/dist/sampling.test.js +150 -0
- package/dist/sampling.test.js.map +1 -0
- package/dist/tools/getAgentIdentity.d.ts +9 -1
- package/dist/tools/getAgentIdentity.js +67 -10
- package/dist/tools/getAgentIdentity.js.map +1 -1
- package/dist/verify.d.ts +34 -0
- package/dist/verify.js +161 -0
- package/dist/verify.js.map +1 -0
- package/dist/verify.test.d.ts +1 -0
- package/dist/verify.test.js +177 -0
- package/dist/verify.test.js.map +1 -0
- package/package.json +7 -3
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
2
|
+
import { fetchUCPManifest, findPayClawCapability, isVersionCompatible, _resetManifestCache, } from "./ucp-manifest.js";
|
|
3
|
+
describe("ucp-manifest", () => {
|
|
4
|
+
beforeEach(() => {
|
|
5
|
+
_resetManifestCache();
|
|
6
|
+
});
|
|
7
|
+
afterEach(() => {
|
|
8
|
+
vi.restoreAllMocks();
|
|
9
|
+
});
|
|
10
|
+
const VALID_MANIFEST = {
|
|
11
|
+
capabilities: {
|
|
12
|
+
"io.payclaw.common.identity": [
|
|
13
|
+
{
|
|
14
|
+
version: "2026-01-11",
|
|
15
|
+
spec: "https://payclaw.io/ucp/spec/identity",
|
|
16
|
+
schema: "https://payclaw.io/ucp/schemas/identity.json",
|
|
17
|
+
config: { required: false },
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
describe("fetchUCPManifest", () => {
|
|
23
|
+
it("fetches and returns manifest", async () => {
|
|
24
|
+
vi.spyOn(globalThis, "fetch").mockResolvedValue(new Response(JSON.stringify(VALID_MANIFEST), { status: 200 }));
|
|
25
|
+
const result = await fetchUCPManifest("https://shop.example.com");
|
|
26
|
+
expect(result).toEqual(VALID_MANIFEST);
|
|
27
|
+
});
|
|
28
|
+
it("caches manifest per domain", async () => {
|
|
29
|
+
const fetchMock = vi.spyOn(globalThis, "fetch").mockResolvedValue(new Response(JSON.stringify(VALID_MANIFEST), { status: 200 }));
|
|
30
|
+
await fetchUCPManifest("https://shop.example.com");
|
|
31
|
+
await fetchUCPManifest("https://shop.example.com");
|
|
32
|
+
expect(fetchMock).toHaveBeenCalledOnce();
|
|
33
|
+
});
|
|
34
|
+
it("normalizes trailing slash", async () => {
|
|
35
|
+
const fetchMock = vi.spyOn(globalThis, "fetch").mockResolvedValue(new Response(JSON.stringify(VALID_MANIFEST), { status: 200 }));
|
|
36
|
+
await fetchUCPManifest("https://shop.example.com/");
|
|
37
|
+
await fetchUCPManifest("https://shop.example.com");
|
|
38
|
+
expect(fetchMock).toHaveBeenCalledOnce();
|
|
39
|
+
});
|
|
40
|
+
it("returns null on network error", async () => {
|
|
41
|
+
vi.spyOn(globalThis, "fetch").mockRejectedValue(new Error("network error"));
|
|
42
|
+
const result = await fetchUCPManifest("https://shop.example.com");
|
|
43
|
+
expect(result).toBeNull();
|
|
44
|
+
});
|
|
45
|
+
it("returns null on non-200 response", async () => {
|
|
46
|
+
vi.spyOn(globalThis, "fetch").mockResolvedValue(new Response("Not Found", { status: 404 }));
|
|
47
|
+
const result = await fetchUCPManifest("https://shop.example.com");
|
|
48
|
+
expect(result).toBeNull();
|
|
49
|
+
});
|
|
50
|
+
it("negative-caches failed fetches", async () => {
|
|
51
|
+
const fetchMock = vi.spyOn(globalThis, "fetch").mockRejectedValue(new Error("down"));
|
|
52
|
+
await fetchUCPManifest("https://down.example.com");
|
|
53
|
+
await fetchUCPManifest("https://down.example.com");
|
|
54
|
+
expect(fetchMock).toHaveBeenCalledOnce();
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
describe("findPayClawCapability", () => {
|
|
58
|
+
it("finds capability in array-wrapped format", () => {
|
|
59
|
+
const result = findPayClawCapability(VALID_MANIFEST);
|
|
60
|
+
expect(result).toEqual({ version: "2026-01-11", required: false });
|
|
61
|
+
});
|
|
62
|
+
it("finds capability in plain object format", () => {
|
|
63
|
+
const manifest = {
|
|
64
|
+
capabilities: {
|
|
65
|
+
"io.payclaw.common.identity": {
|
|
66
|
+
version: "2026-01-11",
|
|
67
|
+
config: { required: true },
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
const result = findPayClawCapability(manifest);
|
|
72
|
+
expect(result).toEqual({ version: "2026-01-11", required: true });
|
|
73
|
+
});
|
|
74
|
+
it("returns null when extension not present", () => {
|
|
75
|
+
const result = findPayClawCapability({ capabilities: {} });
|
|
76
|
+
expect(result).toBeNull();
|
|
77
|
+
});
|
|
78
|
+
it("returns null when no capabilities", () => {
|
|
79
|
+
const result = findPayClawCapability({});
|
|
80
|
+
expect(result).toBeNull();
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
describe("isVersionCompatible", () => {
|
|
84
|
+
it("accepts 2026-01-11", () => {
|
|
85
|
+
expect(isVersionCompatible("2026-01-11")).toBe(true);
|
|
86
|
+
});
|
|
87
|
+
it("rejects unknown version", () => {
|
|
88
|
+
expect(isVersionCompatible("2025-01-01")).toBe(false);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
//# sourceMappingURL=ucp-manifest.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ucp-manifest.test.js","sourceRoot":"","sources":["../../src/lib/ucp-manifest.test.ts"],"names":[],"mappings":"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"}
|
package/dist/sampling.d.ts
CHANGED
|
@@ -12,6 +12,10 @@ export declare function initSampling(server: Server): void;
|
|
|
12
12
|
export declare function onTripStarted(token: string, merchant: string): void;
|
|
13
13
|
export declare function onIdentityPresented(token: string, merchant: string): void;
|
|
14
14
|
export declare function onServerClose(): void;
|
|
15
|
+
/** Test-only: reset state between tests. No-op when VITEST not set. */
|
|
16
|
+
export declare function resetSamplingState(): void;
|
|
17
|
+
/** Test-only: get trip for assertions. Returns undefined when VITEST not set. */
|
|
18
|
+
export declare function getActiveTrip(token: string): ActiveTrip | undefined;
|
|
15
19
|
/**
|
|
16
20
|
* Report outcome from agent (payclaw_reportBadgeOutcome tool).
|
|
17
21
|
* Agent-only path â no sampling prompt. Resolves trip and POSTs to API.
|
package/dist/sampling.js
CHANGED
|
@@ -20,7 +20,9 @@ export function initSampling(server) {
|
|
|
20
20
|
samplingAvailable = useExtendedAuth; // Will catch errors on first attempt if enabled
|
|
21
21
|
if (!reaperStarted) {
|
|
22
22
|
reaperStarted = true;
|
|
23
|
-
|
|
23
|
+
if (process.env.VITEST !== "true") {
|
|
24
|
+
setInterval(() => reapStaleTrips(), REAPER_INTERVAL_MS);
|
|
25
|
+
}
|
|
24
26
|
}
|
|
25
27
|
}
|
|
26
28
|
export function onTripStarted(token, merchant) {
|
|
@@ -169,27 +171,54 @@ async function reportOutcome(token, outcome, merchant, detail) {
|
|
|
169
171
|
}
|
|
170
172
|
function reapStaleTrips() {
|
|
171
173
|
const now = Date.now();
|
|
174
|
+
let reaped = 0;
|
|
172
175
|
for (const [token, trip] of activeTrips) {
|
|
173
176
|
if (now - trip.startedAt > STALE_TRIP_MS) {
|
|
177
|
+
const ageMin = Math.round((now - trip.startedAt) / 60000);
|
|
174
178
|
if (trip.presented && !trip.outcome) {
|
|
179
|
+
process.stderr.write(`[PayClaw] Reaped stale trip: ${token.slice(0, 10)}** (${trip.merchant.slice(0, 64)}, age: ${ageMin}m)\n`);
|
|
175
180
|
resolveTrip(token, "inconclusive", "stale_trip_reaped");
|
|
181
|
+
reaped++;
|
|
176
182
|
}
|
|
177
183
|
else {
|
|
178
184
|
activeTrips.delete(token);
|
|
185
|
+
reaped++;
|
|
179
186
|
}
|
|
180
187
|
}
|
|
181
188
|
}
|
|
189
|
+
if (activeTrips.size > 0 || reaped > 0) {
|
|
190
|
+
process.stderr.write(`[PayClaw] Active trips: ${activeTrips.size} | Reaped: ${reaped}\n`);
|
|
191
|
+
}
|
|
182
192
|
}
|
|
183
193
|
// Called when MCP client disconnects
|
|
184
194
|
export function onServerClose() {
|
|
185
195
|
for (const [token, trip] of activeTrips) {
|
|
186
196
|
if (trip.presented && !trip.outcome) {
|
|
187
|
-
// Agent
|
|
188
|
-
resolveTrip(token, "
|
|
197
|
+
// Agent disconnected â outcome unknown
|
|
198
|
+
resolveTrip(token, "inconclusive", "server_close");
|
|
189
199
|
}
|
|
190
200
|
}
|
|
191
201
|
activeTrips.clear();
|
|
192
202
|
}
|
|
203
|
+
/** Test-only: reset state between tests. No-op when VITEST not set. */
|
|
204
|
+
export function resetSamplingState() {
|
|
205
|
+
if (process.env.VITEST !== "true")
|
|
206
|
+
return;
|
|
207
|
+
for (const trip of activeTrips.values()) {
|
|
208
|
+
if (trip.samplingTimer)
|
|
209
|
+
clearTimeout(trip.samplingTimer);
|
|
210
|
+
}
|
|
211
|
+
activeTrips.clear();
|
|
212
|
+
serverRef = null;
|
|
213
|
+
samplingAvailable = true;
|
|
214
|
+
reaperStarted = false;
|
|
215
|
+
}
|
|
216
|
+
/** Test-only: get trip for assertions. Returns undefined when VITEST not set. */
|
|
217
|
+
export function getActiveTrip(token) {
|
|
218
|
+
if (process.env.VITEST !== "true")
|
|
219
|
+
return undefined;
|
|
220
|
+
return activeTrips.get(token);
|
|
221
|
+
}
|
|
193
222
|
/**
|
|
194
223
|
* Report outcome from agent (payclaw_reportBadgeOutcome tool).
|
|
195
224
|
* Agent-only path â no sampling prompt. Resolves trip and POSTs to API.
|
|
@@ -200,13 +229,21 @@ export function reportOutcomeFromAgent(token, merchant, outcome) {
|
|
|
200
229
|
resolveTrip(token, outcome, "agent_reported");
|
|
201
230
|
return;
|
|
202
231
|
}
|
|
203
|
-
// Token may be from before restart â try to find trip by merchant
|
|
232
|
+
// Token may be from before restart â try to find a unique trip by merchant
|
|
233
|
+
let matchToken = null;
|
|
234
|
+
let matchCount = 0;
|
|
204
235
|
for (const [t, trip] of activeTrips) {
|
|
205
236
|
if (trip.merchant === merchant && trip.presented && !trip.outcome) {
|
|
206
|
-
|
|
207
|
-
|
|
237
|
+
matchToken = t;
|
|
238
|
+
matchCount++;
|
|
239
|
+
if (matchCount > 1)
|
|
240
|
+
break;
|
|
208
241
|
}
|
|
209
242
|
}
|
|
243
|
+
if (matchCount === 1 && matchToken) {
|
|
244
|
+
resolveTrip(matchToken, outcome, "agent_reported");
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
210
247
|
// No matching trip â still report to API so outcome is recorded
|
|
211
248
|
reportOutcome(token, outcome, merchant, "agent_reported").catch((err) => {
|
|
212
249
|
process.stderr.write(`[BADGE] Failed to report outcome: ${err}\n`);
|
package/dist/sampling.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sampling.js","sourceRoot":"","sources":["../src/sampling.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sampling.js","sourceRoot":"","sources":["../src/sampling.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach, } from "vitest";
|
|
2
|
+
import { onTripStarted, onIdentityPresented, onServerClose, resetSamplingState, getActiveTrip, reportOutcomeFromAgent, } from "./sampling.js";
|
|
3
|
+
import * as storage from "./lib/storage.js";
|
|
4
|
+
vi.mock("./lib/storage.js", () => ({
|
|
5
|
+
getStoredConsentKey: vi.fn(),
|
|
6
|
+
storeConsentKey: vi.fn(),
|
|
7
|
+
}));
|
|
8
|
+
vi.mock("./api/client.js", () => ({
|
|
9
|
+
getBaseUrl: vi.fn().mockReturnValue("https://payclaw.io"),
|
|
10
|
+
}));
|
|
11
|
+
describe("sampling â multi-merchant trip lifecycle", () => {
|
|
12
|
+
let originalVitest;
|
|
13
|
+
const mockFetch = vi.fn();
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
originalVitest = process.env.VITEST;
|
|
16
|
+
process.env.VITEST = "true";
|
|
17
|
+
vi.stubGlobal("fetch", mockFetch);
|
|
18
|
+
mockFetch.mockResolvedValue({ ok: true });
|
|
19
|
+
vi.mocked(storage.getStoredConsentKey).mockReturnValue("pk_test_xxx");
|
|
20
|
+
resetSamplingState();
|
|
21
|
+
});
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
vi.useRealTimers();
|
|
24
|
+
resetSamplingState();
|
|
25
|
+
vi.unstubAllGlobals();
|
|
26
|
+
if (originalVitest !== undefined) {
|
|
27
|
+
process.env.VITEST = originalVitest;
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
delete process.env.VITEST;
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
it("starting trip B resolves presented trip A as agent_moved_to_new_merchant", () => {
|
|
34
|
+
onTripStarted("tok_a", "amazon.com");
|
|
35
|
+
onIdentityPresented("tok_a", "amazon.com");
|
|
36
|
+
// Trip A is active and presented
|
|
37
|
+
expect(getActiveTrip("tok_a")).toBeDefined();
|
|
38
|
+
expect(getActiveTrip("tok_a").presented).toBe(true);
|
|
39
|
+
// Start trip B at a different merchant
|
|
40
|
+
onTripStarted("tok_b", "target.com");
|
|
41
|
+
// Trip A should be resolved and evicted (agent moved on)
|
|
42
|
+
expect(getActiveTrip("tok_a")).toBeUndefined();
|
|
43
|
+
// Trip B should be active
|
|
44
|
+
const tripB = getActiveTrip("tok_b");
|
|
45
|
+
expect(tripB).toBeDefined();
|
|
46
|
+
expect(tripB.merchant).toBe("target.com");
|
|
47
|
+
expect(tripB.presented).toBe(false);
|
|
48
|
+
});
|
|
49
|
+
it("trip B can be presented and resolved after trip A is auto-resolved", () => {
|
|
50
|
+
onTripStarted("tok_a", "amazon.com");
|
|
51
|
+
onIdentityPresented("tok_a", "amazon.com");
|
|
52
|
+
onTripStarted("tok_b", "target.com");
|
|
53
|
+
onIdentityPresented("tok_b", "target.com");
|
|
54
|
+
const tripB = getActiveTrip("tok_b");
|
|
55
|
+
expect(tripB).toBeDefined();
|
|
56
|
+
expect(tripB.presented).toBe(true);
|
|
57
|
+
// Resolve trip B via agent report
|
|
58
|
+
reportOutcomeFromAgent("tok_b", "target.com", "accepted");
|
|
59
|
+
expect(getActiveTrip("tok_b")).toBeUndefined();
|
|
60
|
+
});
|
|
61
|
+
it("non-presented trip A is NOT resolved when trip B starts", () => {
|
|
62
|
+
onTripStarted("tok_a", "amazon.com");
|
|
63
|
+
// Never call onIdentityPresented for tok_a
|
|
64
|
+
onTripStarted("tok_b", "target.com");
|
|
65
|
+
// Trip A should still exist (not presented, so agent_moved logic skips it)
|
|
66
|
+
expect(getActiveTrip("tok_a")).toBeDefined();
|
|
67
|
+
expect(getActiveTrip("tok_b")).toBeDefined();
|
|
68
|
+
});
|
|
69
|
+
it("same-merchant trip restart does not resolve existing trip", () => {
|
|
70
|
+
onTripStarted("tok_a", "amazon.com");
|
|
71
|
+
onIdentityPresented("tok_a", "amazon.com");
|
|
72
|
+
// Start another trip at SAME merchant â should NOT resolve trip A
|
|
73
|
+
onTripStarted("tok_b", "amazon.com");
|
|
74
|
+
// Trip A still active (same merchant = not "moved to new merchant")
|
|
75
|
+
expect(getActiveTrip("tok_a")).toBeDefined();
|
|
76
|
+
expect(getActiveTrip("tok_b")).toBeDefined();
|
|
77
|
+
});
|
|
78
|
+
it("three-merchant chain resolves each previous trip correctly", () => {
|
|
79
|
+
onTripStarted("tok_1", "amazon.com");
|
|
80
|
+
onIdentityPresented("tok_1", "amazon.com");
|
|
81
|
+
onTripStarted("tok_2", "target.com");
|
|
82
|
+
expect(getActiveTrip("tok_1")).toBeUndefined(); // resolved
|
|
83
|
+
onIdentityPresented("tok_2", "target.com");
|
|
84
|
+
onTripStarted("tok_3", "walmart.com");
|
|
85
|
+
expect(getActiveTrip("tok_2")).toBeUndefined(); // resolved
|
|
86
|
+
onIdentityPresented("tok_3", "walmart.com");
|
|
87
|
+
// Only tok_3 should remain
|
|
88
|
+
expect(getActiveTrip("tok_3")).toBeDefined();
|
|
89
|
+
expect(getActiveTrip("tok_3").merchant).toBe("walmart.com");
|
|
90
|
+
});
|
|
91
|
+
it("reportOutcomeFromAgent falls back to merchant search when token unknown", () => {
|
|
92
|
+
onTripStarted("tok_a", "amazon.com");
|
|
93
|
+
onIdentityPresented("tok_a", "amazon.com");
|
|
94
|
+
// Report with a different token but matching merchant
|
|
95
|
+
reportOutcomeFromAgent("unknown_tok", "amazon.com", "accepted");
|
|
96
|
+
// Trip should be resolved via merchant search fallback
|
|
97
|
+
expect(getActiveTrip("tok_a")).toBeUndefined();
|
|
98
|
+
});
|
|
99
|
+
it("reportOutcomeFromAgent with no matching trip still POSTs to API", () => {
|
|
100
|
+
mockFetch.mockClear();
|
|
101
|
+
reportOutcomeFromAgent("orphan_tok", "orphan.com", "denied");
|
|
102
|
+
// Should have called fetch to POST directly
|
|
103
|
+
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
104
|
+
const [url, opts] = mockFetch.mock.calls[0];
|
|
105
|
+
expect(url).toContain("/api/badge/report");
|
|
106
|
+
expect(JSON.parse(opts.body)).toMatchObject({
|
|
107
|
+
verification_token: "orphan_tok",
|
|
108
|
+
merchant: "orphan.com",
|
|
109
|
+
outcome: "denied",
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
it("onServerClose resolves all presented trips as inconclusive", () => {
|
|
113
|
+
onTripStarted("tok_a", "amazon.com");
|
|
114
|
+
onIdentityPresented("tok_a", "amazon.com");
|
|
115
|
+
onTripStarted("tok_b", "amazon.com"); // Same merchant â no auto-resolve
|
|
116
|
+
onIdentityPresented("tok_b", "amazon.com");
|
|
117
|
+
onServerClose();
|
|
118
|
+
expect(getActiveTrip("tok_a")).toBeUndefined();
|
|
119
|
+
expect(getActiveTrip("tok_b")).toBeUndefined();
|
|
120
|
+
});
|
|
121
|
+
it("reportOutcomeFromAgent with ambiguous merchant match falls through to API POST", () => {
|
|
122
|
+
mockFetch.mockClear();
|
|
123
|
+
// Two trips at same merchant, both presented
|
|
124
|
+
onTripStarted("tok_a", "amazon.com");
|
|
125
|
+
onIdentityPresented("tok_a", "amazon.com");
|
|
126
|
+
onTripStarted("tok_b", "amazon.com");
|
|
127
|
+
onIdentityPresented("tok_b", "amazon.com");
|
|
128
|
+
mockFetch.mockClear();
|
|
129
|
+
reportOutcomeFromAgent("unknown_tok", "amazon.com", "accepted");
|
|
130
|
+
// Should NOT resolve either trip (ambiguous) â falls through to direct POST
|
|
131
|
+
expect(getActiveTrip("tok_a")).toBeDefined();
|
|
132
|
+
expect(getActiveTrip("tok_b")).toBeDefined();
|
|
133
|
+
expect(mockFetch).toHaveBeenCalledTimes(1);
|
|
134
|
+
expect(String(mockFetch.mock.calls[0][0])).toContain("/api/badge/report");
|
|
135
|
+
});
|
|
136
|
+
it("API report includes correct event_type for outcome", () => {
|
|
137
|
+
mockFetch.mockClear();
|
|
138
|
+
onTripStarted("tok_a", "amazon.com");
|
|
139
|
+
onIdentityPresented("tok_a", "amazon.com");
|
|
140
|
+
// Move to new merchant â resolves trip A as "accepted"
|
|
141
|
+
onTripStarted("tok_b", "target.com");
|
|
142
|
+
// Check the fetch call for trip A resolution
|
|
143
|
+
const reportCalls = mockFetch.mock.calls.filter((c) => String(c[0]).includes("/api/badge/report"));
|
|
144
|
+
expect(reportCalls.length).toBeGreaterThanOrEqual(1);
|
|
145
|
+
const body = JSON.parse(reportCalls[0][1].body);
|
|
146
|
+
expect(body.event_type).toBe("trip_success");
|
|
147
|
+
expect(body.detail).toBe("agent_moved_to_new_merchant");
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
//# sourceMappingURL=sampling.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sampling.test.js","sourceRoot":"","sources":["../src/sampling.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -14,13 +14,21 @@ export interface IdentityResult {
|
|
|
14
14
|
message?: string;
|
|
15
15
|
/** Internal: activation flow â agent should display this to user */
|
|
16
16
|
activation_required?: boolean;
|
|
17
|
+
/** UCP: merchant supports io.payclaw.common.identity */
|
|
18
|
+
ucpCapable?: boolean;
|
|
19
|
+
/** UCP: merchant requires PayClaw credential */
|
|
20
|
+
requiredByMerchant?: boolean;
|
|
21
|
+
/** UCP: checkout patch to merge into checkout payload */
|
|
22
|
+
checkoutPatch?: Record<string, unknown>;
|
|
23
|
+
/** UCP: warning when version mismatch etc. */
|
|
24
|
+
ucpWarning?: string;
|
|
17
25
|
}
|
|
18
26
|
/**
|
|
19
27
|
* Get agent identity token â Badge by PayClaw.
|
|
20
28
|
* When no consent key exists: initiates device flow, returns activation instructions,
|
|
21
29
|
* polls in background. On approval, stores key. Next call uses stored key.
|
|
22
30
|
*/
|
|
23
|
-
export declare function getAgentIdentity(merchant?: string): Promise<IdentityResult>;
|
|
31
|
+
export declare function getAgentIdentity(merchant?: string, merchantUrl?: string): Promise<IdentityResult>;
|
|
24
32
|
/**
|
|
25
33
|
* Format identity result as human-readable text for CLI/agent display.
|
|
26
34
|
*/
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
// Canonical: badge-server | Synced: PRD-3 | mcp-server syncs from here
|
|
1
2
|
import * as api from "../api/client.js";
|
|
2
3
|
import { getStoredConsentKey } from "../lib/storage.js";
|
|
3
4
|
import { initiateDeviceAuth, pollForApproval } from "../lib/device-auth.js";
|
|
5
|
+
import { fetchUCPManifest, findPayClawCapability, isVersionCompatible } from "../lib/ucp-manifest.js";
|
|
4
6
|
const MOCK_TOKEN_PREFIX = "pc_v1_sand";
|
|
7
|
+
/** Must match the kid in the JWKS published at payclaw.io/.well-known/ucp (BUILD 3 / PRD-1) */
|
|
8
|
+
const PAYCLAW_KID = "payclaw-badge-v1";
|
|
5
9
|
function getMockDisclosure(scope = "BROWSE") {
|
|
6
10
|
return `This agent is using PayClaw Badge: Agent Intent for Ecommerce. The principal user token is a SHA-256 starting ${MOCK_TOKEN_PREFIX}***. Intent has been expressly user-authorized for this session for [${scope}]. For inquiries, please message agent_identity@payclaw.io`;
|
|
7
11
|
}
|
|
@@ -22,7 +26,7 @@ function identityFromOAuthToken(token, _assuranceLevel, merchant, assumeVerified
|
|
|
22
26
|
principal_verified: assumeVerified,
|
|
23
27
|
mfa_confirmed: false,
|
|
24
28
|
spend_available: false,
|
|
25
|
-
spend_cta: "
|
|
29
|
+
spend_cta: "For agent payments, use @payclaw/mcp-server â payclaw.io/docs",
|
|
26
30
|
merchant,
|
|
27
31
|
};
|
|
28
32
|
}
|
|
@@ -32,28 +36,69 @@ let pendingActivation = null;
|
|
|
32
36
|
* When no consent key exists: initiates device flow, returns activation instructions,
|
|
33
37
|
* polls in background. On approval, stores key. Next call uses stored key.
|
|
34
38
|
*/
|
|
35
|
-
export async function getAgentIdentity(merchant) {
|
|
39
|
+
export async function getAgentIdentity(merchant, merchantUrl) {
|
|
36
40
|
const consentKey = getStoredConsentKey();
|
|
41
|
+
let result;
|
|
37
42
|
// Backward compat: PAYCLAW_API_KEY set â use it, device flow never triggers
|
|
38
43
|
if (consentKey && process.env.PAYCLAW_API_KEY) {
|
|
39
|
-
|
|
44
|
+
result = await callWithKey(consentKey, merchant);
|
|
40
45
|
}
|
|
41
|
-
|
|
42
|
-
|
|
46
|
+
else if (!consentKey) {
|
|
47
|
+
// No key: initiate device flow (reuse pending to avoid duplicate pollers)
|
|
43
48
|
if (pendingActivation)
|
|
44
49
|
return pendingActivation;
|
|
45
50
|
const p = startActivationFlow(merchant);
|
|
46
51
|
pendingActivation = p;
|
|
47
52
|
try {
|
|
48
|
-
|
|
49
|
-
return result;
|
|
53
|
+
result = await p;
|
|
50
54
|
}
|
|
51
55
|
finally {
|
|
52
56
|
pendingActivation = null;
|
|
53
57
|
}
|
|
54
58
|
}
|
|
55
|
-
|
|
56
|
-
|
|
59
|
+
else {
|
|
60
|
+
// Key from file/memory (OAuth token from device flow)
|
|
61
|
+
result = await callWithOAuthToken(consentKey, merchant);
|
|
62
|
+
}
|
|
63
|
+
// UCP enrichment: check merchant manifest when merchantUrl provided
|
|
64
|
+
// Skip for mock/sandbox tokens â don't generate checkoutPatch for non-real credentials
|
|
65
|
+
if (merchantUrl &&
|
|
66
|
+
result.verification_token &&
|
|
67
|
+
!result.activation_required &&
|
|
68
|
+
!result.verification_token.startsWith(MOCK_TOKEN_PREFIX)) {
|
|
69
|
+
result = await enrichWithUCP(result, merchantUrl);
|
|
70
|
+
}
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
73
|
+
async function enrichWithUCP(result, merchantUrl) {
|
|
74
|
+
const manifest = await fetchUCPManifest(merchantUrl);
|
|
75
|
+
if (!manifest) {
|
|
76
|
+
return { ...result, ucpCapable: false };
|
|
77
|
+
}
|
|
78
|
+
const capability = findPayClawCapability(manifest);
|
|
79
|
+
if (!capability) {
|
|
80
|
+
return { ...result, ucpCapable: false };
|
|
81
|
+
}
|
|
82
|
+
if (!isVersionCompatible(capability.version)) {
|
|
83
|
+
return {
|
|
84
|
+
...result,
|
|
85
|
+
ucpCapable: false,
|
|
86
|
+
ucpWarning: `version mismatch: merchant declares ${capability.version}`,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
const checkoutPatch = {
|
|
90
|
+
"io.payclaw.common.identity": {
|
|
91
|
+
token: result.verification_token,
|
|
92
|
+
kid: PAYCLAW_KID,
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
return {
|
|
96
|
+
...result,
|
|
97
|
+
ucpCapable: true,
|
|
98
|
+
requiredByMerchant: capability.required,
|
|
99
|
+
checkoutPatch,
|
|
100
|
+
instructions: "Merge checkoutPatch into your checkout payload, then call payclaw_reportBadgePresented with the merchantUrl and token.",
|
|
101
|
+
};
|
|
57
102
|
}
|
|
58
103
|
async function callWithKey(apiKey, merchant) {
|
|
59
104
|
if (!api.isApiMode()) {
|
|
@@ -171,6 +216,18 @@ export function formatIdentityResponse(r) {
|
|
|
171
216
|
lines.push(` Merchant: ${r.merchant}`);
|
|
172
217
|
}
|
|
173
218
|
lines.push(` Status: ACTIVE`, ` Trust: ${r.trust_url || "https://payclaw.io/trust"}`, ``, ` Disclosure (present to merchants):`, ` "${r.agent_disclosure}"`);
|
|
219
|
+
if (r.ucpCapable) {
|
|
220
|
+
lines.push(``, ` UCP: Supported`, ` Required: ${r.requiredByMerchant ? "Yes" : "No"}`);
|
|
221
|
+
if (r.instructions) {
|
|
222
|
+
lines.push(` Action: ${r.instructions}`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
else if (r.ucpCapable === false) {
|
|
226
|
+
lines.push(``, ` UCP: Not supported`);
|
|
227
|
+
if (r.ucpWarning) {
|
|
228
|
+
lines.push(` Warning: ${r.ucpWarning}`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
174
231
|
if (r.spend_available) {
|
|
175
232
|
lines.push(``, ` đŗ Spend is available â call payclaw_getCard when ready to pay.`);
|
|
176
233
|
}
|
|
@@ -178,7 +235,7 @@ export function formatIdentityResponse(r) {
|
|
|
178
235
|
lines.push(``, ` âšī¸ ${r.spend_cta}`);
|
|
179
236
|
}
|
|
180
237
|
else {
|
|
181
|
-
lines.push(``, ` âšī¸ Identity only.
|
|
238
|
+
lines.push(``, ` âšī¸ Identity only. For agent payments, use @payclaw/mcp-server â payclaw.io/docs`);
|
|
182
239
|
}
|
|
183
240
|
return lines.join("\n");
|
|
184
241
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"getAgentIdentity.js","sourceRoot":"","sources":["../../src/tools/getAgentIdentity.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,GAAG,MAAM,kBAAkB,CAAC;AACxC,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"getAgentIdentity.js","sourceRoot":"","sources":["../../src/tools/getAgentIdentity.ts"],"names":[],"mappings":"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"}
|
package/dist/verify.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* verify() â merchant-side Badge JWT verification.
|
|
3
|
+
*
|
|
4
|
+
* Import: `import { verify } from '@payclaw/badge/verify'`
|
|
5
|
+
*
|
|
6
|
+
* Fetches PayClaw's JWKS once, caches it, verifies ES256 signature locally.
|
|
7
|
+
* Returns decoded identity or null. Never throws.
|
|
8
|
+
* Works in Node.js 18+ and Cloudflare Workers (Web Crypto API only).
|
|
9
|
+
* Zero runtime dependencies.
|
|
10
|
+
*/
|
|
11
|
+
export interface PayClawIdentity {
|
|
12
|
+
userId: string;
|
|
13
|
+
agentId: string;
|
|
14
|
+
intent: string;
|
|
15
|
+
scopes: string[];
|
|
16
|
+
merchantDomain?: string;
|
|
17
|
+
issuedAt: number;
|
|
18
|
+
expiresAt: number;
|
|
19
|
+
kid: string;
|
|
20
|
+
}
|
|
21
|
+
export interface VerifyOptions {
|
|
22
|
+
/** JWKS source URL. Default: 'https://payclaw.io/.well-known/ucp' */
|
|
23
|
+
jwksUri?: string;
|
|
24
|
+
/** Cache TTL in ms. Default: 3600000 (1 hour) */
|
|
25
|
+
cacheTtlMs?: number;
|
|
26
|
+
/** Clock tolerance in seconds for exp check. Default: 30 */
|
|
27
|
+
clockToleranceSec?: number;
|
|
28
|
+
}
|
|
29
|
+
export declare function verify(token: string, options?: VerifyOptions): Promise<PayClawIdentity | null>;
|
|
30
|
+
/**
|
|
31
|
+
* Reset the JWKS cache. Useful for testing.
|
|
32
|
+
* @internal
|
|
33
|
+
*/
|
|
34
|
+
export declare function _resetCache(): void;
|