@volter/twin-veriff 0.1.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.
@@ -0,0 +1,55 @@
1
+ import { RateBudget, type RateBudgetDeclaration, type RateBudgetOptions, type RateBudgetReservation, type RateBudgetSnapshot } from '@volter/world-core';
2
+ /** Rolling window, in ms. Spend older than this is pruned. */
3
+ export declare const VERIFF_BUDGET_WINDOW_MS = 60000;
4
+ /**
5
+ * Weighted units allowed inside one window. 60/60s = 30 calls a minute at the default weight —
6
+ * exactly Veriff's documented SELF-SERVE session-creation cap, and no more permissive than the
7
+ * kernel's undeclared fallback.
8
+ */
9
+ export declare const VERIFF_BUDGET_CEILING = 60;
10
+ /** Seconds. A `Retry-After` above this means the credential is throttled hard — fail loudly, don't
11
+ * sleep. Veriff documents no `Retry-After`; this caps one if a live response ever carries it. */
12
+ export declare const VERIFF_BUDGET_MAX_RETRY_AFTER_S = 300;
13
+ /** Per-call cost, keyed by `"<METHOD> <path>"`. See the header for what is documented vs. judged. */
14
+ export declare const VERIFF_CALL_WEIGHTS: {
15
+ /** `DELETE /sessions/{id}` — documented at 5/hour + 10/day, which a 60s window cannot express. */
16
+ readonly deleteSession: 12;
17
+ /** Everything else: session create (the documented 30/min tier), decision/attempt/media reads. */
18
+ readonly other: 2;
19
+ };
20
+ /** THE PACK'S DECLARATION — pure data, the only Veriff-specific thing in the whole budget. */
21
+ export declare const VERIFF_RATE_BUDGET: RateBudgetDeclaration;
22
+ /**
23
+ * Price one call. The key is `"<METHOD> <path>"` (the v1 path as the connector states it, without
24
+ * the `/v1` prefix the live executor adds) with the query string split off.
25
+ *
26
+ * NORMALIZED: `fetch` upper-cases a known method before sending, so `execute('delete', …)` really
27
+ * does issue a DELETE and must be priced as one; and a trailing slash otherwise makes a path miss
28
+ * an anchored rule while every router treats it as the same endpoint. Both are input variations,
29
+ * not attacks, and either one silently voids the "expensive endpoints are priced up" claim.
30
+ */
31
+ export declare function veriffCallWeight(method: string, path: string): number;
32
+ /** Where Veriff's ledger lives. Token-keyed and cwd-independent by default (the limits are per
33
+ * integration/API key, so a cwd-scoped ledger would hand the same key a fresh allowance in every
34
+ * checkout, worktree and CI matrix leg); pass `root` to opt into world-scoped accounting. */
35
+ export declare function veriffBudgetPath(opts?: {
36
+ root?: string;
37
+ token?: string;
38
+ } | string): string;
39
+ /** Construction options for Veriff's budget. The vendor is fixed; everything else may only TIGHTEN. */
40
+ export type VeriffBudgetOptions = Omit<RateBudgetOptions, 'vendor'>;
41
+ /**
42
+ * Veriff's budget — the shared kernel guard bound to this vendor's declaration. A real subclass,
43
+ * not an alias, so `budget instanceof VeriffBudget` in `liveVeriffExecute` means "a budget that
44
+ * accounts against VERIFF's ledger under VERIFF's ceiling": another vendor's `RateBudget` (with its
45
+ * own, possibly larger, ceiling) is NOT assignable there.
46
+ */
47
+ export declare class VeriffBudget extends RateBudget {
48
+ constructor(opts?: VeriffBudgetOptions);
49
+ }
50
+ /** The typed refusal. One error class shared with every other vendor's budget; `err.vendor` says
51
+ * which one refused, and `err.kind` says why. */
52
+ export { RateBudgetError as VeriffBudgetError } from '@volter/world-core';
53
+ export type { RateBudgetErrorKind as VeriffBudgetErrorKind } from '@volter/world-core';
54
+ export type VeriffBudgetReservation = RateBudgetReservation;
55
+ export type VeriffBudgetSnapshot = RateBudgetSnapshot;
@@ -0,0 +1,151 @@
1
+ // Veriff's CLIENT-SIDE RATE BUDGET — the pack's DECLARATION (the numbers) plus the thin typed
2
+ // bindings `liveVeriffExecute` uses. The MECHANISM — the durable token-keyed ledger, the rolling
3
+ // window, reserve-under-lock, the `Retry-After`/429 cooldown, fail-CLOSED on a corrupt ledger —
4
+ // lives ONCE in the vendor-agnostic kernel (`@volter/world-core` → `rateBudget.ts`). Read that module's
5
+ // header for the full rationale AND for the honest list of what the guard does not guarantee (an
6
+ // injected clock or ledger path still defeats it — it guards carelessness, not malice). This module
7
+ // is modeled on calcom-budget.ts, the reference "the pack builds its own HTTP client" shape.
8
+ //
9
+ // ── WHY THIS EXISTS ─────────────────────────────────────────────────────────────────────────
10
+ // A real ~4.5-DAY vendor lockout (Figma, 2026-07-25) happened because raw API calls were made
11
+ // outside the pack's connector — no cache, no batching, no ceiling. Discipline only binds the code
12
+ // that follows it; a BUDGET binds the code that does not. For an identity vendor the stakes are not
13
+ // only the lockout: every session created is a BILLED verification against a real end user.
14
+ //
15
+ // ── HOW THE CEILING WAS CHOSEN ──────────────────────────────────────────────────────────────
16
+ // Veriff DOES publish scalar limits — unusually — but not in the API reference. Two, both read
17
+ // 2026-08-20:
18
+ //
19
+ // • devdocs.veriff.com/docs/general-faq, "How many sessions can I create per minute?":
20
+ // "Enterprise customer: 600 sessions per minute" / "Self-Serve customer: 30 sessions per
21
+ // minute".
22
+ // • devdocs.veriff.com/apidocs/v1sessionsid-3 (DELETE /v1/sessions/{id}), "Rate limiting":
23
+ // "This endpoint is rate limited: 10 sessions per 24 hours and 5 sessions per 1 hour".
24
+ //
25
+ // The ceiling is pinned to the SELF-SERVE tier, because a pack cannot know which contract the
26
+ // credential in front of it is on and the enterprise number is not a floor anyone is entitled to.
27
+ // 60 weighted units / 60s at the default weight of 2 is 30 calls a minute — exactly the documented
28
+ // self-serve session ceiling, and identical to the kernel's undeclared fallback. Being MORE
29
+ // permissive would require a documented number that applies to every account, and there isn't one.
30
+ //
31
+ // Veriff publishes NO limit at all for the read endpoints (decision polling, attempts, media), so
32
+ // those are not widened either: they spend from the same conservative allowance rather than being
33
+ // declared free on the strength of an absence.
34
+ //
35
+ // ── WHAT THIS DOES NOT DO: PACE ─────────────────────────────────────────────────────────────
36
+ // It bounds the 60s AVERAGE; it does NOT bound the instantaneous rate. In a tight `await` loop the
37
+ // vendor's own 429 can arrive first, and the backstop is then the COOLDOWN armed from that
38
+ // response. Veriff documents its 429 body (`{"status":"fail","code":"1004","message":"Too many
39
+ // requests."}`) but documents NO `Retry-After` and NO `X-RateLimit-*` response headers — checked
40
+ // across the whole published reference. The kernel reads those headers IF PRESENT and never assumes
41
+ // them; on a bare 429 the cooldown still arms. Do not read the header names below as a vendor fact.
42
+ //
43
+ // ── HOW THE WEIGHTS WERE CHOSEN (and what is a judgement call) ──────────────────────────────
44
+ // • `POST /sessions` costs the default 2 — the faithful price, since 30/min at weight 2 is
45
+ // exactly the documented self-serve cap.
46
+ // • `DELETE /sessions/{id}` costs 12, so one window admits 5 rather than 30. That is a JUDGEMENT
47
+ // CALL standing in for a cap this ledger structurally cannot enforce: the documented limit is
48
+ // 5 per HOUR and 10 per 24 HOURS, and a 60-second rolling window cannot express either. What
49
+ // weight 12 buys is that a delete LOOP is stopped inside the first window instead of burning
50
+ // the whole daily allowance in seconds; staying under 5/hour remains the caller's
51
+ // responsibility, and this module says so rather than implying enforcement it does not have.
52
+ import { declareRateBudget, rateBudgetPath, rateBudgetWeight, RateBudget, } from '@volter/world-core';
53
+ const VENDOR = 'veriff';
54
+ /** Rolling window, in ms. Spend older than this is pruned. */
55
+ export const VERIFF_BUDGET_WINDOW_MS = 60_000;
56
+ /**
57
+ * Weighted units allowed inside one window. 60/60s = 30 calls a minute at the default weight —
58
+ * exactly Veriff's documented SELF-SERVE session-creation cap, and no more permissive than the
59
+ * kernel's undeclared fallback.
60
+ */
61
+ export const VERIFF_BUDGET_CEILING = 60;
62
+ /** Seconds. A `Retry-After` above this means the credential is throttled hard — fail loudly, don't
63
+ * sleep. Veriff documents no `Retry-After`; this caps one if a live response ever carries it. */
64
+ export const VERIFF_BUDGET_MAX_RETRY_AFTER_S = 300;
65
+ /** Per-call cost, keyed by `"<METHOD> <path>"`. See the header for what is documented vs. judged. */
66
+ export const VERIFF_CALL_WEIGHTS = {
67
+ /** `DELETE /sessions/{id}` — documented at 5/hour + 10/day, which a 60s window cannot express. */
68
+ deleteSession: 12,
69
+ /** Everything else: session create (the documented 30/min tier), decision/attempt/media reads. */
70
+ other: 2,
71
+ };
72
+ /** THE PACK'S DECLARATION — pure data, the only Veriff-specific thing in the whole budget. */
73
+ export const VERIFF_RATE_BUDGET = {
74
+ windowMs: VERIFF_BUDGET_WINDOW_MS,
75
+ ceiling: VERIFF_BUDGET_CEILING,
76
+ defaultWeight: VERIFF_CALL_WEIGHTS.other,
77
+ maxRetryAfterSeconds: VERIFF_BUDGET_MAX_RETRY_AFTER_S,
78
+ rules: [
79
+ { match: '^DELETE /sessions/', weight: VERIFF_CALL_WEIGHTS.deleteSession },
80
+ ],
81
+ reason: 'Veriff publishes two scalar limits, neither of them in the API reference (both read 2026-08-20). ' +
82
+ 'devdocs.veriff.com/docs/general-faq: "Enterprise customer: 600 sessions per minute", "Self-Serve ' +
83
+ 'customer: 30 sessions per minute". devdocs.veriff.com/apidocs/v1sessionsid-3 (DELETE ' +
84
+ '/v1/sessions/{id}): "This endpoint is rate limited: 10 sessions per 24 hours and 5 sessions per ' +
85
+ '1 hour". The ceiling is pinned to the SELF-SERVE tier — 60 weighted units / 60s at defaultWeight ' +
86
+ '2 = 30 calls a minute, exactly that documented cap — because a pack cannot know which contract ' +
87
+ "the credential in front of it is on, and the enterprise number is not a floor anyone is " +
88
+ 'entitled to. That is also identical to the kernel\'s undeclared fallback, so this declaration ' +
89
+ 'buys weighting, not permission. Veriff publishes NO limit for the read endpoints (decision ' +
90
+ 'polling, attempts, media), so those are not widened on the strength of an absence. DELETE costs ' +
91
+ '12 so one window admits 5 rather than 30 — a JUDGEMENT CALL, not a published cost: the real cap ' +
92
+ 'is 5/hour and 10/day, which a 60-second rolling window structurally cannot enforce, so staying ' +
93
+ "under it remains the caller's responsibility and the weight only stops a delete LOOP inside the " +
94
+ 'first window. Veriff documents its 429 body ({"status":"fail","code":"1004","message":"Too many ' +
95
+ 'requests."}) but NO Retry-After and NO X-RateLimit-* response headers anywhere in the published ' +
96
+ 'reference; the kernel reads those names if present and never assumes them, and a bare 429 still ' +
97
+ 'arms the cooldown.',
98
+ };
99
+ // Declared at module load, so merely importing this module (which `veriff-connector.ts` does) is
100
+ // enough to arm the real ceiling. `RateBudget` reads its policy live precisely so this declaration
101
+ // takes effect the moment it lands, and constructing through the subclass below (which imports this
102
+ // module) is what makes the ordering a non-issue in practice.
103
+ declareRateBudget(VENDOR, VERIFF_RATE_BUDGET);
104
+ /**
105
+ * Price one call. The key is `"<METHOD> <path>"` (the v1 path as the connector states it, without
106
+ * the `/v1` prefix the live executor adds) with the query string split off.
107
+ *
108
+ * NORMALIZED: `fetch` upper-cases a known method before sending, so `execute('delete', …)` really
109
+ * does issue a DELETE and must be priced as one; and a trailing slash otherwise makes a path miss
110
+ * an anchored rule while every router treats it as the same endpoint. Both are input variations,
111
+ * not attacks, and either one silently voids the "expensive endpoints are priced up" claim.
112
+ */
113
+ export function veriffCallWeight(method, path) {
114
+ const { bare, query } = splitQuery(path);
115
+ return rateBudgetWeight(VENDOR, `${String(method).toUpperCase()} ${bare}`, query);
116
+ }
117
+ /** `/x?a=1` -> `{ bare: '/x', query: { a: '1' } }`. Rules match the path; `whenQuery*` the query. */
118
+ function splitQuery(path) {
119
+ const at = path.indexOf('?');
120
+ const query = {};
121
+ if (at !== -1)
122
+ for (const [k, v] of new URLSearchParams(path.slice(at + 1)))
123
+ query[k] = v;
124
+ const raw = at === -1 ? path : path.slice(0, at);
125
+ const bare = raw.length > 1 && raw.endsWith('/') ? raw.replace(/\/+$/, '') : raw;
126
+ return { bare, query };
127
+ }
128
+ /** Where Veriff's ledger lives. Token-keyed and cwd-independent by default (the limits are per
129
+ * integration/API key, so a cwd-scoped ledger would hand the same key a fresh allowance in every
130
+ * checkout, worktree and CI matrix leg); pass `root` to opt into world-scoped accounting. */
131
+ export function veriffBudgetPath(opts = {}) {
132
+ const o = typeof opts === 'string' ? { root: opts } : opts;
133
+ // VENDOR spread LAST: a loosely-typed `{ vendor: 'other', … }` slipping through (TypeScript's
134
+ // excess-property check only catches object literals) must not redirect this pack's ledger to
135
+ // another vendor's file.
136
+ return rateBudgetPath({ ...o, vendor: VENDOR });
137
+ }
138
+ /**
139
+ * Veriff's budget — the shared kernel guard bound to this vendor's declaration. A real subclass,
140
+ * not an alias, so `budget instanceof VeriffBudget` in `liveVeriffExecute` means "a budget that
141
+ * accounts against VERIFF's ledger under VERIFF's ceiling": another vendor's `RateBudget` (with its
142
+ * own, possibly larger, ceiling) is NOT assignable there.
143
+ */
144
+ export class VeriffBudget extends RateBudget {
145
+ constructor(opts = {}) {
146
+ super({ ...opts, vendor: VENDOR });
147
+ }
148
+ }
149
+ /** The typed refusal. One error class shared with every other vendor's budget; `err.vendor` says
150
+ * which one refused, and `err.kind` says why. */
151
+ export { RateBudgetError as VeriffBudgetError } from '@volter/world-core';
@@ -0,0 +1,10 @@
1
+ import { type CapabilityReport, type CapabilitySpec } from '@volter/world-tooling';
2
+ /**
3
+ * The AREA CENSUS — enumerated TOP-DOWN from Veriff's own documentation navigation (the
4
+ * `llms.txt` index: API reference groups + one page per solution), NOT derived from the manifest
5
+ * below. Deriving it from the manifest would make the bijection check a tautology; the point is
6
+ * that a whole Veriff product area cannot silently vanish from the denominator.
7
+ */
8
+ export declare const VERIFF_AREAS: readonly ["sessions", "decisions", "attempts", "person", "media", "auth", "webhooks", "errors", "watchlist", "idv", "proof_of_address", "database_verification", "fraud", "collected_data", "faces", "registry", "platform", "sdk", "connector"];
9
+ export declare const VERIFF_CAPABILITIES: CapabilitySpec[];
10
+ export declare function veriffCapabilities(): Promise<CapabilityReport>;