ai-model-verifier 1.4.0 → 2.0.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 (224) hide show
  1. package/README.md +79 -21
  2. package/dist/engine/context.d.ts +43 -0
  3. package/dist/engine/context.d.ts.map +1 -0
  4. package/dist/engine/context.js +50 -0
  5. package/dist/engine/context.js.map +1 -0
  6. package/dist/engine/evidence.d.ts +40 -0
  7. package/dist/engine/evidence.d.ts.map +1 -0
  8. package/dist/engine/evidence.js +108 -0
  9. package/dist/engine/evidence.js.map +1 -0
  10. package/dist/engine/handshake.d.ts +16 -0
  11. package/dist/engine/handshake.d.ts.map +1 -0
  12. package/dist/{handshake.js → engine/handshake.js} +35 -63
  13. package/dist/engine/handshake.js.map +1 -0
  14. package/dist/engine/probe-runner.d.ts +14 -0
  15. package/dist/engine/probe-runner.d.ts.map +1 -0
  16. package/dist/engine/probe-runner.js +163 -0
  17. package/dist/engine/probe-runner.js.map +1 -0
  18. package/dist/engine/verdict.d.ts +15 -0
  19. package/dist/engine/verdict.d.ts.map +1 -0
  20. package/dist/engine/verdict.js +19 -0
  21. package/dist/engine/verdict.js.map +1 -0
  22. package/dist/highlight.d.ts +2 -2
  23. package/dist/highlight.d.ts.map +1 -1
  24. package/dist/highlight.js +10 -9
  25. package/dist/highlight.js.map +1 -1
  26. package/dist/{patterns.d.ts → identity/patterns.d.ts} +7 -0
  27. package/dist/identity/patterns.d.ts.map +1 -0
  28. package/dist/{patterns.js → identity/patterns.js} +7 -0
  29. package/dist/identity/patterns.js.map +1 -0
  30. package/dist/identity/signals.d.ts +15 -0
  31. package/dist/identity/signals.d.ts.map +1 -0
  32. package/dist/{signals.js → identity/signals.js} +15 -24
  33. package/dist/identity/signals.js.map +1 -0
  34. package/dist/index.d.ts +15 -5
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +12 -5
  37. package/dist/index.js.map +1 -1
  38. package/dist/internal/utils.d.ts +3 -1
  39. package/dist/internal/utils.d.ts.map +1 -1
  40. package/dist/internal/utils.js +12 -6
  41. package/dist/internal/utils.js.map +1 -1
  42. package/dist/models/catalog.d.ts +7 -0
  43. package/dist/models/catalog.d.ts.map +1 -0
  44. package/dist/models/catalog.js +23 -0
  45. package/dist/models/catalog.js.map +1 -0
  46. package/dist/models/facts.d.ts +56 -0
  47. package/dist/models/facts.d.ts.map +1 -0
  48. package/dist/models/facts.js +91 -0
  49. package/dist/models/facts.js.map +1 -0
  50. package/dist/models/index.d.ts +6 -0
  51. package/dist/models/index.d.ts.map +1 -0
  52. package/dist/models/index.js +5 -0
  53. package/dist/models/index.js.map +1 -0
  54. package/dist/{substitution.d.ts → models/substitution.d.ts} +3 -4
  55. package/dist/models/substitution.d.ts.map +1 -0
  56. package/dist/{substitution.js → models/substitution.js} +3 -4
  57. package/dist/models/substitution.js.map +1 -0
  58. package/dist/models/tiers.d.ts +14 -0
  59. package/dist/models/tiers.d.ts.map +1 -0
  60. package/dist/models/tiers.js +35 -0
  61. package/dist/models/tiers.js.map +1 -0
  62. package/dist/probes/prompts.d.ts +23 -0
  63. package/dist/probes/prompts.d.ts.map +1 -0
  64. package/dist/probes/prompts.js +31 -0
  65. package/dist/probes/prompts.js.map +1 -0
  66. package/dist/probes/table.d.ts +37 -0
  67. package/dist/probes/table.d.ts.map +1 -0
  68. package/dist/probes/table.js +57 -0
  69. package/dist/probes/table.js.map +1 -0
  70. package/dist/registry.d.ts +26 -0
  71. package/dist/registry.d.ts.map +1 -0
  72. package/dist/registry.js +23 -0
  73. package/dist/registry.js.map +1 -0
  74. package/dist/rules/index.d.ts +15 -0
  75. package/dist/rules/index.d.ts.map +1 -0
  76. package/dist/rules/index.js +8 -0
  77. package/dist/rules/index.js.map +1 -0
  78. package/dist/rules/probe-ladder.d.ts +33 -0
  79. package/dist/rules/probe-ladder.d.ts.map +1 -0
  80. package/dist/rules/probe-ladder.js +156 -0
  81. package/dist/rules/probe-ladder.js.map +1 -0
  82. package/dist/{detectors → rules}/response-metadata.d.ts +11 -14
  83. package/dist/rules/response-metadata.d.ts.map +1 -0
  84. package/dist/{detectors → rules}/response-metadata.js +32 -15
  85. package/dist/rules/response-metadata.js.map +1 -0
  86. package/dist/rules/table.d.ts +27 -0
  87. package/dist/rules/table.d.ts.map +1 -0
  88. package/dist/rules/table.js +47 -0
  89. package/dist/rules/table.js.map +1 -0
  90. package/dist/{detectors → rules}/thinking-floor.d.ts +14 -29
  91. package/dist/rules/thinking-floor.d.ts.map +1 -0
  92. package/dist/rules/thinking-floor.js +119 -0
  93. package/dist/rules/thinking-floor.js.map +1 -0
  94. package/dist/rules/thinking-signature.d.ts +43 -0
  95. package/dist/rules/thinking-signature.d.ts.map +1 -0
  96. package/dist/rules/thinking-signature.js +72 -0
  97. package/dist/rules/thinking-signature.js.map +1 -0
  98. package/dist/{detectors → rules}/throughput.d.ts +7 -10
  99. package/dist/rules/throughput.d.ts.map +1 -0
  100. package/dist/{detectors → rules}/throughput.js +32 -11
  101. package/dist/rules/throughput.js.map +1 -0
  102. package/dist/{detectors → rules}/token-truth.d.ts +7 -13
  103. package/dist/rules/token-truth.d.ts.map +1 -0
  104. package/dist/rules/token-truth.js +112 -0
  105. package/dist/rules/token-truth.js.map +1 -0
  106. package/dist/rules/tokenizer-fingerprint.d.ts +46 -0
  107. package/dist/rules/tokenizer-fingerprint.d.ts.map +1 -0
  108. package/dist/rules/tokenizer-fingerprint.js +110 -0
  109. package/dist/rules/tokenizer-fingerprint.js.map +1 -0
  110. package/dist/rules/types.d.ts +57 -0
  111. package/dist/rules/types.d.ts.map +1 -0
  112. package/dist/rules/types.js +2 -0
  113. package/dist/rules/types.js.map +1 -0
  114. package/dist/transport.d.ts +10 -2
  115. package/dist/transport.d.ts.map +1 -1
  116. package/dist/transport.js +40 -32
  117. package/dist/transport.js.map +1 -1
  118. package/dist/types.d.ts +70 -31
  119. package/dist/types.d.ts.map +1 -1
  120. package/dist/vendors/anthropic.d.ts +44 -0
  121. package/dist/vendors/anthropic.d.ts.map +1 -0
  122. package/dist/vendors/anthropic.js +114 -0
  123. package/dist/vendors/anthropic.js.map +1 -0
  124. package/dist/vendors/base-url.d.ts +3 -0
  125. package/dist/vendors/base-url.d.ts.map +1 -0
  126. package/dist/vendors/base-url.js +6 -0
  127. package/dist/vendors/base-url.js.map +1 -0
  128. package/dist/vendors/gemini.d.ts +44 -0
  129. package/dist/vendors/gemini.d.ts.map +1 -0
  130. package/dist/vendors/gemini.js +77 -0
  131. package/dist/vendors/gemini.js.map +1 -0
  132. package/dist/vendors/index.d.ts +9 -0
  133. package/dist/vendors/index.d.ts.map +1 -0
  134. package/dist/vendors/index.js +7 -0
  135. package/dist/vendors/index.js.map +1 -0
  136. package/dist/vendors/openai.d.ts +46 -0
  137. package/dist/vendors/openai.d.ts.map +1 -0
  138. package/dist/vendors/openai.js +77 -0
  139. package/dist/vendors/openai.js.map +1 -0
  140. package/dist/vendors/table.d.ts +113 -0
  141. package/dist/vendors/table.d.ts.map +1 -0
  142. package/dist/vendors/table.js +14 -0
  143. package/dist/vendors/table.js.map +1 -0
  144. package/dist/vendors/types.d.ts +93 -0
  145. package/dist/vendors/types.d.ts.map +1 -0
  146. package/dist/vendors/types.js +2 -0
  147. package/dist/vendors/types.js.map +1 -0
  148. package/dist/verify.d.ts +30 -0
  149. package/dist/verify.d.ts.map +1 -0
  150. package/dist/verify.js +185 -0
  151. package/dist/verify.js.map +1 -0
  152. package/package.json +25 -10
  153. package/dist/detectors/response-metadata.d.ts.map +0 -1
  154. package/dist/detectors/response-metadata.js.map +0 -1
  155. package/dist/detectors/thinking-floor.d.ts.map +0 -1
  156. package/dist/detectors/thinking-floor.js +0 -135
  157. package/dist/detectors/thinking-floor.js.map +0 -1
  158. package/dist/detectors/thinking-signature.d.ts +0 -70
  159. package/dist/detectors/thinking-signature.d.ts.map +0 -1
  160. package/dist/detectors/thinking-signature.js +0 -177
  161. package/dist/detectors/thinking-signature.js.map +0 -1
  162. package/dist/detectors/throughput.d.ts.map +0 -1
  163. package/dist/detectors/throughput.js.map +0 -1
  164. package/dist/detectors/token-truth.d.ts.map +0 -1
  165. package/dist/detectors/token-truth.js +0 -142
  166. package/dist/detectors/token-truth.js.map +0 -1
  167. package/dist/detectors/tokenizer-fingerprint.d.ts +0 -65
  168. package/dist/detectors/tokenizer-fingerprint.d.ts.map +0 -1
  169. package/dist/detectors/tokenizer-fingerprint.js +0 -135
  170. package/dist/detectors/tokenizer-fingerprint.js.map +0 -1
  171. package/dist/handshake.d.ts +0 -23
  172. package/dist/handshake.d.ts.map +0 -1
  173. package/dist/handshake.js.map +0 -1
  174. package/dist/internal/fixed-text.d.ts +0 -13
  175. package/dist/internal/fixed-text.d.ts.map +0 -1
  176. package/dist/internal/fixed-text.js +0 -13
  177. package/dist/internal/fixed-text.js.map +0 -1
  178. package/dist/models.d.ts +0 -5
  179. package/dist/models.d.ts.map +0 -1
  180. package/dist/models.js +0 -54
  181. package/dist/models.js.map +0 -1
  182. package/dist/nonce.d.ts +0 -4
  183. package/dist/nonce.d.ts.map +0 -1
  184. package/dist/nonce.js +0 -12
  185. package/dist/nonce.js.map +0 -1
  186. package/dist/patterns.d.ts.map +0 -1
  187. package/dist/patterns.js.map +0 -1
  188. package/dist/probes.d.ts +0 -10
  189. package/dist/probes.d.ts.map +0 -1
  190. package/dist/probes.js +0 -54
  191. package/dist/probes.js.map +0 -1
  192. package/dist/providers/anthropic.d.ts +0 -3
  193. package/dist/providers/anthropic.d.ts.map +0 -1
  194. package/dist/providers/anthropic.js +0 -67
  195. package/dist/providers/anthropic.js.map +0 -1
  196. package/dist/providers/config.d.ts +0 -34
  197. package/dist/providers/config.d.ts.map +0 -1
  198. package/dist/providers/config.js +0 -14
  199. package/dist/providers/config.js.map +0 -1
  200. package/dist/providers/gemini.d.ts +0 -3
  201. package/dist/providers/gemini.d.ts.map +0 -1
  202. package/dist/providers/gemini.js +0 -58
  203. package/dist/providers/gemini.js.map +0 -1
  204. package/dist/providers/openai.d.ts +0 -3
  205. package/dist/providers/openai.d.ts.map +0 -1
  206. package/dist/providers/openai.js +0 -56
  207. package/dist/providers/openai.js.map +0 -1
  208. package/dist/rules.d.ts +0 -6
  209. package/dist/rules.d.ts.map +0 -1
  210. package/dist/rules.js +0 -28
  211. package/dist/rules.js.map +0 -1
  212. package/dist/runner.d.ts +0 -39
  213. package/dist/runner.d.ts.map +0 -1
  214. package/dist/runner.js +0 -352
  215. package/dist/runner.js.map +0 -1
  216. package/dist/signals.d.ts +0 -11
  217. package/dist/signals.d.ts.map +0 -1
  218. package/dist/signals.js.map +0 -1
  219. package/dist/substitution.d.ts.map +0 -1
  220. package/dist/substitution.js.map +0 -1
  221. package/dist/verdict.d.ts +0 -26
  222. package/dist/verdict.d.ts.map +0 -1
  223. package/dist/verdict.js +0 -84
  224. package/dist/verdict.js.map +0 -1
@@ -0,0 +1,156 @@
1
+ /**
2
+ * The behavioural ladder over the four probes, one rule per rung, in the order
3
+ * the table lists them. Each rule reads every probe's outcome, which is why the
4
+ * probes are data and the rules are here.
5
+ */
6
+ import { detectSubstitution } from "../models/substitution";
7
+ import { detectServedModelMismatch, detectTierMismatch } from "../models/tiers";
8
+ import { defineRule } from "./types";
9
+ const QUORUM = 3;
10
+ const labelsWith = (probes, sig) => probes
11
+ .filter((r) => r.signal === sig)
12
+ .map((r) => r.label)
13
+ .join(", ");
14
+ const signalRule = (id, signal, prefix) => defineRule({
15
+ id,
16
+ layer: "probe",
17
+ needs: ["probes"],
18
+ applies: () => true,
19
+ judge: async (ctx) => {
20
+ const labels = labelsWith(await ctx.evidence("probes"), signal);
21
+ return labels
22
+ ? { severity: "fail", reason: `${prefix}: ${labels}` }
23
+ : null;
24
+ },
25
+ });
26
+ export const codingToolRule = signalRule("coding-tool", "coding-tool", "coding-tool-refusal");
27
+ export const scamRule = signalRule("scam", "scam", "scam-page");
28
+ export const cjkLeakRule = signalRule("cjk-leak", "cjk-leak", "cjk-language-leak");
29
+ /** Two probes whose reply never carried their nonce: the proxy mixes responses. */
30
+ export const muxRule = defineRule({
31
+ id: "mux",
32
+ layer: "probe",
33
+ needs: ["probes"],
34
+ applies: () => true,
35
+ judge: async (ctx) => {
36
+ const labels = (await ctx.evidence("probes"))
37
+ .filter((r) => r.muxFailure)
38
+ .map((r) => r.label);
39
+ return labels.length >= 2
40
+ ? {
41
+ severity: "fail",
42
+ reason: `unsafe-proxy: response-mixing on ${labels.join(", ")}`,
43
+ }
44
+ : null;
45
+ },
46
+ });
47
+ /**
48
+ * A foreign vendor on EITHER identity probe is a hard fail: a real model never
49
+ * names a competitor as its maker. The reason lists every foreign probe.
50
+ */
51
+ export const foreignRule = defineRule({
52
+ id: "foreign",
53
+ layer: "probe",
54
+ needs: ["probes"],
55
+ applies: () => true,
56
+ judge: async (ctx) => {
57
+ const probes = await ctx.evidence("probes");
58
+ const onIdentity = probes.some((r) => r.signal === "foreign" &&
59
+ (r.label === "model-name" || r.label === "identity"));
60
+ return onIdentity
61
+ ? {
62
+ severity: "fail",
63
+ reason: `foreign-identity: ${labelsWith(probes, "foreign")}`,
64
+ }
65
+ : null;
66
+ },
67
+ });
68
+ /** The reply's own `model` field names another tier than the one requested. */
69
+ export const servedModelMismatchRule = defineRule({
70
+ id: "served-model-mismatch",
71
+ layer: "probe",
72
+ needs: ["probes"],
73
+ applies: (ctx) => ctx.wire.tiers !== null,
74
+ judge: async (ctx) => {
75
+ const probes = await ctx.evidence("probes");
76
+ const served = detectServedModelMismatch(ctx.model, probes.map((r) => r.detectedModel), ctx.wire.tiers ?? []);
77
+ return served
78
+ ? {
79
+ severity: "fail",
80
+ reason: `served-model-mismatch: requested ${ctx.model}, response model ${served}`,
81
+ data: { served },
82
+ }
83
+ : null;
84
+ },
85
+ });
86
+ /** The reply's `model` field is not the requested model at all (lenient spelling). */
87
+ export const substitutedRule = defineRule({
88
+ id: "substituted",
89
+ layer: "probe",
90
+ needs: ["probes"],
91
+ applies: () => true,
92
+ judge: async (ctx) => {
93
+ const probes = await ctx.evidence("probes");
94
+ const detected = probes.find((r) => r.detectedModel)?.detectedModel ?? null;
95
+ const served = detectSubstitution(ctx.model, detected);
96
+ return served
97
+ ? { severity: "fail", reason: `substituted:${served}`, data: { served } }
98
+ : null;
99
+ },
100
+ });
101
+ /**
102
+ * Past the hard signals, the remaining failures are quality or transport. A
103
+ * shortfall driven only by transient upstream errors is inconclusive: a real
104
+ * lane that got rate limited mid-probe stays eligible and is probed again.
105
+ */
106
+ export const quorumRule = defineRule({
107
+ id: "quorum",
108
+ layer: "probe",
109
+ needs: ["probes"],
110
+ applies: () => true,
111
+ judge: async (ctx) => {
112
+ const probes = await ctx.evidence("probes");
113
+ const failed = probes.filter((r) => !r.pass);
114
+ if (probes.length - failed.length >= QUORUM)
115
+ return null;
116
+ const labels = failed.map((r) => r.label).join(", ");
117
+ const transientFails = failed.filter((r) => r.transient).length;
118
+ const nonTransientFails = failed.filter((r) => !r.transient && r.signal !== null).length;
119
+ if (transientFails > 0 && nonTransientFails === 0)
120
+ return { severity: "inconclusive", reason: `transient: ${labels}` };
121
+ const blank = failed.filter((r) => r.signal === "blank").length;
122
+ if (blank === failed.length)
123
+ return { severity: "suspect", reason: `blank-response: ${labels}` };
124
+ const mux = failed.filter((r) => r.muxFailure).map((r) => r.label);
125
+ if (mux.length > 0)
126
+ return {
127
+ severity: "suspect",
128
+ reason: `unsafe-proxy: response-mixing on ${mux.join(", ")} (with ${labels} failing)`,
129
+ };
130
+ return { severity: "suspect", reason: `failed: ${labels}` };
131
+ },
132
+ });
133
+ /**
134
+ * What the model calls itself is coachable and unstable: a kiro pool's system
135
+ * prompt makes a real opus-4-6 answer "Claude 3.7 Sonnet" on one probe and
136
+ * refuse on the next. Logged only; the reply's model field and the tokenizer
137
+ * fingerprint are the evidence that fails a lane.
138
+ */
139
+ export const tierSelfReportRule = defineRule({
140
+ id: "tier-self-report",
141
+ layer: "note",
142
+ needs: ["probes"],
143
+ applies: (ctx) => ctx.wire.tiers !== null,
144
+ judge: async (ctx) => {
145
+ const probes = await ctx.evidence("probes");
146
+ const claimed = detectTierMismatch(ctx.model, probes.find((r) => r.label === "model-name")?.text, ctx.wire.tiers ?? []);
147
+ return claimed
148
+ ? {
149
+ severity: "note",
150
+ reason: `tier-self-report: claims ${claimed}`,
151
+ data: { claimed },
152
+ }
153
+ : null;
154
+ },
155
+ });
156
+ //# sourceMappingURL=probe-ladder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"probe-ladder.js","sourceRoot":"","sources":["../../src/rules/probe-ladder.ts"],"names":[],"mappings":"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"}
@@ -1,21 +1,16 @@
1
1
  /**
2
2
  * Passive response metadata: what the envelope reveals about who really
3
- * answered, read from responses the run already made.
3
+ * answered, read from replies the run already made.
4
4
  *
5
- * Costs nothing (no extra requests) and asserts nothing. Everything here is
6
- * reported as observation, because each signal has an honest explanation as
7
- * well as a dishonest one:
8
- *
9
- * - a `chatcmpl-` id inside an Anthropic-shaped reply means a translation
10
- * layer, which is what our own gateway is;
11
- * - OpenAI-style `usage` keys in a Messages reply mean the same;
12
- * - a missing id means a sloppy relay, not a fake model.
13
- *
14
- * The value is in the aggregate: over many lanes, "which of my Claude lanes
15
- * answer with a chatcmpl id" is a question worth being able to ask.
5
+ * Costs nothing and asserts nothing. Everything here is reported as
6
+ * observation, because each signal has an honest explanation as well as a
7
+ * dishonest one: a `chatcmpl-` id inside an Anthropic-shaped reply means a
8
+ * translation layer, which is what our own gateway is; a missing id means a
9
+ * sloppy relay, not a fake model. The value is in the aggregate: over many
10
+ * lanes, "which of my Claude lanes answer with a chatcmpl id" is a question
11
+ * worth being able to ask.
16
12
  */
17
- /** Vendor whose response shape the envelope actually looks like. */
18
- export type EnvelopeShape = "anthropic" | "openai" | "gemini" | "unknown";
13
+ import type { EnvelopeShape } from "../vendors/types";
19
14
  export type ResponseMetadata = {
20
15
  /** Shape the payload matches, regardless of which API was called. */
21
16
  shape: EnvelopeShape;
@@ -33,4 +28,6 @@ export type ResponseMetadata = {
33
28
  notes: string[];
34
29
  };
35
30
  export declare function readResponseMetadata(data: unknown, expected: EnvelopeShape): ResponseMetadata;
31
+ /** Free metadata from the richest probe reply, read as the wire's shape. */
32
+ export declare const envelopeRule: import("./types").Rule<"envelope", "probes", "note">;
36
33
  //# sourceMappingURL=response-metadata.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"response-metadata.d.ts","sourceRoot":"","sources":["../../src/rules/response-metadata.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAGtD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,qEAAqE;IACrE,KAAK,EAAE,aAAa,CAAC;IACrB,6CAA6C;IAC7C,QAAQ,EAAE,aAAa,CAAC;IACxB,iEAAiE;IACjE,UAAU,EAAE,OAAO,CAAC;IACpB,4EAA4E;IAC5E,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,yEAAyE;IACzE,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,8EAA8E;IAC9E,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,kEAAkE;IAClE,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB,CAAC;AAmDF,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,OAAO,EACb,QAAQ,EAAE,aAAa,GACtB,gBAAgB,CAyClB;AAED,4EAA4E;AAC5E,eAAO,MAAM,YAAY,sDAiBvB,CAAC"}
@@ -1,19 +1,17 @@
1
1
  /**
2
2
  * Passive response metadata: what the envelope reveals about who really
3
- * answered, read from responses the run already made.
3
+ * answered, read from replies the run already made.
4
4
  *
5
- * Costs nothing (no extra requests) and asserts nothing. Everything here is
6
- * reported as observation, because each signal has an honest explanation as
7
- * well as a dishonest one:
8
- *
9
- * - a `chatcmpl-` id inside an Anthropic-shaped reply means a translation
10
- * layer, which is what our own gateway is;
11
- * - OpenAI-style `usage` keys in a Messages reply mean the same;
12
- * - a missing id means a sloppy relay, not a fake model.
13
- *
14
- * The value is in the aggregate: over many lanes, "which of my Claude lanes
15
- * answer with a chatcmpl id" is a question worth being able to ask.
5
+ * Costs nothing and asserts nothing. Everything here is reported as
6
+ * observation, because each signal has an honest explanation as well as a
7
+ * dishonest one: a `chatcmpl-` id inside an Anthropic-shaped reply means a
8
+ * translation layer, which is what our own gateway is; a missing id means a
9
+ * sloppy relay, not a fake model. The value is in the aggregate: over many
10
+ * lanes, "which of my Claude lanes answer with a chatcmpl id" is a question
11
+ * worth being able to ask.
16
12
  */
13
+ import { richestProbe } from "../engine/probe-runner";
14
+ import { defineRule } from "./types";
17
15
  const ANTHROPIC_USAGE = new Set([
18
16
  "input_tokens",
19
17
  "output_tokens",
@@ -42,11 +40,14 @@ function idPrefixOf(id) {
42
40
  return cut > 0 ? id.slice(0, cut + 1) : "(none)";
43
41
  }
44
42
  function shapeOf(data, usageKeys) {
45
- if (data["candidates"] !== undefined || usageKeys.some((k) => GEMINI_USAGE.has(k)))
43
+ if (data["candidates"] !== undefined ||
44
+ usageKeys.some((k) => GEMINI_USAGE.has(k)))
46
45
  return "gemini";
47
- if (data["choices"] !== undefined || usageKeys.some((k) => OPENAI_USAGE.has(k)))
46
+ if (data["choices"] !== undefined ||
47
+ usageKeys.some((k) => OPENAI_USAGE.has(k)))
48
48
  return "openai";
49
- if (data["content"] !== undefined || usageKeys.some((k) => ANTHROPIC_USAGE.has(k)))
49
+ if (data["content"] !== undefined ||
50
+ usageKeys.some((k) => ANTHROPIC_USAGE.has(k)))
50
51
  return "anthropic";
51
52
  return "unknown";
52
53
  }
@@ -88,4 +89,20 @@ export function readResponseMetadata(data, expected) {
88
89
  notes,
89
90
  };
90
91
  }
92
+ /** Free metadata from the richest probe reply, read as the wire's shape. */
93
+ export const envelopeRule = defineRule({
94
+ id: "envelope",
95
+ layer: "note",
96
+ needs: ["probes"],
97
+ applies: () => true,
98
+ judge: async () => null,
99
+ report: async (ctx) => {
100
+ const richest = richestProbe(await ctx.evidence("probes"));
101
+ return richest
102
+ ? {
103
+ responseMetadata: readResponseMetadata(richest.raw, ctx.wire.envelope),
104
+ }
105
+ : {};
106
+ },
107
+ });
91
108
  //# sourceMappingURL=response-metadata.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"response-metadata.js","sourceRoot":"","sources":["../../src/rules/response-metadata.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ import type { Rule } from "./types";
2
+ /**
3
+ * Precedence is this order: the first finding that is not a note decides the
4
+ * verdict. Evidence rules sit above the probe ladder because they read facts a
5
+ * coached reply cannot fake; note rules only report.
6
+ */
7
+ export declare const RULES: readonly [Rule<"thinking-floor", "floorReply", "evidence">, Rule<"tokenizer-fingerprint", "fixedText", "evidence">, Rule<"coding-tool", "probes", "probe">, Rule<"scam", "probes", "probe">, Rule<"cjk-leak", "probes", "probe">, Rule<"mux", "probes", "probe">, Rule<"foreign", "probes", "probe">, Rule<"served-model-mismatch", "probes", "probe">, Rule<"substituted", "probes", "probe">, Rule<"quorum", "probes", "probe">, Rule<"tier-self-report", "probes", "note">, Rule<"signature", "thinkingReply", "note">, Rule<"token-truth", "countTokens" | "fixedText", "note">, Rule<"envelope", "probes", "note">, Rule<"throughput", "probes", "note">];
8
+ type RuleEntry = (typeof RULES)[number];
9
+ export type RuleId = RuleEntry["id"];
10
+ /** Rules that can decide a verdict; a UI naming every rule keys off this. */
11
+ export type VerdictRuleId = Extract<RuleEntry, {
12
+ layer: "evidence" | "probe";
13
+ }>["id"];
14
+ export declare const DETECTION_RULES: readonly VerdictRuleId[];
15
+ export declare const DETECTION_EXCEPTIONS: readonly ["version", "transient", "cloud-host", "reshaping", "threshold"];
16
+ export type DetectionExceptionId = (typeof DETECTION_EXCEPTIONS)[number];
17
+ /** The rule a probe's signal feeds, for linking a probe row to a rule. */
18
+ export declare const RULE_FOR_SIGNAL: {
19
+ readonly "coding-tool": "coding-tool";
20
+ readonly scam: "scam";
21
+ readonly "cjk-leak": "cjk-leak";
22
+ readonly foreign: "foreign";
23
+ readonly "cloud-host": null;
24
+ readonly blank: "quorum";
25
+ };
26
+ export {};
27
+ //# sourceMappingURL=table.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"table.d.ts","sourceRoot":"","sources":["../../src/rules/table.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAEpC;;;;GAIG;AACH,eAAO,MAAM,KAAK,goBAgBkB,CAAC;AAErC,KAAK,SAAS,GAAG,CAAC,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC;AACxC,MAAM,MAAM,MAAM,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;AACrC,6EAA6E;AAC7E,MAAM,MAAM,aAAa,GAAG,OAAO,CACjC,SAAS,EACT;IAAE,KAAK,EAAE,UAAU,GAAG,OAAO,CAAA;CAAE,CAChC,CAAC,IAAI,CAAC,CAAC;AAER,eAAO,MAAM,eAAe,EAAE,SAAS,aAAa,EAGlC,CAAC;AAEnB,eAAO,MAAM,oBAAoB,YAC/B,SAAS,EACT,WAAW,EACX,YAAY,EACZ,WAAW,EACX,WAAW,CACH,CAAC;AACX,MAAM,MAAM,oBAAoB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEzE,0EAA0E;AAC1E,eAAO,MAAM,eAAe;4BACX,aAAa;mBACtB,MAAM;yBACA,UAAU;sBACb,SAAS;;oBAEX,QAAQ;CAC0D,CAAC"}
@@ -0,0 +1,47 @@
1
+ import { cjkLeakRule, codingToolRule, foreignRule, muxRule, quorumRule, scamRule, servedModelMismatchRule, substitutedRule, tierSelfReportRule, } from "./probe-ladder";
2
+ import { envelopeRule } from "./response-metadata";
3
+ import { signatureRule } from "./thinking-signature";
4
+ import { thinkingFloorRule } from "./thinking-floor";
5
+ import { throughputRule } from "./throughput";
6
+ import { tokenTruthRule } from "./token-truth";
7
+ import { tokenizerFingerprintRule } from "./tokenizer-fingerprint";
8
+ /**
9
+ * Precedence is this order: the first finding that is not a note decides the
10
+ * verdict. Evidence rules sit above the probe ladder because they read facts a
11
+ * coached reply cannot fake; note rules only report.
12
+ */
13
+ export const RULES = [
14
+ thinkingFloorRule,
15
+ tokenizerFingerprintRule,
16
+ codingToolRule,
17
+ scamRule,
18
+ cjkLeakRule,
19
+ muxRule,
20
+ foreignRule,
21
+ servedModelMismatchRule,
22
+ substitutedRule,
23
+ quorumRule,
24
+ tierSelfReportRule,
25
+ signatureRule,
26
+ tokenTruthRule,
27
+ envelopeRule,
28
+ throughputRule,
29
+ ];
30
+ export const DETECTION_RULES = RULES.filter((r) => r.layer !== "note").map((r) => r.id);
31
+ export const DETECTION_EXCEPTIONS = [
32
+ "version",
33
+ "transient",
34
+ "cloud-host",
35
+ "reshaping",
36
+ "threshold",
37
+ ];
38
+ /** The rule a probe's signal feeds, for linking a probe row to a rule. */
39
+ export const RULE_FOR_SIGNAL = {
40
+ "coding-tool": "coding-tool",
41
+ scam: "scam",
42
+ "cjk-leak": "cjk-leak",
43
+ foreign: "foreign",
44
+ "cloud-host": null,
45
+ blank: "quorum",
46
+ };
47
+ //# sourceMappingURL=table.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"table.js","sourceRoot":"","sources":["../../src/rules/table.ts"],"names":[],"mappings":"AACA,OAAO,EACL,WAAW,EACX,cAAc,EACd,WAAW,EACX,OAAO,EACP,UAAU,EACV,QAAQ,EACR,uBAAuB,EACvB,eAAe,EACf,kBAAkB,GACnB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAC/C,OAAO,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AAGnE;;;;GAIG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG;IACnB,iBAAiB;IACjB,wBAAwB;IACxB,cAAc;IACd,QAAQ;IACR,WAAW;IACX,OAAO;IACP,WAAW;IACX,uBAAuB;IACvB,eAAe;IACf,UAAU;IACV,kBAAkB;IAClB,aAAa;IACb,cAAc;IACd,YAAY;IACZ,cAAc;CACoB,CAAC;AAUrC,MAAM,CAAC,MAAM,eAAe,GAA6B,KAAK,CAAC,MAAM,CACnE,CAAC,CAAC,EAA4D,EAAE,CAC9D,CAAC,CAAC,KAAK,KAAK,MAAM,CACrB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;AAEnB,MAAM,CAAC,MAAM,oBAAoB,GAAG;IAClC,SAAS;IACT,WAAW;IACX,YAAY;IACZ,WAAW;IACX,WAAW;CACH,CAAC;AAGX,0EAA0E;AAC1E,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,aAAa,EAAE,aAAa;IAC5B,IAAI,EAAE,MAAM;IACZ,UAAU,EAAE,UAAU;IACtB,OAAO,EAAE,SAAS;IAClB,YAAY,EAAE,IAAI;IAClB,KAAK,EAAE,QAAQ;CAC0D,CAAC"}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Thinking-floor check for models that cannot switch reasoning off.
2
+ * Thinking floor for models that cannot switch reasoning off.
3
3
  *
4
4
  * Gemini 2.5 Pro has a thinking budget floor (128 tokens): even "Reply with
5
5
  * only the word ok." produces hidden thought tokens, reported as
@@ -10,24 +10,17 @@
10
10
  * prompt was 12, the native thoughts count 99 to 158.
11
11
  *
12
12
  * A flash-class backend sold under the pro name answers the same prompt with
13
- * `completion_tokens: 1` (a6 merchant 3623, 2026-09-09), and it still echoes
14
- * `model: gemini-2.5-pro`, so the envelope substitution check never fires.
15
- * The token count is the only free evidence left.
16
- *
17
- * Not applied to flash tiers: those may legitimately run with thinking off.
13
+ * `completion_tokens: 1`, and it still echoes `model: gemini-2.5-pro`, so the
14
+ * substitution rule never fires. The token count is the only free evidence left.
18
15
  */
19
- import type { TransportFn } from "../transport";
16
+ import type { ReasoningUsage } from "../vendors/types";
17
+ export declare const DEFAULT_MIN_COMPLETION_TOKENS = 10;
20
18
  export type ThinkingFloorOptions = {
21
- /**
22
- * Model id prefixes that must always think. Compared after lowercasing and
23
- * mapping `.`/`_` to `-`, provider prefix stripped.
24
- */
19
+ /** Id globs added to the always-thinks set (normalised, prefix stripped). */
25
20
  models?: readonly string[];
26
21
  /** Below this many completion tokens with no reasoning count, the reply did not think. */
27
22
  minCompletionTokens?: number;
28
23
  };
29
- export declare const DEFAULT_ALWAYS_THINKS: readonly string[];
30
- export declare const DEFAULT_MIN_COMPLETION_TOKENS = 10;
31
24
  export type ThinkingFloorState =
32
25
  /** Reasoning tokens reported, or the completion count is above the floor. */
33
26
  "thought"
@@ -43,23 +36,15 @@ export type ThinkingFloorResult = {
43
36
  reasoningTokens: number | null;
44
37
  reason?: string;
45
38
  };
39
+ export declare const alwaysThinksEntries: (models?: readonly string[]) => {
40
+ match: string;
41
+ alwaysThinks: boolean;
42
+ }[];
46
43
  export declare function mustAlwaysThink(model: string, options?: ThinkingFloorOptions): boolean;
47
44
  /** Completion and reasoning counts from an OpenAI `usage` or a Gemini `usageMetadata`. */
48
- export declare function readThinkingUsage(data: unknown): {
49
- completion: number | null;
50
- reasoning: number | null;
51
- } | null;
45
+ export declare function readThinkingUsage(data: unknown): ReasoningUsage | null;
46
+ export declare function judgeReasoningUsage(usage: ReasoningUsage | null, floor: number): ThinkingFloorResult;
52
47
  /** Judge one reply to the short probe. Pure: pass any response body already collected. */
53
- export declare function checkThinkingFloor(model: string, data: unknown, options?: ThinkingFloorOptions): ThinkingFloorResult;
54
- /** One extra generation: the short prompt through the endpoint, then `checkThinkingFloor`. */
55
- export declare function probeThinkingFloor(opts: {
56
- transport: TransportFn;
57
- baseUrl: string;
58
- apiKey: string;
59
- model: string;
60
- /** Wire shape of the endpoint. */
61
- provider: "openai" | "gemini";
62
- timeoutMs: number;
63
- options?: ThinkingFloorOptions;
64
- }): Promise<ThinkingFloorResult>;
48
+ export declare function judgeThinkingFloor(model: string, data: unknown, options?: ThinkingFloorOptions): ThinkingFloorResult;
49
+ export declare const thinkingFloorRule: import("./types").Rule<"thinking-floor", "floorReply", "evidence">;
65
50
  //# sourceMappingURL=thinking-floor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thinking-floor.d.ts","sourceRoot":"","sources":["../../src/rules/thinking-floor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAGvD,eAAO,MAAM,6BAA6B,KAAK,CAAC;AAEhD,MAAM,MAAM,oBAAoB,GAAG;IACjC,6EAA6E;IAC7E,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,0FAA0F;IAC1F,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,kBAAkB;AAC5B,6EAA6E;AAC3E,SAAS;AACX,2EAA2E;GACzE,aAAa;AACf,+CAA+C;GAC7C,YAAY;AACd,+CAA+C;GAC7C,SAAS,CAAC;AAEd,MAAM,MAAM,mBAAmB,GAAG;IAChC,KAAK,EAAE,kBAAkB,CAAC;IAC1B,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,eAAO,MAAM,mBAAmB,YAAY,SAAS,MAAM,EAAE;;;GACkB,CAAC;AAEhF,wBAAgB,eAAe,CAC7B,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,oBAAoB,GAC7B,OAAO,CAGT;AAED,0FAA0F;AAC1F,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,OAAO,GAAG,cAAc,GAAG,IAAI,CAWtE;AAED,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,cAAc,GAAG,IAAI,EAC5B,KAAK,EAAE,MAAM,GACZ,mBAAmB,CA0CrB;AAQD,0FAA0F;AAC1F,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,OAAO,EACb,OAAO,CAAC,EAAE,oBAAoB,GAC7B,mBAAmB,CAMrB;AAqBD,eAAO,MAAM,iBAAiB,oEAa5B,CAAC"}
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Thinking floor for models that cannot switch reasoning off.
3
+ *
4
+ * Gemini 2.5 Pro has a thinking budget floor (128 tokens): even "Reply with
5
+ * only the word ok." produces hidden thought tokens, reported as
6
+ * `completion_tokens_details.reasoning_tokens` on an OpenAI-shaped hop, folded
7
+ * into `completion_tokens` when the relay strips the breakdown, or as
8
+ * `usageMetadata.thoughtsTokenCount` on the native endpoint. Measured across 66
9
+ * historical probes of genuine 2.5 Pro: the lowest completion count for that
10
+ * prompt was 12, the native thoughts count 99 to 158.
11
+ *
12
+ * A flash-class backend sold under the pro name answers the same prompt with
13
+ * `completion_tokens: 1`, and it still echoes `model: gemini-2.5-pro`, so the
14
+ * substitution rule never fires. The token count is the only free evidence left.
15
+ */
16
+ import { intOf, rec } from "../internal/utils";
17
+ import { normalizeModelId, resolveModelFacts } from "../models/facts";
18
+ import { defineRule } from "./types";
19
+ export const DEFAULT_MIN_COMPLETION_TOKENS = 10;
20
+ export const alwaysThinksEntries = (models = []) => models.map((p) => ({ match: `${normalizeModelId(p)}*`, alwaysThinks: true }));
21
+ export function mustAlwaysThink(model, options) {
22
+ return resolveModelFacts(model, alwaysThinksEntries(options?.models))
23
+ .alwaysThinks;
24
+ }
25
+ /** Completion and reasoning counts from an OpenAI `usage` or a Gemini `usageMetadata`. */
26
+ export function readThinkingUsage(data) {
27
+ const d = rec(data);
28
+ const u = rec(d?.["usage"] ?? d?.["usageMetadata"]);
29
+ if (!u)
30
+ return null;
31
+ const details = rec(u["completion_tokens_details"]);
32
+ return {
33
+ reasoning: intOf(details?.["reasoning_tokens"]) ?? intOf(u["thoughtsTokenCount"]),
34
+ completion: intOf(u["completion_tokens"]) ?? intOf(u["candidatesTokenCount"]),
35
+ };
36
+ }
37
+ export function judgeReasoningUsage(usage, floor) {
38
+ if (!usage)
39
+ return {
40
+ state: "unmeasured",
41
+ completionTokens: null,
42
+ reasoningTokens: null,
43
+ reason: "no usage in reply",
44
+ };
45
+ if (usage.reasoning !== null && usage.reasoning > 0)
46
+ return {
47
+ state: "thought",
48
+ completionTokens: usage.completion,
49
+ reasoningTokens: usage.reasoning,
50
+ };
51
+ if (usage.completion === null)
52
+ return {
53
+ state: "unmeasured",
54
+ completionTokens: null,
55
+ reasoningTokens: usage.reasoning,
56
+ reason: "no completion count in usage",
57
+ };
58
+ // An empty reply carries no evidence: nothing was generated, so nothing was
59
+ // skipped. Truncation and refusals land here, not in "no-thinking".
60
+ if (usage.completion === 0)
61
+ return {
62
+ state: "unmeasured",
63
+ completionTokens: 0,
64
+ reasoningTokens: usage.reasoning,
65
+ reason: "empty reply",
66
+ };
67
+ if (usage.completion < floor)
68
+ return {
69
+ state: "no-thinking",
70
+ completionTokens: usage.completion,
71
+ reasoningTokens: usage.reasoning,
72
+ reason: `completion_tokens ${usage.completion} below floor ${floor} with no reasoning tokens`,
73
+ };
74
+ return {
75
+ state: "thought",
76
+ completionTokens: usage.completion,
77
+ reasoningTokens: usage.reasoning,
78
+ };
79
+ }
80
+ const SKIPPED = {
81
+ state: "skipped",
82
+ completionTokens: null,
83
+ reasoningTokens: null,
84
+ };
85
+ /** Judge one reply to the short probe. Pure: pass any response body already collected. */
86
+ export function judgeThinkingFloor(model, data, options) {
87
+ if (!mustAlwaysThink(model, options))
88
+ return SKIPPED;
89
+ return judgeReasoningUsage(readThinkingUsage(data), options?.minCompletionTokens ?? DEFAULT_MIN_COMPLETION_TOKENS);
90
+ }
91
+ async function floorResult(ctx) {
92
+ const res = await ctx.evidence("floorReply");
93
+ if (!res)
94
+ return SKIPPED;
95
+ if (res.status !== 200 || !res.data)
96
+ return {
97
+ state: "unmeasured",
98
+ completionTokens: null,
99
+ reasoningTokens: null,
100
+ reason: res.error ?? `http ${res.status ?? "none"}`,
101
+ };
102
+ return judgeReasoningUsage(ctx.wire.read.reasoningUsage?.(res.data) ?? null, ctx.checks.thinkingFloor?.minCompletionTokens ??
103
+ DEFAULT_MIN_COMPLETION_TOKENS);
104
+ }
105
+ export const thinkingFloorRule = defineRule({
106
+ id: "thinking-floor",
107
+ layer: "evidence",
108
+ needs: ["floorReply"],
109
+ check: "thinkingFloor",
110
+ applies: (ctx) => ctx.wire.read.reasoningUsage !== undefined,
111
+ judge: async (ctx) => {
112
+ const r = await floorResult(ctx);
113
+ return r.state === "no-thinking"
114
+ ? { severity: "fail", reason: `no-thinking: ${r.reason}`, data: r }
115
+ : null;
116
+ },
117
+ report: async (ctx) => ({ thinkingFloor: await floorResult(ctx) }),
118
+ });
119
+ //# sourceMappingURL=thinking-floor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thinking-floor.js","sourceRoot":"","sources":["../../src/rules/thinking-floor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Anthropic thinking signature.
3
+ *
4
+ * Claude attaches a server-generated `signature` to every thinking block. A
5
+ * relay serving some other model cannot produce one, so this is the only
6
+ * evidence here that does not rest on asking the model who it is: identity and
7
+ * behaviour probes can be coached with a system prompt, a signature cannot.
8
+ *
9
+ * What it does NOT prove: which tier answered. Sonnet returns a perfectly valid
10
+ * signature when sold as Opus, so a pass here still needs the tier checks.
11
+ *
12
+ * The `strict` replay (hand the block back so the vendor re-validates it) was
13
+ * built and measured: it does not work through a relay. A genuine block and the
14
+ * same block with 308 bytes of random base64 in place of the signature both
15
+ * came back 200 from two live upstreams, because a relay re-issues the turn to
16
+ * its own backend rather than passing our block through. It stays off by
17
+ * default and only tells you the endpoint accepts a thinking block.
18
+ */
19
+ export type SignatureState =
20
+ /** Signed thinking block: a genuine Anthropic path answered. */
21
+ "signed"
22
+ /**
23
+ * Thought, but no signature. Expected when the lane crosses an
24
+ * OpenAI-shaped hop, which has no field to carry one, so this is
25
+ * unprovable rather than fake. Never a hard fail on its own.
26
+ */
27
+ | "unsigned"
28
+ /** Thinking was requested and no block came back at all. */
29
+ | "no-thinking"
30
+ /** Model or endpoint cannot be asked; carries no evidence either way. */
31
+ | "skipped";
32
+ export type SignatureResult = {
33
+ state: SignatureState;
34
+ signatureLength: number;
35
+ /** First 32 chars, for the report; never the whole signature. */
36
+ signaturePrefix: string | null;
37
+ thinkingChars: number;
38
+ /** Only set when the replay check actually ran. */
39
+ replayVerified?: boolean;
40
+ reason?: string;
41
+ };
42
+ export declare const signatureRule: import("./types").Rule<"signature", "thinkingReply", "note">;
43
+ //# sourceMappingURL=thinking-signature.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thinking-signature.d.ts","sourceRoot":"","sources":["../../src/rules/thinking-signature.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAQH,MAAM,MAAM,cAAc;AACxB,gEAAgE;AAC9D,QAAQ;AACV;;;;GAIG;GACD,UAAU;AACZ,4DAA4D;GAC1D,aAAa;AACf,yEAAyE;GACvE,SAAS,CAAC;AAEd,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EAAE,cAAc,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,iEAAiE;IACjE,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,aAAa,EAAE,MAAM,CAAC;IACtB,mDAAmD;IACnD,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAwCF,eAAO,MAAM,aAAa,8DAaxB,CAAC"}