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,72 @@
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
+ import { defineRule } from "./types";
20
+ /** Real signatures run to hundreds of chars; this only rejects a stub. */
21
+ const SIGNATURE_MIN_LEN = 50;
22
+ async function signatureResult(ctx) {
23
+ const empty = { signatureLength: 0, signaturePrefix: null, thinkingChars: 0 };
24
+ const reply = await ctx.evidence("thinkingReply");
25
+ if (!reply)
26
+ return { state: "skipped", ...empty, reason: "model-has-no-thinking-mode" };
27
+ const res = reply.res;
28
+ if (res.status === null || res.status >= 400)
29
+ return {
30
+ state: "skipped",
31
+ ...empty,
32
+ reason: `probe-failed:${res.status ?? "network"}`,
33
+ };
34
+ const found = ctx.wire.read.thinkingBlock?.(res.data) ?? null;
35
+ if (!found)
36
+ return { state: "no-thinking", ...empty, reason: "no-thinking-block" };
37
+ const sig = found.signature;
38
+ const base = {
39
+ signatureLength: sig.length,
40
+ signaturePrefix: sig ? sig.slice(0, 32) : null,
41
+ thinkingChars: found.chars,
42
+ };
43
+ if (!sig)
44
+ return { state: "unsigned", ...base, reason: "no-signature-field" };
45
+ if (sig.length < SIGNATURE_MIN_LEN)
46
+ return { state: "unsigned", ...base, reason: "signature-too-short" };
47
+ if (reply.replayAccepted === undefined)
48
+ return { state: "signed", ...base };
49
+ // A rejection is real evidence (the endpoint checked and refused); an
50
+ // acceptance is not, so it never upgrades the state on its own.
51
+ return {
52
+ state: reply.replayAccepted ? "signed" : "unsigned",
53
+ ...base,
54
+ replayVerified: reply.replayAccepted,
55
+ ...(reply.replayAccepted ? {} : { reason: "signature-rejected-on-replay" }),
56
+ };
57
+ }
58
+ export const signatureRule = defineRule({
59
+ id: "signature",
60
+ layer: "note",
61
+ needs: ["thinkingReply"],
62
+ check: "signature",
63
+ applies: (ctx) => ctx.wire.read.thinkingBlock !== undefined,
64
+ judge: async (ctx) => {
65
+ const r = await signatureResult(ctx);
66
+ return r.state === "no-thinking"
67
+ ? { severity: "note", reason: `signature: ${r.reason}` }
68
+ : null;
69
+ },
70
+ report: async (ctx) => ({ signature: await signatureResult(ctx) }),
71
+ });
72
+ //# sourceMappingURL=thinking-signature.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thinking-signature.js","sourceRoot":"","sources":["../../src/rules/thinking-signature.ts"],"names":[],"mappings":"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"}
@@ -2,16 +2,12 @@
2
2
  * Throughput metadata: tokens per second, derived from data the run already
3
3
  * collected.
4
4
  *
5
- * Motivation: bigger models are slower per token, so a lane billed as Opus but
6
- * served by Sonnet tends to run fast. That makes throughput the one tier signal
7
- * that survives a relay refusing to name its model.
8
- *
9
- * It is metadata, not a verdict, and deliberately has no thresholds. Rate
10
- * varies with load, batching, quantisation and time of day, so a single
11
- * measurement cannot separate "cheaper model" from "quiet hour". The useful
12
- * comparison is across lanes serving the SAME model, which only the caller can
13
- * assemble: `compareThroughput` does that part and still only describes what it
14
- * sees.
5
+ * Bigger models are slower per token, so a lane billed as Opus but served by
6
+ * Sonnet tends to run fast. That makes throughput the one tier signal that
7
+ * survives a relay refusing to name its model. It is metadata, not a verdict,
8
+ * and deliberately has no thresholds: rate varies with load, batching,
9
+ * quantisation and time of day. The useful comparison is across lanes serving
10
+ * the SAME model, which only the caller can assemble with `compareThroughput`.
15
11
  */
16
12
  export type ThroughputSample = {
17
13
  /** Identifies the lane in the caller's own terms. */
@@ -48,4 +44,5 @@ export type ThroughputComparison = {
48
44
  export declare function compareThroughput(samples: ThroughputSample[], opts?: {
49
45
  outlierRatio?: number;
50
46
  }): ThroughputComparison[];
47
+ export declare const throughputRule: import("./types").Rule<"throughput", "probes", "note">;
51
48
  //# sourceMappingURL=throughput.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"throughput.d.ts","sourceRoot":"","sources":["../../src/rules/throughput.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAMH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,qDAAqD;IACrD,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IACxC,SAAS,EAAE,MAAM,CAAC;CACnB,GAAG,gBAAgB,GAAG,IAAI,CAa1B;AAED,MAAM,MAAM,oBAAoB,GAAG;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,uEAAuE;IACvE,QAAQ,EAAE;QACR,IAAI,EAAE,MAAM,CAAC;QACb,eAAe,EAAE,MAAM,CAAC;QACxB,aAAa,EAAE,MAAM,CAAC;QACtB,SAAS,EAAE,QAAQ,GAAG,QAAQ,CAAC;KAChC,EAAE,CAAC;IACJ,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAQF;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,gBAAgB,EAAE,EAC3B,IAAI,CAAC,EAAE;IAAE,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,GAC/B,oBAAoB,EAAE,CAgDxB;AAED,eAAO,MAAM,cAAc,wDAmBzB,CAAC"}
@@ -2,17 +2,16 @@
2
2
  * Throughput metadata: tokens per second, derived from data the run already
3
3
  * collected.
4
4
  *
5
- * Motivation: bigger models are slower per token, so a lane billed as Opus but
6
- * served by Sonnet tends to run fast. That makes throughput the one tier signal
7
- * that survives a relay refusing to name its model.
8
- *
9
- * It is metadata, not a verdict, and deliberately has no thresholds. Rate
10
- * varies with load, batching, quantisation and time of day, so a single
11
- * measurement cannot separate "cheaper model" from "quiet hour". The useful
12
- * comparison is across lanes serving the SAME model, which only the caller can
13
- * assemble: `compareThroughput` does that part and still only describes what it
14
- * sees.
5
+ * Bigger models are slower per token, so a lane billed as Opus but served by
6
+ * Sonnet tends to run fast. That makes throughput the one tier signal that
7
+ * survives a relay refusing to name its model. It is metadata, not a verdict,
8
+ * and deliberately has no thresholds: rate varies with load, batching,
9
+ * quantisation and time of day. The useful comparison is across lanes serving
10
+ * the SAME model, which only the caller can assemble with `compareThroughput`.
15
11
  */
12
+ import { richestProbe } from "../engine/probe-runner";
13
+ import { hostOf } from "../internal/utils";
14
+ import { defineRule } from "./types";
16
15
  export function sampleThroughput(args) {
17
16
  const tokens = args.outputTokens;
18
17
  // A handful of tokens over a noisy connection says nothing; the rate is
@@ -62,7 +61,9 @@ export function compareThroughput(samples, opts) {
62
61
  lane: s.lane,
63
62
  tokensPerSecond: s.tokensPerSecond,
64
63
  ratioToMedian: +(s.tokensPerSecond / med).toFixed(2),
65
- direction: s.tokensPerSecond > med ? "faster" : "slower",
64
+ direction: s.tokensPerSecond > med
65
+ ? "faster"
66
+ : "slower",
66
67
  }))
67
68
  .sort((a, b) => b.tokensPerSecond - a.tokensPerSecond)
68
69
  : [];
@@ -80,4 +81,24 @@ export function compareThroughput(samples, opts) {
80
81
  }
81
82
  return out;
82
83
  }
84
+ export const throughputRule = defineRule({
85
+ id: "throughput",
86
+ layer: "note",
87
+ needs: ["probes"],
88
+ applies: () => true,
89
+ judge: async () => null,
90
+ report: async (ctx) => {
91
+ const richest = richestProbe(await ctx.evidence("probes"));
92
+ return richest
93
+ ? {
94
+ throughput: sampleThroughput({
95
+ lane: hostOf(ctx.baseUrl),
96
+ model: ctx.model,
97
+ outputTokens: richest.usage?.completion ?? null,
98
+ elapsedMs: richest.latencyMs,
99
+ }),
100
+ }
101
+ : {};
102
+ },
103
+ });
83
104
  //# sourceMappingURL=throughput.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"throughput.js","sourceRoot":"","sources":["../../src/rules/throughput.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAWrC,MAAM,UAAU,gBAAgB,CAAC,IAKhC;IACC,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC;IACjC,wEAAwE;IACxE,wDAAwD;IACxD,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,GAAG,EAAE;QAAE,OAAO,IAAI,CAAC;IAC3D,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,IAAI,CAAC,SAAS,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACzE,OAAO;QACL,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,YAAY,EAAE,MAAM;QACpB,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,eAAe,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;KAChE,CAAC;AACJ,CAAC;AAgBD,MAAM,MAAM,GAAG,CAAC,EAAY,EAAE,EAAE;IAC9B,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IACxC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACrC,OAAO,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAE,GAAG,CAAC,CAAC,GAAG,CAAE,CAAC,GAAG,CAAC,CAAC;AAC9D,CAAC,CAAC;AAEF;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAC/B,OAA2B,EAC3B,IAAgC;IAEhC,MAAM,KAAK,GAAG,IAAI,EAAE,YAAY,IAAI,CAAC,CAAC;IACtC,MAAM,OAAO,GAAG,IAAI,GAAG,EAA8B,CAAC;IACtD,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QAClC,IAAI,IAAI;YAAE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;;YAClB,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IACjC,CAAC;IAED,MAAM,GAAG,GAA2B,EAAE,CAAC;IACvC,KAAK,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,OAAO,EAAE,CAAC;QACrC,wEAAwE;QACxE,6DAA6D;QAC7D,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC;QACxD,MAAM,QAAQ,GACZ,KAAK,CAAC,MAAM,IAAI,CAAC;YACf,CAAC,CAAC,KAAK;iBACF,MAAM,CACL,CAAC,CAAC,EAAE,EAAE,CACJ,CAAC,CAAC,eAAe,IAAI,GAAG,GAAG,KAAK;gBAChC,CAAC,CAAC,eAAe,IAAI,GAAG,GAAG,KAAK,CACnC;iBACA,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBACX,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,eAAe,EAAE,CAAC,CAAC,eAAe;gBAClC,aAAa,EAAE,CAAC,CAAC,CAAC,CAAC,eAAe,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;gBACpD,SAAS,EACP,CAAC,CAAC,eAAe,GAAG,GAAG;oBACrB,CAAC,CAAE,QAAkB;oBACrB,CAAC,CAAE,QAAkB;aAC1B,CAAC,CAAC;iBACF,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,eAAe,GAAG,CAAC,CAAC,eAAe,CAAC;YAC1D,CAAC,CAAC,EAAE,CAAC;QAET,GAAG,CAAC,IAAI,CAAC;YACP,KAAK;YACL,WAAW,EAAE,KAAK,CAAC,MAAM;YACzB,SAAS,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC;YAC1B,QAAQ;YACR,IAAI,EACF,KAAK,CAAC,MAAM,GAAG,CAAC;gBACd,CAAC,CAAC,0BAA0B;gBAC5B,CAAC,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC;oBACrB,CAAC,CAAC,sCAAsC;oBACxC,CAAC,CAAC,GAAG,QAAQ,CAAC,MAAM,oHAAoH;SAC/I,CAAC,CAAC;IACL,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,CAAC,MAAM,cAAc,GAAG,UAAU,CAAC;IACvC,EAAE,EAAE,YAAY;IAChB,KAAK,EAAE,MAAM;IACb,KAAK,EAAE,CAAC,QAAQ,CAAC;IACjB,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI;IACnB,KAAK,EAAE,KAAK,IAAI,EAAE,CAAC,IAAI;IACvB,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;QACpB,MAAM,OAAO,GAAG,YAAY,CAAC,MAAM,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC;QAC3D,OAAO,OAAO;YACZ,CAAC,CAAC;gBACE,UAAU,EAAE,gBAAgB,CAAC;oBAC3B,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC;oBACzB,KAAK,EAAE,GAAG,CAAC,KAAK;oBAChB,YAAY,EAAE,OAAO,CAAC,KAAK,EAAE,UAAU,IAAI,IAAI;oBAC/C,SAAS,EAAE,OAAO,CAAC,SAAS;iBAC7B,CAAC;aACH;YACH,CAAC,CAAC,EAAE,CAAC;IACT,CAAC;CACF,CAAC,CAAC"}
@@ -1,9 +1,9 @@
1
1
  /**
2
- * Token-accounting checks for Anthropic endpoints.
2
+ * Token accounting.
3
3
  *
4
- * The signature check answers "is this really Claude". These answer a different
5
- * question the signature cannot: "are the numbers you are billing me true".
6
- * A relay can forward genuine Claude and still inflate `input_tokens` on every
4
+ * The signature answers "is this really Claude". This answers a different
5
+ * question the signature cannot: "are the numbers you are billing me true". A
6
+ * relay can forward genuine Claude and still inflate `input_tokens` on every
7
7
  * call, and nothing in an identity probe would notice.
8
8
  *
9
9
  * Ported from veridrop (AGPL-3.0) `token_usage.py` / `integrity.py`, whose
@@ -11,7 +11,7 @@
11
11
  * without re-measuring: the loose-looking bounds exist because real Anthropic
12
12
  * responses sat near them.
13
13
  */
14
- import type { TransportFn } from "../transport";
14
+ import type { ModelFacts } from "../models/facts";
15
15
  export type TokenTruthCheck = {
16
16
  id: "usage-present" | "input-delta" | "output-bounded" | "count-tokens";
17
17
  pass: boolean;
@@ -26,15 +26,9 @@ export type TokenTruthResult = {
26
26
  countTokens: number | null;
27
27
  reason?: string;
28
28
  };
29
- export declare function expectedInputDelta(model: string): {
29
+ export declare const expectedInputDelta: (facts: ModelFacts) => {
30
30
  min: number;
31
31
  max: number;
32
32
  };
33
- export declare function checkTokenTruth(opts: {
34
- transport: TransportFn;
35
- baseUrl: string;
36
- apiKey: string;
37
- model: string;
38
- timeoutMs: number;
39
- }): Promise<TokenTruthResult>;
33
+ export declare const tokenTruthRule: import("./types").Rule<"token-truth", "countTokens" | "fixedText", "note">;
40
34
  //# sourceMappingURL=token-truth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-truth.d.ts","sourceRoot":"","sources":["../../src/rules/token-truth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAelD,MAAM,MAAM,eAAe,GAAG;IAC5B,EAAE,EAAE,eAAe,GAAG,aAAa,GAAG,gBAAgB,GAAG,cAAc,CAAC;IACxE,IAAI,EAAE,OAAO,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,0EAA0E;IAC1E,EAAE,EAAE,OAAO,GAAG,IAAI,CAAC;IACnB,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,eAAO,MAAM,kBAAkB,UAAW,UAAU;;;CACK,CAAC;AAmF1D,eAAO,MAAM,cAAc,4EAazB,CAAC"}
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Token accounting.
3
+ *
4
+ * The signature answers "is this really Claude". This answers a different
5
+ * question the signature cannot: "are the numbers you are billing me true". A
6
+ * relay can forward genuine Claude and still inflate `input_tokens` on every
7
+ * call, and nothing in an identity probe would notice.
8
+ *
9
+ * Ported from veridrop (AGPL-3.0) `token_usage.py` / `integrity.py`, whose
10
+ * tolerances were calibrated against the official API. Do not tighten them
11
+ * without re-measuring: the loose-looking bounds exist because real Anthropic
12
+ * responses sat near them.
13
+ */
14
+ import { COUNT_PROBE_MAX_TOKENS as MAX_TOKENS } from "../probes/prompts";
15
+ import { defineRule } from "./types";
16
+ /**
17
+ * Expected `input_tokens` growth between the short and long prompt. Two bands
18
+ * because Anthropic changed tokenizer: a relay claiming a new-tokenizer model
19
+ * while serving an older one lands in the wrong band, which makes this a tier
20
+ * check as well as a billing check.
21
+ */
22
+ const DELTA_OLD = { min: 45, max: 140 };
23
+ const DELTA_NEW = { min: 90, max: 230 };
24
+ /** `count_tokens` should agree with the billed `input_tokens` this closely. */
25
+ const countTolerance = (n) => Math.max(4, Math.round(n * 0.2));
26
+ export const expectedInputDelta = (facts) => facts.tokenizer === "claude-v2" ? DELTA_NEW : DELTA_OLD;
27
+ async function tokenTruthResult(ctx) {
28
+ const noCounts = {
29
+ shortInputTokens: null,
30
+ longInputTokens: null,
31
+ countTokens: null,
32
+ };
33
+ const ft = await ctx.evidence("fixedText");
34
+ if (ft.short.status === null || ft.short.status >= 400)
35
+ return {
36
+ ok: null,
37
+ checks: [],
38
+ ...noCounts,
39
+ reason: `probe-failed:${ft.short.status ?? "network"}`,
40
+ };
41
+ const s = ctx.wire.read.meta(ft.short.data).usage;
42
+ const l = ctx.wire.read.meta(ft.long.data).usage;
43
+ const checks = [];
44
+ const shortIn = s?.prompt ?? null;
45
+ const longIn = l?.prompt ?? null;
46
+ const usagePresent = shortIn !== null && longIn !== null;
47
+ checks.push({
48
+ id: "usage-present",
49
+ pass: usagePresent,
50
+ detail: usagePresent
51
+ ? `short=${shortIn}, long=${longIn}`
52
+ : "endpoint reported no usage counts",
53
+ });
54
+ // Without usage there is nothing to verify; a missing field is not fraud.
55
+ if (shortIn === null || longIn === null)
56
+ return { ok: null, checks, ...noCounts, reason: "no-usage-reported" };
57
+ const band = expectedInputDelta(ctx.facts);
58
+ const delta = longIn - shortIn;
59
+ checks.push({
60
+ id: "input-delta",
61
+ pass: delta >= band.min && delta <= band.max,
62
+ detail: `delta=${delta}, expected ${band.min}-${band.max}`,
63
+ });
64
+ // Output above the cap we set means the count is invented, not merely padded.
65
+ const outputs = [s?.completion ?? null, l?.completion ?? null].filter((v) => v !== null);
66
+ checks.push({
67
+ id: "output-bounded",
68
+ pass: outputs.every((v) => v > 0 && v <= MAX_TOKENS + 4),
69
+ detail: `outputs=${outputs.join(",") || "none"} (cap ${MAX_TOKENS})`,
70
+ });
71
+ // The same upstream pricing the same request without generating: the
72
+ // strongest anti-inflation signal available, when the endpoint exposes it.
73
+ // A missing count endpoint is common on relays and proves nothing, so it is
74
+ // simply absent from the checks rather than counted as a failure.
75
+ let counted = null;
76
+ const countRes = await ctx.evidence("countTokens");
77
+ if (countRes && countRes.status !== null && countRes.status < 400) {
78
+ counted = ctx.wire.read.countedTokens?.(countRes.data) ?? null;
79
+ if (counted !== null) {
80
+ const drift = Math.abs(counted - shortIn);
81
+ const tol = countTolerance(shortIn);
82
+ checks.push({
83
+ id: "count-tokens",
84
+ pass: drift <= tol,
85
+ detail: `count=${counted} vs billed=${shortIn}, drift=${drift} (tolerance ${tol})`,
86
+ });
87
+ }
88
+ }
89
+ return {
90
+ ok: checks.every((c) => c.pass),
91
+ checks,
92
+ shortInputTokens: shortIn,
93
+ longInputTokens: longIn,
94
+ countTokens: counted,
95
+ };
96
+ }
97
+ export const tokenTruthRule = defineRule({
98
+ id: "token-truth",
99
+ layer: "note",
100
+ needs: ["fixedText", "countTokens"],
101
+ check: "tokenTruth",
102
+ applies: (ctx) => ctx.wire.read.countedTokens !== undefined,
103
+ judge: async (ctx) => {
104
+ const r = await tokenTruthResult(ctx);
105
+ if (r.ok !== false)
106
+ return null;
107
+ const failed = r.checks.filter((c) => !c.pass).map((c) => c.id);
108
+ return { severity: "note", reason: `token-truth: ${failed.join(", ")}` };
109
+ },
110
+ report: async (ctx) => ({ tokenTruth: await tokenTruthResult(ctx) }),
111
+ });
112
+ //# sourceMappingURL=token-truth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-truth.js","sourceRoot":"","sources":["../../src/rules/token-truth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Tokenizer fingerprint: how many input tokens the endpoint bills for a fixed
3
+ * run of text, measured as the difference between a long and a short prompt so
4
+ * anything a relay injects cancels out.
5
+ *
6
+ * What it is good for: drift. The delta is deterministic per endpoint (68
7
+ * marketplace lanes measured twice each agreed to the token on every repeat),
8
+ * so a change between two measurements of the same lane means the backend
9
+ * changed, whatever the reply now calls itself. Keep the prior per lane and
10
+ * compare with `fingerprintDrifted`.
11
+ *
12
+ * What it is NOT good for: naming the tier from the number alone. Opus 4.6,
13
+ * Sonnet 4.6 and Haiku 4.5 share one tokenizer, so they bill the same delta on
14
+ * the same path, and relays count differently from each other (77, 90, 134,
15
+ * 160, 211 and 609 were all seen for one text on lanes echoing one model name)
16
+ * or invent usage outright. A signature table therefore ships EMPTY: only a
17
+ * caller who has calibrated a table on a specific endpoint family should pass
18
+ * one, and even then only between-model differences on the same endpoint are
19
+ * evidence.
20
+ */
21
+ export type TierSignatures = Readonly<Record<string, readonly number[]>>;
22
+ /** Empty on purpose; see the module note. Pass your own calibration. */
23
+ export declare const DEFAULT_TIER_SIGNATURES: TierSignatures;
24
+ export type TokenizerFingerprintState = "measured" | "unmeasured";
25
+ export type TokenizerFingerprintResult = {
26
+ state: TokenizerFingerprintState;
27
+ /** long minus short input tokens; the fingerprint itself. */
28
+ delta: number | null;
29
+ shortInputTokens: number | null;
30
+ longInputTokens: number | null;
31
+ /** `model` echoed on the short reply, when the endpoint reported one. */
32
+ servedModel: string | null;
33
+ /** Tier whose signature the delta matched, when any did. */
34
+ matchedTier: string | null;
35
+ reason?: string;
36
+ };
37
+ export declare function tierForDelta(delta: number, signatures?: TierSignatures): string | null;
38
+ /**
39
+ * The served tier when the fingerprint names one other than requested, or
40
+ * null. `tiers` is the wire's tier vocabulary (opus, sonnet, haiku, fable).
41
+ */
42
+ export declare function judgeTokenizerFingerprint(requestedModel: string, result: TokenizerFingerprintResult, tiers: readonly string[]): string | null;
43
+ /** True when two measurements of the same lane disagree: the backend changed. */
44
+ export declare function fingerprintDrifted(prior: number | null | undefined, current: TokenizerFingerprintResult): boolean;
45
+ export declare const tokenizerFingerprintRule: import("./types").Rule<"tokenizer-fingerprint", "fixedText", "evidence">;
46
+ //# sourceMappingURL=tokenizer-fingerprint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokenizer-fingerprint.d.ts","sourceRoot":"","sources":["../../src/rules/tokenizer-fingerprint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH,MAAM,MAAM,cAAc,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,CAAC;AAEzE,wEAAwE;AACxE,eAAO,MAAM,uBAAuB,EAAE,cAAmB,CAAC;AAE1D,MAAM,MAAM,yBAAyB,GAAG,UAAU,GAAG,YAAY,CAAC;AAElE,MAAM,MAAM,0BAA0B,GAAG;IACvC,KAAK,EAAE,yBAAyB,CAAC;IACjC,6DAA6D;IAC7D,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,yEAAyE;IACzE,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,4DAA4D;IAC5D,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,wBAAgB,YAAY,CAC1B,KAAK,EAAE,MAAM,EACb,UAAU,GAAE,cAAwC,GACnD,MAAM,GAAG,IAAI,CAIf;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CACvC,cAAc,EAAE,MAAM,EACtB,MAAM,EAAE,0BAA0B,EAClC,KAAK,EAAE,SAAS,MAAM,EAAE,GACvB,MAAM,GAAG,IAAI,CAMf;AAED,iFAAiF;AACjF,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAChC,OAAO,EAAE,0BAA0B,GAClC,OAAO,CAOT;AA4CD,eAAO,MAAM,wBAAwB,0EAuBnC,CAAC"}
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Tokenizer fingerprint: how many input tokens the endpoint bills for a fixed
3
+ * run of text, measured as the difference between a long and a short prompt so
4
+ * anything a relay injects cancels out.
5
+ *
6
+ * What it is good for: drift. The delta is deterministic per endpoint (68
7
+ * marketplace lanes measured twice each agreed to the token on every repeat),
8
+ * so a change between two measurements of the same lane means the backend
9
+ * changed, whatever the reply now calls itself. Keep the prior per lane and
10
+ * compare with `fingerprintDrifted`.
11
+ *
12
+ * What it is NOT good for: naming the tier from the number alone. Opus 4.6,
13
+ * Sonnet 4.6 and Haiku 4.5 share one tokenizer, so they bill the same delta on
14
+ * the same path, and relays count differently from each other (77, 90, 134,
15
+ * 160, 211 and 609 were all seen for one text on lanes echoing one model name)
16
+ * or invent usage outright. A signature table therefore ships EMPTY: only a
17
+ * caller who has calibrated a table on a specific endpoint family should pass
18
+ * one, and even then only between-model differences on the same endpoint are
19
+ * evidence.
20
+ */
21
+ import { defineRule } from "./types";
22
+ /** Empty on purpose; see the module note. Pass your own calibration. */
23
+ export const DEFAULT_TIER_SIGNATURES = {};
24
+ export function tierForDelta(delta, signatures = DEFAULT_TIER_SIGNATURES) {
25
+ for (const [tier, values] of Object.entries(signatures))
26
+ if (values.includes(delta))
27
+ return tier;
28
+ return null;
29
+ }
30
+ /**
31
+ * The served tier when the fingerprint names one other than requested, or
32
+ * null. `tiers` is the wire's tier vocabulary (opus, sonnet, haiku, fable).
33
+ */
34
+ export function judgeTokenizerFingerprint(requestedModel, result, tiers) {
35
+ if (result.state !== "measured" || result.matchedTier === null)
36
+ return null;
37
+ const req = requestedModel.toLowerCase();
38
+ const reqTier = tiers.filter((t) => req.includes(t));
39
+ if (reqTier.length !== 1)
40
+ return null;
41
+ return reqTier[0] === result.matchedTier ? null : result.matchedTier;
42
+ }
43
+ /** True when two measurements of the same lane disagree: the backend changed. */
44
+ export function fingerprintDrifted(prior, current) {
45
+ return (typeof prior === "number" &&
46
+ current.state === "measured" &&
47
+ current.delta !== null &&
48
+ current.delta !== prior);
49
+ }
50
+ async function fingerprintResult(ctx) {
51
+ const ft = await ctx.evidence("fixedText");
52
+ const empty = {
53
+ delta: null,
54
+ shortInputTokens: null,
55
+ longInputTokens: null,
56
+ servedModel: null,
57
+ matchedTier: null,
58
+ };
59
+ for (const r of [ft.short, ft.long])
60
+ if (r.status === null || r.status >= 400)
61
+ return {
62
+ state: "unmeasured",
63
+ ...empty,
64
+ reason: `probe-failed:${r.status ?? "network"}`,
65
+ };
66
+ const shortMeta = ctx.wire.read.meta(ft.short.data);
67
+ const short = shortMeta.usage?.prompt ?? null;
68
+ const long = ctx.wire.read.meta(ft.long.data).usage?.prompt ?? null;
69
+ if (short === null || long === null)
70
+ return {
71
+ state: "unmeasured",
72
+ ...empty,
73
+ servedModel: shortMeta.detectedModel,
74
+ reason: "no-usage-reported",
75
+ };
76
+ const delta = long - short;
77
+ return {
78
+ state: "measured",
79
+ delta,
80
+ shortInputTokens: short,
81
+ longInputTokens: long,
82
+ servedModel: shortMeta.detectedModel,
83
+ matchedTier: tierForDelta(delta, ctx.checks.tokenizerFingerprint?.signatures),
84
+ };
85
+ }
86
+ export const tokenizerFingerprintRule = defineRule({
87
+ id: "tokenizer-fingerprint",
88
+ layer: "evidence",
89
+ needs: ["fixedText"],
90
+ check: "tokenizerFingerprint",
91
+ // Claude only, over either wire that carries its usage as is.
92
+ applies: (ctx) => ctx.facts.vendor === "anthropic" && ctx.wire.id !== "gemini",
93
+ judge: async (ctx) => {
94
+ if (!ctx.wire.tiers)
95
+ return null;
96
+ const r = await fingerprintResult(ctx);
97
+ const tier = judgeTokenizerFingerprint(ctx.model, r, ctx.wire.tiers);
98
+ return tier
99
+ ? {
100
+ severity: "fail",
101
+ reason: `tokenizer-fingerprint: delta ${r.delta} is the ${tier} signature, requested ${ctx.model}`,
102
+ data: r,
103
+ }
104
+ : null;
105
+ },
106
+ report: async (ctx) => ({
107
+ tokenizerFingerprint: await fingerprintResult(ctx),
108
+ }),
109
+ });
110
+ //# sourceMappingURL=tokenizer-fingerprint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tokenizer-fingerprint.js","sourceRoot":"","sources":["../../src/rules/tokenizer-fingerprint.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ import type { ResolvedChecks, RunCtx } from "../engine/context";
2
+ import type { EvidenceBag, EvidenceKey } from "../engine/evidence";
3
+ import type { ResponseMetadata } from "./response-metadata";
4
+ import type { SignatureResult } from "./thinking-signature";
5
+ import type { ThinkingFloorResult } from "./thinking-floor";
6
+ import type { ThroughputSample } from "./throughput";
7
+ import type { TokenTruthResult } from "./token-truth";
8
+ import type { TokenizerFingerprintResult } from "./tokenizer-fingerprint";
9
+ /**
10
+ * How a finding weighs on the verdict. `fail` and `suspect` both make the
11
+ * endpoint suspicious (kept apart so a consumer policy can demote one),
12
+ * `inconclusive` leaves it unverified, `note` never touches the verdict.
13
+ */
14
+ export type Severity = "fail" | "suspect" | "inconclusive" | "note";
15
+ /**
16
+ * Which findings outrank which: `evidence` rules read hard facts the probes
17
+ * cannot see and beat everything; `probe` rules read the behavioural probes and
18
+ * decide `versionUnverifiable`; `note` rules only report.
19
+ */
20
+ export type RuleLayer = "evidence" | "probe" | "note";
21
+ /** What a rule returns; the engine stamps the rule id and layer on. */
22
+ export type RuleVerdict = {
23
+ severity: Severity;
24
+ reason: string;
25
+ data?: unknown;
26
+ };
27
+ export type Finding<Id extends string = string> = RuleVerdict & {
28
+ rule: Id;
29
+ layer: RuleLayer;
30
+ };
31
+ /** Per-rule artefacts copied onto the result, present only when the rule ran. */
32
+ export type Reports = {
33
+ signature?: SignatureResult;
34
+ tokenTruth?: TokenTruthResult;
35
+ thinkingFloor?: ThinkingFloorResult;
36
+ tokenizerFingerprint?: TokenizerFingerprintResult;
37
+ responseMetadata?: ResponseMetadata;
38
+ throughput?: ThroughputSample | null;
39
+ };
40
+ export type CheckKey = keyof ResolvedChecks;
41
+ /** A rule sees the run and only the evidence it declared in `needs`. */
42
+ export type RuleCtx<N extends EvidenceKey = EvidenceKey> = RunCtx & {
43
+ evidence<K extends N>(key: K): Promise<EvidenceBag[K]>;
44
+ };
45
+ export type Rule<Id extends string = string, N extends EvidenceKey = EvidenceKey, L extends RuleLayer = RuleLayer> = {
46
+ id: Id;
47
+ layer: L;
48
+ needs: readonly N[];
49
+ /** Opt-in switch in `checks` that enables this rule under `verify`. */
50
+ check?: CheckKey;
51
+ /** Whether the rule has anything to say on this wire and model. */
52
+ applies(ctx: RunCtx): boolean;
53
+ judge(ctx: RuleCtx<N>): Promise<RuleVerdict | null>;
54
+ report?(ctx: RuleCtx<N>): Promise<Partial<Reports>>;
55
+ };
56
+ export declare const defineRule: <const Id extends string, const N extends EvidenceKey, const L extends RuleLayer>(rule: Rule<Id, N, L>) => Rule<Id, N, L>;
57
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/rules/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export const defineRule = (rule) => rule;
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/rules/types.ts"],"names":[],"mappings":"AAoEA,MAAM,CAAC,MAAM,UAAU,GAAG,CAKxB,IAAoB,EACJ,EAAE,CAAC,IAAI,CAAC"}
@@ -1,4 +1,4 @@
1
- import type { TransportMode } from "./types";
1
+ export type TransportMode = "direct" | "server";
2
2
  export type TransportResult = {
3
3
  status: number | null;
4
4
  data: unknown;
@@ -13,5 +13,13 @@ export type TransportArgs = {
13
13
  timeoutMs: number;
14
14
  };
15
15
  export type TransportFn = (args: TransportArgs) => Promise<TransportResult>;
16
- export declare function probeTransport(args: TransportArgs): Promise<TransportResult>;
16
+ /** Plain fetch from wherever the code runs. */
17
+ export declare const directTransport: TransportFn;
18
+ /**
19
+ * From a browser through the caller's own backend, which forwards
20
+ * `{ url, headers, reqBody }` and answers `{ data: { status, data } }`.
21
+ */
22
+ export declare function browserTransport(opts: {
23
+ serverProxyUrl: string;
24
+ }): TransportFn;
17
25
  //# sourceMappingURL=transport.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAE7C,MAAM,MAAM,eAAe,GAAG;IAC5B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,WAAW,EAAE,OAAO,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,aAAa,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG,CAAC,IAAI,EAAE,aAAa,KAAK,OAAO,CAAC,eAAe,CAAC,CAAC;AAgF5E,wBAAgB,cAAc,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,eAAe,CAAC,CAE5E"}
1
+ {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEhD,MAAM,MAAM,eAAe,GAAG;IAC5B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,WAAW,EAAE,OAAO,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,aAAa,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG,CAAC,IAAI,EAAE,aAAa,KAAK,OAAO,CAAC,eAAe,CAAC,CAAC;AAsB5E,+CAA+C;AAC/C,eAAO,MAAM,eAAe,EAAE,WAuB7B,CAAC;AAEF;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE;IACrC,cAAc,EAAE,MAAM,CAAC;CACxB,GAAG,WAAW,CAqCd"}