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.
- package/README.md +79 -21
- package/dist/engine/context.d.ts +43 -0
- package/dist/engine/context.d.ts.map +1 -0
- package/dist/engine/context.js +50 -0
- package/dist/engine/context.js.map +1 -0
- package/dist/engine/evidence.d.ts +40 -0
- package/dist/engine/evidence.d.ts.map +1 -0
- package/dist/engine/evidence.js +108 -0
- package/dist/engine/evidence.js.map +1 -0
- package/dist/engine/handshake.d.ts +16 -0
- package/dist/engine/handshake.d.ts.map +1 -0
- package/dist/{handshake.js → engine/handshake.js} +35 -63
- package/dist/engine/handshake.js.map +1 -0
- package/dist/engine/probe-runner.d.ts +14 -0
- package/dist/engine/probe-runner.d.ts.map +1 -0
- package/dist/engine/probe-runner.js +163 -0
- package/dist/engine/probe-runner.js.map +1 -0
- package/dist/engine/verdict.d.ts +15 -0
- package/dist/engine/verdict.d.ts.map +1 -0
- package/dist/engine/verdict.js +19 -0
- package/dist/engine/verdict.js.map +1 -0
- package/dist/highlight.d.ts +2 -2
- package/dist/highlight.d.ts.map +1 -1
- package/dist/highlight.js +10 -9
- package/dist/highlight.js.map +1 -1
- package/dist/{patterns.d.ts → identity/patterns.d.ts} +7 -0
- package/dist/identity/patterns.d.ts.map +1 -0
- package/dist/{patterns.js → identity/patterns.js} +7 -0
- package/dist/identity/patterns.js.map +1 -0
- package/dist/identity/signals.d.ts +15 -0
- package/dist/identity/signals.d.ts.map +1 -0
- package/dist/{signals.js → identity/signals.js} +15 -24
- package/dist/identity/signals.js.map +1 -0
- package/dist/index.d.ts +15 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -5
- package/dist/index.js.map +1 -1
- package/dist/internal/utils.d.ts +3 -1
- package/dist/internal/utils.d.ts.map +1 -1
- package/dist/internal/utils.js +12 -6
- package/dist/internal/utils.js.map +1 -1
- package/dist/models/catalog.d.ts +7 -0
- package/dist/models/catalog.d.ts.map +1 -0
- package/dist/models/catalog.js +23 -0
- package/dist/models/catalog.js.map +1 -0
- package/dist/models/facts.d.ts +56 -0
- package/dist/models/facts.d.ts.map +1 -0
- package/dist/models/facts.js +91 -0
- package/dist/models/facts.js.map +1 -0
- package/dist/models/index.d.ts +6 -0
- package/dist/models/index.d.ts.map +1 -0
- package/dist/models/index.js +5 -0
- package/dist/models/index.js.map +1 -0
- package/dist/{substitution.d.ts → models/substitution.d.ts} +3 -4
- package/dist/models/substitution.d.ts.map +1 -0
- package/dist/{substitution.js → models/substitution.js} +3 -4
- package/dist/models/substitution.js.map +1 -0
- package/dist/models/tiers.d.ts +14 -0
- package/dist/models/tiers.d.ts.map +1 -0
- package/dist/models/tiers.js +35 -0
- package/dist/models/tiers.js.map +1 -0
- package/dist/probes/prompts.d.ts +23 -0
- package/dist/probes/prompts.d.ts.map +1 -0
- package/dist/probes/prompts.js +31 -0
- package/dist/probes/prompts.js.map +1 -0
- package/dist/probes/table.d.ts +37 -0
- package/dist/probes/table.d.ts.map +1 -0
- package/dist/probes/table.js +57 -0
- package/dist/probes/table.js.map +1 -0
- package/dist/registry.d.ts +26 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +23 -0
- package/dist/registry.js.map +1 -0
- package/dist/rules/index.d.ts +15 -0
- package/dist/rules/index.d.ts.map +1 -0
- package/dist/rules/index.js +8 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/rules/probe-ladder.d.ts +33 -0
- package/dist/rules/probe-ladder.d.ts.map +1 -0
- package/dist/rules/probe-ladder.js +156 -0
- package/dist/rules/probe-ladder.js.map +1 -0
- package/dist/{detectors → rules}/response-metadata.d.ts +11 -14
- package/dist/rules/response-metadata.d.ts.map +1 -0
- package/dist/{detectors → rules}/response-metadata.js +32 -15
- package/dist/rules/response-metadata.js.map +1 -0
- package/dist/rules/table.d.ts +27 -0
- package/dist/rules/table.d.ts.map +1 -0
- package/dist/rules/table.js +47 -0
- package/dist/rules/table.js.map +1 -0
- package/dist/{detectors → rules}/thinking-floor.d.ts +14 -29
- package/dist/rules/thinking-floor.d.ts.map +1 -0
- package/dist/rules/thinking-floor.js +119 -0
- package/dist/rules/thinking-floor.js.map +1 -0
- package/dist/rules/thinking-signature.d.ts +43 -0
- package/dist/rules/thinking-signature.d.ts.map +1 -0
- package/dist/rules/thinking-signature.js +72 -0
- package/dist/rules/thinking-signature.js.map +1 -0
- package/dist/{detectors → rules}/throughput.d.ts +7 -10
- package/dist/rules/throughput.d.ts.map +1 -0
- package/dist/{detectors → rules}/throughput.js +32 -11
- package/dist/rules/throughput.js.map +1 -0
- package/dist/{detectors → rules}/token-truth.d.ts +7 -13
- package/dist/rules/token-truth.d.ts.map +1 -0
- package/dist/rules/token-truth.js +112 -0
- package/dist/rules/token-truth.js.map +1 -0
- package/dist/rules/tokenizer-fingerprint.d.ts +46 -0
- package/dist/rules/tokenizer-fingerprint.d.ts.map +1 -0
- package/dist/rules/tokenizer-fingerprint.js +110 -0
- package/dist/rules/tokenizer-fingerprint.js.map +1 -0
- package/dist/rules/types.d.ts +57 -0
- package/dist/rules/types.d.ts.map +1 -0
- package/dist/rules/types.js +2 -0
- package/dist/rules/types.js.map +1 -0
- package/dist/transport.d.ts +10 -2
- package/dist/transport.d.ts.map +1 -1
- package/dist/transport.js +40 -32
- package/dist/transport.js.map +1 -1
- package/dist/types.d.ts +70 -31
- package/dist/types.d.ts.map +1 -1
- package/dist/vendors/anthropic.d.ts +44 -0
- package/dist/vendors/anthropic.d.ts.map +1 -0
- package/dist/vendors/anthropic.js +114 -0
- package/dist/vendors/anthropic.js.map +1 -0
- package/dist/vendors/base-url.d.ts +3 -0
- package/dist/vendors/base-url.d.ts.map +1 -0
- package/dist/vendors/base-url.js +6 -0
- package/dist/vendors/base-url.js.map +1 -0
- package/dist/vendors/gemini.d.ts +44 -0
- package/dist/vendors/gemini.d.ts.map +1 -0
- package/dist/vendors/gemini.js +77 -0
- package/dist/vendors/gemini.js.map +1 -0
- package/dist/vendors/index.d.ts +9 -0
- package/dist/vendors/index.d.ts.map +1 -0
- package/dist/vendors/index.js +7 -0
- package/dist/vendors/index.js.map +1 -0
- package/dist/vendors/openai.d.ts +46 -0
- package/dist/vendors/openai.d.ts.map +1 -0
- package/dist/vendors/openai.js +77 -0
- package/dist/vendors/openai.js.map +1 -0
- package/dist/vendors/table.d.ts +113 -0
- package/dist/vendors/table.d.ts.map +1 -0
- package/dist/vendors/table.js +14 -0
- package/dist/vendors/table.js.map +1 -0
- package/dist/vendors/types.d.ts +93 -0
- package/dist/vendors/types.d.ts.map +1 -0
- package/dist/vendors/types.js +2 -0
- package/dist/vendors/types.js.map +1 -0
- package/dist/verify.d.ts +30 -0
- package/dist/verify.d.ts.map +1 -0
- package/dist/verify.js +185 -0
- package/dist/verify.js.map +1 -0
- package/package.json +25 -10
- package/dist/detectors/response-metadata.d.ts.map +0 -1
- package/dist/detectors/response-metadata.js.map +0 -1
- package/dist/detectors/thinking-floor.d.ts.map +0 -1
- package/dist/detectors/thinking-floor.js +0 -135
- package/dist/detectors/thinking-floor.js.map +0 -1
- package/dist/detectors/thinking-signature.d.ts +0 -70
- package/dist/detectors/thinking-signature.d.ts.map +0 -1
- package/dist/detectors/thinking-signature.js +0 -177
- package/dist/detectors/thinking-signature.js.map +0 -1
- package/dist/detectors/throughput.d.ts.map +0 -1
- package/dist/detectors/throughput.js.map +0 -1
- package/dist/detectors/token-truth.d.ts.map +0 -1
- package/dist/detectors/token-truth.js +0 -142
- package/dist/detectors/token-truth.js.map +0 -1
- package/dist/detectors/tokenizer-fingerprint.d.ts +0 -65
- package/dist/detectors/tokenizer-fingerprint.d.ts.map +0 -1
- package/dist/detectors/tokenizer-fingerprint.js +0 -135
- package/dist/detectors/tokenizer-fingerprint.js.map +0 -1
- package/dist/handshake.d.ts +0 -23
- package/dist/handshake.d.ts.map +0 -1
- package/dist/handshake.js.map +0 -1
- package/dist/internal/fixed-text.d.ts +0 -13
- package/dist/internal/fixed-text.d.ts.map +0 -1
- package/dist/internal/fixed-text.js +0 -13
- package/dist/internal/fixed-text.js.map +0 -1
- package/dist/models.d.ts +0 -5
- package/dist/models.d.ts.map +0 -1
- package/dist/models.js +0 -54
- package/dist/models.js.map +0 -1
- package/dist/nonce.d.ts +0 -4
- package/dist/nonce.d.ts.map +0 -1
- package/dist/nonce.js +0 -12
- package/dist/nonce.js.map +0 -1
- package/dist/patterns.d.ts.map +0 -1
- package/dist/patterns.js.map +0 -1
- package/dist/probes.d.ts +0 -10
- package/dist/probes.d.ts.map +0 -1
- package/dist/probes.js +0 -54
- package/dist/probes.js.map +0 -1
- package/dist/providers/anthropic.d.ts +0 -3
- package/dist/providers/anthropic.d.ts.map +0 -1
- package/dist/providers/anthropic.js +0 -67
- package/dist/providers/anthropic.js.map +0 -1
- package/dist/providers/config.d.ts +0 -34
- package/dist/providers/config.d.ts.map +0 -1
- package/dist/providers/config.js +0 -14
- package/dist/providers/config.js.map +0 -1
- package/dist/providers/gemini.d.ts +0 -3
- package/dist/providers/gemini.d.ts.map +0 -1
- package/dist/providers/gemini.js +0 -58
- package/dist/providers/gemini.js.map +0 -1
- package/dist/providers/openai.d.ts +0 -3
- package/dist/providers/openai.d.ts.map +0 -1
- package/dist/providers/openai.js +0 -56
- package/dist/providers/openai.js.map +0 -1
- package/dist/rules.d.ts +0 -6
- package/dist/rules.d.ts.map +0 -1
- package/dist/rules.js +0 -28
- package/dist/rules.js.map +0 -1
- package/dist/runner.d.ts +0 -39
- package/dist/runner.d.ts.map +0 -1
- package/dist/runner.js +0 -352
- package/dist/runner.js.map +0 -1
- package/dist/signals.d.ts +0 -11
- package/dist/signals.d.ts.map +0 -1
- package/dist/signals.js.map +0 -1
- package/dist/substitution.d.ts.map +0 -1
- package/dist/substitution.js.map +0 -1
- package/dist/verdict.d.ts +0 -26
- package/dist/verdict.d.ts.map +0 -1
- package/dist/verdict.js +0 -84
- 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":"AAAA;;;;GAIG;AAIH,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EAAE,yBAAyB,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAChF,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAErC,MAAM,MAAM,GAAG,CAAC,CAAC;AAEjB,MAAM,UAAU,GAAG,CAAC,MAA4B,EAAE,GAAgB,EAAE,EAAE,CACpE,MAAM;KACH,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,GAAG,CAAC;KAC/B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;KACnB,IAAI,CAAC,IAAI,CAAC,CAAC;AAEhB,MAAM,UAAU,GAAG,CACjB,EAAM,EACN,MAAmB,EACnB,MAAc,EACd,EAAE,CACF,UAAU,CAAC;IACT,EAAE;IACF,KAAK,EAAE,OAAO;IACd,KAAK,EAAE,CAAC,QAAQ,CAAC;IACjB,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI;IACnB,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;QACnB,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,CAAC;QAChE,OAAO,MAAM;YACX,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,KAAK,MAAM,EAAE,EAAE;YACtD,CAAC,CAAC,IAAI,CAAC;IACX,CAAC;CACF,CAAC,CAAC;AAEL,MAAM,CAAC,MAAM,cAAc,GAAG,UAAU,CACtC,aAAa,EACb,aAAa,EACb,qBAAqB,CACtB,CAAC;AACF,MAAM,CAAC,MAAM,QAAQ,GAAG,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC;AAChE,MAAM,CAAC,MAAM,WAAW,GAAG,UAAU,CACnC,UAAU,EACV,UAAU,EACV,mBAAmB,CACpB,CAAC;AAEF,mFAAmF;AACnF,MAAM,CAAC,MAAM,OAAO,GAAG,UAAU,CAAC;IAChC,EAAE,EAAE,KAAK;IACT,KAAK,EAAE,OAAO;IACd,KAAK,EAAE,CAAC,QAAQ,CAAC;IACjB,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI;IACnB,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;QACnB,MAAM,MAAM,GAAG,CAAC,MAAM,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;aAC1C,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC;aAC3B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QACvB,OAAO,MAAM,CAAC,MAAM,IAAI,CAAC;YACvB,CAAC,CAAC;gBACE,QAAQ,EAAE,MAAM;gBAChB,MAAM,EAAE,oCAAoC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;aAChE;YACH,CAAC,CAAC,IAAI,CAAC;IACX,CAAC;CACF,CAAC,CAAC;AAEH;;;GAGG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,UAAU,CAAC;IACpC,EAAE,EAAE,SAAS;IACb,KAAK,EAAE,OAAO;IACd,KAAK,EAAE,CAAC,QAAQ,CAAC;IACjB,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI;IACnB,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;QACnB,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAC5C,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAC5B,CAAC,CAAC,EAAE,EAAE,CACJ,CAAC,CAAC,MAAM,KAAK,SAAS;YACtB,CAAC,CAAC,CAAC,KAAK,KAAK,YAAY,IAAI,CAAC,CAAC,KAAK,KAAK,UAAU,CAAC,CACvD,CAAC;QACF,OAAO,UAAU;YACf,CAAC,CAAC;gBACE,QAAQ,EAAE,MAAM;gBAChB,MAAM,EAAE,qBAAqB,UAAU,CAAC,MAAM,EAAE,SAAS,CAAC,EAAE;aAC7D;YACH,CAAC,CAAC,IAAI,CAAC;IACX,CAAC;CACF,CAAC,CAAC;AAEH,+EAA+E;AAC/E,MAAM,CAAC,MAAM,uBAAuB,GAAG,UAAU,CAAC;IAChD,EAAE,EAAE,uBAAuB;IAC3B,KAAK,EAAE,OAAO;IACd,KAAK,EAAE,CAAC,QAAQ,CAAC;IACjB,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,KAAK,IAAI;IACzC,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;QACnB,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAC5C,MAAM,MAAM,GAAG,yBAAyB,CACtC,GAAG,CAAC,KAAK,EACT,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,EAClC,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CACrB,CAAC;QACF,OAAO,MAAM;YACX,CAAC,CAAC;gBACE,QAAQ,EAAE,MAAM;gBAChB,MAAM,EAAE,oCAAoC,GAAG,CAAC,KAAK,oBAAoB,MAAM,EAAE;gBACjF,IAAI,EAAE,EAAE,MAAM,EAAE;aACjB;YACH,CAAC,CAAC,IAAI,CAAC;IACX,CAAC;CACF,CAAC,CAAC;AAEH,sFAAsF;AACtF,MAAM,CAAC,MAAM,eAAe,GAAG,UAAU,CAAC;IACxC,EAAE,EAAE,aAAa;IACjB,KAAK,EAAE,OAAO;IACd,KAAK,EAAE,CAAC,QAAQ,CAAC;IACjB,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI;IACnB,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;QACnB,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAC5C,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,EAAE,aAAa,IAAI,IAAI,CAAC;QAC5E,MAAM,MAAM,GAAG,kBAAkB,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;QACvD,OAAO,MAAM;YACX,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,MAAM,EAAE,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,EAAE;YACzE,CAAC,CAAC,IAAI,CAAC;IACX,CAAC;CACF,CAAC,CAAC;AAEH;;;;GAIG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,UAAU,CAAC;IACnC,EAAE,EAAE,QAAQ;IACZ,KAAK,EAAE,OAAO;IACd,KAAK,EAAE,CAAC,QAAQ,CAAC;IACjB,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI;IACnB,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;QACnB,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAC5C,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QAC7C,IAAI,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI,MAAM;YAAE,OAAO,IAAI,CAAC;QACzD,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACrD,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC;QAChE,MAAM,iBAAiB,GAAG,MAAM,CAAC,MAAM,CACrC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,MAAM,KAAK,IAAI,CACzC,CAAC,MAAM,CAAC;QACT,IAAI,cAAc,GAAG,CAAC,IAAI,iBAAiB,KAAK,CAAC;YAC/C,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,EAAE,cAAc,MAAM,EAAE,EAAE,CAAC;QACtE,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,OAAO,CAAC,CAAC,MAAM,CAAC;QAChE,IAAI,KAAK,KAAK,MAAM,CAAC,MAAM;YACzB,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,mBAAmB,MAAM,EAAE,EAAE,CAAC;QACtE,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QACnE,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC;YAChB,OAAO;gBACL,QAAQ,EAAE,SAAS;gBACnB,MAAM,EAAE,oCAAoC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,MAAM,WAAW;aACtF,CAAC;QACJ,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,MAAM,EAAE,EAAE,CAAC;IAC9D,CAAC;CACF,CAAC,CAAC;AAEH;;;;;GAKG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,UAAU,CAAC;IAC3C,EAAE,EAAE,kBAAkB;IACtB,KAAK,EAAE,MAAM;IACb,KAAK,EAAE,CAAC,QAAQ,CAAC;IACjB,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,KAAK,IAAI;IACzC,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;QACnB,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAC5C,MAAM,OAAO,GAAG,kBAAkB,CAChC,GAAG,CAAC,KAAK,EACT,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,YAAY,CAAC,EAAE,IAAI,EAClD,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CACrB,CAAC;QACF,OAAO,OAAO;YACZ,CAAC,CAAC;gBACE,QAAQ,EAAE,MAAM;gBAChB,MAAM,EAAE,4BAA4B,OAAO,EAAE;gBAC7C,IAAI,EAAE,EAAE,OAAO,EAAE;aAClB;YACH,CAAC,CAAC,IAAI,CAAC;IACX,CAAC;CACF,CAAC,CAAC"}
|
|
@@ -1,21 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Passive response metadata: what the envelope reveals about who really
|
|
3
|
-
* answered, read from
|
|
3
|
+
* answered, read from replies the run already made.
|
|
4
4
|
*
|
|
5
|
-
* Costs nothing
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
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
|
-
|
|
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
|
|
3
|
+
* answered, read from replies the run already made.
|
|
4
4
|
*
|
|
5
|
-
* Costs nothing
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
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 ||
|
|
43
|
+
if (data["candidates"] !== undefined ||
|
|
44
|
+
usageKeys.some((k) => GEMINI_USAGE.has(k)))
|
|
46
45
|
return "gemini";
|
|
47
|
-
if (data["choices"] !== undefined ||
|
|
46
|
+
if (data["choices"] !== undefined ||
|
|
47
|
+
usageKeys.some((k) => OPENAI_USAGE.has(k)))
|
|
48
48
|
return "openai";
|
|
49
|
-
if (data["content"] !== undefined ||
|
|
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
|
|
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
|
|
14
|
-
*
|
|
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 {
|
|
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
|
-
|
|
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
|
|
54
|
-
|
|
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":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEtE,OAAO,EAAE,UAAU,EAAgB,MAAM,SAAS,CAAC;AAEnD,MAAM,CAAC,MAAM,6BAA6B,GAAG,EAAE,CAAC;AA0BhD,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,MAAM,GAAsB,EAAE,EAAE,EAAE,CACpE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,gBAAgB,CAAC,CAAC,CAAC,GAAG,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;AAEhF,MAAM,UAAU,eAAe,CAC7B,KAAa,EACb,OAA8B;IAE9B,OAAO,iBAAiB,CAAC,KAAK,EAAE,mBAAmB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;SAClE,YAAY,CAAC;AAClB,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,iBAAiB,CAAC,IAAa;IAC7C,MAAM,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC;IACpB,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,eAAe,CAAC,CAAC,CAAC;IACpD,IAAI,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IACpB,MAAM,OAAO,GAAG,GAAG,CAAC,CAAC,CAAC,2BAA2B,CAAC,CAAC,CAAC;IACpD,OAAO;QACL,SAAS,EACP,KAAK,CAAC,OAAO,EAAE,CAAC,kBAAkB,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC;QACxE,UAAU,EACR,KAAK,CAAC,CAAC,CAAC,mBAAmB,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC;KACpE,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,mBAAmB,CACjC,KAA4B,EAC5B,KAAa;IAEb,IAAI,CAAC,KAAK;QACR,OAAO;YACL,KAAK,EAAE,YAAY;YACnB,gBAAgB,EAAE,IAAI;YACtB,eAAe,EAAE,IAAI;YACrB,MAAM,EAAE,mBAAmB;SAC5B,CAAC;IACJ,IAAI,KAAK,CAAC,SAAS,KAAK,IAAI,IAAI,KAAK,CAAC,SAAS,GAAG,CAAC;QACjD,OAAO;YACL,KAAK,EAAE,SAAS;YAChB,gBAAgB,EAAE,KAAK,CAAC,UAAU;YAClC,eAAe,EAAE,KAAK,CAAC,SAAS;SACjC,CAAC;IACJ,IAAI,KAAK,CAAC,UAAU,KAAK,IAAI;QAC3B,OAAO;YACL,KAAK,EAAE,YAAY;YACnB,gBAAgB,EAAE,IAAI;YACtB,eAAe,EAAE,KAAK,CAAC,SAAS;YAChC,MAAM,EAAE,8BAA8B;SACvC,CAAC;IACJ,4EAA4E;IAC5E,oEAAoE;IACpE,IAAI,KAAK,CAAC,UAAU,KAAK,CAAC;QACxB,OAAO;YACL,KAAK,EAAE,YAAY;YACnB,gBAAgB,EAAE,CAAC;YACnB,eAAe,EAAE,KAAK,CAAC,SAAS;YAChC,MAAM,EAAE,aAAa;SACtB,CAAC;IACJ,IAAI,KAAK,CAAC,UAAU,GAAG,KAAK;QAC1B,OAAO;YACL,KAAK,EAAE,aAAa;YACpB,gBAAgB,EAAE,KAAK,CAAC,UAAU;YAClC,eAAe,EAAE,KAAK,CAAC,SAAS;YAChC,MAAM,EAAE,qBAAqB,KAAK,CAAC,UAAU,gBAAgB,KAAK,2BAA2B;SAC9F,CAAC;IACJ,OAAO;QACL,KAAK,EAAE,SAAS;QAChB,gBAAgB,EAAE,KAAK,CAAC,UAAU;QAClC,eAAe,EAAE,KAAK,CAAC,SAAS;KACjC,CAAC;AACJ,CAAC;AAED,MAAM,OAAO,GAAwB;IACnC,KAAK,EAAE,SAAS;IAChB,gBAAgB,EAAE,IAAI;IACtB,eAAe,EAAE,IAAI;CACtB,CAAC;AAEF,0FAA0F;AAC1F,MAAM,UAAU,kBAAkB,CAChC,KAAa,EACb,IAAa,EACb,OAA8B;IAE9B,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,OAAO,CAAC;QAAE,OAAO,OAAO,CAAC;IACrD,OAAO,mBAAmB,CACxB,iBAAiB,CAAC,IAAI,CAAC,EACvB,OAAO,EAAE,mBAAmB,IAAI,6BAA6B,CAC9D,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,WAAW,CACxB,GAA0B;IAE1B,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;IAC7C,IAAI,CAAC,GAAG;QAAE,OAAO,OAAO,CAAC;IACzB,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI;QACjC,OAAO;YACL,KAAK,EAAE,YAAY;YACnB,gBAAgB,EAAE,IAAI;YACtB,eAAe,EAAE,IAAI;YACrB,MAAM,EAAE,GAAG,CAAC,KAAK,IAAI,QAAQ,GAAG,CAAC,MAAM,IAAI,MAAM,EAAE;SACpD,CAAC;IACJ,OAAO,mBAAmB,CACxB,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,EAChD,GAAG,CAAC,MAAM,CAAC,aAAa,EAAE,mBAAmB;QAC3C,6BAA6B,CAChC,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,MAAM,iBAAiB,GAAG,UAAU,CAAC;IAC1C,EAAE,EAAE,gBAAgB;IACpB,KAAK,EAAE,UAAU;IACjB,KAAK,EAAE,CAAC,YAAY,CAAC;IACrB,KAAK,EAAE,eAAe;IACtB,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,KAAK,SAAS;IAC5D,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;QACnB,MAAM,CAAC,GAAG,MAAM,WAAW,CAAC,GAAG,CAAC,CAAC;QACjC,OAAO,CAAC,CAAC,KAAK,KAAK,aAAa;YAC9B,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB,CAAC,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE;YACnE,CAAC,CAAC,IAAI,CAAC;IACX,CAAC;IACD,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC;CACnE,CAAC,CAAC"}
|
|
@@ -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"}
|