agent402-tollbooth 0.6.1 → 0.6.2
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/package.json +1 -1
- package/replay.js +23 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent402-tollbooth",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"description": "Open-source, self-hostable x402 pay-per-crawl tollbooth: charge AI bots per request (USDC via x402 — or USDG on Robinhood Chain — or free proof-of-work) while humans browse free. Drop-in Express middleware or reverse proxy — no Cloudflare, no Stripe, no signup.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
package/replay.js
CHANGED
|
@@ -115,12 +115,29 @@ export function redisReplayStore(client, { prefix = "tollbooth:pow:" } = {}) {
|
|
|
115
115
|
// Detection above is a heuristic; this is the observation. Two claims of a
|
|
116
116
|
// throwaway key must produce a win and then a refusal. Anything else means
|
|
117
117
|
// single-use is not working, and the store refuses rather than pretending.
|
|
118
|
+
// Tri-state on PURPOSE: null = not yet observed, true = proven, false = proven
|
|
119
|
+
// BROKEN and permanently refused.
|
|
120
|
+
//
|
|
121
|
+
// A verdict of false must keep throwing on EVERY later call, not just the one
|
|
122
|
+
// that discovered it. Memoising false and returning it as a resolved value
|
|
123
|
+
// would make the store refuse exactly once and then grant every replay
|
|
124
|
+
// forever - the precise failure this self-test exists to prevent, reached one
|
|
125
|
+
// request later. A caller that solves one challenge could then replay that
|
|
126
|
+
// token for its whole TTL.
|
|
127
|
+
//
|
|
128
|
+
// A probe that THROWS (Redis unreachable) leaves the verdict at null so the
|
|
129
|
+
// next call re-probes: a transient outage must not be remembered as a verdict.
|
|
118
130
|
let nxProven = null;
|
|
131
|
+
const NX_BROKEN =
|
|
132
|
+
"redisReplayStore: SET NX is not honoured by this client, so proof-of-work single-use cannot be enforced. " +
|
|
133
|
+
"The gate refuses rather than granting replays. Pass a client whose SET supports NX, or supply your own { claim(token, expiresAtMs) } store.";
|
|
119
134
|
const proveNx = async () => {
|
|
120
|
-
if (nxProven
|
|
135
|
+
if (nxProven === true) return true;
|
|
136
|
+
if (nxProven === false) throw new Error(NX_BROKEN);
|
|
121
137
|
const probe = `${prefix}__nxselftest__${Math.random().toString(36).slice(2)}${Date.now()}`;
|
|
122
138
|
const px = 5000;
|
|
123
139
|
const set = (k) => (isIoredis ? client.set(k, "1", "PX", px, "NX") : client.set(k, "1", { NX: true, PX: px }));
|
|
140
|
+
// Deliberately NOT wrapped: a throw here leaves nxProven null (re-probe).
|
|
124
141
|
const first = await set(probe);
|
|
125
142
|
const second = await set(probe);
|
|
126
143
|
const firstWon = first === "OK" || first === true;
|
|
@@ -128,15 +145,16 @@ export function redisReplayStore(client, { prefix = "tollbooth:pow:" } = {}) {
|
|
|
128
145
|
nxProven = firstWon && secondRefused;
|
|
129
146
|
if (!nxProven) {
|
|
130
147
|
throw new Error(
|
|
131
|
-
|
|
132
|
-
"Proof-of-work single-use cannot be enforced against this client, so the gate refuses rather than granting replays."
|
|
148
|
+
`${NX_BROKEN} (first claim won: ${firstWon}, second refused: ${secondRefused})`
|
|
133
149
|
);
|
|
134
150
|
}
|
|
135
|
-
return
|
|
151
|
+
return true;
|
|
136
152
|
};
|
|
137
153
|
return {
|
|
138
154
|
claim: async (token, expiresAtMs) => {
|
|
139
|
-
|
|
155
|
+
// Respect the verdict: proveNx throws when unproven, but assert anyway so
|
|
156
|
+
// no future refactor can turn "unproven" into a silently ignored value.
|
|
157
|
+
if ((await proveNx()) !== true) throw new Error(NX_BROKEN);
|
|
140
158
|
const key = prefix + token;
|
|
141
159
|
// Floor of 1ms: Redis rejects a non-positive PX. pow.js has already
|
|
142
160
|
// refused anything actually expired, so a tiny window here just means the
|