ai-model-verifier 1.4.1 → 2.0.1
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 -22
- package/dist/engine/context.d.ts +50 -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/{detectors → rules}/tokenizer-fingerprint.d.ts +7 -19
- package/dist/rules/tokenizer-fingerprint.d.ts.map +1 -0
- package/dist/{detectors → rules}/tokenizer-fingerprint.js +40 -58
- 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 +196 -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.map +0 -1
- 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,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
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
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
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
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
|
|
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":"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"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Token
|
|
2
|
+
* Token accounting.
|
|
3
3
|
*
|
|
4
|
-
* The signature
|
|
5
|
-
* question the signature cannot: "are the numbers you are billing me true".
|
|
6
|
-
*
|
|
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 {
|
|
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
|
|
29
|
+
export declare const expectedInputDelta: (facts: ModelFacts) => {
|
|
30
30
|
min: number;
|
|
31
31
|
max: number;
|
|
32
32
|
};
|
|
33
|
-
export declare
|
|
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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"}
|
|
@@ -13,15 +13,11 @@
|
|
|
13
13
|
* Sonnet 4.6 and Haiku 4.5 share one tokenizer, so they bill the same delta on
|
|
14
14
|
* the same path, and relays count differently from each other (77, 90, 134,
|
|
15
15
|
* 160, 211 and 609 were all seen for one text on lanes echoing one model name)
|
|
16
|
-
* or invent usage outright
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* between-model differences on the same endpoint are evidence.
|
|
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.
|
|
21
20
|
*/
|
|
22
|
-
import type { TransportFn } from "../transport";
|
|
23
|
-
/** Wire format the endpoint speaks; Claude is sold over both. */
|
|
24
|
-
export type CountWire = "anthropic" | "openai";
|
|
25
21
|
export type TierSignatures = Readonly<Record<string, readonly number[]>>;
|
|
26
22
|
/** Empty on purpose; see the module note. Pass your own calibration. */
|
|
27
23
|
export declare const DEFAULT_TIER_SIGNATURES: TierSignatures;
|
|
@@ -40,19 +36,11 @@ export type TokenizerFingerprintResult = {
|
|
|
40
36
|
};
|
|
41
37
|
export declare function tierForDelta(delta: number, signatures?: TierSignatures): string | null;
|
|
42
38
|
/**
|
|
43
|
-
* The served tier when the fingerprint names one
|
|
44
|
-
* null. `tiers` is the
|
|
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).
|
|
45
41
|
*/
|
|
46
42
|
export declare function judgeTokenizerFingerprint(requestedModel: string, result: TokenizerFingerprintResult, tiers: readonly string[]): string | null;
|
|
47
43
|
/** True when two measurements of the same lane disagree: the backend changed. */
|
|
48
44
|
export declare function fingerprintDrifted(prior: number | null | undefined, current: TokenizerFingerprintResult): boolean;
|
|
49
|
-
export declare
|
|
50
|
-
transport: TransportFn;
|
|
51
|
-
baseUrl: string;
|
|
52
|
-
apiKey: string;
|
|
53
|
-
model: string;
|
|
54
|
-
wire: CountWire;
|
|
55
|
-
timeoutMs: number;
|
|
56
|
-
signatures?: TierSignatures;
|
|
57
|
-
}): Promise<TokenizerFingerprintResult>;
|
|
45
|
+
export declare const tokenizerFingerprintRule: import("./types").Rule<"tokenizer-fingerprint", "fixedText", "evidence">;
|
|
58
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"}
|
|
@@ -13,28 +13,14 @@
|
|
|
13
13
|
* Sonnet 4.6 and Haiku 4.5 share one tokenizer, so they bill the same delta on
|
|
14
14
|
* the same path, and relays count differently from each other (77, 90, 134,
|
|
15
15
|
* 160, 211 and 609 were all seen for one text on lanes echoing one model name)
|
|
16
|
-
* or invent usage outright
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* between-model differences on the same endpoint are evidence.
|
|
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.
|
|
21
20
|
*/
|
|
22
|
-
import {
|
|
21
|
+
import { defineRule } from "./types";
|
|
23
22
|
/** Empty on purpose; see the module note. Pass your own calibration. */
|
|
24
23
|
export const DEFAULT_TIER_SIGNATURES = {};
|
|
25
|
-
const intOf = (v) => typeof v === "number" && Number.isInteger(v) && v >= 0 ? v : null;
|
|
26
|
-
function readInputTokens(data, wire) {
|
|
27
|
-
const u = data?.usage;
|
|
28
|
-
if (!u || typeof u !== "object")
|
|
29
|
-
return null;
|
|
30
|
-
return wire === "anthropic"
|
|
31
|
-
? intOf(u["input_tokens"])
|
|
32
|
-
: intOf(u["prompt_tokens"]);
|
|
33
|
-
}
|
|
34
|
-
function readServedModel(data) {
|
|
35
|
-
const m = data?.model;
|
|
36
|
-
return typeof m === "string" && m.length > 0 ? m : null;
|
|
37
|
-
}
|
|
38
24
|
export function tierForDelta(delta, signatures = DEFAULT_TIER_SIGNATURES) {
|
|
39
25
|
for (const [tier, values] of Object.entries(signatures))
|
|
40
26
|
if (values.includes(delta))
|
|
@@ -42,8 +28,8 @@ export function tierForDelta(delta, signatures = DEFAULT_TIER_SIGNATURES) {
|
|
|
42
28
|
return null;
|
|
43
29
|
}
|
|
44
30
|
/**
|
|
45
|
-
* The served tier when the fingerprint names one
|
|
46
|
-
* null. `tiers` is the
|
|
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).
|
|
47
33
|
*/
|
|
48
34
|
export function judgeTokenizerFingerprint(requestedModel, result, tiers) {
|
|
49
35
|
if (result.state !== "measured" || result.matchedTier === null)
|
|
@@ -61,36 +47,8 @@ export function fingerprintDrifted(prior, current) {
|
|
|
61
47
|
current.delta !== null &&
|
|
62
48
|
current.delta !== prior);
|
|
63
49
|
}
|
|
64
|
-
|
|
65
|
-
const
|
|
66
|
-
const url = opts.wire === "anthropic"
|
|
67
|
-
? `${base}/v1/messages`
|
|
68
|
-
: `${base}/v1/chat/completions`;
|
|
69
|
-
const headers = opts.wire === "anthropic"
|
|
70
|
-
? {
|
|
71
|
-
"Content-Type": "application/json",
|
|
72
|
-
"x-api-key": opts.apiKey,
|
|
73
|
-
"anthropic-version": "2023-06-01",
|
|
74
|
-
}
|
|
75
|
-
: {
|
|
76
|
-
"Content-Type": "application/json",
|
|
77
|
-
Authorization: `Bearer ${opts.apiKey}`,
|
|
78
|
-
};
|
|
79
|
-
const ask = (prompt) => opts.transport({
|
|
80
|
-
mode: "direct",
|
|
81
|
-
url,
|
|
82
|
-
headers,
|
|
83
|
-
reqBody: {
|
|
84
|
-
model: opts.model,
|
|
85
|
-
max_tokens: COUNT_PROBE_MAX_TOKENS,
|
|
86
|
-
messages: [{ role: "user", content: prompt }],
|
|
87
|
-
},
|
|
88
|
-
timeoutMs: opts.timeoutMs,
|
|
89
|
-
});
|
|
90
|
-
const [shortRes, longRes] = await Promise.all([
|
|
91
|
-
ask(SHORT_PROMPT),
|
|
92
|
-
ask(LONG_PROMPT),
|
|
93
|
-
]);
|
|
50
|
+
async function fingerprintResult(ctx) {
|
|
51
|
+
const ft = await ctx.evidence("fixedText");
|
|
94
52
|
const empty = {
|
|
95
53
|
delta: null,
|
|
96
54
|
shortInputTokens: null,
|
|
@@ -98,21 +56,21 @@ export async function measureTokenizerFingerprint(opts) {
|
|
|
98
56
|
servedModel: null,
|
|
99
57
|
matchedTier: null,
|
|
100
58
|
};
|
|
101
|
-
for (const r of [
|
|
59
|
+
for (const r of [ft.short, ft.long])
|
|
102
60
|
if (r.status === null || r.status >= 400)
|
|
103
61
|
return {
|
|
104
62
|
state: "unmeasured",
|
|
105
63
|
...empty,
|
|
106
64
|
reason: `probe-failed:${r.status ?? "network"}`,
|
|
107
65
|
};
|
|
108
|
-
const
|
|
109
|
-
const
|
|
110
|
-
const
|
|
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;
|
|
111
69
|
if (short === null || long === null)
|
|
112
70
|
return {
|
|
113
71
|
state: "unmeasured",
|
|
114
72
|
...empty,
|
|
115
|
-
servedModel,
|
|
73
|
+
servedModel: shortMeta.detectedModel,
|
|
116
74
|
reason: "no-usage-reported",
|
|
117
75
|
};
|
|
118
76
|
const delta = long - short;
|
|
@@ -121,8 +79,32 @@ export async function measureTokenizerFingerprint(opts) {
|
|
|
121
79
|
delta,
|
|
122
80
|
shortInputTokens: short,
|
|
123
81
|
longInputTokens: long,
|
|
124
|
-
servedModel,
|
|
125
|
-
matchedTier: tierForDelta(delta,
|
|
82
|
+
servedModel: shortMeta.detectedModel,
|
|
83
|
+
matchedTier: tierForDelta(delta, ctx.checks.tokenizerFingerprint?.signatures),
|
|
126
84
|
};
|
|
127
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.tiers)
|
|
95
|
+
return null;
|
|
96
|
+
const r = await fingerprintResult(ctx);
|
|
97
|
+
const tier = judgeTokenizerFingerprint(ctx.model, r, ctx.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
|
+
});
|
|
128
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 @@
|
|
|
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"}
|