@adcp/sdk 7.8.0 → 7.9.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.
Files changed (57) hide show
  1. package/AGENTS.md +2 -0
  2. package/bin/adcp.js +3 -1
  3. package/dist/lib/core/AgentClient.d.ts.map +1 -1
  4. package/dist/lib/core/SingleAgentClient.d.ts +8 -0
  5. package/dist/lib/core/SingleAgentClient.d.ts.map +1 -1
  6. package/dist/lib/core/SingleAgentClient.js +15 -0
  7. package/dist/lib/core/SingleAgentClient.js.map +1 -1
  8. package/dist/lib/index.d.ts +2 -2
  9. package/dist/lib/index.d.ts.map +1 -1
  10. package/dist/lib/index.js +8 -7
  11. package/dist/lib/index.js.map +1 -1
  12. package/dist/lib/protocols/mcp.d.ts.map +1 -1
  13. package/dist/lib/protocols/mcp.js +63 -4
  14. package/dist/lib/protocols/mcp.js.map +1 -1
  15. package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
  16. package/dist/lib/server/ctx-metadata/backends/pg.d.ts +12 -3
  17. package/dist/lib/server/ctx-metadata/backends/pg.d.ts.map +1 -1
  18. package/dist/lib/server/ctx-metadata/backends/pg.js +59 -23
  19. package/dist/lib/server/ctx-metadata/backends/pg.js.map +1 -1
  20. package/dist/lib/server/ctx-metadata/backends/redis.d.ts +163 -0
  21. package/dist/lib/server/ctx-metadata/backends/redis.d.ts.map +1 -0
  22. package/dist/lib/server/ctx-metadata/backends/redis.js +204 -0
  23. package/dist/lib/server/ctx-metadata/backends/redis.js.map +1 -0
  24. package/dist/lib/server/ctx-metadata/index.d.ts +2 -0
  25. package/dist/lib/server/ctx-metadata/index.d.ts.map +1 -1
  26. package/dist/lib/server/ctx-metadata/index.js +3 -1
  27. package/dist/lib/server/ctx-metadata/index.js.map +1 -1
  28. package/dist/lib/server/idempotency/backends/redis.d.ts +171 -0
  29. package/dist/lib/server/idempotency/backends/redis.d.ts.map +1 -0
  30. package/dist/lib/server/idempotency/backends/redis.js +253 -0
  31. package/dist/lib/server/idempotency/backends/redis.js.map +1 -0
  32. package/dist/lib/server/idempotency/index.d.ts +2 -0
  33. package/dist/lib/server/idempotency/index.d.ts.map +1 -1
  34. package/dist/lib/server/idempotency/index.js +3 -1
  35. package/dist/lib/server/idempotency/index.js.map +1 -1
  36. package/dist/lib/server/idempotency/store.d.ts +44 -0
  37. package/dist/lib/server/idempotency/store.d.ts.map +1 -1
  38. package/dist/lib/server/idempotency/store.js.map +1 -1
  39. package/dist/lib/server/index.d.ts +4 -4
  40. package/dist/lib/server/index.d.ts.map +1 -1
  41. package/dist/lib/server/index.js +3 -1
  42. package/dist/lib/server/index.js.map +1 -1
  43. package/dist/lib/signing/redis-replay-store.d.ts +180 -0
  44. package/dist/lib/signing/redis-replay-store.d.ts.map +1 -0
  45. package/dist/lib/signing/redis-replay-store.js +270 -0
  46. package/dist/lib/signing/redis-replay-store.js.map +1 -0
  47. package/dist/lib/signing/server.d.ts +1 -0
  48. package/dist/lib/signing/server.d.ts.map +1 -1
  49. package/dist/lib/signing/server.js +4 -2
  50. package/dist/lib/signing/server.js.map +1 -1
  51. package/dist/lib/utils/redis-default-prefix-warn.d.ts +60 -0
  52. package/dist/lib/utils/redis-default-prefix-warn.d.ts.map +1 -0
  53. package/dist/lib/utils/redis-default-prefix-warn.js +95 -0
  54. package/dist/lib/utils/redis-default-prefix-warn.js.map +1 -0
  55. package/dist/lib/version.d.ts +3 -3
  56. package/dist/lib/version.js +3 -3
  57. package/package.json +8 -2
@@ -0,0 +1,180 @@
1
+ /**
2
+ * Redis-backed `ReplayStore` for distributed AdCP verifier deployments.
3
+ *
4
+ * Sister of `PostgresReplayStore`. The spec comment on `ReplayStore.insert`
5
+ * (`src/lib/signing/replay.ts`) literally names this as a canonical
6
+ * implementation: "Multi-replica adopters writing Redis / Postgres
7
+ * stores MUST implement this as a single atomic operation (e.g. Redis
8
+ * `SET NX EX`, Postgres `INSERT ... ON CONFLICT DO NOTHING RETURNING`,
9
+ * a `WATCH/MULTI/EXEC` transaction)."
10
+ *
11
+ * **Data model.** One Redis sorted set per `(keyid, scope)` pair. The
12
+ * sorted-set members are nonces; the score is `expiresAt` (unix epoch
13
+ * seconds). Sorted sets give us all three primitives the `ReplayStore`
14
+ * interface needs in single Redis commands:
15
+ *
16
+ * - `has(nonce)` → `ZSCORE` + JS-side `score > now` check.
17
+ * - `isCapHit()` → `ZCOUNT key (now +inf` (active nonces).
18
+ * - `insert()` → a Lua script that runs `ZREMRANGEBYSCORE -inf now`
19
+ * (drop expired) → `ZSCORE` (replay check) → `ZCARD` (cap check) →
20
+ * `ZADD` (insert) + `PEXPIREAT` (extend set's own TTL) atomically.
21
+ *
22
+ * **No sweeper needed.** Expired nonces are dropped at the start of
23
+ * every `insert` (the `ZREMRANGEBYSCORE` step), and Redis evicts the
24
+ * entire sorted set when its `PEXPIREAT` lapses if no further inserts
25
+ * arrive. The Postgres backend's `sweepExpiredReplays` cron has no
26
+ * Redis equivalent — eviction is automatic.
27
+ *
28
+ * **Atomicity.** The Lua script runs as a single uninterruptable
29
+ * operation against the Redis server (Redis is single-threaded; Lua
30
+ * blocks everything else for the script duration). Two concurrent
31
+ * `insert` calls with the same `(keyid, scope, nonce)` are guaranteed
32
+ * to see exactly one `'ok'` and one `'replayed'` — matches the
33
+ * `InMemoryReplayStore` semantics the spec mandates.
34
+ *
35
+ * **Redis memory policy — set this on the deployment.** Each
36
+ * `(keyid, scope)` sorted set is capped (default 100k nonces). A
37
+ * hostile signer with a valid keyid + scope can fill the set to its
38
+ * cap; legitimate fan-out (many sibling verifiers, many scopes) grows
39
+ * the number of sets. Configure your Redis with:
40
+ *
41
+ * - **`maxmemory-policy volatile-lru`** (recommended) — evicts only
42
+ * TTL'd keys. All sets this store writes carry `PEXPIREAT`, so
43
+ * they're all evictable. Safe on a shared Redis instance.
44
+ * - **`maxmemory-policy allkeys-lru`** — only on a dedicated db.
45
+ * Otherwise evicts unrelated app keys.
46
+ * - **`maxmemory-policy noeviction`** (Redis default) — fail-closed:
47
+ * writes start erroring at the memory limit; the verifier will
48
+ * throw and the middleware will return 500 (replay protection
49
+ * stays intact — never fail-open). Operationally noisy but
50
+ * never serves stale replay decisions.
51
+ *
52
+ * Eviction-on-replay implication: if `volatile-lru` evicts a sorted
53
+ * set before its members would have expired naturally, an attacker's
54
+ * later replay of one of those nonces sees a fresh set with no prior
55
+ * entry, returns `'ok'`. The cap is the primary defense; memory-policy
56
+ * eviction is a secondary recovery mode. Size Redis to keep the
57
+ * working set in memory under normal traffic and treat eviction as
58
+ * pressure to scale up, not as a feature.
59
+ *
60
+ * @example
61
+ * ```typescript
62
+ * import { createClient } from 'redis';
63
+ * import { RedisReplayStore } from '@adcp/sdk/signing/server';
64
+ *
65
+ * const client = createClient({ url: process.env.REDIS_URL });
66
+ * client.on('error', (err) => console.error('redis error', err));
67
+ * await client.connect();
68
+ *
69
+ * const replayStore = new RedisReplayStore(client);
70
+ *
71
+ * app.use(createExpressVerifier({
72
+ * capability: { ... },
73
+ * jwks,
74
+ * replayStore, // <-- shared across instances
75
+ * resolveOperation: mcpToolNameResolver,
76
+ * }));
77
+ * ```
78
+ */
79
+ import type { RedisClientType } from 'redis';
80
+ import type { ReplayInsertResult, ReplayStore } from './replay';
81
+ /**
82
+ * Escape-hatch interface for adopters not using the official `redis`
83
+ * client (node-redis v4/v5) — `ioredis`, Upstash, test doubles.
84
+ * Mirrors the methods this store calls.
85
+ *
86
+ * **Footgun: `ioredis.zscore` returns `Promise<string | null>`**, not
87
+ * `Promise<number | null>` like node-redis. The `Number(v)` coercion
88
+ * in the adapter is **mandatory** — without it, the `score > now`
89
+ * comparison inside `has()` becomes a string-vs-number compare that's
90
+ * silently wrong near unix-second boundaries (JS coerces the number
91
+ * to a string for `>`, producing lexicographic ordering — replay
92
+ * decisions break). Test doubles writing their own `zScore` MUST
93
+ * also return `number | null`.
94
+ *
95
+ * @example ioredis adapter
96
+ * ```typescript
97
+ * import Redis from 'ioredis';
98
+ * const ioredis = new Redis(process.env.REDIS_URL!);
99
+ *
100
+ * const client: ReplayRedisLikeClient = {
101
+ * eval: (script, opts) =>
102
+ * ioredis.eval(script, opts.keys.length, ...opts.keys, ...opts.arguments),
103
+ * // Number() coercion is REQUIRED — ioredis.zscore returns string|null.
104
+ * zScore: (k, m) => ioredis.zscore(k, m).then(v => (v === null ? null : Number(v))),
105
+ * zCount: (k, min, max) => ioredis.zcount(k, min, max),
106
+ * ping: () => ioredis.ping(),
107
+ * };
108
+ * ```
109
+ */
110
+ export interface ReplayRedisLikeClient {
111
+ eval(script: string, options: {
112
+ keys: string[];
113
+ arguments: string[];
114
+ }): Promise<unknown>;
115
+ zScore(key: string, member: string): Promise<number | null>;
116
+ zCount(key: string, min: number | string, max: number | string): Promise<number>;
117
+ ping(): Promise<string>;
118
+ }
119
+ /**
120
+ * Accepted client shape: either a real node-redis client (the typical
121
+ * path — pass `createClient(...)` straight in) or an adapter
122
+ * conforming to `ReplayRedisLikeClient`.
123
+ */
124
+ export type ReplayRedisBackendClient = RedisClientType<any, any, any> | ReplayRedisLikeClient;
125
+ export interface RedisReplayStoreOptions {
126
+ /**
127
+ * Key prefix prepended to every `(keyid, scope)` Redis key. Defaults
128
+ * to `"adcp:replay:"`.
129
+ *
130
+ * **Sharing a Redis db across deployments? Override this.** The
131
+ * default is fine for a dedicated Redis (or db index). Two verifier
132
+ * deployments sharing the same db with the same default prefix can
133
+ * collide on overlapping `keyid`s — set a deployment-unique prefix
134
+ * or use separate dbs.
135
+ */
136
+ keyPrefix?: string;
137
+ /**
138
+ * Max retained (unexpired) nonces per `(keyid, scope)` pair before
139
+ * `insert` returns `'rate_abuse'`. Mirrors `PostgresReplayStore`'s
140
+ * cap. Defaults to 100,000.
141
+ *
142
+ * Unlike the Postgres backend, the cap check is **strictly atomic**
143
+ * — it runs inside the same Lua script as the insert. Concurrent
144
+ * inserts at `cap - 1` can never both succeed.
145
+ */
146
+ cap?: number;
147
+ /**
148
+ * How many seconds past the latest entry's `expiresAt` to keep the
149
+ * sorted set alive in Redis. Defaults to 3600 (1h). See the
150
+ * `DEFAULT_SET_TTL_GRACE_SECONDS` comment for rationale.
151
+ */
152
+ setTtlGraceSeconds?: number;
153
+ /**
154
+ * Suppress the one-time `console.warn` at construction when the
155
+ * default `keyPrefix` is used against a node-redis client on db 0.
156
+ */
157
+ suppressDefaultPrefixWarning?: boolean;
158
+ }
159
+ export declare class RedisReplayStore implements ReplayStore {
160
+ private readonly c;
161
+ private readonly keyPrefix;
162
+ private readonly cap;
163
+ private readonly setTtlGraceSeconds;
164
+ constructor(client: ReplayRedisBackendClient, options?: RedisReplayStoreOptions);
165
+ /**
166
+ * Compose the sorted-set Redis key for a `(keyid, scope)` pair.
167
+ * Uses `\x1f` (unit separator) between segments — illegal in RFC
168
+ * 7517 JWK kids and in URLs, so no legitimate input collides.
169
+ */
170
+ private redisKey;
171
+ /**
172
+ * Probe — ping Redis. Not part of the `ReplayStore` interface; call
173
+ * before serving traffic if you want a boot-time readiness check.
174
+ */
175
+ probe(): Promise<void>;
176
+ has(keyid: string, scope: string, nonce: string, now: number): Promise<boolean>;
177
+ isCapHit(keyid: string, scope: string, now: number): Promise<boolean>;
178
+ insert(keyid: string, scope: string, nonce: string, ttlSeconds: number, now: number): Promise<ReplayInsertResult>;
179
+ }
180
+ //# sourceMappingURL=redis-replay-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redis-replay-store.d.ts","sourceRoot":"","sources":["../../../src/lib/signing/redis-replay-store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6EG;AAGH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,OAAO,CAAC;AAC7C,OAAO,KAAK,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAGhE;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,WAAW,qBAAqB;IACpC,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,EAAE,CAAC;QAAC,SAAS,EAAE,MAAM,EAAE,CAAA;KAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACzF,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC5D,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACjF,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC;CACzB;AAED;;;;GAIG;AACH,MAAM,MAAM,wBAAwB,GAAG,eAAe,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,qBAAqB,CAAC;AAe9F,MAAM,WAAW,uBAAuB;IACtC;;;;;;;;;OASG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;;;OAQG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;OAGG;IACH,4BAA4B,CAAC,EAAE,OAAO,CAAC;CACxC;AAuFD,qBAAa,gBAAiB,YAAW,WAAW;IAClD,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAwB;IAC1C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAS;gBAEhC,MAAM,EAAE,wBAAwB,EAAE,OAAO,GAAE,uBAA4B;IAwBnF;;;;OAIG;IACH,OAAO,CAAC,QAAQ;IAIhB;;;OAGG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAatB,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAU/E,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAUrE,MAAM,CACV,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,kBAAkB,CAAC;CA0B/B"}
@@ -0,0 +1,270 @@
1
+ "use strict";
2
+ /**
3
+ * Redis-backed `ReplayStore` for distributed AdCP verifier deployments.
4
+ *
5
+ * Sister of `PostgresReplayStore`. The spec comment on `ReplayStore.insert`
6
+ * (`src/lib/signing/replay.ts`) literally names this as a canonical
7
+ * implementation: "Multi-replica adopters writing Redis / Postgres
8
+ * stores MUST implement this as a single atomic operation (e.g. Redis
9
+ * `SET NX EX`, Postgres `INSERT ... ON CONFLICT DO NOTHING RETURNING`,
10
+ * a `WATCH/MULTI/EXEC` transaction)."
11
+ *
12
+ * **Data model.** One Redis sorted set per `(keyid, scope)` pair. The
13
+ * sorted-set members are nonces; the score is `expiresAt` (unix epoch
14
+ * seconds). Sorted sets give us all three primitives the `ReplayStore`
15
+ * interface needs in single Redis commands:
16
+ *
17
+ * - `has(nonce)` → `ZSCORE` + JS-side `score > now` check.
18
+ * - `isCapHit()` → `ZCOUNT key (now +inf` (active nonces).
19
+ * - `insert()` → a Lua script that runs `ZREMRANGEBYSCORE -inf now`
20
+ * (drop expired) → `ZSCORE` (replay check) → `ZCARD` (cap check) →
21
+ * `ZADD` (insert) + `PEXPIREAT` (extend set's own TTL) atomically.
22
+ *
23
+ * **No sweeper needed.** Expired nonces are dropped at the start of
24
+ * every `insert` (the `ZREMRANGEBYSCORE` step), and Redis evicts the
25
+ * entire sorted set when its `PEXPIREAT` lapses if no further inserts
26
+ * arrive. The Postgres backend's `sweepExpiredReplays` cron has no
27
+ * Redis equivalent — eviction is automatic.
28
+ *
29
+ * **Atomicity.** The Lua script runs as a single uninterruptable
30
+ * operation against the Redis server (Redis is single-threaded; Lua
31
+ * blocks everything else for the script duration). Two concurrent
32
+ * `insert` calls with the same `(keyid, scope, nonce)` are guaranteed
33
+ * to see exactly one `'ok'` and one `'replayed'` — matches the
34
+ * `InMemoryReplayStore` semantics the spec mandates.
35
+ *
36
+ * **Redis memory policy — set this on the deployment.** Each
37
+ * `(keyid, scope)` sorted set is capped (default 100k nonces). A
38
+ * hostile signer with a valid keyid + scope can fill the set to its
39
+ * cap; legitimate fan-out (many sibling verifiers, many scopes) grows
40
+ * the number of sets. Configure your Redis with:
41
+ *
42
+ * - **`maxmemory-policy volatile-lru`** (recommended) — evicts only
43
+ * TTL'd keys. All sets this store writes carry `PEXPIREAT`, so
44
+ * they're all evictable. Safe on a shared Redis instance.
45
+ * - **`maxmemory-policy allkeys-lru`** — only on a dedicated db.
46
+ * Otherwise evicts unrelated app keys.
47
+ * - **`maxmemory-policy noeviction`** (Redis default) — fail-closed:
48
+ * writes start erroring at the memory limit; the verifier will
49
+ * throw and the middleware will return 500 (replay protection
50
+ * stays intact — never fail-open). Operationally noisy but
51
+ * never serves stale replay decisions.
52
+ *
53
+ * Eviction-on-replay implication: if `volatile-lru` evicts a sorted
54
+ * set before its members would have expired naturally, an attacker's
55
+ * later replay of one of those nonces sees a fresh set with no prior
56
+ * entry, returns `'ok'`. The cap is the primary defense; memory-policy
57
+ * eviction is a secondary recovery mode. Size Redis to keep the
58
+ * working set in memory under normal traffic and treat eviction as
59
+ * pressure to scale up, not as a feature.
60
+ *
61
+ * @example
62
+ * ```typescript
63
+ * import { createClient } from 'redis';
64
+ * import { RedisReplayStore } from '@adcp/sdk/signing/server';
65
+ *
66
+ * const client = createClient({ url: process.env.REDIS_URL });
67
+ * client.on('error', (err) => console.error('redis error', err));
68
+ * await client.connect();
69
+ *
70
+ * const replayStore = new RedisReplayStore(client);
71
+ *
72
+ * app.use(createExpressVerifier({
73
+ * capability: { ... },
74
+ * jwks,
75
+ * replayStore, // <-- shared across instances
76
+ * resolveOperation: mcpToolNameResolver,
77
+ * }));
78
+ * ```
79
+ */
80
+ Object.defineProperty(exports, "__esModule", { value: true });
81
+ exports.RedisReplayStore = void 0;
82
+ const redis_default_prefix_warn_1 = require("../utils/redis-default-prefix-warn");
83
+ const DEFAULT_KEY_PREFIX = 'adcp:replay:';
84
+ const DEFAULT_CAP = 100_000;
85
+ /**
86
+ * Extra seconds added to the sorted-set's own `PEXPIREAT` past the
87
+ * latest entry's `expiresAt`. Without this, a sorted set whose last
88
+ * entry just expired would be evicted by Redis between `insert` calls,
89
+ * losing the cap-counter accounting briefly. 1 hour is conservative —
90
+ * memory-cheap because the set is also empty (`ZREMRANGEBYSCORE` ran
91
+ * before the eventual eviction) and short enough that abandoned
92
+ * `(keyid, scope)` tuples don't leak indefinitely.
93
+ */
94
+ const DEFAULT_SET_TTL_GRACE_SECONDS = 3600;
95
+ /**
96
+ * Lua script — atomic replay check, cap check, and insert.
97
+ *
98
+ * KEYS[1] — the sorted-set key for this `(keyid, scope)` pair.
99
+ * ARGV[1] — nonce.
100
+ * ARGV[2] — expiresAt (unix epoch seconds).
101
+ * ARGV[3] — now (unix epoch seconds).
102
+ * ARGV[4] — cap (max retained nonces).
103
+ * ARGV[5] — setTtlGraceSeconds (extra TTL on the sorted set itself).
104
+ *
105
+ * Returns the literal string `'ok'`, `'replayed'`, or `'rate_abuse'`
106
+ * to match the `ReplayInsertResult` enum.
107
+ *
108
+ * Precedence (matches `InMemoryReplayStore` and `PostgresReplayStore`):
109
+ * 1. replay wins,
110
+ * 2. then rate_abuse,
111
+ * 3. then ok.
112
+ *
113
+ * The `ZREMRANGEBYSCORE` step drops entries whose score is `<= now` —
114
+ * matching the pg backend's `expires_at > to_timestamp($4)` "still
115
+ * valid" semantic.
116
+ */
117
+ const INSERT_LUA = `
118
+ local key = KEYS[1]
119
+ local nonce = ARGV[1]
120
+ local expiresAt = tonumber(ARGV[2])
121
+ local now = tonumber(ARGV[3])
122
+ local cap = tonumber(ARGV[4])
123
+ local grace = tonumber(ARGV[5])
124
+
125
+ -- Drop expired entries first (inclusive: score <= now is expired)
126
+ redis.call('ZREMRANGEBYSCORE', key, '-inf', now)
127
+
128
+ -- Replay check — only unexpired entries can be found by ZSCORE because
129
+ -- the cleanup above ran.
130
+ if redis.call('ZSCORE', key, nonce) then
131
+ return 'replayed'
132
+ end
133
+
134
+ -- Cap check (count of unexpired entries — already pruned above)
135
+ if redis.call('ZCARD', key) >= cap then
136
+ return 'rate_abuse'
137
+ end
138
+
139
+ -- Insert the new nonce.
140
+ redis.call('ZADD', key, expiresAt, nonce)
141
+
142
+ -- Extend set TTL — but only forward, never backward. A short-lived
143
+ -- insert after a long-lived one must NOT shrink the set's eviction
144
+ -- time below the longest-still-valid nonce's expiry; otherwise the
145
+ -- set would evict early and take still-valid nonces with it (replay
146
+ -- bypass: has() returns false because the key disappeared, and the
147
+ -- attacker's retry of the long-lived nonce is wrongly accepted).
148
+ --
149
+ -- Desired set expiry = max(existing PEXPIREAT, expiresAt + grace).
150
+ -- PTTL returns -1 (no TTL set) or ms-remaining. Since ZADD ran above,
151
+ -- the key exists, so PTTL won't return -2. nowMs is passed from the
152
+ -- SDK side as ARGV[6] — using Redis's TIME command would make the
153
+ -- script non-deterministic for replication.
154
+ local desiredExpireAtMs = (expiresAt + grace) * 1000
155
+ local currentTtlMs = redis.call('PTTL', key)
156
+ local nowMs = tonumber(ARGV[6])
157
+ if currentTtlMs < 0 then
158
+ -- -1: no TTL set. (ZADD just wrote the key so -2 is impossible.)
159
+ redis.call('PEXPIREAT', key, desiredExpireAtMs)
160
+ else
161
+ local currentExpireAtMs = nowMs + currentTtlMs
162
+ if desiredExpireAtMs > currentExpireAtMs then
163
+ redis.call('PEXPIREAT', key, desiredExpireAtMs)
164
+ end
165
+ end
166
+ return 'ok'
167
+ `;
168
+ /**
169
+ * Reject non-finite / out-of-range timestamps before they reach Redis.
170
+ * Matches the Postgres backend's defensive guard so a buggy `options.now()`
171
+ * injection can't DoS the verifier with Redis parse errors.
172
+ */
173
+ function assertFiniteSeconds(label, value) {
174
+ if (!Number.isFinite(value) || value < 0 || value > Number.MAX_SAFE_INTEGER) {
175
+ throw new TypeError(`RedisReplayStore: ${label} must be a finite non-negative number; received ${value}`);
176
+ }
177
+ }
178
+ class RedisReplayStore {
179
+ c;
180
+ keyPrefix;
181
+ cap;
182
+ setTtlGraceSeconds;
183
+ constructor(client, options = {}) {
184
+ this.c = client;
185
+ this.keyPrefix = options.keyPrefix ?? DEFAULT_KEY_PREFIX;
186
+ this.cap = options.cap ?? DEFAULT_CAP;
187
+ this.setTtlGraceSeconds = options.setTtlGraceSeconds ?? DEFAULT_SET_TTL_GRACE_SECONDS;
188
+ if (!Number.isFinite(this.cap) || this.cap <= 0) {
189
+ throw new Error(`RedisReplayStore: cap must be a positive finite number. Got ${this.cap}.`);
190
+ }
191
+ if (!Number.isFinite(this.setTtlGraceSeconds) || this.setTtlGraceSeconds < 0) {
192
+ throw new Error(`RedisReplayStore: setTtlGraceSeconds must be a non-negative finite number. Got ${this.setTtlGraceSeconds}.`);
193
+ }
194
+ (0, redis_default_prefix_warn_1.maybeWarnOnSharedRedisPrefix)({
195
+ client,
196
+ callerKeyPrefix: options.keyPrefix,
197
+ defaultKeyPrefix: DEFAULT_KEY_PREFIX,
198
+ suppress: options.suppressDefaultPrefixWarning,
199
+ backendName: 'RedisReplayStore',
200
+ });
201
+ }
202
+ /**
203
+ * Compose the sorted-set Redis key for a `(keyid, scope)` pair.
204
+ * Uses `\x1f` (unit separator) between segments — illegal in RFC
205
+ * 7517 JWK kids and in URLs, so no legitimate input collides.
206
+ */
207
+ redisKey(keyid, scope) {
208
+ return `${this.keyPrefix}${keyid}\x1f${scope}`;
209
+ }
210
+ /**
211
+ * Probe — ping Redis. Not part of the `ReplayStore` interface; call
212
+ * before serving traffic if you want a boot-time readiness check.
213
+ */
214
+ async probe() {
215
+ try {
216
+ await this.c.ping();
217
+ }
218
+ catch (err) {
219
+ throw new Error(`RedisReplayStore probe failed: Redis is unreachable or misconfigured. ` +
220
+ `The verifier would accept signed requests but every replay-cache write would fail. ` +
221
+ `Check REDIS_URL and that the instance is up. See server logs for the underlying cause.`, { cause: err });
222
+ }
223
+ }
224
+ async has(keyid, scope, nonce, now) {
225
+ assertFiniteSeconds('now', now);
226
+ const score = await this.c.zScore(this.redisKey(keyid, scope), nonce);
227
+ // Score === expiresAt. An entry is "present" only if it's still
228
+ // unexpired (score > now). The Lua script also prunes expired
229
+ // entries on insert, but `has` is called independently and can
230
+ // race a never-since-touched key.
231
+ return score !== null && score > now;
232
+ }
233
+ async isCapHit(keyid, scope, now) {
234
+ assertFiniteSeconds('now', now);
235
+ // ZCOUNT with `(now` lower bound is exclusive — only entries
236
+ // strictly in the future count toward the cap. Stale entries
237
+ // beyond their expiresAt don't inflate the count even before the
238
+ // next insert prunes them.
239
+ const active = await this.c.zCount(this.redisKey(keyid, scope), `(${now}`, '+inf');
240
+ return active >= this.cap;
241
+ }
242
+ async insert(keyid, scope, nonce, ttlSeconds, now) {
243
+ assertFiniteSeconds('now', now);
244
+ assertFiniteSeconds('ttlSeconds', ttlSeconds);
245
+ const expiresAt = now + ttlSeconds;
246
+ // `nowMs` (ARGV[6]) is `now * 1000` rather than `Date.now()` so the
247
+ // script's "extend forward only" branch uses the SAME clock as the
248
+ // rest of the verifier path (`now` is the verifier-supplied second-
249
+ // precision time, used for prune + replay + cap). Using `Date.now()`
250
+ // here would let scripts disagree with the surrounding logic when
251
+ // tests inject `options.now()`.
252
+ const result = await this.c.eval(INSERT_LUA, {
253
+ keys: [this.redisKey(keyid, scope)],
254
+ arguments: [
255
+ nonce,
256
+ String(expiresAt),
257
+ String(now),
258
+ String(this.cap),
259
+ String(this.setTtlGraceSeconds),
260
+ String(now * 1000),
261
+ ],
262
+ });
263
+ if (result !== 'ok' && result !== 'replayed' && result !== 'rate_abuse') {
264
+ throw new Error(`RedisReplayStore.insert: Lua script returned unexpected value: ${String(result)}`);
265
+ }
266
+ return result;
267
+ }
268
+ }
269
+ exports.RedisReplayStore = RedisReplayStore;
270
+ //# sourceMappingURL=redis-replay-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redis-replay-store.js","sourceRoot":"","sources":["../../../src/lib/signing/redis-replay-store.ts"],"names":[],"mappings":"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"}
@@ -19,6 +19,7 @@ export { BrandJsonJwksResolver, BrandJsonResolverError, type BrandAgentType, typ
19
19
  export { parseSignature, parseSignatureInput, type ParsedSignature, type ParsedSignatureInput } from './parser';
20
20
  export { InMemoryReplayStore, type InMemoryReplayStoreOptions, type ReplayInsertResult, type ReplayStore, } from './replay';
21
21
  export { PostgresReplayStore, REPLAY_CACHE_MIGRATION, getReplayStoreMigration, sweepExpiredReplays, type PostgresReplayStoreOptions, type SweepExpiredReplaysOptions, } from './postgres-replay-store';
22
+ export { RedisReplayStore, type RedisReplayStoreOptions, type ReplayRedisBackendClient, type ReplayRedisLikeClient, } from './redis-replay-store';
22
23
  export { InMemoryRevocationStore, type RevocationStore } from './revocation';
23
24
  export { HttpsRevocationStore, type HttpsRevocationStoreOptions } from './revocation-https';
24
25
  export { ALLOWED_ALGS, CLOCK_SKEW_TOLERANCE_SECONDS, MANDATORY_COMPONENTS, MAX_SIGNATURE_WINDOW_SECONDS, REQUEST_SIGNING_TAG, RESPONSE_MANDATORY_COMPONENTS, RESPONSE_SIGNING_TAG, type AdcpJsonWebKey, type ContentDigestPolicy, type RevocationSnapshot, type VerifiedSigner, type VerifierCapability, type VerifyResult, } from './types';
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../src/lib/signing/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EACL,0BAA0B,EAC1B,kBAAkB,EAClB,kBAAkB,EAClB,eAAe,EACf,kBAAkB,EAClB,qBAAqB,EACrB,cAAc,EACd,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,eAAe,GACrB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAClG,OAAO,EACL,yBAAyB,EACzB,uBAAuB,EACvB,wBAAwB,EACxB,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EACvB,KAAK,gCAAgC,EACrC,KAAK,+BAA+B,GACrC,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAC3D,OAAO,EACL,qBAAqB,EACrB,KAAK,yBAAyB,EAC9B,sBAAsB,EACtB,KAAK,0BAA0B,EAC/B,qBAAqB,EACrB,KAAK,yBAAyB,GAC/B,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,kBAAkB,EAAE,KAAK,YAAY,EAAE,MAAM,QAAQ,CAAC;AAC/D,OAAO,EAAE,iBAAiB,EAAE,KAAK,wBAAwB,EAAE,MAAM,cAAc,CAAC;AAChF,OAAO,EACL,qBAAqB,EACrB,sBAAsB,EACtB,KAAK,cAAc,EACnB,KAAK,4BAA4B,EACjC,KAAK,0BAA0B,GAChC,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,KAAK,eAAe,EAAE,KAAK,oBAAoB,EAAE,MAAM,UAAU,CAAC;AAChH,OAAO,EACL,mBAAmB,EACnB,KAAK,0BAA0B,EAC/B,KAAK,kBAAkB,EACvB,KAAK,WAAW,GACjB,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,mBAAmB,EACnB,sBAAsB,EACtB,uBAAuB,EACvB,mBAAmB,EACnB,KAAK,0BAA0B,EAC/B,KAAK,0BAA0B,GAChC,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,uBAAuB,EAAE,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AAC7E,OAAO,EAAE,oBAAoB,EAAE,KAAK,2BAA2B,EAAE,MAAM,oBAAoB,CAAC;AAC5F,OAAO,EACL,YAAY,EACZ,4BAA4B,EAC5B,oBAAoB,EACpB,4BAA4B,EAC5B,mBAAmB,EACnB,6BAA6B,EAC7B,oBAAoB,EACpB,KAAK,cAAc,EACnB,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACvB,KAAK,YAAY,GAClB,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,sBAAsB,EAAE,KAAK,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAC/E,OAAO,EACL,sBAAsB,EACtB,uBAAuB,EACvB,KAAK,6BAA6B,EAClC,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,GAC1B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,qBAAqB,EACrB,sBAAsB,EACtB,4BAA4B,EAC5B,mBAAmB,EACnB,KAAK,4BAA4B,EACjC,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,GACzB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,qBAAqB,EAAE,KAAK,WAAW,EAAE,KAAK,wBAAwB,EAAE,MAAM,cAAc,CAAC;AACtG,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,kBAAkB,EAClB,kBAAkB,EAClB,wBAAwB,EACxB,mBAAmB,EACnB,gBAAgB,EAChB,mBAAmB,EACnB,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,sBAAsB,EAC3B,KAAK,wBAAwB,EAC7B,KAAK,UAAU,EACf,KAAK,eAAe,EACpB,KAAK,4BAA4B,EACjC,KAAK,yBAAyB,EAC9B,KAAK,mBAAmB,EACxB,KAAK,mBAAmB,EACxB,KAAK,wBAAwB,EAC7B,KAAK,kBAAkB,EACvB,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACxB,KAAK,SAAS,GACf,MAAM,kBAAkB,CAAC"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../src/lib/signing/server.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.AgentResolverError = exports.createAgentJwksSet = exports.getAgentJwks = exports.resolveAgent = exports.createExpressVerifier = exports.WEBHOOK_SIGNING_TAG = exports.WEBHOOK_MANDATORY_COMPONENTS = exports.verifyWebhookSignature = exports.createWebhookVerifier = exports.verifyResponseSignature = exports.createResponseVerifier = exports.verifyRequestSignature = exports.RESPONSE_SIGNING_TAG = exports.RESPONSE_MANDATORY_COMPONENTS = exports.REQUEST_SIGNING_TAG = exports.MAX_SIGNATURE_WINDOW_SECONDS = exports.MANDATORY_COMPONENTS = exports.CLOCK_SKEW_TOLERANCE_SECONDS = exports.ALLOWED_ALGS = exports.HttpsRevocationStore = exports.InMemoryRevocationStore = exports.sweepExpiredReplays = exports.getReplayStoreMigration = exports.REPLAY_CACHE_MIGRATION = exports.PostgresReplayStore = exports.InMemoryReplayStore = exports.parseSignatureInput = exports.parseSignature = exports.BrandJsonResolverError = exports.BrandJsonJwksResolver = exports.HttpsJwksResolver = exports.StaticJwksResolver = exports.WebhookSignatureError = exports.ResponseSignatureError = exports.RequestSignatureError = exports.verifySignature = exports.jwkToPublicKey = exports.requestContextFromLambda = exports.requestContextFromFetch = exports.requestContextFromExpress = exports.parseContentDigest = exports.contentDigestMatches = exports.computeContentDigest = exports.getHeaderValue = exports.formatSignatureParams = exports.canonicalTargetUri = exports.canonicalMethod = exports.canonicalAuthority = exports.buildSignatureBase = exports.buildResponseSignatureBase = void 0;
4
- exports.readIdentityPosture = exports.readBrandJsonUrl = exports.ATTACKER_INFLUENCED = exports.attackerInfluencedFields = void 0;
3
+ exports.createAgentJwksSet = exports.getAgentJwks = exports.resolveAgent = exports.createExpressVerifier = exports.WEBHOOK_SIGNING_TAG = exports.WEBHOOK_MANDATORY_COMPONENTS = exports.verifyWebhookSignature = exports.createWebhookVerifier = exports.verifyResponseSignature = exports.createResponseVerifier = exports.verifyRequestSignature = exports.RESPONSE_SIGNING_TAG = exports.RESPONSE_MANDATORY_COMPONENTS = exports.REQUEST_SIGNING_TAG = exports.MAX_SIGNATURE_WINDOW_SECONDS = exports.MANDATORY_COMPONENTS = exports.CLOCK_SKEW_TOLERANCE_SECONDS = exports.ALLOWED_ALGS = exports.HttpsRevocationStore = exports.InMemoryRevocationStore = exports.RedisReplayStore = exports.sweepExpiredReplays = exports.getReplayStoreMigration = exports.REPLAY_CACHE_MIGRATION = exports.PostgresReplayStore = exports.InMemoryReplayStore = exports.parseSignatureInput = exports.parseSignature = exports.BrandJsonResolverError = exports.BrandJsonJwksResolver = exports.HttpsJwksResolver = exports.StaticJwksResolver = exports.WebhookSignatureError = exports.ResponseSignatureError = exports.RequestSignatureError = exports.verifySignature = exports.jwkToPublicKey = exports.requestContextFromLambda = exports.requestContextFromFetch = exports.requestContextFromExpress = exports.parseContentDigest = exports.contentDigestMatches = exports.computeContentDigest = exports.getHeaderValue = exports.formatSignatureParams = exports.canonicalTargetUri = exports.canonicalMethod = exports.canonicalAuthority = exports.buildSignatureBase = exports.buildResponseSignatureBase = void 0;
4
+ exports.readIdentityPosture = exports.readBrandJsonUrl = exports.ATTACKER_INFLUENCED = exports.attackerInfluencedFields = exports.AgentResolverError = void 0;
5
5
  /**
6
6
  * Server-side signing surface: what a seller running an AdCP agent needs to
7
7
  * verify inbound RFC 9421 signatures — verifier pipeline, Express-shaped
@@ -52,6 +52,8 @@ Object.defineProperty(exports, "PostgresReplayStore", { enumerable: true, get: f
52
52
  Object.defineProperty(exports, "REPLAY_CACHE_MIGRATION", { enumerable: true, get: function () { return postgres_replay_store_1.REPLAY_CACHE_MIGRATION; } });
53
53
  Object.defineProperty(exports, "getReplayStoreMigration", { enumerable: true, get: function () { return postgres_replay_store_1.getReplayStoreMigration; } });
54
54
  Object.defineProperty(exports, "sweepExpiredReplays", { enumerable: true, get: function () { return postgres_replay_store_1.sweepExpiredReplays; } });
55
+ var redis_replay_store_1 = require("./redis-replay-store");
56
+ Object.defineProperty(exports, "RedisReplayStore", { enumerable: true, get: function () { return redis_replay_store_1.RedisReplayStore; } });
55
57
  var revocation_1 = require("./revocation");
56
58
  Object.defineProperty(exports, "InMemoryRevocationStore", { enumerable: true, get: function () { return revocation_1.InMemoryRevocationStore; } });
57
59
  var revocation_https_1 = require("./revocation-https");
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../../../src/lib/signing/server.ts"],"names":[],"mappings":";;;;AAAA;;;;;;;;;GASG;AACH,+CAWwB;AAVtB,0HAAA,0BAA0B,OAAA;AAC1B,kHAAA,kBAAkB,OAAA;AAClB,kHAAA,kBAAkB,OAAA;AAClB,+GAAA,eAAe,OAAA;AACf,kHAAA,kBAAkB,OAAA;AAClB,qHAAA,qBAAqB,OAAA;AACrB,8GAAA,cAAc,OAAA;AAKhB,mDAAkG;AAAzF,sHAAA,oBAAoB,OAAA;AAAE,sHAAA,oBAAoB,OAAA;AAAE,oHAAA,kBAAkB,OAAA;AACvE,qDAS2B;AARzB,4HAAA,yBAAyB,OAAA;AACzB,0HAAA,uBAAuB,OAAA;AACvB,2HAAA,wBAAwB,OAAA;AAO1B,mCAA2D;AAAlD,wGAAA,cAAc,OAAA;AAAE,yGAAA,eAAe,OAAA;AACxC,mCAOkB;AANhB,+GAAA,qBAAqB,OAAA;AAErB,gHAAA,sBAAsB,OAAA;AAEtB,+GAAA,qBAAqB,OAAA;AAGvB,+BAA+D;AAAtD,0GAAA,kBAAkB,OAAA;AAC3B,2CAAgF;AAAvE,+GAAA,iBAAiB,OAAA;AAC1B,2CAMsB;AALpB,mHAAA,qBAAqB,OAAA;AACrB,oHAAA,sBAAsB,OAAA;AAKxB,mCAAgH;AAAvG,wGAAA,cAAc,OAAA;AAAE,6GAAA,mBAAmB,OAAA;AAC5C,mCAKkB;AAJhB,6GAAA,mBAAmB,OAAA;AAKrB,iEAOiC;AAN/B,4HAAA,mBAAmB,OAAA;AACnB,+HAAA,sBAAsB,OAAA;AACtB,gIAAA,uBAAuB,OAAA;AACvB,4HAAA,mBAAmB,OAAA;AAIrB,2CAA6E;AAApE,qHAAA,uBAAuB,OAAA;AAChC,uDAA4F;AAAnF,wHAAA,oBAAoB,OAAA;AAC7B,iCAciB;AAbf,qGAAA,YAAY,OAAA;AACZ,qHAAA,4BAA4B,OAAA;AAC5B,6GAAA,oBAAoB,OAAA;AACpB,qHAAA,4BAA4B,OAAA;AAC5B,4GAAA,mBAAmB,OAAA;AACnB,sHAAA,6BAA6B,OAAA;AAC7B,6GAAA,oBAAoB,OAAA;AAQtB,uCAA+E;AAAtE,kHAAA,sBAAsB,OAAA;AAC/B,yDAM6B;AAL3B,2HAAA,sBAAsB,OAAA;AACtB,4HAAA,uBAAuB,OAAA;AAKzB,uDAQ4B;AAP1B,yHAAA,qBAAqB,OAAA;AACrB,0HAAA,sBAAsB,OAAA;AACtB,gIAAA,4BAA4B,OAAA;AAC5B,uHAAA,mBAAmB,OAAA;AAKrB,2CAAsG;AAA7F,mHAAA,qBAAqB,OAAA;AAC9B,mDAwB0B;AAvBxB,8GAAA,YAAY,OAAA;AACZ,8GAAA,YAAY,OAAA;AACZ,oHAAA,kBAAkB,OAAA;AAClB,oHAAA,kBAAkB,OAAA;AAClB,0HAAA,wBAAwB,OAAA;AACxB,qHAAA,mBAAmB,OAAA;AACnB,kHAAA,gBAAgB,OAAA;AAChB,qHAAA,mBAAmB,OAAA"}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../../src/lib/signing/server.ts"],"names":[],"mappings":";;;;AAAA;;;;;;;;;GASG;AACH,+CAWwB;AAVtB,0HAAA,0BAA0B,OAAA;AAC1B,kHAAA,kBAAkB,OAAA;AAClB,kHAAA,kBAAkB,OAAA;AAClB,+GAAA,eAAe,OAAA;AACf,kHAAA,kBAAkB,OAAA;AAClB,qHAAA,qBAAqB,OAAA;AACrB,8GAAA,cAAc,OAAA;AAKhB,mDAAkG;AAAzF,sHAAA,oBAAoB,OAAA;AAAE,sHAAA,oBAAoB,OAAA;AAAE,oHAAA,kBAAkB,OAAA;AACvE,qDAS2B;AARzB,4HAAA,yBAAyB,OAAA;AACzB,0HAAA,uBAAuB,OAAA;AACvB,2HAAA,wBAAwB,OAAA;AAO1B,mCAA2D;AAAlD,wGAAA,cAAc,OAAA;AAAE,yGAAA,eAAe,OAAA;AACxC,mCAOkB;AANhB,+GAAA,qBAAqB,OAAA;AAErB,gHAAA,sBAAsB,OAAA;AAEtB,+GAAA,qBAAqB,OAAA;AAGvB,+BAA+D;AAAtD,0GAAA,kBAAkB,OAAA;AAC3B,2CAAgF;AAAvE,+GAAA,iBAAiB,OAAA;AAC1B,2CAMsB;AALpB,mHAAA,qBAAqB,OAAA;AACrB,oHAAA,sBAAsB,OAAA;AAKxB,mCAAgH;AAAvG,wGAAA,cAAc,OAAA;AAAE,6GAAA,mBAAmB,OAAA;AAC5C,mCAKkB;AAJhB,6GAAA,mBAAmB,OAAA;AAKrB,iEAOiC;AAN/B,4HAAA,mBAAmB,OAAA;AACnB,+HAAA,sBAAsB,OAAA;AACtB,gIAAA,uBAAuB,OAAA;AACvB,4HAAA,mBAAmB,OAAA;AAIrB,2DAK8B;AAJ5B,sHAAA,gBAAgB,OAAA;AAKlB,2CAA6E;AAApE,qHAAA,uBAAuB,OAAA;AAChC,uDAA4F;AAAnF,wHAAA,oBAAoB,OAAA;AAC7B,iCAciB;AAbf,qGAAA,YAAY,OAAA;AACZ,qHAAA,4BAA4B,OAAA;AAC5B,6GAAA,oBAAoB,OAAA;AACpB,qHAAA,4BAA4B,OAAA;AAC5B,4GAAA,mBAAmB,OAAA;AACnB,sHAAA,6BAA6B,OAAA;AAC7B,6GAAA,oBAAoB,OAAA;AAQtB,uCAA+E;AAAtE,kHAAA,sBAAsB,OAAA;AAC/B,yDAM6B;AAL3B,2HAAA,sBAAsB,OAAA;AACtB,4HAAA,uBAAuB,OAAA;AAKzB,uDAQ4B;AAP1B,yHAAA,qBAAqB,OAAA;AACrB,0HAAA,sBAAsB,OAAA;AACtB,gIAAA,4BAA4B,OAAA;AAC5B,uHAAA,mBAAmB,OAAA;AAKrB,2CAAsG;AAA7F,mHAAA,qBAAqB,OAAA;AAC9B,mDAwB0B;AAvBxB,8GAAA,YAAY,OAAA;AACZ,8GAAA,YAAY,OAAA;AACZ,oHAAA,kBAAkB,OAAA;AAClB,oHAAA,kBAAkB,OAAA;AAClB,0HAAA,wBAAwB,OAAA;AACxB,qHAAA,mBAAmB,OAAA;AACnB,kHAAA,gBAAgB,OAAA;AAChB,qHAAA,mBAAmB,OAAA"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Shared default-prefix-on-db-0 warning helper for Redis backends.
3
+ *
4
+ * Every Redis-backed SDK store (idempotency, ctx-metadata, replay)
5
+ * prefixes its keys (`"adcp:idem:"`, `"adcp:ctx_meta:"`, `"adcp:replay:"`)
6
+ * so a shared Redis instance can host multiple AdCP servers — or AdCP
7
+ * alongside other apps — without collision. But two AdCP deployments
8
+ * sharing the *same* db with the *same* default prefix collide on any
9
+ * overlapping principal/account/keyid namespace.
10
+ *
11
+ * This helper emits a one-time `console.warn` at backend construction
12
+ * when the default prefix meets a node-redis client we can confidently
13
+ * identify as being on db 0 (the most likely signal of a shared,
14
+ * non-dedicated Redis). Once per process across every backend that uses
15
+ * it — operators standing up multiple Redis backends shouldn't see N
16
+ * identical warnings.
17
+ *
18
+ * Best-effort: stays silent for escape-hatch clients (ioredis, Upstash,
19
+ * test doubles) where we can't introspect the db index. Prefer
20
+ * false-negative over a noisy false-positive warning.
21
+ */
22
+ /**
23
+ * Detect the Redis db index when the client is a node-redis v4/v5
24
+ * `RedisClientType`. Returns `null` for clients we can't introspect.
25
+ *
26
+ * Reads `client.options.database` (numeric, present when the client was
27
+ * constructed with `createClient({ database: N })`) or parses the path
28
+ * component of `client.options.url`. Escape-hatch clients (no `options`
29
+ * object of the canonical shape) return `null` and skip the warn.
30
+ */
31
+ export declare function detectNodeRedisDbIndex(client: unknown): number | null;
32
+ export interface WarnOnSharedRedisPrefixOptions {
33
+ /** The Redis client passed to the backend constructor. */
34
+ client: unknown;
35
+ /** `options.keyPrefix` as the caller passed it — `undefined` means default. */
36
+ callerKeyPrefix: string | undefined;
37
+ /** The backend's own default prefix (e.g., `"adcp:idem:"`). */
38
+ defaultKeyPrefix: string;
39
+ /** Caller-supplied suppression switch. */
40
+ suppress: boolean | undefined;
41
+ /** Backend label for the warning text (e.g., `"redisBackend"`, `"redisCtxMetadataStore"`). */
42
+ backendName: string;
43
+ }
44
+ /**
45
+ * Emit the one-time warn if (a) the caller used the default `keyPrefix`,
46
+ * (b) didn't pass `suppress`, (c) we can confidently see db 0 from the
47
+ * client. Otherwise silent.
48
+ *
49
+ * **Once per process.** All Redis backends share the warn flag — an
50
+ * adopter who configures three Redis-backed stores against the same
51
+ * misconfigured Redis sees one warning total, not three.
52
+ */
53
+ export declare function maybeWarnOnSharedRedisPrefix(options: WarnOnSharedRedisPrefixOptions): void;
54
+ /**
55
+ * Test-only escape hatch to reset the once-warn flag between test runs.
56
+ * Not exported through any public index — adopters can't reach it from
57
+ * outside the SDK.
58
+ */
59
+ export declare function __resetDefaultPrefixWarningForTests(): void;
60
+ //# sourceMappingURL=redis-default-prefix-warn.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redis-default-prefix-warn.d.ts","sourceRoot":"","sources":["../../../src/lib/utils/redis-default-prefix-warn.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAIH;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAkBrE;AAED,MAAM,WAAW,8BAA8B;IAC7C,0DAA0D;IAC1D,MAAM,EAAE,OAAO,CAAC;IAChB,+EAA+E;IAC/E,eAAe,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,+DAA+D;IAC/D,gBAAgB,EAAE,MAAM,CAAC;IACzB,0CAA0C;IAC1C,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;IAC9B,8FAA8F;IAC9F,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;GAQG;AACH,wBAAgB,4BAA4B,CAAC,OAAO,EAAE,8BAA8B,GAAG,IAAI,CAc1F;AAED;;;;GAIG;AACH,wBAAgB,mCAAmC,IAAI,IAAI,CAE1D"}