@secondlayer/shared 11.20.0 → 11.22.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 (43) hide show
  1. package/dist/src/archive/range-digest.d.ts +65 -0
  2. package/dist/src/archive/semantic-digest-builder.d.ts +65 -0
  3. package/dist/src/classify/index.d.ts +83 -0
  4. package/dist/src/classify/index.js +464 -0
  5. package/dist/src/classify/index.js.map +18 -0
  6. package/dist/src/coverage/index.d.ts +65 -0
  7. package/dist/src/db/index.d.ts +72 -1
  8. package/dist/src/db/index.js +3 -1
  9. package/dist/src/db/index.js.map +3 -3
  10. package/dist/src/db/queries/chain-reorgs.d.ts +65 -0
  11. package/dist/src/db/queries/chain-reorgs.js +3 -1
  12. package/dist/src/db/queries/chain-reorgs.js.map +3 -3
  13. package/dist/src/db/queries/contracts.d.ts +65 -0
  14. package/dist/src/db/queries/instance.d.ts +65 -0
  15. package/dist/src/db/queries/integrity.d.ts +65 -0
  16. package/dist/src/db/queries/subgraph-gaps.d.ts +65 -0
  17. package/dist/src/db/queries/subgraph-operations.d.ts +65 -0
  18. package/dist/src/db/queries/subgraphs.d.ts +69 -0
  19. package/dist/src/db/queries/subgraphs.js +10 -2
  20. package/dist/src/db/queries/subgraphs.js.map +4 -4
  21. package/dist/src/db/queries/sync-scope.d.ts +65 -0
  22. package/dist/src/db/queries/webhooks.d.ts +65 -0
  23. package/dist/src/db/schema.d.ts +70 -1
  24. package/dist/src/error-envelope.d.ts +48 -0
  25. package/dist/src/error-envelope.js +76 -0
  26. package/dist/src/error-envelope.js.map +10 -0
  27. package/dist/src/index.d.ts +288 -208
  28. package/dist/src/index.js +6 -1
  29. package/dist/src/index.js.map +5 -5
  30. package/dist/src/node/local-client.d.ts +65 -0
  31. package/dist/src/queue/listener.js +3 -1
  32. package/dist/src/queue/listener.js.map +3 -3
  33. package/dist/src/schemas/index.d.ts +30 -1
  34. package/dist/src/schemas/index.js.map +2 -2
  35. package/dist/src/schemas/subgraphs.d.ts +30 -1
  36. package/dist/src/schemas/subgraphs.js.map +2 -2
  37. package/dist/src/subgraphs/spec.d.ts +27 -0
  38. package/dist/src/subgraphs/spec.js +4 -1
  39. package/dist/src/subgraphs/spec.js.map +3 -3
  40. package/migrations/0155_api_failed_requests.ts +37 -0
  41. package/migrations/0156_feedback_tickets.ts +47 -0
  42. package/migrations/0157_subgraph_pin_verification.ts +35 -0
  43. package/package.json +17 -1
@@ -0,0 +1,464 @@
1
+ // src/env.ts
2
+ import { z } from "zod";
3
+ var networksSchema = z.string().transform((val) => {
4
+ const networks = val.split(",").map((n) => n.trim()).filter(Boolean);
5
+ const valid = ["mainnet", "testnet"];
6
+ for (const n of networks) {
7
+ if (!valid.includes(n)) {
8
+ throw new Error(`Invalid network: ${n}. Must be one of: ${valid.join(", ")}`);
9
+ }
10
+ }
11
+ return networks;
12
+ });
13
+ var envSchema = z.object({
14
+ DATABASE_URL: z.preprocess((val) => typeof val === "string" && val.length === 0 ? undefined : val, z.string().url().optional()),
15
+ SOURCE_DATABASE_URL: z.preprocess((val) => typeof val === "string" && val.length === 0 ? undefined : val, z.string().url().optional()),
16
+ TARGET_DATABASE_URL: z.preprocess((val) => typeof val === "string" && val.length === 0 ? undefined : val, z.string().url().optional()),
17
+ NETWORK: z.enum(["mainnet", "testnet"]).optional(),
18
+ NETWORKS: networksSchema.optional(),
19
+ LOG_LEVEL: z.enum(["debug", "info", "warn", "error"]).default("info"),
20
+ NODE_ENV: z.enum(["development", "production", "test"]).default("development")
21
+ });
22
+ var cachedEnv = null;
23
+ function getEnv() {
24
+ if (cachedEnv) {
25
+ return cachedEnv;
26
+ }
27
+ const result = envSchema.safeParse(process.env);
28
+ if (!result.success) {
29
+ console.error("❌ Invalid environment configuration:");
30
+ console.error(z.treeifyError(result.error));
31
+ throw new Error("Invalid environment configuration");
32
+ }
33
+ let enabledNetworks;
34
+ if (result.data.NETWORKS && result.data.NETWORKS.length > 0) {
35
+ enabledNetworks = result.data.NETWORKS;
36
+ } else if (result.data.NETWORK) {
37
+ enabledNetworks = [result.data.NETWORK];
38
+ } else {
39
+ enabledNetworks = ["mainnet"];
40
+ }
41
+ cachedEnv = { ...result.data, enabledNetworks };
42
+ return cachedEnv;
43
+ }
44
+ function isProductionEnv() {
45
+ return process.env["NODE_ENV"] === "production";
46
+ }
47
+ function isPox4DecoderEnabled(env = process.env) {
48
+ return env.POX4_DECODER_ENABLED !== "false";
49
+ }
50
+ function isPox5DecoderEnabled(env = process.env) {
51
+ return env.POX5_DECODER_ENABLED !== "false";
52
+ }
53
+ function isSbtcDecoderEnabled(env = process.env) {
54
+ return env.SBTC_DECODER_ENABLED !== "false";
55
+ }
56
+ function isBnsDecoderEnabled(env = process.env) {
57
+ return env.BNS_DECODER_ENABLED === "true";
58
+ }
59
+ // src/logger.ts
60
+ var LOG_LEVELS = {
61
+ debug: 0,
62
+ info: 1,
63
+ warn: 2,
64
+ error: 3
65
+ };
66
+
67
+ class Logger {
68
+ _level;
69
+ _isProduction;
70
+ _initialized = false;
71
+ init() {
72
+ if (this._initialized)
73
+ return;
74
+ this._initialized = true;
75
+ try {
76
+ const env = getEnv();
77
+ this._level = env.LOG_LEVEL;
78
+ this._isProduction = env.NODE_ENV === "production";
79
+ } catch {
80
+ this._level = "info";
81
+ this._isProduction = false;
82
+ }
83
+ }
84
+ get level() {
85
+ this.init();
86
+ return this._level;
87
+ }
88
+ get isProduction() {
89
+ this.init();
90
+ return this._isProduction;
91
+ }
92
+ shouldLog(level) {
93
+ return LOG_LEVELS[level] >= LOG_LEVELS[this.level];
94
+ }
95
+ formatMessage(level, message, meta) {
96
+ const timestamp = new Date().toISOString();
97
+ if (this.isProduction) {
98
+ return JSON.stringify({
99
+ timestamp,
100
+ level,
101
+ message,
102
+ ...meta
103
+ });
104
+ }
105
+ const metaStr = meta ? ` ${JSON.stringify(meta)}` : "";
106
+ return `[${timestamp}] ${level.toUpperCase()}: ${message}${metaStr}`;
107
+ }
108
+ debug(message, meta) {
109
+ if (this.shouldLog("debug")) {
110
+ console.debug(this.formatMessage("debug", message, meta));
111
+ }
112
+ }
113
+ info(message, meta) {
114
+ if (this.shouldLog("info")) {
115
+ console.info(this.formatMessage("info", message, meta));
116
+ }
117
+ }
118
+ warn(message, meta) {
119
+ if (this.shouldLog("warn")) {
120
+ console.warn(this.formatMessage("warn", message, meta));
121
+ }
122
+ }
123
+ error(message, meta) {
124
+ if (this.shouldLog("error")) {
125
+ console.error(this.formatMessage("error", message, meta));
126
+ }
127
+ }
128
+ }
129
+ var logger = new Logger;
130
+
131
+ // src/classify/confidence.ts
132
+ var clamp01 = (n) => Math.min(1, Math.max(0, n));
133
+ function choiceConfidence(probabilities) {
134
+ const ps = Object.values(probabilities);
135
+ const k = ps.length;
136
+ if (k <= 1)
137
+ return 1;
138
+ const pmax = Math.max(...ps);
139
+ return clamp01((pmax - 1 / k) / (1 - 1 / k));
140
+ }
141
+ function scoreConfidence(probabilities, levels) {
142
+ if (levels <= 1)
143
+ return 1;
144
+ let mode = 0;
145
+ let best = -1;
146
+ for (let i = 0;i < levels; i++) {
147
+ const p = probabilities[String(i)] ?? 0;
148
+ if (p > best) {
149
+ best = p;
150
+ mode = i;
151
+ }
152
+ }
153
+ let e = 0;
154
+ let d = 0;
155
+ for (let i = 0;i < levels; i++) {
156
+ e += (probabilities[String(i)] ?? 0) * Math.abs(i - mode);
157
+ d += Math.abs(i - (levels - 1) / 2);
158
+ }
159
+ d /= levels;
160
+ return clamp01(1 - e / d);
161
+ }
162
+ function booleanConfidence(p) {
163
+ return Math.abs(2 * p - 1);
164
+ }
165
+ function normalizeAnswer(question, raw) {
166
+ if (raw.type === "refusal")
167
+ return { type: "refusal" };
168
+ if (raw.type === "boolean" && question.type === "boolean") {
169
+ return {
170
+ type: "boolean",
171
+ probability: raw.probability,
172
+ confidence: booleanConfidence(raw.probability)
173
+ };
174
+ }
175
+ if (raw.type === "choice" && question.type === "choice") {
176
+ const probabilities = raw.probabilities ?? {};
177
+ return {
178
+ type: "choice",
179
+ choice: raw.choice,
180
+ probabilities,
181
+ confidence: raw.probabilities ? choiceConfidence(probabilities) : 0
182
+ };
183
+ }
184
+ if (raw.type === "score" && question.type === "score") {
185
+ const probabilities = raw.probabilities ?? {};
186
+ return {
187
+ type: "score",
188
+ score: raw.score,
189
+ probabilities,
190
+ confidence: raw.probabilities ? scoreConfidence(probabilities, question.criteria.length) : 0
191
+ };
192
+ }
193
+ return { type: "refusal" };
194
+ }
195
+
196
+ // src/classify/types.ts
197
+ class ClassifierInputError extends Error {
198
+ constructor(message) {
199
+ super(message);
200
+ this.name = "ClassifierInputError";
201
+ }
202
+ }
203
+
204
+ // src/classify/limits.ts
205
+ var MAX_QUESTIONS = 64;
206
+ var MIN_CHOICE = 2;
207
+ var MAX_CHOICE = 255;
208
+ var MIN_SCORE = 2;
209
+ var MAX_SCORE = 10;
210
+ var QUESTION_ID = /^[A-Za-z0-9_.-]{1,100}$/;
211
+ var MAX_STATE_CHARS = 24000;
212
+ function validateQuestions(questions) {
213
+ const ids = Object.keys(questions);
214
+ if (ids.length === 0)
215
+ throw new ClassifierInputError("no questions");
216
+ if (ids.length > MAX_QUESTIONS) {
217
+ throw new ClassifierInputError(`more than ${MAX_QUESTIONS} questions`);
218
+ }
219
+ for (const id of ids) {
220
+ if (!QUESTION_ID.test(id)) {
221
+ throw new ClassifierInputError(`invalid question id: ${id}`);
222
+ }
223
+ const q = questions[id];
224
+ if (q.type === "choice") {
225
+ const n = Object.keys(q.criteria).length;
226
+ if (n < MIN_CHOICE || n > MAX_CHOICE) {
227
+ throw new ClassifierInputError(`question ${id}: choice needs ${MIN_CHOICE}-${MAX_CHOICE} options`);
228
+ }
229
+ } else if (q.type === "score") {
230
+ const n = q.criteria.length;
231
+ if (n < MIN_SCORE || n > MAX_SCORE) {
232
+ throw new ClassifierInputError(`question ${id}: score needs ${MIN_SCORE}-${MAX_SCORE} levels`);
233
+ }
234
+ } else if (q.type !== "boolean") {
235
+ throw new ClassifierInputError(`question ${id}: unknown type`);
236
+ }
237
+ }
238
+ }
239
+ function stateSize(state) {
240
+ return typeof state === "string" ? state.length : JSON.stringify(state).length;
241
+ }
242
+
243
+ // src/classify/system-one.ts
244
+ function toWireState(state) {
245
+ const parts = state.map((p) => {
246
+ if (p.type === "text")
247
+ return p.text;
248
+ if (p.type === "json")
249
+ return p.value;
250
+ throw new Error("file state unsupported");
251
+ });
252
+ return parts.length === 1 ? parts[0] : parts;
253
+ }
254
+ function renormalize(p) {
255
+ const sum = Object.values(p).reduce((a, b) => a + b, 0);
256
+ if (!(sum > 0))
257
+ return p;
258
+ return Object.fromEntries(Object.entries(p).map(([k, v]) => [k, v / sum]));
259
+ }
260
+ function mapAnswer(a) {
261
+ if (a.type === "noul" && typeof a.noul === "number") {
262
+ return { type: "boolean", probability: a.noul };
263
+ }
264
+ if (a.type === "choice" && typeof a.choice === "string") {
265
+ return {
266
+ type: "choice",
267
+ choice: a.choice,
268
+ ...a.probabilities && { probabilities: renormalize(a.probabilities) }
269
+ };
270
+ }
271
+ if (a.type === "score" && typeof a.score === "number") {
272
+ if (!a.probabilities)
273
+ return { type: "score", score: a.score };
274
+ const probabilities = renormalize(a.probabilities);
275
+ const score = Object.entries(probabilities).reduce((s, [level, p]) => s + Number(level) * p, 0);
276
+ return { type: "score", score, probabilities };
277
+ }
278
+ return { type: "refusal" };
279
+ }
280
+ function systemOneModel(opts) {
281
+ const doFetch = opts.fetchImpl ?? fetch;
282
+ return {
283
+ specificationVersion: "v4",
284
+ provider: `secondlayer.${opts.provider}`,
285
+ modelId: opts.modelId,
286
+ supportedQuestionTypes: ["choice", "score", "boolean"],
287
+ async doDecide({ state, questions, abortSignal }) {
288
+ const wireQuestions = Object.fromEntries(Object.entries(questions).map(([id, q]) => [
289
+ id,
290
+ q.type === "boolean" ? { ...q, type: "noul" } : q
291
+ ]));
292
+ const res = await doFetch(opts.url, {
293
+ method: "POST",
294
+ headers: {
295
+ "content-type": "application/json",
296
+ ...opts.apiKey && { authorization: `Bearer ${opts.apiKey}` }
297
+ },
298
+ body: JSON.stringify({
299
+ model: opts.modelId,
300
+ state: toWireState(state),
301
+ questions: wireQuestions
302
+ }),
303
+ signal: abortSignal
304
+ });
305
+ if (!res.ok)
306
+ throw new Error(`${opts.provider} ${res.status}`);
307
+ const json = await res.json();
308
+ const body = json.result ?? json;
309
+ const answers = Object.fromEntries(Object.entries(body.answers ?? {}).map(([id, a]) => [id, mapAnswer(a)]));
310
+ return {
311
+ answers,
312
+ warnings: [],
313
+ usage: {
314
+ inputTokens: body.usage?.input_tokens,
315
+ outputTokens: body.usage?.output_tokens
316
+ },
317
+ response: { modelId: body.model ?? opts.modelId }
318
+ };
319
+ }
320
+ };
321
+ }
322
+
323
+ // src/classify/providers.ts
324
+ var DEFAULT_MODEL = {
325
+ jev: "typesafe-ai/jev",
326
+ kev: "kev-latest",
327
+ clef: "clef"
328
+ };
329
+ var REQUIRED_ENV = {
330
+ jev: ["AI_GATEWAY_API_KEY"],
331
+ kev: ["KEV_URL"],
332
+ clef: ["CLOUDFLARE_ACCOUNT_ID", "CLOUDFLARE_API_TOKEN"]
333
+ };
334
+ var envModel = (env) => env.CLASSIFIER_MODEL?.trim() || undefined;
335
+ function resolveProvider(env = process.env) {
336
+ const rules = { name: "rules", modelId: "rules" };
337
+ const requested = env.CLASSIFIER?.trim();
338
+ let name;
339
+ if (!requested) {
340
+ name = env.AI_GATEWAY_API_KEY ? "jev" : "rules";
341
+ } else if (requested === "jev" || requested === "kev" || requested === "clef" || requested === "rules") {
342
+ name = requested;
343
+ } else {
344
+ logger.warn(`classifier: unknown CLASSIFIER=${requested}; using rules`);
345
+ return rules;
346
+ }
347
+ if (name === "rules")
348
+ return rules;
349
+ const missing = REQUIRED_ENV[name].find((v) => !env[v]);
350
+ if (missing) {
351
+ logger.warn(`classifier: CLASSIFIER=${name} missing ${missing}; using rules`);
352
+ return rules;
353
+ }
354
+ return { name, modelId: envModel(env) ?? DEFAULT_MODEL[name] };
355
+ }
356
+ function buildModel(name, modelId, env = process.env, fetchImpl) {
357
+ if (name === "jev")
358
+ return modelId;
359
+ if (name === "kev") {
360
+ return systemOneModel({
361
+ provider: "kev",
362
+ url: `${(env.KEV_URL ?? "").replace(/\/$/, "")}/v1/systemone`,
363
+ modelId,
364
+ apiKey: env.KEV_API_KEY,
365
+ fetchImpl
366
+ });
367
+ }
368
+ return systemOneModel({
369
+ provider: "clef",
370
+ url: `https://api.cloudflare.com/client/v4/accounts/${env.CLOUDFLARE_ACCOUNT_ID}/ai/run/@cf/cloudflare/${modelId}`,
371
+ modelId,
372
+ apiKey: env.CLOUDFLARE_API_TOKEN,
373
+ fetchImpl
374
+ });
375
+ }
376
+
377
+ // src/classify/rules.ts
378
+ function answerFor(question, value) {
379
+ if (value === undefined)
380
+ return { type: "refusal" };
381
+ if (question.type === "choice") {
382
+ if (typeof value !== "string" || !(value in question.criteria)) {
383
+ return { type: "refusal" };
384
+ }
385
+ const probabilities = Object.fromEntries(Object.keys(question.criteria).map((k) => [k, k === value ? 1 : 0]));
386
+ return { type: "choice", choice: value, probabilities, confidence: 1 };
387
+ }
388
+ if (question.type === "score") {
389
+ const n = question.criteria.length;
390
+ if (typeof value !== "number" || !Number.isInteger(value)) {
391
+ return { type: "refusal" };
392
+ }
393
+ if (value < 0 || value >= n)
394
+ return { type: "refusal" };
395
+ const probabilities = Object.fromEntries(Array.from({ length: n }, (_, i) => [String(i), i === value ? 1 : 0]));
396
+ return { type: "score", score: value, probabilities, confidence: 1 };
397
+ }
398
+ if (typeof value !== "boolean")
399
+ return { type: "refusal" };
400
+ return { type: "boolean", probability: value ? 1 : 0, confidence: 1 };
401
+ }
402
+ function runRules(questions, fn, state) {
403
+ const values = fn(state) ?? {};
404
+ const out = {};
405
+ for (const id of Object.keys(questions)) {
406
+ out[id] = answerFor(questions[id], values[id]);
407
+ }
408
+ return out;
409
+ }
410
+
411
+ // src/classify/classify.ts
412
+ async function classify(input) {
413
+ validateQuestions(input.questions);
414
+ const env = input.env ?? process.env;
415
+ const resolved = resolveProvider(env);
416
+ const name = input.provider ?? resolved.name;
417
+ if (name === "rules") {
418
+ if (!input.rules)
419
+ return null;
420
+ return {
421
+ provider: "rules",
422
+ modelId: "rules",
423
+ answers: runRules(input.questions, input.rules, input.state)
424
+ };
425
+ }
426
+ const modelId = input.modelId ?? (input.provider ? undefined : envModel(env)) ?? DEFAULT_MODEL[name];
427
+ if (stateSize(input.state) > MAX_STATE_CHARS) {
428
+ logger.warn(`classifier: state over ${MAX_STATE_CHARS} chars; skipped`);
429
+ return null;
430
+ }
431
+ try {
432
+ const { experimental_decide: decide } = await import("ai");
433
+ const r = await decide({
434
+ model: buildModel(name, modelId, env, input.fetchImpl),
435
+ state: input.state,
436
+ questions: input.questions,
437
+ maxRetries: 1,
438
+ abortSignal: AbortSignal.timeout(input.timeoutMs ?? 5000)
439
+ });
440
+ const answers = {};
441
+ for (const id of Object.keys(input.questions)) {
442
+ answers[id] = normalizeAnswer(input.questions[id], r.answers[id] ?? { type: "refusal" });
443
+ }
444
+ return {
445
+ provider: name,
446
+ modelId: r.response.modelId ?? modelId,
447
+ answers
448
+ };
449
+ } catch (err) {
450
+ logger.warn(`classifier: ${name} failed: ${err instanceof Error ? err.name : "error"}`);
451
+ return null;
452
+ }
453
+ }
454
+ export {
455
+ ClassifierInputError,
456
+ booleanConfidence,
457
+ choiceConfidence,
458
+ classify,
459
+ resolveProvider,
460
+ scoreConfidence
461
+ };
462
+
463
+ //# debugId=6AA8B48DE6A7F1DB64756E2164756E21
464
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,18 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/env.ts", "../src/logger.ts", "../src/classify/confidence.ts", "../src/classify/types.ts", "../src/classify/limits.ts", "../src/classify/system-one.ts", "../src/classify/providers.ts", "../src/classify/rules.ts", "../src/classify/classify.ts"],
4
+ "sourcesContent": [
5
+ "import { z } from \"zod\";\n\n// Parse comma-separated networks\nconst networksSchema = z.string().transform((val) => {\n\tconst networks = val\n\t\t.split(\",\")\n\t\t.map((n) => n.trim())\n\t\t.filter(Boolean);\n\tconst valid = [\"mainnet\", \"testnet\"];\n\tfor (const n of networks) {\n\t\tif (!valid.includes(n)) {\n\t\t\tthrow new Error(\n\t\t\t\t`Invalid network: ${n}. Must be one of: ${valid.join(\", \")}`,\n\t\t\t);\n\t\t}\n\t}\n\treturn networks as (\"mainnet\" | \"testnet\")[];\n});\n\ninterface EnvSchemaOutput {\n\tDATABASE_URL?: string;\n\t/**\n\t * Shared indexer DB (blocks/txs/events). Falls back to DATABASE_URL.\n\t * Set this alongside TARGET_DATABASE_URL to enable dual-DB mode.\n\t */\n\tSOURCE_DATABASE_URL?: string;\n\t/**\n\t * Tenant DB (subgraph schemas + subgraphs table). Falls back to DATABASE_URL.\n\t * Set this alongside SOURCE_DATABASE_URL to enable dual-DB mode.\n\t */\n\tTARGET_DATABASE_URL?: string;\n\tNETWORK?: \"mainnet\" | \"testnet\";\n\tNETWORKS?: (\"mainnet\" | \"testnet\")[];\n\tLOG_LEVEL: \"debug\" | \"info\" | \"warn\" | \"error\";\n\tNODE_ENV: \"development\" | \"production\" | \"test\";\n}\n\n// Cast needed: z.preprocess / z.default create different _input vs _output types\n// that z.ZodType<T> can't represent without explicit input type param\nconst envSchema: z.ZodType<EnvSchemaOutput> = z.object({\n\tDATABASE_URL: z.preprocess(\n\t\t(val) => (typeof val === \"string\" && val.length === 0 ? undefined : val),\n\t\tz.string().url().optional(),\n\t),\n\tSOURCE_DATABASE_URL: z.preprocess(\n\t\t(val) => (typeof val === \"string\" && val.length === 0 ? undefined : val),\n\t\tz.string().url().optional(),\n\t),\n\tTARGET_DATABASE_URL: z.preprocess(\n\t\t(val) => (typeof val === \"string\" && val.length === 0 ? undefined : val),\n\t\tz.string().url().optional(),\n\t),\n\tNETWORK: z.enum([\"mainnet\", \"testnet\"]).optional(),\n\tNETWORKS: networksSchema.optional(),\n\tLOG_LEVEL: z.enum([\"debug\", \"info\", \"warn\", \"error\"]).default(\"info\"),\n\tNODE_ENV: z\n\t\t.enum([\"development\", \"production\", \"test\"])\n\t\t.default(\"development\"),\n}) as unknown as z.ZodType<EnvSchemaOutput>;\n\nexport type Env = EnvSchemaOutput & {\n\tenabledNetworks: (\"mainnet\" | \"testnet\")[];\n};\n\nlet cachedEnv: Env | null = null;\n\nexport function getEnv(): Env {\n\tif (cachedEnv) {\n\t\treturn cachedEnv;\n\t}\n\n\tconst result = envSchema.safeParse(process.env);\n\n\tif (!result.success) {\n\t\tconsole.error(\"❌ Invalid environment configuration:\");\n\t\tconsole.error(z.treeifyError(result.error));\n\t\tthrow new Error(\"Invalid environment configuration\");\n\t}\n\n\t// Compute enabled networks from NETWORKS or NETWORK\n\tlet enabledNetworks: (\"mainnet\" | \"testnet\")[];\n\tif (result.data.NETWORKS && result.data.NETWORKS.length > 0) {\n\t\tenabledNetworks = result.data.NETWORKS;\n\t} else if (result.data.NETWORK) {\n\t\tenabledNetworks = [result.data.NETWORK];\n\t} else {\n\t\tenabledNetworks = [\"mainnet\"]; // Default\n\t}\n\n\tcachedEnv = { ...result.data, enabledNetworks };\n\treturn cachedEnv;\n}\n\n/**\n * True when `NODE_ENV=production`, read at RUNTIME — the single source of truth\n * for prod-vs-dev branching in this package.\n *\n * Why a helper instead of inlining `process.env.NODE_ENV === \"production\"`:\n * bunup/esbuild constant-folds the dot-access `process.env.NODE_ENV` to its\n * BUILD-time value. `@secondlayer/shared` is consumed by other services as its\n * built `dist`, so an inlined check freezes to a literal in the shipped bundle\n * (e.g. `const isProd = false`) and silently ignores the container's real\n * NODE_ENV. The bracket access below is NOT folded, so it stays a runtime read.\n * Route every prod check through here so the footgun lives in exactly one place.\n */\nexport function isProductionEnv(): boolean {\n\t// biome-ignore lint/complexity/useLiteralKeys: bracket access is deliberate — dot-access gets constant-folded by the bundler, freezing this to a build-time literal in the shipped dist.\n\treturn process.env[\"NODE_ENV\"] === \"production\";\n}\n\ntype EnvMap = NodeJS.ProcessEnv;\n\n/**\n * PoX-4 stacking decoder is ON by default — `/v1/index/stacking` is part of the\n * public surface, so the decoder that fills `pox4_calls` runs unless explicitly\n * opted out with `POX4_DECODER_ENABLED=false` (mirrors the sBTC decoder policy).\n */\nexport function isPox4DecoderEnabled(env: EnvMap = process.env): boolean {\n\treturn env.POX4_DECODER_ENABLED !== \"false\";\n}\n\n/**\n * PoX-5 print-event decoder is ON by default (mirrors pox-4/sBTC policy).\n * No activation gating needed: pox-5 events cannot exist before the Epoch 4.0\n * hard fork (Bitcoin block 960,230), so pre-activation the decoder idles at\n * tip. Opt out with `POX5_DECODER_ENABLED=false`.\n */\nexport function isPox5DecoderEnabled(env: EnvMap = process.env): boolean {\n\treturn env.POX5_DECODER_ENABLED !== \"false\";\n}\n\n/** Default ON. Opt out with `SBTC_DECODER_ENABLED=false`. */\nexport function isSbtcDecoderEnabled(env: EnvMap = process.env): boolean {\n\treturn env.SBTC_DECODER_ENABLED !== \"false\";\n}\n\n/** Default OFF. Opt in with `BNS_DECODER_ENABLED=true`. */\nexport function isBnsDecoderEnabled(env: EnvMap = process.env): boolean {\n\treturn env.BNS_DECODER_ENABLED === \"true\";\n}\n\n// Export for testing\nexport { envSchema };\n",
6
+ "import { getEnv } from \"./env.ts\";\n\ntype LogLevel = \"debug\" | \"info\" | \"warn\" | \"error\";\n\nconst LOG_LEVELS: Record<LogLevel, number> = {\n\tdebug: 0,\n\tinfo: 1,\n\twarn: 2,\n\terror: 3,\n};\n\nclass Logger {\n\tprivate _level?: LogLevel;\n\tprivate _isProduction?: boolean;\n\tprivate _initialized = false;\n\n\tprivate init() {\n\t\tif (this._initialized) return;\n\t\tthis._initialized = true;\n\t\ttry {\n\t\t\tconst env = getEnv();\n\t\t\tthis._level = env.LOG_LEVEL;\n\t\t\tthis._isProduction = env.NODE_ENV === \"production\";\n\t\t} catch {\n\t\t\t// Fallback when env is unavailable (e.g. tests without DATABASE_URL)\n\t\t\tthis._level = \"info\";\n\t\t\tthis._isProduction = false;\n\t\t}\n\t}\n\n\tprivate get level(): LogLevel {\n\t\tthis.init();\n\t\t// biome-ignore lint/style/noNonNullAssertion: value is non-null after preceding check or by construction; TS narrowing limitation\n\t\treturn this._level!;\n\t}\n\n\tprivate get isProduction(): boolean {\n\t\tthis.init();\n\t\t// biome-ignore lint/style/noNonNullAssertion: value is non-null after preceding check or by construction; TS narrowing limitation\n\t\treturn this._isProduction!;\n\t}\n\n\tprivate shouldLog(level: LogLevel): boolean {\n\t\treturn LOG_LEVELS[level] >= LOG_LEVELS[this.level];\n\t}\n\n\tprivate formatMessage(\n\t\tlevel: LogLevel,\n\t\tmessage: string,\n\t\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\t\tmeta?: Record<string, any>,\n\t) {\n\t\tconst timestamp = new Date().toISOString();\n\n\t\tif (this.isProduction) {\n\t\t\t// JSON output for production\n\t\t\treturn JSON.stringify({\n\t\t\t\ttimestamp,\n\t\t\t\tlevel,\n\t\t\t\tmessage,\n\t\t\t\t...meta,\n\t\t\t});\n\t\t}\n\n\t\t// Human-readable output for development\n\t\tconst metaStr = meta ? ` ${JSON.stringify(meta)}` : \"\";\n\t\treturn `[${timestamp}] ${level.toUpperCase()}: ${message}${metaStr}`;\n\t}\n\n\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\tdebug(message: string, meta?: Record<string, any>): void {\n\t\tif (this.shouldLog(\"debug\")) {\n\t\t\tconsole.debug(this.formatMessage(\"debug\", message, meta));\n\t\t}\n\t}\n\n\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\tinfo(message: string, meta?: Record<string, any>): void {\n\t\tif (this.shouldLog(\"info\")) {\n\t\t\tconsole.info(this.formatMessage(\"info\", message, meta));\n\t\t}\n\t}\n\n\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\twarn(message: string, meta?: Record<string, any>): void {\n\t\tif (this.shouldLog(\"warn\")) {\n\t\t\tconsole.warn(this.formatMessage(\"warn\", message, meta));\n\t\t}\n\t}\n\n\t// biome-ignore lint/suspicious/noExplicitAny: interop boundary or dynamic-shape value where typing adds friction without runtime safety\n\terror(message: string, meta?: Record<string, any>): void {\n\t\tif (this.shouldLog(\"error\")) {\n\t\t\tconsole.error(this.formatMessage(\"error\", message, meta));\n\t\t}\n\t}\n}\n\n// Export singleton instance\nexport const logger: Logger = new Logger();\n",
7
+ "import type { Answer, Question } from \"./types.ts\";\n\nconst clamp01 = (n: number) => Math.min(1, Math.max(0, n));\n\n/** (p_max - 1/K) / (1 - 1/K). One option gives 1. */\nexport function choiceConfidence(\n\tprobabilities: Record<string, number>,\n): number {\n\tconst ps = Object.values(probabilities);\n\tconst k = ps.length;\n\tif (k <= 1) return 1;\n\tconst pmax = Math.max(...ps);\n\treturn clamp01((pmax - 1 / k) / (1 - 1 / k));\n}\n\n/** max(0, 1 - E|level - mode| / D); D = mean distance of a uniform\n * distribution over the levels from the middle level. */\nexport function scoreConfidence(\n\tprobabilities: Record<string, number>,\n\tlevels: number,\n): number {\n\tif (levels <= 1) return 1;\n\tlet mode = 0;\n\tlet best = -1;\n\tfor (let i = 0; i < levels; i++) {\n\t\tconst p = probabilities[String(i)] ?? 0;\n\t\tif (p > best) {\n\t\t\tbest = p;\n\t\t\tmode = i;\n\t\t}\n\t}\n\tlet e = 0;\n\tlet d = 0;\n\tfor (let i = 0; i < levels; i++) {\n\t\te += (probabilities[String(i)] ?? 0) * Math.abs(i - mode);\n\t\td += Math.abs(i - (levels - 1) / 2);\n\t}\n\td /= levels;\n\treturn clamp01(1 - e / d);\n}\n\n/** Host-defined: |2p - 1|. Not comparable with choice/score confidence. */\nexport function booleanConfidence(p: number): number {\n\treturn Math.abs(2 * p - 1);\n}\n\n/** Shape of an `ai` DecisionAnswer, or the rules output, before normalizing. */\nexport type RawAnswer =\n\t| { type: \"choice\"; choice: string; probabilities?: Record<string, number> }\n\t| { type: \"score\"; score: number; probabilities?: Record<string, number> }\n\t| { type: \"boolean\"; probability: number }\n\t| { type: \"refusal\" };\n\nexport function normalizeAnswer(question: Question, raw: RawAnswer): Answer {\n\tif (raw.type === \"refusal\") return { type: \"refusal\" };\n\tif (raw.type === \"boolean\" && question.type === \"boolean\") {\n\t\treturn {\n\t\t\ttype: \"boolean\",\n\t\t\tprobability: raw.probability,\n\t\t\tconfidence: booleanConfidence(raw.probability),\n\t\t};\n\t}\n\tif (raw.type === \"choice\" && question.type === \"choice\") {\n\t\tconst probabilities = raw.probabilities ?? {};\n\t\t// Never fabricate a distribution: no probabilities means confidence 0.\n\t\treturn {\n\t\t\ttype: \"choice\",\n\t\t\tchoice: raw.choice,\n\t\t\tprobabilities,\n\t\t\tconfidence: raw.probabilities ? choiceConfidence(probabilities) : 0,\n\t\t};\n\t}\n\tif (raw.type === \"score\" && question.type === \"score\") {\n\t\tconst probabilities = raw.probabilities ?? {};\n\t\treturn {\n\t\t\ttype: \"score\",\n\t\t\tscore: raw.score,\n\t\t\tprobabilities,\n\t\t\tconfidence: raw.probabilities\n\t\t\t\t? scoreConfidence(probabilities, question.criteria.length)\n\t\t\t\t: 0,\n\t\t};\n\t}\n\treturn { type: \"refusal\" };\n}\n",
8
+ "export type ClassifierName = \"jev\" | \"kev\" | \"clef\" | \"rules\";\n\nexport type Question =\n\t| { type: \"choice\"; instructions: string; criteria: Record<string, string> }\n\t| { type: \"score\"; instructions: string; criteria: readonly string[] }\n\t| {\n\t\t\ttype: \"boolean\";\n\t\t\tinstructions: string;\n\t\t\tcriteria: { true: string; false: string };\n\t };\n\nexport type Answer =\n\t| {\n\t\t\ttype: \"choice\";\n\t\t\tchoice: string;\n\t\t\tprobabilities: Record<string, number>;\n\t\t\tconfidence: number;\n\t }\n\t| {\n\t\t\ttype: \"score\";\n\t\t\tscore: number;\n\t\t\tprobabilities: Record<string, number>;\n\t\t\tconfidence: number;\n\t }\n\t| { type: \"boolean\"; probability: number; confidence: number }\n\t| { type: \"refusal\" };\n\nexport type Answers<Q extends Record<string, Question>> = {\n\t[K in keyof Q]: Answer;\n};\n\nexport type ClassifyResult<Q extends Record<string, Question>> = {\n\tprovider: ClassifierName;\n\t/** response.modelId when the backend reports one. */\n\tmodelId: string;\n\tanswers: Answers<Q>;\n};\n\n/** Deterministic answers for the offline provider. Return a value per question\n * it can decide; omitted questions come back as `{ type: \"refusal\" }`. */\nexport type RuleValue =\n\t| string /* choice option */\n\t| number /* score level index */\n\t| boolean;\nexport type RulesFn<Q extends Record<string, Question>> = (\n\tstate: string | Record<string, unknown>,\n) => { [K in keyof Q]?: RuleValue } | null;\n\nexport class ClassifierInputError extends Error {\n\tconstructor(message: string) {\n\t\tsuper(message);\n\t\tthis.name = \"ClassifierInputError\";\n\t}\n}\n",
9
+ "import { ClassifierInputError, type Question } from \"./types.ts\";\n\n// Strictest common limits across jev, kev and clef.\nexport const MAX_QUESTIONS = 64;\nexport const MIN_CHOICE = 2;\nexport const MAX_CHOICE = 255;\nexport const MIN_SCORE = 2;\nexport const MAX_SCORE = 10;\nexport const QUESTION_ID = /^[A-Za-z0-9_.-]{1,100}$/;\nexport const MAX_STATE_CHARS = 24_000;\n\n/** A bad question definition is a programmer bug: throw, never fail open. */\nexport function validateQuestions(questions: Record<string, Question>): void {\n\tconst ids = Object.keys(questions);\n\tif (ids.length === 0) throw new ClassifierInputError(\"no questions\");\n\tif (ids.length > MAX_QUESTIONS) {\n\t\tthrow new ClassifierInputError(`more than ${MAX_QUESTIONS} questions`);\n\t}\n\tfor (const id of ids) {\n\t\tif (!QUESTION_ID.test(id)) {\n\t\t\tthrow new ClassifierInputError(`invalid question id: ${id}`);\n\t\t}\n\t\tconst q = questions[id] as Question;\n\t\tif (q.type === \"choice\") {\n\t\t\tconst n = Object.keys(q.criteria).length;\n\t\t\tif (n < MIN_CHOICE || n > MAX_CHOICE) {\n\t\t\t\tthrow new ClassifierInputError(\n\t\t\t\t\t`question ${id}: choice needs ${MIN_CHOICE}-${MAX_CHOICE} options`,\n\t\t\t\t);\n\t\t\t}\n\t\t} else if (q.type === \"score\") {\n\t\t\tconst n = q.criteria.length;\n\t\t\tif (n < MIN_SCORE || n > MAX_SCORE) {\n\t\t\t\tthrow new ClassifierInputError(\n\t\t\t\t\t`question ${id}: score needs ${MIN_SCORE}-${MAX_SCORE} levels`,\n\t\t\t\t);\n\t\t\t}\n\t\t} else if (q.type !== \"boolean\") {\n\t\t\tthrow new ClassifierInputError(`question ${id}: unknown type`);\n\t\t}\n\t}\n}\n\nexport function stateSize(state: string | Record<string, unknown>): number {\n\treturn typeof state === \"string\"\n\t\t? state.length\n\t\t: JSON.stringify(state).length;\n}\n",
10
+ "import type { Experimental_DecisionModel } from \"ai\"; // type-only: erased at runtime\n\ntype DecisionModelV4 = Extract<\n\tExperimental_DecisionModel,\n\t{ doDecide: unknown }\n>;\ntype DoDecideOptions = Parameters<DecisionModelV4[\"doDecide\"]>[0];\ntype DoDecideResult = Awaited<ReturnType<DecisionModelV4[\"doDecide\"]>>;\n\ntype WireAnswer = {\n\ttype: string;\n\tchoice?: string;\n\tscore?: number;\n\tnoul?: number;\n\tprobabilities?: Record<string, number>;\n};\n\ntype WireBody = {\n\tmodel?: string;\n\tanswers?: Record<string, WireAnswer>;\n\tusage?: { input_tokens?: number; output_tokens?: number };\n};\n\nfunction toWireState(state: DoDecideOptions[\"state\"]): unknown {\n\tconst parts = state.map((p) => {\n\t\tif (p.type === \"text\") return p.text;\n\t\tif (p.type === \"json\") return p.value;\n\t\tthrow new Error(\"file state unsupported\");\n\t});\n\treturn parts.length === 1 ? parts[0] : parts;\n}\n\n/** Divide by the sum so the distribution sums to exactly 1. */\nfunction renormalize(p: Record<string, number>): Record<string, number> {\n\tconst sum = Object.values(p).reduce((a, b) => a + b, 0);\n\tif (!(sum > 0)) return p;\n\treturn Object.fromEntries(Object.entries(p).map(([k, v]) => [k, v / sum]));\n}\n\nfunction mapAnswer(a: WireAnswer): DoDecideResult[\"answers\"][string] {\n\tif (a.type === \"noul\" && typeof a.noul === \"number\") {\n\t\treturn { type: \"boolean\", probability: a.noul };\n\t}\n\tif (a.type === \"choice\" && typeof a.choice === \"string\") {\n\t\treturn {\n\t\t\ttype: \"choice\",\n\t\t\tchoice: a.choice,\n\t\t\t...(a.probabilities && { probabilities: renormalize(a.probabilities) }),\n\t\t};\n\t}\n\tif (a.type === \"score\" && typeof a.score === \"number\") {\n\t\tif (!a.probabilities) return { type: \"score\", score: a.score };\n\t\tconst probabilities = renormalize(a.probabilities);\n\t\t// decide() checks score equals the weighted mean of the distribution.\n\t\tconst score = Object.entries(probabilities).reduce(\n\t\t\t(s, [level, p]) => s + Number(level) * p,\n\t\t\t0,\n\t\t);\n\t\treturn { type: \"score\", score, probabilities };\n\t}\n\treturn { type: \"refusal\" };\n}\n\n/** Adapter for the System One wire protocol spoken by kev and clef. */\nexport function systemOneModel(opts: {\n\tprovider: \"kev\" | \"clef\";\n\turl: string;\n\t/** Sent as body.model (\"kev-latest\", \"clef\", \"clef-flash\"). */\n\tmodelId: string;\n\tapiKey?: string;\n\tfetchImpl?: typeof fetch;\n}): DecisionModelV4 {\n\tconst doFetch = opts.fetchImpl ?? fetch;\n\treturn {\n\t\tspecificationVersion: \"v4\",\n\t\tprovider: `secondlayer.${opts.provider}`,\n\t\tmodelId: opts.modelId,\n\t\tsupportedQuestionTypes: [\"choice\", \"score\", \"boolean\"],\n\t\tasync doDecide({ state, questions, abortSignal }) {\n\t\t\tconst wireQuestions = Object.fromEntries(\n\t\t\t\tObject.entries(questions).map(([id, q]) => [\n\t\t\t\t\tid,\n\t\t\t\t\tq.type === \"boolean\" ? { ...q, type: \"noul\" } : q,\n\t\t\t\t]),\n\t\t\t);\n\t\t\tconst res = await doFetch(opts.url, {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders: {\n\t\t\t\t\t\"content-type\": \"application/json\",\n\t\t\t\t\t...(opts.apiKey && { authorization: `Bearer ${opts.apiKey}` }),\n\t\t\t\t},\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tmodel: opts.modelId,\n\t\t\t\t\tstate: toWireState(state),\n\t\t\t\t\tquestions: wireQuestions,\n\t\t\t\t}),\n\t\t\t\tsignal: abortSignal,\n\t\t\t});\n\t\t\t// Never include the body: it may echo state.\n\t\t\tif (!res.ok) throw new Error(`${opts.provider} ${res.status}`);\n\t\t\tconst json = (await res.json()) as { result?: WireBody } & WireBody;\n\t\t\tconst body = json.result ?? json;\n\t\t\tconst answers = Object.fromEntries(\n\t\t\t\tObject.entries(body.answers ?? {}).map(([id, a]) => [id, mapAnswer(a)]),\n\t\t\t);\n\t\t\treturn {\n\t\t\t\tanswers,\n\t\t\t\twarnings: [],\n\t\t\t\tusage: {\n\t\t\t\t\tinputTokens: body.usage?.input_tokens,\n\t\t\t\t\toutputTokens: body.usage?.output_tokens,\n\t\t\t\t},\n\t\t\t\tresponse: { modelId: body.model ?? opts.modelId },\n\t\t\t} as DoDecideResult;\n\t\t},\n\t};\n}\n",
11
+ "import type { Experimental_DecisionModel } from \"ai\"; // type-only: erased at runtime\nimport { logger } from \"../logger.ts\";\nimport { systemOneModel } from \"./system-one.ts\";\nimport type { ClassifierName } from \"./types.ts\";\n\ntype DecisionModelV4 = Extract<\n\tExperimental_DecisionModel,\n\t{ doDecide: unknown }\n>;\ntype Env = Record<string, string | undefined>;\n\nexport const DEFAULT_MODEL: Record<Exclude<ClassifierName, \"rules\">, string> = {\n\tjev: \"typesafe-ai/jev\",\n\tkev: \"kev-latest\",\n\tclef: \"clef\",\n};\n\nconst REQUIRED_ENV: Record<Exclude<ClassifierName, \"rules\">, string[]> = {\n\tjev: [\"AI_GATEWAY_API_KEY\"],\n\tkev: [\"KEV_URL\"],\n\tclef: [\"CLOUDFLARE_ACCOUNT_ID\", \"CLOUDFLARE_API_TOKEN\"],\n};\n\n/** Compose passes CLASSIFIER_MODEL=\"\" when unset; blank means unset. */\nexport const envModel = (env: Env): string | undefined =>\n\tenv.CLASSIFIER_MODEL?.trim() || undefined;\n\n/** CLASSIFIER picks the backend; unset means jev when the gateway key is set,\n * else offline rules. A backend missing credentials falls back to rules. */\nexport function resolveProvider(env: Env = process.env): {\n\tname: ClassifierName;\n\tmodelId: string;\n} {\n\tconst rules = { name: \"rules\", modelId: \"rules\" } as const;\n\tconst requested = env.CLASSIFIER?.trim();\n\tlet name: ClassifierName;\n\tif (!requested) {\n\t\tname = env.AI_GATEWAY_API_KEY ? \"jev\" : \"rules\";\n\t} else if (\n\t\trequested === \"jev\" ||\n\t\trequested === \"kev\" ||\n\t\trequested === \"clef\" ||\n\t\trequested === \"rules\"\n\t) {\n\t\tname = requested;\n\t} else {\n\t\tlogger.warn(`classifier: unknown CLASSIFIER=${requested}; using rules`);\n\t\treturn rules;\n\t}\n\tif (name === \"rules\") return rules;\n\tconst missing = REQUIRED_ENV[name].find((v) => !env[v]);\n\tif (missing) {\n\t\tlogger.warn(\n\t\t\t`classifier: CLASSIFIER=${name} missing ${missing}; using rules`,\n\t\t);\n\t\treturn rules;\n\t}\n\treturn { name, modelId: envModel(env) ?? DEFAULT_MODEL[name] };\n}\n\nexport function buildModel(\n\tname: \"jev\" | \"kev\" | \"clef\",\n\tmodelId: string,\n\tenv: Env = process.env,\n\tfetchImpl?: typeof fetch,\n): string | DecisionModelV4 {\n\t// The AI Gateway resolves a plain model id string.\n\tif (name === \"jev\") return modelId;\n\tif (name === \"kev\") {\n\t\treturn systemOneModel({\n\t\t\tprovider: \"kev\",\n\t\t\turl: `${(env.KEV_URL ?? \"\").replace(/\\/$/, \"\")}/v1/systemone`,\n\t\t\tmodelId,\n\t\t\tapiKey: env.KEV_API_KEY,\n\t\t\tfetchImpl,\n\t\t});\n\t}\n\treturn systemOneModel({\n\t\tprovider: \"clef\",\n\t\turl: `https://api.cloudflare.com/client/v4/accounts/${env.CLOUDFLARE_ACCOUNT_ID}/ai/run/@cf/cloudflare/${modelId}`,\n\t\tmodelId,\n\t\tapiKey: env.CLOUDFLARE_API_TOKEN,\n\t\tfetchImpl,\n\t});\n}\n",
12
+ "import type { Answer, Answers, Question, RuleValue, RulesFn } from \"./types.ts\";\n\nfunction answerFor(question: Question, value: RuleValue | undefined): Answer {\n\tif (value === undefined) return { type: \"refusal\" };\n\tif (question.type === \"choice\") {\n\t\tif (typeof value !== \"string\" || !(value in question.criteria)) {\n\t\t\treturn { type: \"refusal\" };\n\t\t}\n\t\tconst probabilities = Object.fromEntries(\n\t\t\tObject.keys(question.criteria).map((k) => [k, k === value ? 1 : 0]),\n\t\t);\n\t\treturn { type: \"choice\", choice: value, probabilities, confidence: 1 };\n\t}\n\tif (question.type === \"score\") {\n\t\tconst n = question.criteria.length;\n\t\tif (typeof value !== \"number\" || !Number.isInteger(value)) {\n\t\t\treturn { type: \"refusal\" };\n\t\t}\n\t\tif (value < 0 || value >= n) return { type: \"refusal\" };\n\t\tconst probabilities = Object.fromEntries(\n\t\t\tArray.from({ length: n }, (_, i) => [String(i), i === value ? 1 : 0]),\n\t\t);\n\t\treturn { type: \"score\", score: value, probabilities, confidence: 1 };\n\t}\n\tif (typeof value !== \"boolean\") return { type: \"refusal\" };\n\treturn { type: \"boolean\", probability: value ? 1 : 0, confidence: 1 };\n}\n\n/** Offline provider: deterministic, never touches the network. */\nexport function runRules<Q extends Record<string, Question>>(\n\tquestions: Q,\n\tfn: RulesFn<Q>,\n\tstate: string | Record<string, unknown>,\n): Answers<Q> {\n\tconst values = fn(state) ?? {};\n\tconst out: Record<string, Answer> = {};\n\tfor (const id of Object.keys(questions)) {\n\t\tout[id] = answerFor(\n\t\t\tquestions[id] as Question,\n\t\t\t(values as Record<string, RuleValue | undefined>)[id],\n\t\t);\n\t}\n\treturn out as Answers<Q>;\n}\n",
13
+ "import { logger } from \"../logger.ts\";\nimport { type RawAnswer, normalizeAnswer } from \"./confidence.ts\";\nimport { MAX_STATE_CHARS, stateSize, validateQuestions } from \"./limits.ts\";\nimport {\n\tDEFAULT_MODEL,\n\tbuildModel,\n\tenvModel,\n\tresolveProvider,\n} from \"./providers.ts\";\nimport { runRules } from \"./rules.ts\";\nimport type {\n\tAnswers,\n\tClassifierName,\n\tClassifyResult,\n\tQuestion,\n\tRulesFn,\n} from \"./types.ts\";\n\n/**\n * One classifier call over jev, kev, clef or offline rules.\n * Returns null on any provider failure (fail-open: callers decide what null\n * means). Only ClassifierInputError, a bad question definition, escapes.\n * Never logs state or response bodies.\n */\nexport async function classify<\n\tconst Q extends Record<string, Question>,\n>(input: {\n\tstate: string | Record<string, unknown>;\n\tquestions: Q;\n\trules?: RulesFn<Q>;\n\t/** Overrides resolveProvider(); used by classifier-compare. */\n\tprovider?: ClassifierName;\n\t/** Overrides CLASSIFIER_MODEL / the provider default. */\n\tmodelId?: string;\n\t/** Default 5000. */\n\ttimeoutMs?: number;\n\tenv?: Record<string, string | undefined>;\n\t/** Tests only (kev/clef). */\n\tfetchImpl?: typeof fetch;\n}): Promise<ClassifyResult<Q> | null> {\n\tvalidateQuestions(input.questions);\n\tconst env = input.env ?? process.env;\n\tconst resolved = resolveProvider(env);\n\tconst name = input.provider ?? resolved.name;\n\tif (name === \"rules\") {\n\t\tif (!input.rules) return null;\n\t\treturn {\n\t\t\tprovider: \"rules\",\n\t\t\tmodelId: \"rules\",\n\t\t\tanswers: runRules(input.questions, input.rules, input.state),\n\t\t};\n\t}\n\t// An explicit provider override ignores CLASSIFIER_MODEL (it pins one backend).\n\tconst modelId =\n\t\tinput.modelId ??\n\t\t(input.provider ? undefined : envModel(env)) ??\n\t\tDEFAULT_MODEL[name];\n\n\tif (stateSize(input.state) > MAX_STATE_CHARS) {\n\t\tlogger.warn(`classifier: state over ${MAX_STATE_CHARS} chars; skipped`);\n\t\treturn null;\n\t}\n\ttry {\n\t\tconst { experimental_decide: decide } = await import(\"ai\");\n\t\tconst r = await decide({\n\t\t\tmodel: buildModel(name, modelId, env, input.fetchImpl),\n\t\t\tstate: input.state as string,\n\t\t\t// Our Question is a structural subset of the SDK's question type.\n\t\t\tquestions: input.questions as Parameters<typeof decide>[0][\"questions\"],\n\t\t\tmaxRetries: 1,\n\t\t\tabortSignal: AbortSignal.timeout(input.timeoutMs ?? 5000),\n\t\t});\n\t\tconst answers: Record<string, ReturnType<typeof normalizeAnswer>> = {};\n\t\tfor (const id of Object.keys(input.questions)) {\n\t\t\tanswers[id] = normalizeAnswer(\n\t\t\t\tinput.questions[id] as Question,\n\t\t\t\t(r.answers as Record<string, RawAnswer>)[id] ?? { type: \"refusal\" },\n\t\t\t);\n\t\t}\n\t\treturn {\n\t\t\tprovider: name,\n\t\t\tmodelId: r.response.modelId ?? modelId,\n\t\t\tanswers: answers as Answers<Q>,\n\t\t};\n\t} catch (err) {\n\t\tlogger.warn(\n\t\t\t`classifier: ${name} failed: ${err instanceof Error ? err.name : \"error\"}`,\n\t\t);\n\t\treturn null;\n\t}\n}\n"
14
+ ],
15
+ "mappings": "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",
16
+ "debugId": "6AA8B48DE6A7F1DB64756E2164756E21",
17
+ "names": []
18
+ }
@@ -336,6 +336,27 @@ declare function cursorIsBeforeBlock(cursor: StreamsCursor, height: number): boo
336
336
  declare function planDecoderReceipts(input: DecoderClockInput): DecoderClockResult;
337
337
  import { Kysely } from "kysely";
338
338
  import { ColumnType, Generated } from "kysely";
339
+ /**
340
+ * How far a subgraph's rows can be checked without trusting the server,
341
+ * derived from its sources and a scan of its handler. Never configured.
342
+ *
343
+ * - `state`: every input is a named state write, provable from block headers.
344
+ * Handlers run in the deterministic realm. A deployed `state` subgraph is
345
+ * verifiable: one with any reason is refused at deploy.
346
+ * - `events`: an input (events, prints, tx results, `ctx.client`) needs
347
+ * re-execution proofs, which are not served yet.
348
+ * - `none`: a source can never be proven as written (trait scope, tip-first
349
+ * backfill).
350
+ */
351
+ interface SubgraphVerification {
352
+ level: "state" | "events" | "none";
353
+ /** What stands between the subgraph and checkable rows, e.g.
354
+ * `print_event source "swaps" needs event proofs` or
355
+ * `subgraph.ts:41:12 Date: wall-clock time differs per run`. */
356
+ reasons: string[];
357
+ /** What stays unproven even at this level, e.g. tx attribution of writes. */
358
+ unproven: string[];
359
+ }
339
360
  interface BlocksTable {
340
361
  height: number;
341
362
  hash: string;
@@ -496,6 +517,11 @@ interface SubgraphsTable {
496
517
  expires_at: Date | null;
497
518
  /** (event type, contract) probe pairs persisted at deploy for weight classification. */
498
519
  sparse_probe_targets: unknown | null;
520
+ /** sha256 over everything that shapes rows (schema, handler bundle,
521
+ * startBlock, network, runtime). NULL for unbundled or older deploys. */
522
+ pin: string | null;
523
+ /** Level derived at deploy. NULL (older deploys) = not verifiable. */
524
+ verification: SubgraphVerification | null;
499
525
  created_at: Generated<Date>;
500
526
  updated_at: Generated<Date>;
501
527
  }
@@ -1164,6 +1190,8 @@ interface Database {
1164
1190
  sentinel_accounts: SentinelAccountsTable;
1165
1191
  waitlists: WaitlistsTable;
1166
1192
  waitlist_signups: WaitlistSignupsTable;
1193
+ api_failed_requests: ApiFailedRequestsTable;
1194
+ feedback_tickets: FeedbackTicketsTable;
1167
1195
  }
1168
1196
  interface ServiceHeartbeatsTable {
1169
1197
  name: string;
@@ -1371,6 +1399,43 @@ interface WaitlistSignupsTable {
1371
1399
  answers: Record<string, unknown>;
1372
1400
  created_at: Generated<Date>;
1373
1401
  }
1402
+ /**
1403
+ * 24h hosted record of a failed API request, keyed by `request_id` and
1404
+ * scoped to the account that made it. `query` is redacted before insert.
1405
+ */
1406
+ interface ApiFailedRequestsTable {
1407
+ request_id: string;
1408
+ account_id: string;
1409
+ method: string;
1410
+ path: string;
1411
+ status: number;
1412
+ code: string;
1413
+ message: Generated<string>;
1414
+ query: Record<string, unknown>;
1415
+ origin: string | null;
1416
+ created_at: Generated<Date>;
1417
+ }
1418
+ /**
1419
+ * Hosted problem report. `attempted` is the server's own copy of the failed
1420
+ * request (never caller-supplied); `status` starts `new` until classified.
1421
+ */
1422
+ interface FeedbackTicketsTable {
1423
+ id: Generated<string>;
1424
+ account_id: string;
1425
+ idempotency_key: string | null;
1426
+ request_id: string | null;
1427
+ intent: string;
1428
+ expected: Record<string, unknown> | null;
1429
+ kind_hint: string | null;
1430
+ evidence: Record<string, unknown> | null;
1431
+ attempted: Record<string, unknown> | null;
1432
+ origin: string;
1433
+ status: Generated<"new" | "classified">;
1434
+ route: string | null;
1435
+ classification: Record<string, unknown> | null;
1436
+ classified_at: Date | null;
1437
+ created_at: Generated<Date>;
1438
+ }
1374
1439
  declare const DECODER_COMMIT_STEPS: readonly ["output", "checkpoint", "receipt", "failure"];
1375
1440
  type DecoderCommitStep = (typeof DECODER_COMMIT_STEPS)[number];
1376
1441
  declare class DecoderAdapterCrash extends Error {