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,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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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tokenizer fingerprint: how many input tokens the endpoint bills for a fixed
|
|
3
|
+
* run of text, measured as the difference between a long and a short prompt so
|
|
4
|
+
* anything a relay injects cancels out.
|
|
5
|
+
*
|
|
6
|
+
* What it is good for: drift. The delta is deterministic per endpoint (68
|
|
7
|
+
* marketplace lanes measured twice each agreed to the token on every repeat),
|
|
8
|
+
* so a change between two measurements of the same lane means the backend
|
|
9
|
+
* changed, whatever the reply now calls itself. Keep the prior per lane and
|
|
10
|
+
* compare with `fingerprintDrifted`.
|
|
11
|
+
*
|
|
12
|
+
* What it is NOT good for: naming the tier from the number alone. Opus 4.6,
|
|
13
|
+
* Sonnet 4.6 and Haiku 4.5 share one tokenizer, so they bill the same delta on
|
|
14
|
+
* the same path, and relays count differently from each other (77, 90, 134,
|
|
15
|
+
* 160, 211 and 609 were all seen for one text on lanes echoing one model name)
|
|
16
|
+
* or invent usage outright. A signature table therefore ships EMPTY: only a
|
|
17
|
+
* caller who has calibrated a table on a specific endpoint family should pass
|
|
18
|
+
* one, and even then only between-model differences on the same endpoint are
|
|
19
|
+
* evidence.
|
|
20
|
+
*/
|
|
21
|
+
export type TierSignatures = Readonly<Record<string, readonly number[]>>;
|
|
22
|
+
/** Empty on purpose; see the module note. Pass your own calibration. */
|
|
23
|
+
export declare const DEFAULT_TIER_SIGNATURES: TierSignatures;
|
|
24
|
+
export type TokenizerFingerprintState = "measured" | "unmeasured";
|
|
25
|
+
export type TokenizerFingerprintResult = {
|
|
26
|
+
state: TokenizerFingerprintState;
|
|
27
|
+
/** long minus short input tokens; the fingerprint itself. */
|
|
28
|
+
delta: number | null;
|
|
29
|
+
shortInputTokens: number | null;
|
|
30
|
+
longInputTokens: number | null;
|
|
31
|
+
/** `model` echoed on the short reply, when the endpoint reported one. */
|
|
32
|
+
servedModel: string | null;
|
|
33
|
+
/** Tier whose signature the delta matched, when any did. */
|
|
34
|
+
matchedTier: string | null;
|
|
35
|
+
reason?: string;
|
|
36
|
+
};
|
|
37
|
+
export declare function tierForDelta(delta: number, signatures?: TierSignatures): string | null;
|
|
38
|
+
/**
|
|
39
|
+
* The served tier when the fingerprint names one other than requested, or
|
|
40
|
+
* null. `tiers` is the wire's tier vocabulary (opus, sonnet, haiku, fable).
|
|
41
|
+
*/
|
|
42
|
+
export declare function judgeTokenizerFingerprint(requestedModel: string, result: TokenizerFingerprintResult, tiers: readonly string[]): string | null;
|
|
43
|
+
/** True when two measurements of the same lane disagree: the backend changed. */
|
|
44
|
+
export declare function fingerprintDrifted(prior: number | null | undefined, current: TokenizerFingerprintResult): boolean;
|
|
45
|
+
export declare const tokenizerFingerprintRule: import("./types").Rule<"tokenizer-fingerprint", "fixedText", "evidence">;
|
|
46
|
+
//# sourceMappingURL=tokenizer-fingerprint.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokenizer-fingerprint.d.ts","sourceRoot":"","sources":["../../src/rules/tokenizer-fingerprint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH,MAAM,MAAM,cAAc,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,CAAC;AAEzE,wEAAwE;AACxE,eAAO,MAAM,uBAAuB,EAAE,cAAmB,CAAC;AAE1D,MAAM,MAAM,yBAAyB,GAAG,UAAU,GAAG,YAAY,CAAC;AAElE,MAAM,MAAM,0BAA0B,GAAG;IACvC,KAAK,EAAE,yBAAyB,CAAC;IACjC,6DAA6D;IAC7D,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,yEAAyE;IACzE,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,4DAA4D;IAC5D,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,wBAAgB,YAAY,CAC1B,KAAK,EAAE,MAAM,EACb,UAAU,GAAE,cAAwC,GACnD,MAAM,GAAG,IAAI,CAIf;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CACvC,cAAc,EAAE,MAAM,EACtB,MAAM,EAAE,0BAA0B,EAClC,KAAK,EAAE,SAAS,MAAM,EAAE,GACvB,MAAM,GAAG,IAAI,CAMf;AAED,iFAAiF;AACjF,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAChC,OAAO,EAAE,0BAA0B,GAClC,OAAO,CAOT;AA4CD,eAAO,MAAM,wBAAwB,0EAuBnC,CAAC"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tokenizer fingerprint: how many input tokens the endpoint bills for a fixed
|
|
3
|
+
* run of text, measured as the difference between a long and a short prompt so
|
|
4
|
+
* anything a relay injects cancels out.
|
|
5
|
+
*
|
|
6
|
+
* What it is good for: drift. The delta is deterministic per endpoint (68
|
|
7
|
+
* marketplace lanes measured twice each agreed to the token on every repeat),
|
|
8
|
+
* so a change between two measurements of the same lane means the backend
|
|
9
|
+
* changed, whatever the reply now calls itself. Keep the prior per lane and
|
|
10
|
+
* compare with `fingerprintDrifted`.
|
|
11
|
+
*
|
|
12
|
+
* What it is NOT good for: naming the tier from the number alone. Opus 4.6,
|
|
13
|
+
* Sonnet 4.6 and Haiku 4.5 share one tokenizer, so they bill the same delta on
|
|
14
|
+
* the same path, and relays count differently from each other (77, 90, 134,
|
|
15
|
+
* 160, 211 and 609 were all seen for one text on lanes echoing one model name)
|
|
16
|
+
* or invent usage outright. A signature table therefore ships EMPTY: only a
|
|
17
|
+
* caller who has calibrated a table on a specific endpoint family should pass
|
|
18
|
+
* one, and even then only between-model differences on the same endpoint are
|
|
19
|
+
* evidence.
|
|
20
|
+
*/
|
|
21
|
+
import { defineRule } from "./types";
|
|
22
|
+
/** Empty on purpose; see the module note. Pass your own calibration. */
|
|
23
|
+
export const DEFAULT_TIER_SIGNATURES = {};
|
|
24
|
+
export function tierForDelta(delta, signatures = DEFAULT_TIER_SIGNATURES) {
|
|
25
|
+
for (const [tier, values] of Object.entries(signatures))
|
|
26
|
+
if (values.includes(delta))
|
|
27
|
+
return tier;
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The served tier when the fingerprint names one other than requested, or
|
|
32
|
+
* null. `tiers` is the wire's tier vocabulary (opus, sonnet, haiku, fable).
|
|
33
|
+
*/
|
|
34
|
+
export function judgeTokenizerFingerprint(requestedModel, result, tiers) {
|
|
35
|
+
if (result.state !== "measured" || result.matchedTier === null)
|
|
36
|
+
return null;
|
|
37
|
+
const req = requestedModel.toLowerCase();
|
|
38
|
+
const reqTier = tiers.filter((t) => req.includes(t));
|
|
39
|
+
if (reqTier.length !== 1)
|
|
40
|
+
return null;
|
|
41
|
+
return reqTier[0] === result.matchedTier ? null : result.matchedTier;
|
|
42
|
+
}
|
|
43
|
+
/** True when two measurements of the same lane disagree: the backend changed. */
|
|
44
|
+
export function fingerprintDrifted(prior, current) {
|
|
45
|
+
return (typeof prior === "number" &&
|
|
46
|
+
current.state === "measured" &&
|
|
47
|
+
current.delta !== null &&
|
|
48
|
+
current.delta !== prior);
|
|
49
|
+
}
|
|
50
|
+
async function fingerprintResult(ctx) {
|
|
51
|
+
const ft = await ctx.evidence("fixedText");
|
|
52
|
+
const empty = {
|
|
53
|
+
delta: null,
|
|
54
|
+
shortInputTokens: null,
|
|
55
|
+
longInputTokens: null,
|
|
56
|
+
servedModel: null,
|
|
57
|
+
matchedTier: null,
|
|
58
|
+
};
|
|
59
|
+
for (const r of [ft.short, ft.long])
|
|
60
|
+
if (r.status === null || r.status >= 400)
|
|
61
|
+
return {
|
|
62
|
+
state: "unmeasured",
|
|
63
|
+
...empty,
|
|
64
|
+
reason: `probe-failed:${r.status ?? "network"}`,
|
|
65
|
+
};
|
|
66
|
+
const shortMeta = ctx.wire.read.meta(ft.short.data);
|
|
67
|
+
const short = shortMeta.usage?.prompt ?? null;
|
|
68
|
+
const long = ctx.wire.read.meta(ft.long.data).usage?.prompt ?? null;
|
|
69
|
+
if (short === null || long === null)
|
|
70
|
+
return {
|
|
71
|
+
state: "unmeasured",
|
|
72
|
+
...empty,
|
|
73
|
+
servedModel: shortMeta.detectedModel,
|
|
74
|
+
reason: "no-usage-reported",
|
|
75
|
+
};
|
|
76
|
+
const delta = long - short;
|
|
77
|
+
return {
|
|
78
|
+
state: "measured",
|
|
79
|
+
delta,
|
|
80
|
+
shortInputTokens: short,
|
|
81
|
+
longInputTokens: long,
|
|
82
|
+
servedModel: shortMeta.detectedModel,
|
|
83
|
+
matchedTier: tierForDelta(delta, ctx.checks.tokenizerFingerprint?.signatures),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
export const tokenizerFingerprintRule = defineRule({
|
|
87
|
+
id: "tokenizer-fingerprint",
|
|
88
|
+
layer: "evidence",
|
|
89
|
+
needs: ["fixedText"],
|
|
90
|
+
check: "tokenizerFingerprint",
|
|
91
|
+
// Claude only, over either wire that carries its usage as is.
|
|
92
|
+
applies: (ctx) => ctx.facts.vendor === "anthropic" && ctx.wire.id !== "gemini",
|
|
93
|
+
judge: async (ctx) => {
|
|
94
|
+
if (!ctx.wire.tiers)
|
|
95
|
+
return null;
|
|
96
|
+
const r = await fingerprintResult(ctx);
|
|
97
|
+
const tier = judgeTokenizerFingerprint(ctx.model, r, ctx.wire.tiers);
|
|
98
|
+
return tier
|
|
99
|
+
? {
|
|
100
|
+
severity: "fail",
|
|
101
|
+
reason: `tokenizer-fingerprint: delta ${r.delta} is the ${tier} signature, requested ${ctx.model}`,
|
|
102
|
+
data: r,
|
|
103
|
+
}
|
|
104
|
+
: null;
|
|
105
|
+
},
|
|
106
|
+
report: async (ctx) => ({
|
|
107
|
+
tokenizerFingerprint: await fingerprintResult(ctx),
|
|
108
|
+
}),
|
|
109
|
+
});
|
|
110
|
+
//# sourceMappingURL=tokenizer-fingerprint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokenizer-fingerprint.js","sourceRoot":"","sources":["../../src/rules/tokenizer-fingerprint.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { ResolvedChecks, RunCtx } from "../engine/context";
|
|
2
|
+
import type { EvidenceBag, EvidenceKey } from "../engine/evidence";
|
|
3
|
+
import type { ResponseMetadata } from "./response-metadata";
|
|
4
|
+
import type { SignatureResult } from "./thinking-signature";
|
|
5
|
+
import type { ThinkingFloorResult } from "./thinking-floor";
|
|
6
|
+
import type { ThroughputSample } from "./throughput";
|
|
7
|
+
import type { TokenTruthResult } from "./token-truth";
|
|
8
|
+
import type { TokenizerFingerprintResult } from "./tokenizer-fingerprint";
|
|
9
|
+
/**
|
|
10
|
+
* How a finding weighs on the verdict. `fail` and `suspect` both make the
|
|
11
|
+
* endpoint suspicious (kept apart so a consumer policy can demote one),
|
|
12
|
+
* `inconclusive` leaves it unverified, `note` never touches the verdict.
|
|
13
|
+
*/
|
|
14
|
+
export type Severity = "fail" | "suspect" | "inconclusive" | "note";
|
|
15
|
+
/**
|
|
16
|
+
* Which findings outrank which: `evidence` rules read hard facts the probes
|
|
17
|
+
* cannot see and beat everything; `probe` rules read the behavioural probes and
|
|
18
|
+
* decide `versionUnverifiable`; `note` rules only report.
|
|
19
|
+
*/
|
|
20
|
+
export type RuleLayer = "evidence" | "probe" | "note";
|
|
21
|
+
/** What a rule returns; the engine stamps the rule id and layer on. */
|
|
22
|
+
export type RuleVerdict = {
|
|
23
|
+
severity: Severity;
|
|
24
|
+
reason: string;
|
|
25
|
+
data?: unknown;
|
|
26
|
+
};
|
|
27
|
+
export type Finding<Id extends string = string> = RuleVerdict & {
|
|
28
|
+
rule: Id;
|
|
29
|
+
layer: RuleLayer;
|
|
30
|
+
};
|
|
31
|
+
/** Per-rule artefacts copied onto the result, present only when the rule ran. */
|
|
32
|
+
export type Reports = {
|
|
33
|
+
signature?: SignatureResult;
|
|
34
|
+
tokenTruth?: TokenTruthResult;
|
|
35
|
+
thinkingFloor?: ThinkingFloorResult;
|
|
36
|
+
tokenizerFingerprint?: TokenizerFingerprintResult;
|
|
37
|
+
responseMetadata?: ResponseMetadata;
|
|
38
|
+
throughput?: ThroughputSample | null;
|
|
39
|
+
};
|
|
40
|
+
export type CheckKey = keyof ResolvedChecks;
|
|
41
|
+
/** A rule sees the run and only the evidence it declared in `needs`. */
|
|
42
|
+
export type RuleCtx<N extends EvidenceKey = EvidenceKey> = RunCtx & {
|
|
43
|
+
evidence<K extends N>(key: K): Promise<EvidenceBag[K]>;
|
|
44
|
+
};
|
|
45
|
+
export type Rule<Id extends string = string, N extends EvidenceKey = EvidenceKey, L extends RuleLayer = RuleLayer> = {
|
|
46
|
+
id: Id;
|
|
47
|
+
layer: L;
|
|
48
|
+
needs: readonly N[];
|
|
49
|
+
/** Opt-in switch in `checks` that enables this rule under `verify`. */
|
|
50
|
+
check?: CheckKey;
|
|
51
|
+
/** Whether the rule has anything to say on this wire and model. */
|
|
52
|
+
applies(ctx: RunCtx): boolean;
|
|
53
|
+
judge(ctx: RuleCtx<N>): Promise<RuleVerdict | null>;
|
|
54
|
+
report?(ctx: RuleCtx<N>): Promise<Partial<Reports>>;
|
|
55
|
+
};
|
|
56
|
+
export declare const defineRule: <const Id extends string, const N extends EvidenceKey, const L extends RuleLayer>(rule: Rule<Id, N, L>) => Rule<Id, N, L>;
|
|
57
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/rules/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
package/dist/transport.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
export type TransportMode = "direct" | "server";
|
|
2
2
|
export type TransportResult = {
|
|
3
3
|
status: number | null;
|
|
4
4
|
data: unknown;
|
|
@@ -13,5 +13,13 @@ export type TransportArgs = {
|
|
|
13
13
|
timeoutMs: number;
|
|
14
14
|
};
|
|
15
15
|
export type TransportFn = (args: TransportArgs) => Promise<TransportResult>;
|
|
16
|
-
|
|
16
|
+
/** Plain fetch from wherever the code runs. */
|
|
17
|
+
export declare const directTransport: TransportFn;
|
|
18
|
+
/**
|
|
19
|
+
* From a browser through the caller's own backend, which forwards
|
|
20
|
+
* `{ url, headers, reqBody }` and answers `{ data: { status, data } }`.
|
|
21
|
+
*/
|
|
22
|
+
export declare function browserTransport(opts: {
|
|
23
|
+
serverProxyUrl: string;
|
|
24
|
+
}): TransportFn;
|
|
17
25
|
//# sourceMappingURL=transport.d.ts.map
|
package/dist/transport.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEhD,MAAM,MAAM,eAAe,GAAG;IAC5B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,WAAW,EAAE,OAAO,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,aAAa,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG,CAAC,IAAI,EAAE,aAAa,KAAK,OAAO,CAAC,eAAe,CAAC,CAAC;AAsB5E,+CAA+C;AAC/C,eAAO,MAAM,eAAe,EAAE,WAuB7B,CAAC;AAEF;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE;IACrC,cAAc,EAAE,MAAM,CAAC;CACxB,GAAG,WAAW,CAqCd"}
|