@chitmark/sdk 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,384 @@
1
+ /**
2
+ * Shared API types for Chitmark by Open Agent Ledger.
3
+ * Aligns with openapi/openapi.yaml and SDK.md.
4
+ */
5
+ /** Routing decision from verify. When degraded, this is always "challenge". */
6
+ type Decision = "allow" | "challenge" | "deny";
7
+ /** Classified actor behind the inbound action. */
8
+ type ActorType = "human" | "agent_principal" | "agent_abusive" | "unknown";
9
+ /** Whether a returned verdict is meant to gate traffic or only be observed. */
10
+ type EnforcementMode = "enforce" | "log_only";
11
+ /** Known action surfaces; custom strings are also accepted. */
12
+ type Action = "signup" | "trial_activation" | "api_key" | "demo_request" | "quote" | "ticket" | "custom" | (string & {});
13
+ /** Outcomes reported via feedback — the data moat. Prefer fast labels first. */
14
+ type Outcome = "credit_burn" | "multi_account_cluster" | "abuse_confirmed" | "converted" | "chargeback" | "churned" | "false_positive";
15
+ /** Asymmetric-cost challenge methods (best → worst for anti-abuse). */
16
+ type ChallengeMethod = "payment_preauth" | "proof_of_work" | "device_attestation" | "web_bot_auth_stepup" | "email_otp";
17
+ /** Client-side PII minimization mode. Default is hashed. Raw is opt-in only. */
18
+ type PiiMode = "raw" | "hashed" | "none";
19
+ /** Environment target; selects default base URL. */
20
+ type ChitmarkEnv = "production" | "staging" | "development";
21
+ /** On timeout/error, client decision fallback. NEVER "allow". */
22
+ type OnDegraded = "challenge";
23
+ /**
24
+ * Principal resolution — mostly populated for good signed agents;
25
+ * sparse for farmers.
26
+ */
27
+ interface Principal {
28
+ resolved: boolean;
29
+ /** Bot maker from Web Bot Auth Signature-Agent (e.g. "openai"). */
30
+ operator?: string;
31
+ /** End-user principal ("on whose behalf"); often null at the wedge. */
32
+ onBehalfOf?: string;
33
+ identityBasis: string[];
34
+ }
35
+ /** Verdict object — single source of truth for a verify call. */
36
+ interface Verdict {
37
+ /** When degraded === true, this is ALWAYS "challenge", never "allow". */
38
+ decision: Decision;
39
+ actorType: ActorType;
40
+ /** 0..1 */
41
+ confidence: number;
42
+ /** Human-readable; always populated for ops teams. */
43
+ reasons: string[];
44
+ principal: Principal;
45
+ /** ES256 JWT decision receipt, bound to session. */
46
+ verdictToken: string;
47
+ /** Store on the account row — feedback join key. */
48
+ eventId: string;
49
+ /** true → served under timeout/error → decision forced to challenge. */
50
+ degraded: boolean;
51
+ /** log_only verdicts are for dashboards/observation, not partner-side blocking. */
52
+ enforcementMode: EnforcementMode;
53
+ /** true → this allow honors a challenge completed for the session (context.challengeId). */
54
+ challengeCleared?: boolean;
55
+ }
56
+ /** Subject signals for verify. Prefer hashed fields over raw PII. */
57
+ interface Subject {
58
+ email?: string;
59
+ emailSha256?: string;
60
+ ip?: string;
61
+ ipTrunc?: string;
62
+ userAgent?: string;
63
+ /** Includes Web Bot Auth headers when present. */
64
+ headers?: Record<string, string | string[] | undefined>;
65
+ formData?: Record<string, unknown>;
66
+ formDataHashed?: Record<string, unknown>;
67
+ }
68
+ /** Request body for POST /v1/verify. */
69
+ interface VerifyRequest {
70
+ idempotencyKey?: string;
71
+ action: Action;
72
+ /** Binds any resulting challenge + verdictToken to THIS session. */
73
+ session?: string;
74
+ surface?: string;
75
+ subject: Subject;
76
+ context?: Record<string, unknown>;
77
+ }
78
+ /** Request body for POST /v1/feedback. */
79
+ interface FeedbackRequest {
80
+ eventId: string;
81
+ outcome: Outcome;
82
+ /** $ of infra/credit burned, or ARR — units in `unit`. */
83
+ value?: number;
84
+ unit?: string;
85
+ observedAt?: string;
86
+ }
87
+ /** Response from POST /v1/feedback. */
88
+ interface FeedbackResponse {
89
+ ok: true;
90
+ eventId: string;
91
+ }
92
+ /** Request body for POST /v1/challenge — issue or complete. */
93
+ interface ChallengeRequest {
94
+ /** eventId from the verify verdict that routed to challenge. */
95
+ eventId: string;
96
+ /** Binds the challenge to this session; completion and re-verify must match. */
97
+ session?: string;
98
+ /** Ranked method preference, best → worst. Never defaults to email_otp. */
99
+ prefer?: ChallengeMethod[];
100
+ /** Completion mode — the challenge being solved. */
101
+ challengeId?: string;
102
+ /** Completion mode — server-verified proof bound to challengeId + session. */
103
+ proof?: ChallengeProof;
104
+ }
105
+ /** Server-verified completion proof. */
106
+ type ChallengeProof = {
107
+ type: "proof_of_work";
108
+ /** First nonce where sha256(`${challengeId}:${seed}:${nonce}`) hex starts with `difficulty` zero nibbles. */
109
+ nonce: string;
110
+ } | {
111
+ type: "stripe_setup_intent";
112
+ /** SetupIntent id from the issued instructions; status must be succeeded. */
113
+ setupIntentId: string;
114
+ };
115
+ /** Response from POST /v1/challenge (issue mode). */
116
+ interface ChallengeResponse {
117
+ challengeId: string;
118
+ method: ChallengeMethod;
119
+ instructions: {
120
+ type: "pow";
121
+ /** Leading zero nibbles required in the sha256 hex digest. */
122
+ difficulty: number;
123
+ /** Combine as `${challengeId}:${seed}:${nonce}`. */
124
+ seed: string;
125
+ algorithm: "sha256-prefix";
126
+ } | {
127
+ type: "stripe_setup_intent";
128
+ /** test = real Stripe test-mode; stub = deterministic local/CI id. */
129
+ mode: "test" | "stub";
130
+ clientSecret: string;
131
+ publishableKey?: string;
132
+ };
133
+ /** Solving only clears THIS session (anti-replay). */
134
+ boundTo: string;
135
+ expiresAt: string;
136
+ }
137
+ /** Response from POST /v1/challenge (completion mode). */
138
+ interface ChallengeCompletionResponse {
139
+ ok: true;
140
+ challengeId: string;
141
+ /** Session that was cleared (single use, anti-replay). */
142
+ boundTo: string;
143
+ clearedAt: string;
144
+ }
145
+ /** Client constructor options. */
146
+ interface ChitmarkOptions {
147
+ /** API key (Bearer). Use process.env.CHITMARK_API_KEY. */
148
+ apiKey: string;
149
+ /**
150
+ * Timeout/error behavior. Fixed to "challenge" — never allow silent bypass.
151
+ * @default "challenge"
152
+ */
153
+ onDegraded?: OnDegraded;
154
+ /**
155
+ * Client-side request budget in ms. Edge path is far faster.
156
+ * @default 800
157
+ */
158
+ timeoutMs?: number;
159
+ /**
160
+ * Client-side PII minimization before send.
161
+ * @default "hashed"
162
+ */
163
+ piiMode?: PiiMode;
164
+ /**
165
+ * Selects default base URL unless baseUrl or CHITMARK_BASE_URL is set.
166
+ * @default "production"
167
+ */
168
+ env?: ChitmarkEnv;
169
+ /**
170
+ * Override API host (e.g. local edge). Precedence:
171
+ * baseUrl > CHITMARK_BASE_URL env > env default.
172
+ */
173
+ baseUrl?: string;
174
+ /** Custom fetch implementation (Workers, tests). */
175
+ fetch?: typeof fetch;
176
+ /** User-Agent / client identification sent as X-Chitmark-Client. */
177
+ clientName?: string;
178
+ }
179
+ interface ChitmarkErrorBody {
180
+ error: string;
181
+ message?: string;
182
+ code?: string;
183
+ }
184
+
185
+ /**
186
+ * Base error for all Chitmark client failures.
187
+ */
188
+ declare class ChitmarkError extends Error {
189
+ name: string;
190
+ readonly status?: number;
191
+ readonly code?: string;
192
+ readonly body?: ChitmarkErrorBody | string;
193
+ constructor(message: string, options?: {
194
+ status?: number;
195
+ code?: string;
196
+ body?: ChitmarkErrorBody | string;
197
+ });
198
+ }
199
+ /**
200
+ * Raised when the API returns a non-2xx response.
201
+ */
202
+ declare class ChitmarkApiError extends ChitmarkError {
203
+ name: string;
204
+ constructor(status: number, body: ChitmarkErrorBody | string, message?: string);
205
+ }
206
+ /**
207
+ * Build the degraded challenge verdict used on timeout / transport failure.
208
+ * Golden rule: fail to challenge, never to allow.
209
+ */
210
+ declare function degradedChallengeVerdict(reason: string, session?: string): Verdict;
211
+ /**
212
+ * Ensure degraded responses never surface as allow (defense in depth client-side).
213
+ */
214
+ declare function enforceDegradedNeverAllow(verdict: Verdict): Verdict;
215
+
216
+ /**
217
+ * Client-side verdictToken verification (MVP-016).
218
+ *
219
+ * A verdictToken is an ES256 JWT decision receipt minted by the edge and bound
220
+ * to session (`sub`), origin (`aud`), and `jti` with a short `exp`. This module
221
+ * validates shape, signature (against the JWKS served from
222
+ * GET /.well-known/jwks.json), expiry, session binding, and — when an expected
223
+ * audience is supplied — origin binding. Rejection reasons are surfaced as
224
+ * `VerdictTokenError.code`.
225
+ */
226
+
227
+ interface VerdictTokenClaims {
228
+ eventId: string;
229
+ decision: Decision;
230
+ actorType: ActorType;
231
+ confidence: number;
232
+ aud: string;
233
+ sub: string;
234
+ jti: string;
235
+ iat: number;
236
+ exp: number;
237
+ degraded: boolean;
238
+ }
239
+ type VerdictTokenErrorCode = "malformed_token" | "wrong_algorithm" | "jwks_unavailable" | "unknown_kid" | "invalid_signature" | "expired" | "not_yet_valid" | "wrong_session" | "wrong_aud";
240
+ declare class VerdictTokenError extends ChitmarkError {
241
+ name: string;
242
+ constructor(code: VerdictTokenErrorCode, message: string);
243
+ }
244
+ interface VerifyVerdictTokenOptions {
245
+ /** Expected session — token `sub` must equal this or verification fails. */
246
+ session?: string;
247
+ /** Expected origin — token `aud` must equal this or verification fails. */
248
+ aud?: string;
249
+ /** JWKS URL; defaults to https://api.chitmark.com/.well-known/jwks.json. */
250
+ jwksUrl?: string;
251
+ /** Pre-fetched JWKS; skips the network fetch when provided. */
252
+ jwks?: {
253
+ keys: Array<Record<string, unknown>>;
254
+ };
255
+ /** Injectable fetch (Node 24+/browsers have a global). */
256
+ fetch?: typeof fetch;
257
+ /** Clock override for tests. */
258
+ nowMs?: number;
259
+ /** Clock skew tolerance in seconds; defaults to 30. */
260
+ leewaySeconds?: number;
261
+ }
262
+ declare function verifyVerdictToken(token: string, options?: VerifyVerdictTokenOptions): Promise<VerdictTokenClaims>;
263
+
264
+ /**
265
+ * Official Chitmark client (Open Agent Ledger) — three verbs:
266
+ * verify, feedback, challenge. Trust decisions on agent-mediated actions,
267
+ * tuned by business outcomes.
268
+ *
269
+ * @example
270
+ * ```ts
271
+ * const chitmark = new Chitmark({ apiKey: process.env.CHITMARK_API_KEY! });
272
+ * const verdict = await chitmark.verify({
273
+ * action: "signup",
274
+ * subject: { email, ip, userAgent },
275
+ * });
276
+ * ```
277
+ */
278
+ declare class Chitmark {
279
+ private readonly apiKey;
280
+ private readonly baseUrl;
281
+ private readonly timeoutMs;
282
+ private readonly piiMode;
283
+ private readonly onDegraded;
284
+ private readonly fetchImpl;
285
+ private readonly clientName;
286
+ constructor(options: ChitmarkOptions);
287
+ /**
288
+ * Score + route an inbound action (signup / trial / …).
289
+ * Hot path at the edge; client budget defaults to 800ms.
290
+ * On timeout/transport error — or a malformed verdict shape from the API —
291
+ * returns a degraded challenge verdict (never allow, never crashes).
292
+ *
293
+ * Persist `v.eventId` on the account row: feedback joins only on that id.
294
+ */
295
+ verify(request: VerifyRequest): Promise<Verdict>;
296
+ /**
297
+ * Report a downstream outcome joined on the stored eventId from verify.
298
+ * This is the data moat — attribution is a schema contract, not a fuzzy match.
299
+ */
300
+ feedback(request: FeedbackRequest): Promise<FeedbackResponse>;
301
+ /**
302
+ * Issue an asymmetric-cost challenge after decision === challenge | degraded.
303
+ */
304
+ challenge(request: ChallengeRequest): Promise<ChallengeResponse>;
305
+ /**
306
+ * Complete a challenge by submitting its solved proof. Clears only the bound
307
+ * session, once. Re-verify with `context.challengeId` to have the next
308
+ * verify honor the cleared session.
309
+ */
310
+ completeChallenge(request: {
311
+ eventId: string;
312
+ challengeId: string;
313
+ session?: string;
314
+ proof: ChallengeProof;
315
+ }): Promise<ChallengeCompletionResponse>;
316
+ /**
317
+ * Verify a verdictToken's signature against the API's JWKS and its session /
318
+ * origin binding (MVP-016). Rejects expired tokens, unknown kids, and
319
+ * invalid signatures with `VerdictTokenError`. Pass `session` to enforce
320
+ * session binding (recommended) and `aud` to enforce origin binding.
321
+ */
322
+ verifyVerdictToken(token: string, options?: Omit<VerifyVerdictTokenOptions, "jwksUrl" | "fetch">): Promise<VerdictTokenClaims>;
323
+ /**
324
+ * Low-level HTTP helper. Prefer verify / feedback / challenge.
325
+ */
326
+ request<T>(path: string, init: {
327
+ method: string;
328
+ body?: unknown;
329
+ idempotencyKey?: string;
330
+ }): Promise<T>;
331
+ /** Public accessors for inspection / tests. */
332
+ get config(): {
333
+ baseUrl: string;
334
+ timeoutMs: number;
335
+ piiMode: PiiMode;
336
+ onDegraded: OnDegraded;
337
+ };
338
+ }
339
+
340
+ /**
341
+ * Client-side PII minimization helpers.
342
+ * Default SDK mode hashes email and truncates IP before leaving the customer process.
343
+ */
344
+ /**
345
+ * SHA-256 hex digest of lowercased, trimmed email.
346
+ * Uses Web Crypto (Node 18+, browsers, Workers).
347
+ */
348
+ declare function hashEmailSha256(email: string): Promise<string>;
349
+ /**
350
+ * Truncate IPv4 to /24 or IPv6 to /48 for coarse geo without host identity.
351
+ */
352
+ declare function truncateIp(ip: string): string;
353
+ /**
354
+ * Reduce form payload to non-PII keys (domain-style fields only for hashed mode).
355
+ * Free-text is dropped; companyDomain-style keys are kept as-is when domain-like.
356
+ */
357
+ declare function minimizeFormData(formData: Record<string, unknown> | undefined): Record<string, unknown> | undefined;
358
+
359
+ declare const CHITMARK_HTTP_STATUS: {
360
+ readonly ok: 200;
361
+ readonly created: 201;
362
+ readonly badRequest: 400;
363
+ readonly unauthorized: 401;
364
+ readonly forbidden: 403;
365
+ readonly notFound: 404;
366
+ readonly conflict: 409;
367
+ readonly preconditionRequired: 428;
368
+ readonly tooManyRequests: 429;
369
+ };
370
+ type ChitmarkHttpStatus = (typeof CHITMARK_HTTP_STATUS)[keyof typeof CHITMARK_HTTP_STATUS];
371
+ declare const CHITMARK_HTTP_STATUS_MEANING: {
372
+ readonly ok: "Request succeeded. For verify gates, continue only after an explicit non-degraded allow.";
373
+ readonly created: "Resource was created after an explicit non-degraded allow.";
374
+ readonly badRequest: "Required request fields are missing or malformed.";
375
+ readonly unauthorized: "API key is missing or invalid.";
376
+ readonly forbidden: "Verify returned deny; block the action.";
377
+ readonly notFound: "Referenced Chitmark resource was not found.";
378
+ readonly conflict: "Idempotency key was reused with a materially different payload.";
379
+ readonly preconditionRequired: "Verify returned challenge or degraded; require friction before continuing.";
380
+ readonly tooManyRequests: "Request was rate limited.";
381
+ };
382
+ declare function verdictToHttpStatus(verdict: Verdict): ChitmarkHttpStatus;
383
+
384
+ export { type Action as A, truncateIp as B, Chitmark as C, type Decision as D, type EnforcementMode as E, type FeedbackRequest as F, verdictToHttpStatus as G, verifyVerdictToken as H, type OnDegraded as O, type PiiMode as P, type Subject as S, type VerifyRequest as V, type Verdict as a, type ActorType as b, CHITMARK_HTTP_STATUS as c, CHITMARK_HTTP_STATUS_MEANING as d, type ChallengeCompletionResponse as e, type ChallengeMethod as f, type ChallengeProof as g, type ChallengeRequest as h, type ChallengeResponse as i, ChitmarkApiError as j, type ChitmarkEnv as k, ChitmarkError as l, type ChitmarkErrorBody as m, type ChitmarkHttpStatus as n, type ChitmarkOptions as o, type FeedbackResponse as p, type Outcome as q, type Principal as r, type VerdictTokenClaims as s, VerdictTokenError as t, type VerdictTokenErrorCode as u, type VerifyVerdictTokenOptions as v, degradedChallengeVerdict as w, enforceDegradedNeverAllow as x, hashEmailSha256 as y, minimizeFormData as z };