theorum 1.0.0 → 1.1.3
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 +81 -25
- package/esm/mod.d.ts +48 -29
- package/esm/mod.js +37 -24
- package/esm/src/cli/commands/bench.js +13 -16
- package/esm/src/cli/commands/fuzz-canary.js +25 -29
- package/esm/src/cli/commands/guardrails-eval.d.ts +14 -0
- package/esm/src/cli/commands/guardrails-eval.js +15 -0
- package/esm/src/cli/commands/profile.js +12 -3
- package/esm/src/cli/commands/run.js +8 -9
- package/esm/src/cli/commands/test.js +8 -8
- package/esm/src/cli/event-log.d.ts +1 -1
- package/esm/src/cli/index.js +12 -0
- package/esm/src/cli/matrix/synthesizer.d.ts +6 -6
- package/esm/src/cli/matrix/synthesizer.js +23 -22
- package/esm/src/guardrails/canary-gate.d.ts +2 -1
- package/esm/src/guardrails/canary-gate.js +2 -1
- package/esm/src/guardrails/canary.d.ts +9 -1
- package/esm/src/guardrails/canary.js +21 -10
- package/esm/src/guardrails/corpus/canary-egress-attacks.d.ts +1 -0
- package/esm/src/guardrails/corpus/fuzz-inbound.js +12 -15
- package/esm/src/guardrails/corpus/inbound-payloads.js +1 -0
- package/esm/src/guardrails/corpus/live-attacks.js +0 -1
- package/esm/src/guardrails/corpus/mod.d.ts +1 -0
- package/esm/src/guardrails/corpus/secrets.d.ts +1 -0
- package/esm/src/guardrails/corpus/secrets.js +1 -0
- package/esm/src/guardrails/corpus/strings.d.ts +1 -0
- package/esm/src/guardrails/corpus/strings.js +1 -0
- package/esm/src/guardrails/corpus/types.d.ts +1 -0
- package/esm/src/guardrails/egress.d.ts +26 -3
- package/esm/src/guardrails/egress.js +69 -18
- package/esm/src/guardrails/error.d.ts +14 -23
- package/esm/src/guardrails/error.js +86 -77
- package/esm/src/guardrails/eval/corpus.d.ts +108 -0
- package/esm/src/guardrails/eval/corpus.js +978 -0
- package/esm/src/guardrails/eval/mod.d.ts +51 -0
- package/esm/src/guardrails/eval/mod.js +133 -0
- package/esm/src/guardrails/eval/score.d.ts +66 -0
- package/esm/src/guardrails/eval/score.js +114 -0
- package/esm/src/guardrails/events.d.ts +25 -0
- package/esm/src/guardrails/events.js +56 -0
- package/esm/src/guardrails/hits.d.ts +24 -0
- package/esm/src/guardrails/hits.js +45 -0
- package/esm/src/guardrails/lexicon.d.ts +39 -0
- package/esm/src/guardrails/lexicon.js +200 -0
- package/esm/src/guardrails/live-outbound-gate.d.ts +15 -13
- package/esm/src/guardrails/live-outbound-gate.js +131 -89
- package/esm/src/guardrails/mod.d.ts +19 -4
- package/esm/src/guardrails/mod.js +12 -3
- package/esm/src/guardrails/network.d.ts +19 -0
- package/esm/src/guardrails/network.js +234 -0
- package/esm/src/guardrails/policy.d.ts +35 -0
- package/esm/src/guardrails/policy.js +50 -0
- package/esm/src/guardrails/progressive-yield.d.ts +51 -0
- package/esm/src/guardrails/progressive-yield.js +98 -0
- package/esm/src/guardrails/quota.d.ts +17 -3
- package/esm/src/guardrails/quota.js +18 -4
- package/esm/src/guardrails/sanitize.d.ts +36 -6
- package/esm/src/guardrails/sanitize.js +152 -60
- package/esm/src/guardrails/serialize.d.ts +35 -0
- package/esm/src/guardrails/serialize.js +58 -0
- package/esm/src/guardrails/testing.d.ts +7 -0
- package/esm/src/guardrails/testing.js +4 -0
- package/esm/src/guardrails/theorum-error.d.ts +12 -0
- package/esm/src/guardrails/theorum-error.js +15 -0
- package/esm/src/guardrails/tool-directives.d.ts +48 -0
- package/esm/src/guardrails/tool-directives.js +124 -0
- package/esm/src/guardrails/tool-result.d.ts +93 -0
- package/esm/src/guardrails/tool-result.js +276 -0
- package/esm/src/guardrails/types.d.ts +291 -0
- package/esm/src/guardrails/types.js +72 -0
- package/esm/src/host/client-turn.js +4 -0
- package/esm/src/host/mint-trace.d.ts +1 -1
- package/esm/src/kernel/auth/crypto.d.ts +42 -0
- package/esm/src/kernel/auth/crypto.js +106 -0
- package/esm/src/kernel/auth/mod.d.ts +11 -0
- package/esm/src/kernel/auth/mod.js +11 -0
- package/esm/src/kernel/auth/oauth.d.ts +47 -0
- package/esm/src/kernel/auth/oauth.js +278 -0
- package/esm/src/kernel/auth/types.d.ts +133 -0
- package/esm/src/kernel/auth/types.js +13 -0
- package/esm/src/kernel/engine/delta.js +43 -2
- package/esm/src/kernel/engine/live-inbound.d.ts +15 -3
- package/esm/src/kernel/engine/live-inbound.js +22 -8
- package/esm/src/kernel/engine/live-ingress.d.ts +19 -0
- package/esm/src/kernel/engine/live-ingress.js +47 -0
- package/esm/src/kernel/engine/repair.js +13 -12
- package/esm/src/kernel/engine/runner/gates.js +127 -40
- package/esm/src/kernel/engine/runner/mod.d.ts +6 -4
- package/esm/src/kernel/engine/runner/mod.js +182 -43
- package/esm/src/kernel/engine/runner/schema-validation.js +3 -3
- package/esm/src/kernel/engine/runner/stages.d.ts +39 -0
- package/esm/src/kernel/engine/runner/stages.js +89 -0
- package/esm/src/kernel/engine/runner/state.d.ts +24 -0
- package/esm/src/kernel/engine/runner/steps.js +128 -61
- package/esm/src/kernel/engine/runner/stream.d.ts +9 -2
- package/esm/src/kernel/engine/runner/stream.js +114 -55
- package/esm/src/kernel/engine/session/mod.d.ts +3 -1
- package/esm/src/kernel/engine/session/mod.js +386 -34
- package/esm/src/kernel/interaction-parts.d.ts +14 -0
- package/esm/src/kernel/interaction-parts.js +23 -0
- package/esm/src/kernel/mod.d.ts +19 -7
- package/esm/src/kernel/mod.js +10 -5
- package/esm/src/kernel/profile-graph.d.ts +159 -0
- package/esm/src/kernel/profile-graph.js +156 -0
- package/esm/src/kernel/registry/attachments.d.ts +10 -10
- package/esm/src/kernel/registry/attachments.js +29 -26
- package/esm/src/kernel/registry/catalog.d.ts +25 -11
- package/esm/src/kernel/registry/catalog.js +59 -24
- package/esm/src/kernel/registry/ingress.js +34 -22
- package/esm/src/kernel/registry/profile-outputs.d.ts +4 -0
- package/esm/src/kernel/registry/profile-outputs.js +8 -0
- package/esm/src/kernel/registry/profiles.d.ts +34 -14
- package/esm/src/kernel/registry/profiles.js +306 -59
- package/esm/src/kernel/registry/provider-request.js +2 -0
- package/esm/src/kernel/registry/resolve.d.ts +7 -6
- package/esm/src/kernel/registry/resolve.js +102 -117
- package/esm/src/kernel/registry/schemas.js +1 -1
- package/esm/src/kernel/registry/sole-model.d.ts +8 -0
- package/esm/src/kernel/registry/sole-model.js +10 -0
- package/esm/src/kernel/registry/system-prompt.d.ts +10 -0
- package/esm/src/kernel/registry/system-prompt.js +40 -0
- package/esm/src/kernel/registry/system-role.d.ts +8 -0
- package/esm/src/kernel/registry/system-role.js +14 -0
- package/esm/src/kernel/registry/vault.d.ts +2 -2
- package/esm/src/kernel/registry/vault.js +4 -4
- package/esm/src/kernel/schema.d.ts +97 -11
- package/esm/src/kernel/schema.js +256 -111
- package/esm/src/kernel/stages.d.ts +175 -0
- package/esm/src/kernel/stages.js +476 -0
- package/esm/src/kernel/stop.d.ts +65 -11
- package/esm/src/kernel/stop.js +46 -11
- package/esm/src/kernel/tools/events.d.ts +41 -0
- package/esm/src/kernel/tools/events.js +71 -0
- package/esm/src/kernel/tools/execute.d.ts +63 -24
- package/esm/src/kernel/tools/execute.js +475 -224
- package/esm/src/kernel/tools/harness.js +16 -14
- package/esm/src/kernel/tools/invoke.js +26 -8
- package/esm/src/kernel/tools/mod.d.ts +3 -1
- package/esm/src/kernel/tools/mod.js +2 -1
- package/esm/src/kernel/tools/permission.d.ts +15 -0
- package/esm/src/kernel/tools/permission.js +47 -0
- package/esm/src/kernel/tools/project.js +5 -3
- package/esm/src/kernel/tools/registry.js +20 -10
- package/esm/src/kernel/tools/remote.d.ts +94 -0
- package/esm/src/kernel/tools/remote.js +577 -0
- package/esm/src/kernel/tools/resolve.d.ts +17 -7
- package/esm/src/kernel/tools/resolve.js +38 -17
- package/esm/src/kernel/tools/schema.d.ts +2 -0
- package/esm/src/kernel/tools/schema.js +23 -6
- package/esm/src/kernel/tools/stage-run.d.ts +105 -0
- package/esm/src/kernel/tools/stage-run.js +155 -0
- package/esm/src/kernel/tools/types.d.ts +198 -20
- package/esm/src/kernel/types.d.ts +253 -114
- package/esm/src/kernel/util/find-last.d.ts +2 -0
- package/esm/src/kernel/util/find-last.js +10 -0
- package/esm/src/observability/destinations.d.ts +31 -0
- package/esm/src/observability/destinations.js +67 -0
- package/esm/src/observability/mod.d.ts +10 -3
- package/esm/src/observability/mod.js +6 -2
- package/esm/src/observability/policy.d.ts +27 -0
- package/esm/src/observability/policy.js +80 -0
- package/esm/src/observability/resolve-policy.d.ts +16 -0
- package/esm/src/observability/resolve-policy.js +64 -0
- package/esm/src/observability/trace-attach.d.ts +3 -1
- package/esm/src/observability/trace-attach.js +17 -10
- package/esm/src/observability/trace-record.d.ts +12 -4
- package/esm/src/observability/trace-record.js +63 -19
- package/esm/src/observability/trace-sink.d.ts +19 -0
- package/esm/src/observability/trace-sink.js +10 -0
- package/esm/src/observability/trace.d.ts +14 -11
- package/esm/src/observability/trace.js +23 -14
- package/esm/src/observability/types.d.ts +113 -0
- package/esm/src/observability/types.js +11 -0
- package/esm/src/providers/create-provider.d.ts +7 -4
- package/esm/src/providers/create-provider.js +21 -7
- package/esm/src/providers/google/interactions/framing.d.ts +8 -0
- package/esm/src/providers/google/interactions/framing.js +74 -13
- package/esm/src/providers/google/live/framing.d.ts +5 -2
- package/esm/src/providers/google/live/framing.js +49 -12
- package/esm/src/providers/google/live/openapi-schema.js +3 -2
- package/esm/src/providers/google/live/stream.d.ts +9 -0
- package/esm/src/providers/google/live/stream.js +16 -3
- package/esm/src/providers/openrouter/cache-control.d.ts +24 -0
- package/esm/src/providers/openrouter/cache-control.js +23 -0
- package/esm/src/providers/openrouter/chat.d.ts +21 -1
- package/esm/src/providers/openrouter/chat.js +51 -6
- package/esm/src/providers/openrouter/image.js +8 -19
- package/esm/src/providers/openrouter/openai/chat-payload.js +23 -1
- package/esm/src/providers/openrouter/openai/compat.d.ts +7 -1
- package/esm/src/providers/openrouter/openai/compat.js +23 -4
- package/esm/src/providers/openrouter/openai/image-payload.js +8 -2
- package/esm/src/providers/openrouter/openai/sdk-messages.d.ts +1 -1
- package/esm/src/providers/openrouter/openai/sdk-messages.js +32 -5
- package/package.json +1 -1
- package/esm/src/interface/blocks.d.ts +0 -20
- package/esm/src/interface/blocks.js +0 -180
- package/esm/src/interface/from-profile.d.ts +0 -14
- package/esm/src/interface/from-profile.js +0 -104
- package/esm/src/interface/inputs.d.ts +0 -8
- package/esm/src/interface/inputs.js +0 -127
- package/esm/src/interface/mod.d.ts +0 -10
- package/esm/src/interface/mod.js +0 -9
- package/esm/src/interface/types.d.ts +0 -153
- package/esm/src/interface/types.js +0 -13
|
@@ -4,33 +4,84 @@
|
|
|
4
4
|
* @module
|
|
5
5
|
*/
|
|
6
6
|
import { scanTextForCanaryLeak } from './canary.js';
|
|
7
|
+
import { hitFromSpan } from './hits.js';
|
|
7
8
|
import { injectionSpans } from './injection.js';
|
|
8
9
|
import { sensitiveSpans } from './sensitive.js';
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
import { textForScan } from './serialize.js';
|
|
11
|
+
const SYSTEM_BOUNDARY = /This turn's canary is|<\/?user_data>/i; // lexicon-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
|
|
12
|
+
/** Rule ids emitted by the bundled outbound policy. */
|
|
13
|
+
export const EGRESS_RULES = {
|
|
14
|
+
canary: 'egress.canary-leak',
|
|
15
|
+
sensitive: 'egress.sensitive-echo',
|
|
16
|
+
boundary: 'egress.system-boundary',
|
|
17
|
+
injection: 'egress.injection-echo', // lexicon-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
|
|
18
|
+
/** Payload could not be rendered for inspection — released output is unverified. */
|
|
19
|
+
unscannable: 'egress.unscannable', // lexicon-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
|
|
20
|
+
/** The host policy threw instead of returning a verdict. */
|
|
21
|
+
enforcerError: 'egress.enforcer-error', // lexicon-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
|
|
22
|
+
};
|
|
23
|
+
function hitsFromSpans(text, spans, rule, severity) {
|
|
24
|
+
return spans.map((span) => hitFromSpan(text, span, rule, severity));
|
|
25
|
+
}
|
|
26
|
+
/** Hits from the bundled outbound policy (canary / sensitive / boundary / injection). */
|
|
27
|
+
function collectEgressHits(text, canary) {
|
|
13
28
|
const hits = [];
|
|
14
29
|
if (canary && scanTextForCanaryLeak(text, canary)) {
|
|
15
|
-
|
|
30
|
+
// Never put the live canary token into match — placeholder only.
|
|
31
|
+
hits.push({ rule: EGRESS_RULES.canary, severity: 'high', match: '[canary]' });
|
|
32
|
+
}
|
|
33
|
+
hits.push(...hitsFromSpans(text, sensitiveSpans(text), EGRESS_RULES.sensitive, 'high')); // lexicon-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
|
|
34
|
+
const boundary = SYSTEM_BOUNDARY.exec(text);
|
|
35
|
+
if (boundary && boundary.index !== undefined) {
|
|
36
|
+
hits.push(hitFromSpan(text, { start: boundary.index, end: boundary.index + boundary[0].length }, EGRESS_RULES.boundary, 'medium'));
|
|
16
37
|
}
|
|
17
|
-
|
|
18
|
-
|
|
38
|
+
hits.push(...hitsFromSpans(text, injectionSpans(text), EGRESS_RULES.injection, 'medium')); // lexicon-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
|
|
39
|
+
return hits;
|
|
40
|
+
}
|
|
41
|
+
/** Distinct rule ids in a hit list, in first-seen order — for rejection copy. */
|
|
42
|
+
function hitRules(hits) {
|
|
43
|
+
return [...new Set(hits.map((hit) => hit.rule))];
|
|
44
|
+
}
|
|
45
|
+
/** Default egress enforce — canary leak, sensitive echo, fence markers, injection echo. */
|
|
46
|
+
function standardEgressEnforce(payload, context) {
|
|
47
|
+
const hits = collectEgressHits(payload.text, context.canary);
|
|
48
|
+
if (payload.structured !== undefined) {
|
|
49
|
+
const structured = textForScan(payload.structured);
|
|
50
|
+
if (structured.unscannable) {
|
|
51
|
+
// Cannot inspect it, so cannot vouch for it. Fail closed.
|
|
52
|
+
hits.push({ rule: EGRESS_RULES.unscannable, severity: 'high' }); // lexicon-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
hits.push(...collectEgressHits(structured.text, context.canary));
|
|
56
|
+
}
|
|
19
57
|
}
|
|
20
|
-
if (
|
|
21
|
-
|
|
58
|
+
if (hits.length === 0) {
|
|
59
|
+
return { action: 'allow' };
|
|
22
60
|
}
|
|
23
|
-
|
|
24
|
-
|
|
61
|
+
return {
|
|
62
|
+
action: 'block', // lexicon-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
|
|
63
|
+
hits,
|
|
64
|
+
rejection: `Egress blocked: ${hitRules(hits).join(', ')}`, // lexicon-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Run a host policy without letting it break the turn.
|
|
69
|
+
*
|
|
70
|
+
* A policy that throws has reached no decision, so it cannot vouch for the output:
|
|
71
|
+
* the failure becomes a `block`, not a pass. The turn then follows the profile's
|
|
72
|
+
* ordinary `onBlock` handling instead of surfacing a raw host stack trace.
|
|
73
|
+
*/
|
|
74
|
+
async function runEnforcer(enforce, payload, context) {
|
|
75
|
+
try {
|
|
76
|
+
return await enforce(payload, context);
|
|
25
77
|
}
|
|
26
|
-
|
|
78
|
+
catch (err) {
|
|
79
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
27
80
|
return {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
rejectionMessage: `Egress blocked: ${hits.join(', ')}`,
|
|
81
|
+
action: 'block',
|
|
82
|
+
hits: [{ rule: EGRESS_RULES.enforcerError, severity: 'high' }],
|
|
83
|
+
rejection: `Egress policy failed to reach a decision: ${detail}`, // lexicon-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
|
|
32
84
|
};
|
|
33
85
|
}
|
|
34
|
-
return { blocked: false, text };
|
|
35
86
|
}
|
|
36
|
-
export { standardEgressEnforce };
|
|
87
|
+
export { collectEgressHits, hitRules, runEnforcer, standardEgressEnforce };
|
|
@@ -2,34 +2,25 @@
|
|
|
2
2
|
* Public-safe error mapping for THEORUM.
|
|
3
3
|
*
|
|
4
4
|
* Kernel internals may contain provider status text, tool names, or exception
|
|
5
|
-
* details. This module maps those failures to stable user-safe strings
|
|
5
|
+
* details. This module maps those failures to stable user-safe strings from the
|
|
6
|
+
* kernel lexicon (`public.*` keys) so hosts can override them via
|
|
7
|
+
* `overrideLexicon`.
|
|
6
8
|
*
|
|
7
9
|
* @module
|
|
8
10
|
*/
|
|
9
|
-
|
|
10
|
-
declare class TheorumError extends Error {
|
|
11
|
-
constructor(message?: string, options?: ErrorOptions);
|
|
12
|
-
}
|
|
11
|
+
import { TheorumError } from './theorum-error.js';
|
|
13
12
|
/** Internal marker for provider or transport failure. */
|
|
14
13
|
declare const UPSTREAM_FAILED = "upstream failed";
|
|
15
|
-
/**
|
|
16
|
-
declare const PUBLIC_GENERIC
|
|
17
|
-
|
|
18
|
-
declare const
|
|
19
|
-
|
|
20
|
-
declare const
|
|
21
|
-
|
|
22
|
-
declare const
|
|
23
|
-
|
|
24
|
-
declare const
|
|
25
|
-
/** Safe copy for oversized files. */
|
|
26
|
-
declare const PUBLIC_FILE_SIZE = "That file is too large.";
|
|
27
|
-
/** Safe copy for too many files in one turn. */
|
|
28
|
-
declare const PUBLIC_FILE_COUNT = "Too many files for one message.";
|
|
29
|
-
/** Safe copy for unsupported generated image dimensions. */
|
|
30
|
-
declare const PUBLIC_IMAGE_SIZE = "That image size isn't supported.";
|
|
31
|
-
/** Safe copy when the host aborts a turn. */
|
|
32
|
-
declare const PUBLIC_CANCELLED = "Cancelled.";
|
|
14
|
+
/** Snapshot of the registered default (ignores host overrides). Stable for tests. */
|
|
15
|
+
declare const PUBLIC_GENERIC: string;
|
|
16
|
+
declare const PUBLIC_UNAVAILABLE: string;
|
|
17
|
+
declare const PUBLIC_CANARY: string;
|
|
18
|
+
declare const PUBLIC_ACTION: string;
|
|
19
|
+
declare const PUBLIC_FILE_TYPE: string;
|
|
20
|
+
declare const PUBLIC_FILE_SIZE: string;
|
|
21
|
+
declare const PUBLIC_FILE_COUNT: string;
|
|
22
|
+
declare const PUBLIC_IMAGE_SIZE: string;
|
|
23
|
+
declare const PUBLIC_CANCELLED: string;
|
|
33
24
|
/** True when `err` is an abort (DOMException or Error named AbortError). */
|
|
34
25
|
declare function isAbortError(err: unknown): boolean;
|
|
35
26
|
/** Throw if `signal` is already aborted. */
|
|
@@ -2,37 +2,26 @@
|
|
|
2
2
|
* Public-safe error mapping for THEORUM.
|
|
3
3
|
*
|
|
4
4
|
* Kernel internals may contain provider status text, tool names, or exception
|
|
5
|
-
* details. This module maps those failures to stable user-safe strings
|
|
5
|
+
* details. This module maps those failures to stable user-safe strings from the
|
|
6
|
+
* kernel lexicon (`public.*` keys) so hosts can override them via
|
|
7
|
+
* `overrideLexicon`.
|
|
6
8
|
*
|
|
7
9
|
* @module
|
|
8
10
|
*/
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
constructor(message = '', options) {
|
|
12
|
-
super(message, options);
|
|
13
|
-
this.name = 'TheorumError';
|
|
14
|
-
}
|
|
15
|
-
}
|
|
11
|
+
import { lexiconDefault, lexiconText } from './lexicon.js';
|
|
12
|
+
import { TheorumError } from './theorum-error.js';
|
|
16
13
|
/** Internal marker for provider or transport failure. */
|
|
17
14
|
const UPSTREAM_FAILED = 'upstream failed';
|
|
18
|
-
/**
|
|
19
|
-
const PUBLIC_GENERIC = '
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
/** Safe copy for oversized files. */
|
|
29
|
-
const PUBLIC_FILE_SIZE = 'That file is too large.';
|
|
30
|
-
/** Safe copy for too many files in one turn. */
|
|
31
|
-
const PUBLIC_FILE_COUNT = 'Too many files for one message.';
|
|
32
|
-
/** Safe copy for unsupported generated image dimensions. */
|
|
33
|
-
const PUBLIC_IMAGE_SIZE = "That image size isn't supported.";
|
|
34
|
-
/** Safe copy when the host aborts a turn. */
|
|
35
|
-
const PUBLIC_CANCELLED = 'Cancelled.';
|
|
15
|
+
/** Snapshot of the registered default (ignores host overrides). Stable for tests. */
|
|
16
|
+
const PUBLIC_GENERIC = lexiconDefault('public.generic');
|
|
17
|
+
const PUBLIC_UNAVAILABLE = lexiconDefault('public.unavailable');
|
|
18
|
+
const PUBLIC_CANARY = lexiconDefault('public.canary');
|
|
19
|
+
const PUBLIC_ACTION = lexiconDefault('public.action');
|
|
20
|
+
const PUBLIC_FILE_TYPE = lexiconDefault('public.file_type');
|
|
21
|
+
const PUBLIC_FILE_SIZE = lexiconDefault('public.file_size');
|
|
22
|
+
const PUBLIC_FILE_COUNT = lexiconDefault('public.file_count');
|
|
23
|
+
const PUBLIC_IMAGE_SIZE = lexiconDefault('public.image_size');
|
|
24
|
+
const PUBLIC_CANCELLED = lexiconDefault('public.cancelled');
|
|
36
25
|
/** True when `err` is an abort (DOMException or Error named AbortError). */
|
|
37
26
|
function isAbortError(err) {
|
|
38
27
|
if (!err || typeof err !== 'object') {
|
|
@@ -50,103 +39,123 @@ function throwIfAborted(signal) {
|
|
|
50
39
|
if (isAbortError(reason)) {
|
|
51
40
|
throw reason;
|
|
52
41
|
}
|
|
53
|
-
throw new DOMException('The operation was aborted.', 'AbortError');
|
|
42
|
+
throw new DOMException('The operation was aborted.', 'AbortError'); // lexicon-exempt: DOM AbortError fingerprint
|
|
43
|
+
}
|
|
44
|
+
/** Resolve public copy at call time so `overrideLexicon` takes effect. */
|
|
45
|
+
function publicCopy(key) {
|
|
46
|
+
return lexiconText(key);
|
|
54
47
|
}
|
|
48
|
+
/** Upstream/internal message fingerprints → public lexicon keys (not emit copy). */
|
|
55
49
|
const EXACT = {
|
|
56
|
-
[UPSTREAM_FAILED]:
|
|
57
|
-
'empty Gemini stream':
|
|
58
|
-
'canary leaked':
|
|
59
|
-
'The operation was aborted.':
|
|
60
|
-
'This operation was aborted':
|
|
61
|
-
'Turn withheld: egress disclosure violation':
|
|
62
|
-
'expected JSON object': '
|
|
63
|
-
'structured output was not valid JSON': '
|
|
64
|
-
'malformed Gemini Live message':
|
|
65
|
-
'malformed Gemini Live message during setup':
|
|
66
|
-
'user input cannot be placed in the system block':
|
|
67
|
-
'attachment data must be base64':
|
|
68
|
-
'attachment is too large':
|
|
69
|
-
'attachments exceed the per-turn budget':
|
|
70
|
-
'Tool input validation failed':
|
|
71
|
-
'This profile does not accept text input':
|
|
50
|
+
[UPSTREAM_FAILED]: 'public.unavailable', // lexicon-exempt: internal marker
|
|
51
|
+
'empty Gemini stream': 'public.unavailable', // lexicon-exempt: upstream fingerprint
|
|
52
|
+
'canary leaked': 'public.canary', // lexicon-exempt: internal marker
|
|
53
|
+
'The operation was aborted.': 'public.cancelled', // lexicon-exempt: AbortError fingerprint
|
|
54
|
+
'This operation was aborted': 'public.cancelled', // lexicon-exempt: AbortError fingerprint
|
|
55
|
+
'Turn withheld: egress disclosure violation': 'public.canary', // lexicon-exempt: internal marker
|
|
56
|
+
'expected JSON object': 'public.bad_request', // lexicon-exempt: upstream fingerprint
|
|
57
|
+
'structured output was not valid JSON': 'public.bad_request', // lexicon-exempt: upstream fingerprint
|
|
58
|
+
'malformed Gemini Live message': 'public.unavailable', // lexicon-exempt: upstream fingerprint
|
|
59
|
+
'malformed Gemini Live message during setup': 'public.unavailable', // lexicon-exempt: upstream fingerprint
|
|
60
|
+
'user input cannot be placed in the system block': 'public.generic', // lexicon-exempt: internal marker
|
|
61
|
+
'attachment data must be base64': 'public.file_type', // lexicon-exempt: internal marker
|
|
62
|
+
'attachment is too large': 'public.file_size', // lexicon-exempt: internal marker
|
|
63
|
+
'attachments exceed the per-turn budget': 'public.file_size', // lexicon-exempt: internal marker
|
|
64
|
+
'Tool input validation failed': 'public.invalid_question', // lexicon-exempt: internal marker
|
|
65
|
+
'This profile does not accept text input': 'public.action', // lexicon-exempt: internal marker
|
|
72
66
|
};
|
|
73
67
|
const RULES = [
|
|
74
68
|
{
|
|
75
69
|
match: (t) => /^(Gemini|OpenRouter|TTS|OpenRouter TTS|Speech) HTTP/.test(t) ||
|
|
76
70
|
t.includes('TTS HTTP') ||
|
|
77
71
|
t.includes('Speech HTTP'),
|
|
78
|
-
resolve: () =>
|
|
72
|
+
resolve: () => publicCopy('public.unavailable'),
|
|
79
73
|
},
|
|
80
74
|
{
|
|
81
|
-
match: (t) =>
|
|
75
|
+
match: (t) =>
|
|
76
|
+
// lexicon-exempt: substring fingerprints against internal TheorumError messages
|
|
77
|
+
t.includes('not enabled on this turn') ||
|
|
82
78
|
t.includes('not allowed') ||
|
|
83
79
|
t.includes('not registered') ||
|
|
84
|
-
t.includes('Unknown model select') ||
|
|
85
|
-
t.includes('Grounding tools')
|
|
86
|
-
|
|
80
|
+
t.includes('Unknown model select') || // lexicon-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
|
|
81
|
+
t.includes('Grounding tools') ||
|
|
82
|
+
(t.includes('live.ingress.') && t.includes('is disabled')),
|
|
83
|
+
resolve: () => publicCopy('public.action'),
|
|
87
84
|
},
|
|
88
85
|
{
|
|
89
|
-
match: (t) =>
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
86
|
+
match: (t) =>
|
|
87
|
+
// lexicon-exempt: substring fingerprints against internal TheorumError messages
|
|
88
|
+
t.includes('MIME') ||
|
|
89
|
+
t.includes('does not accept attachments') || // lexicon-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
|
|
90
|
+
t.includes('does not accept voice'), // lexicon-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
|
|
91
|
+
resolve: () => publicCopy('public.file_type'),
|
|
93
92
|
},
|
|
94
93
|
{
|
|
95
94
|
match: (t) => t.startsWith('At most'),
|
|
96
|
-
resolve: () =>
|
|
95
|
+
resolve: () => publicCopy('public.file_count'),
|
|
97
96
|
},
|
|
98
97
|
{
|
|
99
|
-
match: (t) =>
|
|
100
|
-
|
|
101
|
-
|
|
98
|
+
match: (t) =>
|
|
99
|
+
// lexicon-exempt: match already-lexicon attachment copy before remapping
|
|
100
|
+
(t.startsWith('Only ') && t.includes('file')) ||
|
|
101
|
+
t.startsWith('Each file must be') || // lexicon-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
|
|
102
|
+
t.startsWith('Those files together'), // lexicon-exempt: developer contract / internal diagnostic — not end-user or model copy (P2)
|
|
102
103
|
resolve: (t) => t,
|
|
103
104
|
},
|
|
104
105
|
{
|
|
105
106
|
match: (t) => t.includes('attachment'),
|
|
106
|
-
resolve: () =>
|
|
107
|
+
resolve: () => publicCopy('public.file_size'),
|
|
107
108
|
},
|
|
108
109
|
{
|
|
110
|
+
// lexicon-exempt: substring fingerprint against internal TheorumError messages
|
|
109
111
|
match: (t) => t.includes('aspect or size'),
|
|
110
|
-
resolve: () =>
|
|
112
|
+
resolve: () => publicCopy('public.image_size'),
|
|
111
113
|
},
|
|
112
114
|
{
|
|
113
|
-
match: (t) =>
|
|
114
|
-
|
|
115
|
+
match: (t) =>
|
|
116
|
+
// lexicon-exempt: substring fingerprints against internal TheorumError messages
|
|
117
|
+
t.includes('must pin thinking') || t.includes('has no models'),
|
|
118
|
+
resolve: () => publicCopy('public.generic'),
|
|
115
119
|
},
|
|
116
120
|
];
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
121
|
+
const PUBLIC_KEYS = [
|
|
122
|
+
'public.generic',
|
|
123
|
+
'public.unavailable',
|
|
124
|
+
'public.canary',
|
|
125
|
+
'public.action',
|
|
126
|
+
'public.file_type',
|
|
127
|
+
'public.file_size',
|
|
128
|
+
'public.file_count',
|
|
129
|
+
'public.image_size',
|
|
130
|
+
'public.cancelled',
|
|
131
|
+
'public.bad_request',
|
|
132
|
+
'public.invalid_question',
|
|
133
|
+
];
|
|
134
|
+
function isAlreadyPublic(text) {
|
|
135
|
+
return PUBLIC_KEYS.some((key) => lexiconText(key) === text || lexiconDefault(key) === text);
|
|
136
|
+
}
|
|
128
137
|
function publicText(text) {
|
|
129
138
|
if (/aborted/i.test(text)) {
|
|
130
|
-
return
|
|
139
|
+
return publicCopy('public.cancelled');
|
|
131
140
|
}
|
|
132
|
-
if (
|
|
141
|
+
if (isAlreadyPublic(text)) {
|
|
133
142
|
return text;
|
|
134
143
|
}
|
|
135
144
|
const exact = EXACT[text];
|
|
136
145
|
if (exact) {
|
|
137
|
-
return exact;
|
|
146
|
+
return publicCopy(exact);
|
|
138
147
|
}
|
|
139
148
|
for (const rule of RULES) {
|
|
140
149
|
if (rule.match(text)) {
|
|
141
150
|
return rule.resolve(text);
|
|
142
151
|
}
|
|
143
152
|
}
|
|
144
|
-
return
|
|
153
|
+
return publicCopy('public.generic');
|
|
145
154
|
}
|
|
146
155
|
/** Convert an unknown thrown value or internal message to user-safe text. */
|
|
147
156
|
function publicError(err) {
|
|
148
157
|
if (isAbortError(err)) {
|
|
149
|
-
return
|
|
158
|
+
return publicCopy('public.cancelled');
|
|
150
159
|
}
|
|
151
160
|
if (typeof err === 'string') {
|
|
152
161
|
return publicText(err);
|
|
@@ -154,7 +163,7 @@ function publicError(err) {
|
|
|
154
163
|
if (err instanceof TheorumError) {
|
|
155
164
|
return publicText(err.message);
|
|
156
165
|
}
|
|
157
|
-
return
|
|
166
|
+
return publicCopy('public.unavailable');
|
|
158
167
|
}
|
|
159
168
|
/** Raw diagnostic text for hosts, traces, and logs (never shown to end users). */
|
|
160
169
|
function describeError(err) {
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Corpus acquisition for guardrail evaluation.
|
|
3
|
+
*
|
|
4
|
+
* Nothing is vendored. Corpora are fetched on demand and cached locally, so the
|
|
5
|
+
* published package carries no third-party data and no licence obligations beyond
|
|
6
|
+
* attribution here.
|
|
7
|
+
*
|
|
8
|
+
* Two sources, deliberately different in shape:
|
|
9
|
+
*
|
|
10
|
+
* - **S-Labs/prompt-injection-dataset** (MIT) — ~11k labelled prompts whose benign
|
|
11
|
+
* half deliberately includes security-adjacent questions ("explain output
|
|
12
|
+
* validation best practices", "how do I implement stress testing"). This is
|
|
13
|
+
* where user-text detectors are most likely to misfire.
|
|
14
|
+
*
|
|
15
|
+
* Chosen over `prodnull/prompt-injection-repo-dataset`, which has richer hard
|
|
16
|
+
* negatives but is gated: licence and access are separate axes, and a gated
|
|
17
|
+
* corpus cannot be fetched by an unattended run.
|
|
18
|
+
* - **AgentDojo** (MIT, ETH Zurich) — simulated environments for a tool-using
|
|
19
|
+
* agent. Its fixtures are read directly; the benchmark is never run, so no model
|
|
20
|
+
* or API key is involved. This supplies benign output in the shape a *tool*
|
|
21
|
+
* returns, which the repo dataset does not cover.
|
|
22
|
+
*
|
|
23
|
+
* The two are kept separate on purpose. Pooling sources and reporting one number
|
|
24
|
+
* hides the domain shift between them, and that shift is the thing most likely to
|
|
25
|
+
* make a detector look better than it is.
|
|
26
|
+
*
|
|
27
|
+
* @module
|
|
28
|
+
*/
|
|
29
|
+
export interface CorpusSample {
|
|
30
|
+
text: string;
|
|
31
|
+
/** True when the sample is an attack. */
|
|
32
|
+
attack: boolean;
|
|
33
|
+
/** Source dataset id, kept so results are never pooled silently. */
|
|
34
|
+
source: string;
|
|
35
|
+
/** Benign sub-category, so "security docs" never averages into "work email". */
|
|
36
|
+
category: string;
|
|
37
|
+
}
|
|
38
|
+
export interface CorpusSource {
|
|
39
|
+
id: string;
|
|
40
|
+
licence: string;
|
|
41
|
+
attribution: string;
|
|
42
|
+
/**
|
|
43
|
+
* Rows fetched by default.
|
|
44
|
+
*
|
|
45
|
+
* Several of these corpora are far larger than a fast run wants. The cap is
|
|
46
|
+
* declared rather than buried in the loader so a report can say what fraction
|
|
47
|
+
* was actually sampled — a rate over 1% of a corpus is not a rate over the
|
|
48
|
+
* corpus.
|
|
49
|
+
*/
|
|
50
|
+
sampleLimit: number;
|
|
51
|
+
/** Rows available upstream, when known, so sampling is visible in the report. */
|
|
52
|
+
upstreamRows?: number;
|
|
53
|
+
/** Set when the dataset is gated and needs `HF_TOKEN` to fetch. */
|
|
54
|
+
requiresToken?: boolean;
|
|
55
|
+
load: (cache: CorpusCache, limit: number) => Promise<CorpusSample[]>;
|
|
56
|
+
}
|
|
57
|
+
/** Local cache directory for fetched corpora; gitignored, never published. */
|
|
58
|
+
export interface CorpusCache {
|
|
59
|
+
dir: string;
|
|
60
|
+
fetchText: (url: string, key: string) => Promise<string>;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Page through a dataset via the rows API.
|
|
64
|
+
*
|
|
65
|
+
* Pages are fetched in batches rather than one at a time: a full corpus here runs
|
|
66
|
+
* to thousands of pages, and a serial walk is slow enough that it pressures whoever
|
|
67
|
+
* runs it into sampling a slice and quoting the result as if it covered the whole.
|
|
68
|
+
*/
|
|
69
|
+
/** Paging options. An object rather than more positionals, which had reached five. */
|
|
70
|
+
export interface FetchRowsOptions {
|
|
71
|
+
split?: string;
|
|
72
|
+
config?: string;
|
|
73
|
+
/** Base retry delay. Seconds in production; tests pass milliseconds. */
|
|
74
|
+
retryBaseMs?: number;
|
|
75
|
+
}
|
|
76
|
+
declare function fetchRows(cache: CorpusCache, dataset: string, limit: number, options?: FetchRowsOptions): Promise<Record<string, unknown>[]>;
|
|
77
|
+
/** Create a cache that reads from disk when present and fetches when not. */
|
|
78
|
+
declare function createCorpusCache(dir: string): CorpusCache;
|
|
79
|
+
/**
|
|
80
|
+
* Parse a two-column `text,label` CSV.
|
|
81
|
+
*
|
|
82
|
+
* Hand-rolled because the corpus text is adversarial by construction: it contains
|
|
83
|
+
* quotes, commas, and embedded newlines, and a naive split would shred exactly the
|
|
84
|
+
* samples that matter most.
|
|
85
|
+
*/
|
|
86
|
+
declare function parseLabelledCsv(input: string): {
|
|
87
|
+
text: string;
|
|
88
|
+
label: number;
|
|
89
|
+
}[];
|
|
90
|
+
declare function recordsFromYaml(yaml: string): string[];
|
|
91
|
+
/**
|
|
92
|
+
* Corpora evaluated and deliberately not enabled, with the reason.
|
|
93
|
+
*
|
|
94
|
+
* Kept in the repo so the search does not have to be repeated, and so a later
|
|
95
|
+
* decision to include one starts from the objection rather than from scratch.
|
|
96
|
+
*/
|
|
97
|
+
/**
|
|
98
|
+
* Corpora evaluated and deliberately left out of SOURCES, with the objection.
|
|
99
|
+
* See docs/contracts/guardrails.md.
|
|
100
|
+
*/
|
|
101
|
+
export declare const REVIEWED_SOURCES: readonly {
|
|
102
|
+
dataset: string;
|
|
103
|
+
rows: number;
|
|
104
|
+
licence: string;
|
|
105
|
+
verdict: string;
|
|
106
|
+
}[];
|
|
107
|
+
declare const SOURCES: readonly CorpusSource[];
|
|
108
|
+
export { createCorpusCache, fetchRows, parseLabelledCsv, recordsFromYaml, SOURCES };
|