theorum 0.1.15 → 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.
Files changed (305) hide show
  1. package/README.md +241 -98
  2. package/esm/mod.d.ts +57 -28
  3. package/esm/mod.js +43 -23
  4. package/esm/src/cli/commands/bench.js +18 -18
  5. package/esm/src/cli/commands/fuzz-canary.d.ts +13 -0
  6. package/esm/src/cli/commands/fuzz-canary.js +191 -0
  7. package/esm/src/cli/commands/fuzz-guardrails.d.ts +3 -5
  8. package/esm/src/cli/commands/fuzz-guardrails.js +4 -581
  9. package/esm/src/cli/commands/guardrails-eval.d.ts +14 -0
  10. package/esm/src/cli/commands/guardrails-eval.js +15 -0
  11. package/esm/src/cli/commands/profile.js +35 -15
  12. package/esm/src/cli/commands/run.d.ts +3 -0
  13. package/esm/src/cli/commands/run.js +23 -32
  14. package/esm/src/cli/commands/test.d.ts +10 -1
  15. package/esm/src/cli/commands/test.js +34 -34
  16. package/esm/src/cli/event-log.d.ts +19 -0
  17. package/esm/src/cli/event-log.js +147 -0
  18. package/esm/src/cli/index.js +57 -11
  19. package/esm/src/cli/matrix/synthesizer.d.ts +10 -12
  20. package/esm/src/cli/matrix/synthesizer.js +45 -118
  21. package/esm/src/guardrails/canary-gate.d.ts +21 -0
  22. package/esm/src/guardrails/canary-gate.js +32 -0
  23. package/esm/src/guardrails/canary.d.ts +34 -0
  24. package/esm/src/guardrails/canary.js +150 -0
  25. package/esm/src/guardrails/corpus/canary-egress-attacks.d.ts +17 -0
  26. package/esm/src/guardrails/corpus/canary-egress-attacks.js +151 -0
  27. package/esm/src/guardrails/corpus/fuzz-inbound.d.ts +11 -0
  28. package/esm/src/guardrails/corpus/fuzz-inbound.js +213 -0
  29. package/esm/src/guardrails/corpus/inbound-payloads.d.ts +10 -0
  30. package/esm/src/guardrails/corpus/inbound-payloads.js +125 -0
  31. package/esm/src/guardrails/corpus/live-attacks.d.ts +20 -0
  32. package/esm/src/guardrails/corpus/live-attacks.js +231 -0
  33. package/esm/src/guardrails/corpus/mod.d.ts +14 -0
  34. package/esm/src/guardrails/corpus/mod.js +11 -0
  35. package/esm/src/guardrails/corpus/secrets.d.ts +17 -0
  36. package/esm/src/guardrails/corpus/secrets.js +17 -0
  37. package/esm/src/guardrails/corpus/strings.d.ts +28 -0
  38. package/esm/src/guardrails/corpus/strings.js +34 -0
  39. package/esm/src/guardrails/corpus/types.d.ts +38 -0
  40. package/esm/src/guardrails/corpus/types.js +6 -0
  41. package/esm/src/guardrails/egress.d.ts +32 -0
  42. package/esm/src/guardrails/egress.js +87 -0
  43. package/esm/src/guardrails/error.d.ts +14 -23
  44. package/esm/src/guardrails/error.js +87 -76
  45. package/esm/src/guardrails/eval/corpus.d.ts +108 -0
  46. package/esm/src/guardrails/eval/corpus.js +978 -0
  47. package/esm/src/guardrails/eval/mod.d.ts +51 -0
  48. package/esm/src/guardrails/eval/mod.js +133 -0
  49. package/esm/src/guardrails/eval/score.d.ts +66 -0
  50. package/esm/src/guardrails/eval/score.js +114 -0
  51. package/esm/src/guardrails/events.d.ts +25 -0
  52. package/esm/src/guardrails/events.js +56 -0
  53. package/esm/src/guardrails/hits.d.ts +24 -0
  54. package/esm/src/guardrails/hits.js +45 -0
  55. package/esm/src/guardrails/injection.js +28 -5
  56. package/esm/src/guardrails/lexicon.d.ts +39 -0
  57. package/esm/src/guardrails/lexicon.js +200 -0
  58. package/esm/src/guardrails/live-outbound-gate.d.ts +41 -0
  59. package/esm/src/guardrails/live-outbound-gate.js +222 -0
  60. package/esm/src/guardrails/mod.d.ts +30 -6
  61. package/esm/src/guardrails/mod.js +20 -5
  62. package/esm/src/guardrails/network.d.ts +19 -0
  63. package/esm/src/guardrails/network.js +234 -0
  64. package/esm/src/guardrails/policy.d.ts +35 -0
  65. package/esm/src/guardrails/policy.js +50 -0
  66. package/esm/src/guardrails/progressive-yield.d.ts +51 -0
  67. package/esm/src/guardrails/progressive-yield.js +98 -0
  68. package/esm/src/guardrails/quota.d.ts +17 -3
  69. package/esm/src/guardrails/quota.js +18 -4
  70. package/esm/src/guardrails/sanitize.d.ts +45 -19
  71. package/esm/src/guardrails/sanitize.js +177 -94
  72. package/esm/src/guardrails/sensitive.js +2 -1
  73. package/esm/src/guardrails/serialize.d.ts +35 -0
  74. package/esm/src/guardrails/serialize.js +58 -0
  75. package/esm/src/guardrails/testing.d.ts +17 -0
  76. package/esm/src/guardrails/testing.js +13 -0
  77. package/esm/src/guardrails/theorum-error.d.ts +12 -0
  78. package/esm/src/guardrails/theorum-error.js +15 -0
  79. package/esm/src/guardrails/tool-directives.d.ts +48 -0
  80. package/esm/src/guardrails/tool-directives.js +124 -0
  81. package/esm/src/guardrails/tool-result.d.ts +93 -0
  82. package/esm/src/guardrails/tool-result.js +276 -0
  83. package/esm/src/guardrails/types.d.ts +291 -0
  84. package/esm/src/guardrails/types.js +72 -0
  85. package/esm/src/host/client-turn.d.ts +19 -0
  86. package/esm/src/host/client-turn.js +36 -0
  87. package/esm/src/host/mint-trace.d.ts +1 -1
  88. package/esm/src/host/mod.d.ts +5 -3
  89. package/esm/src/host/mod.js +4 -3
  90. package/esm/src/kernel/auth/crypto.d.ts +42 -0
  91. package/esm/src/kernel/auth/crypto.js +106 -0
  92. package/esm/src/kernel/auth/mod.d.ts +11 -0
  93. package/esm/src/kernel/auth/mod.js +11 -0
  94. package/esm/src/kernel/auth/oauth.d.ts +47 -0
  95. package/esm/src/kernel/auth/oauth.js +278 -0
  96. package/esm/src/kernel/auth/types.d.ts +133 -0
  97. package/esm/src/kernel/auth/types.js +13 -0
  98. package/esm/src/kernel/engine/delta.d.ts +24 -2
  99. package/esm/src/kernel/engine/delta.js +478 -39
  100. package/esm/src/kernel/engine/live-inbound.d.ts +21 -0
  101. package/esm/src/kernel/engine/live-inbound.js +31 -0
  102. package/esm/src/kernel/engine/live-ingress.d.ts +19 -0
  103. package/esm/src/kernel/engine/live-ingress.js +47 -0
  104. package/esm/src/kernel/engine/repair.js +13 -12
  105. package/esm/src/kernel/engine/runner/gates.d.ts +1 -1
  106. package/esm/src/kernel/engine/runner/gates.js +130 -43
  107. package/esm/src/kernel/engine/runner/mod.d.ts +6 -4
  108. package/esm/src/kernel/engine/runner/mod.js +192 -53
  109. package/esm/src/kernel/engine/runner/schema-validation.js +3 -3
  110. package/esm/src/kernel/engine/runner/stages.d.ts +39 -0
  111. package/esm/src/kernel/engine/runner/stages.js +89 -0
  112. package/esm/src/kernel/engine/runner/state.d.ts +31 -0
  113. package/esm/src/kernel/engine/runner/steps.d.ts +1 -1
  114. package/esm/src/kernel/engine/runner/steps.js +244 -43
  115. package/esm/src/kernel/engine/runner/stream.d.ts +9 -3
  116. package/esm/src/kernel/engine/runner/stream.js +140 -44
  117. package/esm/src/kernel/engine/session/mod.d.ts +25 -0
  118. package/esm/src/kernel/engine/session/mod.js +557 -0
  119. package/esm/src/kernel/interaction-parts.d.ts +14 -0
  120. package/esm/src/kernel/interaction-parts.js +23 -0
  121. package/esm/src/kernel/mod.d.ts +21 -10
  122. package/esm/src/kernel/mod.js +11 -8
  123. package/esm/src/kernel/profile-graph.d.ts +159 -0
  124. package/esm/src/kernel/profile-graph.js +156 -0
  125. package/esm/src/kernel/registry/attachments.d.ts +12 -10
  126. package/esm/src/kernel/registry/attachments.js +33 -27
  127. package/esm/src/kernel/registry/catalog.d.ts +25 -24
  128. package/esm/src/kernel/registry/catalog.js +60 -101
  129. package/esm/src/kernel/registry/ingress.d.ts +9 -4
  130. package/esm/src/kernel/registry/ingress.js +97 -75
  131. package/esm/src/kernel/registry/profile-outputs.d.ts +4 -0
  132. package/esm/src/kernel/registry/profile-outputs.js +8 -0
  133. package/esm/src/kernel/registry/profiles.d.ts +55 -12
  134. package/esm/src/kernel/registry/profiles.js +413 -73
  135. package/esm/src/kernel/registry/provider-request.js +13 -7
  136. package/esm/src/kernel/registry/resolve.d.ts +8 -8
  137. package/esm/src/kernel/registry/resolve.js +169 -154
  138. package/esm/src/kernel/registry/schemas.js +1 -1
  139. package/esm/src/kernel/registry/sole-model.d.ts +8 -0
  140. package/esm/src/kernel/registry/sole-model.js +10 -0
  141. package/esm/src/kernel/registry/system-prompt.d.ts +10 -0
  142. package/esm/src/kernel/registry/system-prompt.js +40 -0
  143. package/esm/src/kernel/registry/system-role.d.ts +8 -0
  144. package/esm/src/kernel/registry/system-role.js +14 -0
  145. package/esm/src/kernel/registry/vault.d.ts +12 -7
  146. package/esm/src/kernel/registry/vault.js +32 -10
  147. package/esm/src/kernel/schema.d.ts +231 -0
  148. package/esm/src/kernel/schema.js +607 -0
  149. package/esm/src/kernel/stages.d.ts +175 -0
  150. package/esm/src/kernel/stages.js +476 -0
  151. package/esm/src/kernel/stop.d.ts +78 -19
  152. package/esm/src/kernel/stop.js +51 -16
  153. package/esm/src/kernel/tools/events.d.ts +41 -0
  154. package/esm/src/kernel/tools/events.js +71 -0
  155. package/esm/src/kernel/tools/execute.d.ts +84 -0
  156. package/esm/src/kernel/tools/execute.js +614 -0
  157. package/esm/src/kernel/tools/harness.d.ts +8 -0
  158. package/esm/src/kernel/tools/harness.js +46 -0
  159. package/esm/src/kernel/tools/invoke.d.ts +10 -0
  160. package/esm/src/kernel/tools/invoke.js +101 -0
  161. package/esm/src/kernel/tools/mod.d.ts +13 -0
  162. package/esm/src/kernel/tools/mod.js +11 -0
  163. package/esm/src/kernel/tools/permission.d.ts +15 -0
  164. package/esm/src/kernel/tools/permission.js +47 -0
  165. package/esm/src/kernel/tools/project.d.ts +12 -0
  166. package/esm/src/kernel/tools/project.js +36 -0
  167. package/esm/src/kernel/tools/registry.d.ts +23 -0
  168. package/esm/src/kernel/tools/registry.js +81 -0
  169. package/esm/src/kernel/tools/remote.d.ts +94 -0
  170. package/esm/src/kernel/tools/remote.js +577 -0
  171. package/esm/src/kernel/tools/resolve.d.ts +39 -0
  172. package/esm/src/kernel/tools/resolve.js +283 -0
  173. package/esm/src/kernel/tools/schema.d.ts +15 -0
  174. package/esm/src/kernel/tools/schema.js +176 -0
  175. package/esm/src/kernel/tools/stage-run.d.ts +105 -0
  176. package/esm/src/kernel/tools/stage-run.js +155 -0
  177. package/esm/src/kernel/tools/types.d.ts +394 -0
  178. package/esm/src/kernel/tools/types.js +9 -0
  179. package/esm/src/kernel/types.d.ts +540 -256
  180. package/esm/src/kernel/util/find-last.d.ts +2 -0
  181. package/esm/src/kernel/util/find-last.js +10 -0
  182. package/esm/src/observability/destinations.d.ts +31 -0
  183. package/esm/src/observability/destinations.js +67 -0
  184. package/esm/src/observability/mod.d.ts +10 -3
  185. package/esm/src/observability/mod.js +6 -2
  186. package/esm/src/observability/policy.d.ts +27 -0
  187. package/esm/src/observability/policy.js +80 -0
  188. package/esm/src/observability/resolve-policy.d.ts +16 -0
  189. package/esm/src/observability/resolve-policy.js +64 -0
  190. package/esm/src/observability/trace-attach.d.ts +8 -4
  191. package/esm/src/observability/trace-attach.js +50 -29
  192. package/esm/src/observability/trace-record.d.ts +23 -13
  193. package/esm/src/observability/trace-record.js +96 -39
  194. package/esm/src/observability/trace-sink.d.ts +19 -0
  195. package/esm/src/observability/trace-sink.js +10 -0
  196. package/esm/src/observability/trace-usage.d.ts +10 -3
  197. package/esm/src/observability/trace-usage.js +70 -17
  198. package/esm/src/observability/trace.d.ts +18 -7
  199. package/esm/src/observability/trace.js +34 -17
  200. package/esm/src/observability/types.d.ts +113 -0
  201. package/esm/src/observability/types.js +11 -0
  202. package/esm/src/presets/google/speech-voices.d.ts +11 -0
  203. package/esm/src/presets/google/speech-voices.js +41 -0
  204. package/esm/src/presets/google.d.ts +36 -24
  205. package/esm/src/presets/google.js +50 -63
  206. package/esm/src/presets/mod.d.ts +2 -2
  207. package/esm/src/presets/mod.js +1 -1
  208. package/esm/src/providers/create-provider.d.ts +20 -17
  209. package/esm/src/providers/create-provider.js +72 -26
  210. package/esm/src/providers/google/interactions/framing.d.ts +23 -0
  211. package/esm/src/providers/google/interactions/framing.js +269 -0
  212. package/esm/src/providers/google/interactions/mod.d.ts +7 -0
  213. package/esm/src/providers/google/interactions/mod.js +7 -0
  214. package/esm/src/providers/google/interactions/stream.d.ts +83 -0
  215. package/esm/src/providers/google/interactions/stream.js +588 -0
  216. package/esm/src/providers/google/keys.d.ts +26 -0
  217. package/esm/src/providers/{keys.js → google/keys.js} +19 -31
  218. package/esm/src/providers/google/live/framing.d.ts +49 -0
  219. package/esm/src/providers/google/live/framing.js +552 -0
  220. package/esm/src/providers/google/live/openapi-schema.d.ts +6 -0
  221. package/esm/src/providers/google/live/openapi-schema.js +46 -0
  222. package/esm/src/providers/google/live/session.d.ts +25 -0
  223. package/esm/src/providers/google/live/session.js +134 -0
  224. package/esm/src/providers/google/live/stream.d.ts +45 -0
  225. package/esm/src/providers/google/live/stream.js +214 -0
  226. package/esm/src/providers/google/urls.d.ts +6 -0
  227. package/esm/src/providers/google/urls.js +6 -0
  228. package/esm/src/providers/local/local.d.ts +30 -0
  229. package/esm/src/providers/{local.js → local/local.js} +66 -126
  230. package/esm/src/providers/local/mod.d.ts +9 -0
  231. package/esm/src/providers/local/mod.js +9 -0
  232. package/esm/src/providers/mod.d.ts +6 -3
  233. package/esm/src/providers/mod.js +3 -1
  234. package/esm/src/providers/openrouter/cache-control.d.ts +24 -0
  235. package/esm/src/providers/openrouter/cache-control.js +23 -0
  236. package/esm/src/providers/openrouter/chat.d.ts +107 -0
  237. package/esm/src/providers/{openrouter.js → openrouter/chat.js} +117 -231
  238. package/esm/src/providers/openrouter/image.d.ts +34 -0
  239. package/esm/src/providers/openrouter/image.js +275 -0
  240. package/esm/src/providers/openrouter/openai/chat-payload.d.ts +24 -0
  241. package/esm/src/providers/openrouter/openai/chat-payload.js +82 -0
  242. package/esm/src/providers/openrouter/openai/compat.d.ts +53 -0
  243. package/esm/src/providers/openrouter/openai/compat.js +213 -0
  244. package/esm/src/providers/openrouter/openai/image-payload.d.ts +18 -0
  245. package/esm/src/providers/openrouter/openai/image-payload.js +90 -0
  246. package/esm/src/providers/openrouter/openai/sdk-messages.d.ts +22 -0
  247. package/esm/src/providers/openrouter/openai/sdk-messages.js +122 -0
  248. package/esm/src/providers/openrouter/resolve-api-key.d.ts +9 -0
  249. package/esm/src/providers/openrouter/resolve-api-key.js +24 -0
  250. package/esm/src/providers/openrouter/speech.d.ts +23 -0
  251. package/esm/src/providers/{speech.js → openrouter/speech.js} +32 -55
  252. package/esm/src/providers/probe.d.ts +1 -0
  253. package/esm/src/providers/probe.js +22 -0
  254. package/esm/src/providers/shared/pcm.d.ts +12 -0
  255. package/esm/src/providers/{pcm.js → shared/pcm.js} +16 -3
  256. package/esm/src/providers/shared/sse.d.ts +18 -0
  257. package/esm/src/providers/shared/sse.js +87 -0
  258. package/esm/src/providers/shared/tool-args.d.ts +17 -0
  259. package/esm/src/providers/shared/tool-args.js +45 -0
  260. package/esm/src/providers/shared/upstream-tap.d.ts +5 -0
  261. package/esm/src/providers/{google-tap.js → shared/upstream-tap.js} +4 -7
  262. package/esm/src/providers/shared/upstream-tape.d.ts +6 -0
  263. package/esm/src/providers/{gemini-tape.js → shared/upstream-tape.js} +12 -22
  264. package/esm/src/providers/types.d.ts +27 -0
  265. package/esm/src/providers/types.js +1 -0
  266. package/package.json +11 -7
  267. package/docs/cli.md +0 -97
  268. package/docs/guardrails.md +0 -178
  269. package/docs/host.md +0 -97
  270. package/docs/kernel.md +0 -404
  271. package/docs/observability.md +0 -105
  272. package/docs/openrouter.md +0 -125
  273. package/docs/presets-google.md +0 -91
  274. package/docs/presets.md +0 -88
  275. package/docs/providers.md +0 -202
  276. package/docs/streaming.md +0 -96
  277. package/esm/src/kernel/engine/boundary.d.ts +0 -10
  278. package/esm/src/kernel/engine/boundary.js +0 -55
  279. package/esm/src/kernel/engine/runner/tools.d.ts +0 -13
  280. package/esm/src/kernel/engine/runner/tools.js +0 -198
  281. package/esm/src/kernel/registry/tools.d.ts +0 -12
  282. package/esm/src/kernel/registry/tools.js +0 -36
  283. package/esm/src/providers/expose-for-tests.d.ts +0 -1
  284. package/esm/src/providers/expose-for-tests.js +0 -25
  285. package/esm/src/providers/gemini-tape.d.ts +0 -2
  286. package/esm/src/providers/google-tap.d.ts +0 -3
  287. package/esm/src/providers/interactions.d.ts +0 -5
  288. package/esm/src/providers/interactions.js +0 -169
  289. package/esm/src/providers/keys.d.ts +0 -19
  290. package/esm/src/providers/local.d.ts +0 -29
  291. package/esm/src/providers/openrouter-mod.d.ts +0 -13
  292. package/esm/src/providers/openrouter-mod.js +0 -12
  293. package/esm/src/providers/openrouter-payload.d.ts +0 -39
  294. package/esm/src/providers/openrouter-payload.js +0 -195
  295. package/esm/src/providers/openrouter.d.ts +0 -15
  296. package/esm/src/providers/pcm.d.ts +0 -7
  297. package/esm/src/providers/provider.d.ts +0 -15
  298. package/esm/src/providers/provider.js +0 -202
  299. package/esm/src/providers/speech.d.ts +0 -23
  300. package/esm/src/providers/sse.d.ts +0 -7
  301. package/esm/src/providers/sse.js +0 -55
  302. package/esm/src/streaming/mod.d.ts +0 -9
  303. package/esm/src/streaming/mod.js +0 -8
  304. /package/esm/src/{streaming → host}/readStreamingJsonStringField.d.ts +0 -0
  305. /package/esm/src/{streaming → host}/readStreamingJsonStringField.js +0 -0
@@ -1,87 +1,41 @@
1
1
  /**
2
2
  * Stress / matrix turn synthesis for the THEORUM CLI.
3
3
  *
4
- * Tool enabling walks the profile allowlist and registered catalog metadata
5
- * (`conflictsWith`). Google `--search` / `--map` flags remain as convenience
6
- * aliases for `googleSearch` / `googleMaps` when those ids are allowed.
4
+ * Custom tools come from `profile.tools.allow` (visibility via loadTier).
5
+ * Provider builtins come from `models.*.builtInTools`. `--search` /
6
+ * `--map` only verify those ids are listed on the selected model.
7
7
  *
8
8
  * @module
9
9
  */
10
- import { getTool } from '../../kernel/registry/catalog.js';
11
10
  import { FIXTURE_PNG_BASE64, FIXTURE_WAV_BASE64, getFixtureForMime } from './fixtures.js';
11
+ function defaultModelId(profile) {
12
+ const ids = Object.keys(profile.models);
13
+ return profile.defaultModel ?? ids[0] ?? '';
14
+ }
12
15
  export function synthesizeLiteCombo(profile) {
13
- const select = profile.model.select?.fast ? 'fast' : undefined;
16
+ const model = profile.allowModelSelect && profile.models.fast ? 'fast' : undefined;
14
17
  return {
15
18
  profile: profile.id,
16
- select,
17
- tools: {},
19
+ model,
18
20
  input: {
19
21
  text: `Ping test for profile ${profile.id}. Respond concisely with confirmation.`,
20
22
  },
21
23
  };
22
24
  }
23
- function resolveStressReasoning(profile) {
24
- if (profile.model.select?.smart) {
25
- return 'smart';
25
+ function resolveStressModel(profile) {
26
+ if (!profile.allowModelSelect) {
27
+ return undefined;
26
28
  }
27
- if (profile.model.select) {
28
- const keys = Object.keys(profile.model.select);
29
- return keys[keys.length - 1];
30
- }
31
- return undefined;
32
- }
33
- function isEnabled(active, id) {
34
- return active[id] === true;
35
- }
36
- /** Drop tools whose registered `conflictsWith` siblings are also enabled. */
37
- function applyRegisteredConflicts(allowed, active) {
38
- for (const id of allowed) {
39
- if (!isEnabled(active, id)) {
40
- continue;
41
- }
42
- const conflicts = getTool(id)?.conflictsWith ?? [];
43
- if (conflicts.some((other) => isEnabled(active, other))) {
44
- active[id] = false;
45
- }
46
- }
47
- }
48
- /**
49
- * Enable allowlisted tools, optionally forcing one preferred tool and clearing
50
- * what it conflicts with (and tools that conflict with it).
51
- */
52
- function resolveStressTools(allowed, prefer) {
53
- const active = {};
54
- for (const t of allowed) {
55
- active[t] = true;
56
- }
57
- if (prefer && allowed.includes(prefer)) {
58
- active[prefer] = true;
59
- for (const c of getTool(prefer)?.conflictsWith ?? []) {
60
- if (allowed.includes(c)) {
61
- active[c] = false;
62
- }
63
- }
64
- for (const t of allowed) {
65
- if (t === prefer) {
66
- continue;
67
- }
68
- const otherConflicts = getTool(t)?.conflictsWith ?? [];
69
- if (otherConflicts.includes(prefer)) {
70
- active[t] = false;
71
- }
72
- }
29
+ if (profile.models.smart) {
30
+ return 'smart';
73
31
  }
74
- applyRegisteredConflicts(allowed, active);
75
- return active;
76
- }
77
- /** Builtins on the allowlist that declare conflicts (candidates for matrix variants). */
78
- function conflictingAllowlistedTools(allowed) {
79
- return allowed.filter((id) => {
80
- const entry = getTool(id);
81
- return entry?.kind === 'builtin' && (entry.conflictsWith?.length ?? 0) > 0;
82
- });
32
+ const ids = Object.keys(profile.models);
33
+ return ids.length > 1 ? ids[ids.length - 1] : undefined;
83
34
  }
84
35
  function resolveStressAttachments(profile) {
36
+ if (profile.type === 'speech' || profile.type === 'live' || !profile.inputs) {
37
+ return [];
38
+ }
85
39
  const attachments = [];
86
40
  const accept = profile.inputs.attachments?.accept;
87
41
  if (accept && accept.length > 0) {
@@ -98,22 +52,23 @@ function resolveStressAttachments(profile) {
98
52
  return attachments;
99
53
  }
100
54
  function resolveStressVoice(profile) {
55
+ if (profile.type === 'speech' || profile.type === 'live' || !profile.inputs) {
56
+ return [];
57
+ }
101
58
  const voice = [];
102
59
  if (profile.inputs.voice?.accept && profile.inputs.voice.accept.length > 0) {
103
60
  voice.push({ mimeType: 'audio/wav', data: FIXTURE_WAV_BASE64 });
104
61
  }
105
62
  return voice;
106
63
  }
107
- export function synthesizeStressCombo(profile, options = {}) {
108
- const select = resolveStressReasoning(profile);
109
- const activeTools = resolveStressTools(profile.tools.allow ?? [], options.preferTool);
64
+ export function synthesizeStressCombo(profile) {
65
+ const model = resolveStressModel(profile);
110
66
  const attachments = resolveStressAttachments(profile);
111
67
  const voice = resolveStressVoice(profile);
112
68
  const promptText = `Execute comprehensive test turn for profile ${profile.id}. Validate all instructions and produce required outputs.`;
113
69
  return {
114
70
  profile: profile.id,
115
- select,
116
- tools: activeTools,
71
+ model,
117
72
  input: {
118
73
  text: promptText,
119
74
  attachments: attachments.length > 0 ? attachments : undefined,
@@ -122,55 +77,27 @@ export function synthesizeStressCombo(profile, options = {}) {
122
77
  };
123
78
  }
124
79
  export function synthesizeMatrixCombos(profile) {
125
- const combos = [];
126
- const allowed = profile.tools.allow ?? [];
127
- combos.push({
128
- name: 'Lite (connectivity)',
129
- req: synthesizeLiteCombo(profile),
130
- });
131
- const primary = synthesizeStressCombo(profile);
132
- combos.push({
133
- name: 'Stress (all modalities + primary tools)',
134
- req: primary,
135
- });
136
- // Variants: each allowlisted conflicting builtin that the primary pass dropped.
137
- for (const id of conflictingAllowlistedTools(allowed)) {
138
- if (primary.tools?.[id] === true) {
139
- continue;
140
- }
141
- const variant = synthesizeStressCombo(profile, { preferTool: id });
142
- if (variant.tools?.[id] !== true) {
143
- continue;
144
- }
145
- combos.push({
146
- name: `Conflict variant (${id} preferred)`,
147
- req: variant,
148
- });
149
- }
150
- return combos;
80
+ return [
81
+ {
82
+ name: 'Lite (connectivity)',
83
+ req: synthesizeLiteCombo(profile),
84
+ },
85
+ {
86
+ name: 'Stress (all modalities + primary tools)',
87
+ req: synthesizeStressCombo(profile),
88
+ },
89
+ ];
151
90
  }
152
- /** Map legacy Google CLI flags onto tool ids when the profile allowlists them. */
153
- function applyGoogleFlagAliases(active, allowed, options) {
154
- if (options.search !== undefined && allowed.includes('googleSearch')) {
155
- active.googleSearch = options.search;
91
+ /** Ensure CLI grounding flags match model builtInTools. */
92
+ function assertGroundingFlagsOnModel(profile, options) {
93
+ const modelId = options.mode && profile.models[options.mode] ? options.mode : defaultModelId(profile);
94
+ const builtins = new Set(profile.models[modelId]?.builtInTools ?? []);
95
+ if (options.search === true && !builtins.has('googleSearch')) {
96
+ throw new Error(`--search requires googleSearch on models.${modelId}.builtInTools`);
156
97
  }
157
- if (options.map !== undefined && allowed.includes('googleMaps')) {
158
- active.googleMaps = options.map;
159
- }
160
- }
161
- function applyExplicitToolOverrides(base, profile, options) {
162
- const allowed = profile.tools.allow ?? [];
163
- base.tools = base.tools ?? {};
164
- applyGoogleFlagAliases(base.tools, allowed, options);
165
- // If map was explicitly requested on, prefer it against its registered conflicts.
166
- if (options.map === true && allowed.includes('googleMaps')) {
167
- const preferred = resolveStressTools(allowed, 'googleMaps');
168
- for (const id of allowed) {
169
- base.tools[id] = preferred[id] === true;
170
- }
171
- return;
98
+ if (options.map === true && !builtins.has('googleMaps')) {
99
+ throw new Error(`--map requires googleMaps on models.${modelId}.builtInTools`);
172
100
  }
173
- applyRegisteredConflicts(allowed, base.tools);
174
101
  }
175
102
  export function buildCustomTurnRequest(profile, options) {
176
103
  if (options.lite) {
@@ -178,10 +105,10 @@ export function buildCustomTurnRequest(profile, options) {
178
105
  }
179
106
  const base = synthesizeStressCombo(profile);
180
107
  if (options.mode) {
181
- base.select = options.mode;
108
+ base.model = options.mode;
182
109
  }
183
110
  if (options.search !== undefined || options.map !== undefined) {
184
- applyExplicitToolOverrides(base, profile, options);
111
+ assertGroundingFlagsOnModel(profile, options);
185
112
  }
186
113
  return base;
187
114
  }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Canary-only batch helper for hosts that need stream lookback without full
3
+ * egress. Live production path: `live-outbound-gate.ts` (progressive yield).
4
+ *
5
+ * @module
6
+ */
7
+ import type { TurnEvent } from '../kernel/types.js';
8
+ import { type CanaryStreamGate } from './canary.js';
9
+ export interface CanaryGateSession {
10
+ canary: string;
11
+ gate: CanaryStreamGate;
12
+ lastStreamType?: 'text' | 'thought';
13
+ }
14
+ declare function createCanaryGateSession(canary: string): CanaryGateSession;
15
+ declare function filterCanaryGatedEvents(session: CanaryGateSession, events: TurnEvent[]): {
16
+ leaked: true;
17
+ } | {
18
+ leaked: false;
19
+ events: TurnEvent[];
20
+ };
21
+ export { createCanaryGateSession, filterCanaryGatedEvents };
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Canary-only batch helper for hosts that need stream lookback without full
3
+ * egress. Live production path: `live-outbound-gate.ts` (progressive yield).
4
+ *
5
+ * @module
6
+ */
7
+ import { createCanaryStreamGate, eventHasCanary, isStreamedCanaryEvent, } from './canary.js';
8
+ function createCanaryGateSession(canary) {
9
+ return { canary, gate: createCanaryStreamGate(canary) };
10
+ }
11
+ function filterCanaryGatedEvents(session, events) {
12
+ const out = [];
13
+ for (const event of events) {
14
+ if (isStreamedCanaryEvent(event)) {
15
+ session.lastStreamType = event.type;
16
+ const result = session.gate.process(event.text ?? '');
17
+ if (result.leak) {
18
+ return { leaked: true };
19
+ }
20
+ if (result.emit) {
21
+ out.push({ ...event, text: result.emit });
22
+ }
23
+ continue;
24
+ }
25
+ if (eventHasCanary(event, session.canary)) {
26
+ return { leaked: true };
27
+ }
28
+ out.push(event);
29
+ }
30
+ return { leaked: false, events: out };
31
+ }
32
+ export { createCanaryGateSession, filterCanaryGatedEvents };
@@ -0,0 +1,34 @@
1
+ import type { TurnEvent } from '../kernel/types.js';
2
+ declare const USER_OPEN = "<user_data>";
3
+ declare const USER_CLOSE = "</user_data>";
4
+ declare const OMIT_CANARY = "[omitted - canary]";
5
+ declare function mintCanary(): string;
6
+ declare function wrapUserData(text: string): string;
7
+ /**
8
+ * Append the canary bind note to the host's system prompt.
9
+ *
10
+ * The note is mechanism text with an overridable registered default
11
+ * (`canary.bind_note` in the lexicon) or a per-profile template
12
+ * (`guardrails.canary.bindNote`). Either way the template must contain the
13
+ * `{canary}` placeholder — a note without the token binds nothing.
14
+ */
15
+ declare function bindCanary(system: string, canary: string, bindNote?: string): string;
16
+ declare function scanTextForCanaryLeak(text: string, canary: string): boolean;
17
+ declare function eventHasCanary(event: TurnEvent, canary: string): boolean;
18
+ type CanaryGateResult = {
19
+ leak: true;
20
+ } | {
21
+ leak: false;
22
+ emit: string;
23
+ };
24
+ interface CanaryStreamGate {
25
+ process: (fragment: string) => CanaryGateResult;
26
+ flush: () => CanaryGateResult;
27
+ }
28
+ declare function createCanaryStreamGate(canary: string): CanaryStreamGate;
29
+ declare function isStreamedCanaryEvent(event: TurnEvent): event is TurnEvent & {
30
+ type: 'text' | 'thought';
31
+ };
32
+ declare function redactCanary(event: TurnEvent, canary: string): TurnEvent;
33
+ export type { CanaryGateResult, CanaryStreamGate };
34
+ export { bindCanary, createCanaryStreamGate, eventHasCanary, isStreamedCanaryEvent, mintCanary, OMIT_CANARY, redactCanary, scanTextForCanaryLeak, USER_CLOSE, USER_OPEN, wrapUserData, };
@@ -0,0 +1,150 @@
1
+ import { mapStrings } from '../kernel/engine/tree.js';
2
+ import { TheorumError } from './error.js';
3
+ import { lexiconText } from './lexicon.js';
4
+ import { scanTextOf } from './serialize.js';
5
+ const USER_OPEN = '<user_data>';
6
+ const USER_CLOSE = '</user_data>';
7
+ const CANARY_PREFIX = 'theo-';
8
+ const CANARY_BYTES = 16;
9
+ const HEX_RADIX = 16;
10
+ const HEX_PAD = 2;
11
+ const OMIT_CANARY = '[omitted - canary]';
12
+ const FENCE = /<\/?user_data>/gi;
13
+ /** Base64 prefix hint for the literal string "theo". */
14
+ const B64_THEO_HINT = 'dGhlbw';
15
+ function mintCanary() {
16
+ const bytes = new Uint8Array(CANARY_BYTES);
17
+ crypto.getRandomValues(bytes);
18
+ let hex = '';
19
+ for (const byte of bytes) {
20
+ hex += byte.toString(HEX_RADIX).padStart(HEX_PAD, '0');
21
+ }
22
+ return `${CANARY_PREFIX}${hex}`;
23
+ }
24
+ function stripUserFences(text) {
25
+ return text.replaceAll(FENCE, '').trim();
26
+ }
27
+ function wrapUserData(text) {
28
+ return `${USER_OPEN}\n${stripUserFences(text)}\n${USER_CLOSE}`;
29
+ }
30
+ /**
31
+ * Append the canary bind note to the host's system prompt.
32
+ *
33
+ * The note is mechanism text with an overridable registered default
34
+ * (`canary.bind_note` in the lexicon) or a per-profile template
35
+ * (`guardrails.canary.bindNote`). Either way the template must contain the
36
+ * `{canary}` placeholder — a note without the token binds nothing.
37
+ */
38
+ function bindCanary(system, canary, bindNote) {
39
+ if (!canary) {
40
+ return system;
41
+ }
42
+ if (bindNote !== undefined && !bindNote.includes('{canary}')) {
43
+ throw new TheorumError('guardrails.canary.bindNote must contain the {canary} placeholder');
44
+ }
45
+ const note = lexiconText('canary.bind_note', { canary }, bindNote);
46
+ if (!system) {
47
+ return note;
48
+ }
49
+ return `${system}\n\n${note}`;
50
+ }
51
+ function scanTextForCanaryLeak(text, canary) {
52
+ if (!text || !canary) {
53
+ return false;
54
+ }
55
+ if (text.includes(canary)) {
56
+ return true;
57
+ }
58
+ try {
59
+ const encoded = btoa(canary);
60
+ if (text.includes(encoded)) {
61
+ return true;
62
+ }
63
+ }
64
+ catch {
65
+ /* ignore invalid btoa input */
66
+ }
67
+ if (!text.includes('theo') && !text.includes(B64_THEO_HINT)) {
68
+ return false;
69
+ }
70
+ const hex = canary.startsWith(CANARY_PREFIX) ? canary.slice(CANARY_PREFIX.length) : '';
71
+ if (hex.length > 0) {
72
+ const spaced = hex.split('').join(' ');
73
+ if (text.includes(spaced)) {
74
+ return true;
75
+ }
76
+ }
77
+ return false;
78
+ }
79
+ function eventHasCanary(event, canary) {
80
+ if (!canary) {
81
+ return false;
82
+ }
83
+ if (event.text && scanTextForCanaryLeak(event.text, canary)) {
84
+ return true;
85
+ }
86
+ if (event.error && scanTextForCanaryLeak(event.error, canary)) {
87
+ return true;
88
+ }
89
+ if (event.structured !== undefined &&
90
+ scanTextForCanaryLeak(scanTextOf(event.structured), canary)) {
91
+ return true;
92
+ }
93
+ if (event.tool !== undefined && scanTextForCanaryLeak(scanTextOf(event.tool), canary)) {
94
+ return true;
95
+ }
96
+ if (event.grounding !== undefined && scanTextForCanaryLeak(scanTextOf(event.grounding), canary)) {
97
+ return true;
98
+ }
99
+ if (event.evidence !== undefined && scanTextForCanaryLeak(scanTextOf(event.evidence), canary)) {
100
+ return true;
101
+ }
102
+ if (event.session !== undefined && scanTextForCanaryLeak(scanTextOf(event.session), canary)) {
103
+ return true;
104
+ }
105
+ if (event.sessionResumptionHandle &&
106
+ scanTextForCanaryLeak(event.sessionResumptionHandle, canary)) {
107
+ return true;
108
+ }
109
+ return false;
110
+ }
111
+ function createCanaryStreamGate(canary) {
112
+ const overlap = Math.max(0, canary.length - 1);
113
+ let pending = '';
114
+ function step(window) {
115
+ if (scanTextForCanaryLeak(window, canary)) {
116
+ return { leak: true };
117
+ }
118
+ const safeEnd = Math.max(0, window.length - overlap);
119
+ const emit = window.slice(0, safeEnd);
120
+ pending = window.slice(safeEnd);
121
+ return { leak: false, emit };
122
+ }
123
+ return {
124
+ process(fragment) {
125
+ if (!fragment) {
126
+ return { leak: false, emit: '' };
127
+ }
128
+ return step(pending + fragment);
129
+ },
130
+ flush() {
131
+ if (scanTextForCanaryLeak(pending, canary)) {
132
+ return { leak: true };
133
+ }
134
+ const emit = pending;
135
+ pending = '';
136
+ return { leak: false, emit };
137
+ },
138
+ };
139
+ }
140
+ function isStreamedCanaryEvent(event) {
141
+ return event.type === 'text' || event.type === 'thought';
142
+ }
143
+ function redactCanary(event, canary) {
144
+ const next = mapStrings(event, (text) => text.replaceAll(canary, OMIT_CANARY));
145
+ if (next && typeof next === 'object') {
146
+ return next;
147
+ }
148
+ return event;
149
+ }
150
+ export { bindCanary, createCanaryStreamGate, eventHasCanary, isStreamedCanaryEvent, mintCanary, OMIT_CANARY, redactCanary, scanTextForCanaryLeak, USER_CLOSE, USER_OPEN, wrapUserData, };
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Synthetic canary egress attack catalog — events built at fuzz time from a canary token.
3
+ *
4
+ * @module
5
+ */
6
+ /** lexicon-exempt-file: adversarial corpus fixture — not runtime user or model copy (P2) */
7
+ import type { TurnEvent } from '../../kernel/types.js';
8
+ import type { CanaryEgressCatalogEntry } from './types.js';
9
+ /** Fixed token so canary fuzz reports are reproducible across runs. */
10
+ export declare const FIXED_CANARY = "theo-0123456789abcdef0123456789abcdef";
11
+ export interface CanaryEgressAttack extends CanaryEgressCatalogEntry {
12
+ events: TurnEvent[];
13
+ }
14
+ /** Build the full canary egress adversarial bank for a given token. */
15
+ export declare function buildCanaryEgressAttacks(canary: string): CanaryEgressAttack[];
16
+ /** Catalog metadata without event payloads (for docs / inventory). */
17
+ export declare function canaryEgressCatalog(canary: string): CanaryEgressCatalogEntry[];
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Synthetic canary egress attack catalog — events built at fuzz time from a canary token.
3
+ *
4
+ * @module
5
+ */
6
+ /** Fixed token so canary fuzz reports are reproducible across runs. */
7
+ export const FIXED_CANARY = 'theo-0123456789abcdef0123456789abcdef';
8
+ function splitTextChunks(canary, text) {
9
+ const idx = text.indexOf(canary);
10
+ if (idx < 0) {
11
+ return [{ type: 'text', text }];
12
+ }
13
+ const before = text.slice(0, idx + Math.ceil(canary.length / 2));
14
+ const after = text.slice(idx + Math.ceil(canary.length / 2));
15
+ return [
16
+ { type: 'text', text: before },
17
+ { type: 'text', text: after },
18
+ ];
19
+ }
20
+ function allSplitPoints(canary) {
21
+ const cases = [];
22
+ for (let i = 1; i < canary.length; i++) {
23
+ cases.push({
24
+ name: `split-at-${i}`,
25
+ category: 'split-stream',
26
+ shouldBlock: true,
27
+ events: [
28
+ { type: 'text', text: canary.slice(0, i) },
29
+ { type: 'text', text: canary.slice(i) },
30
+ ],
31
+ });
32
+ }
33
+ return cases;
34
+ }
35
+ function charByChar(canary) {
36
+ return {
37
+ name: 'char-by-char',
38
+ category: 'split-stream',
39
+ shouldBlock: true,
40
+ events: [...canary].map((ch) => ({ type: 'text', text: ch })),
41
+ };
42
+ }
43
+ /** Build the full canary egress adversarial bank for a given token. */
44
+ export function buildCanaryEgressAttacks(canary) {
45
+ const hex = canary.slice('theo-'.length);
46
+ const spacedHex = hex.split('').join(' ');
47
+ const b64 = btoa(canary);
48
+ const attacks = [
49
+ {
50
+ name: 'literal-text',
51
+ category: 'literal',
52
+ shouldBlock: true,
53
+ events: [{ type: 'text', text: `Here is the token: ${canary}` }],
54
+ },
55
+ {
56
+ name: 'literal-thought',
57
+ category: 'literal',
58
+ shouldBlock: true,
59
+ events: [{ type: 'thought', text: `internal ${canary}` }],
60
+ },
61
+ {
62
+ name: 'literal-structured',
63
+ category: 'literal',
64
+ shouldBlock: true,
65
+ events: [{ type: 'structured', structured: { secret: canary } }],
66
+ },
67
+ {
68
+ name: 'literal-tool',
69
+ category: 'literal',
70
+ shouldBlock: true,
71
+ events: [{ type: 'tool', tool: { name: 'leak', arguments: { token: canary } } }],
72
+ },
73
+ {
74
+ name: 'literal-grounding',
75
+ category: 'literal',
76
+ shouldBlock: true,
77
+ events: [{ type: 'grounding', grounding: { sources: [], metadata: { canary } } }],
78
+ },
79
+ {
80
+ name: 'literal-evidence',
81
+ category: 'literal',
82
+ shouldBlock: true,
83
+ events: [{ type: 'evidence', evidence: { provider: 'google', raw: { canary } } }],
84
+ },
85
+ {
86
+ name: 'base64-text',
87
+ category: 'encoding',
88
+ shouldBlock: true,
89
+ events: [{ type: 'text', text: `decode: ${b64}` }],
90
+ },
91
+ {
92
+ name: 'spaced-hex-text',
93
+ category: 'encoding',
94
+ shouldBlock: true,
95
+ events: [{ type: 'text', text: `theo- ${spacedHex}` }],
96
+ },
97
+ {
98
+ name: 'split-wrapper',
99
+ category: 'split-stream',
100
+ shouldBlock: true,
101
+ events: splitTextChunks(canary, `prefix ${canary} suffix`),
102
+ },
103
+ charByChar(canary),
104
+ {
105
+ name: 'thought-then-text-split',
106
+ category: 'split-stream',
107
+ shouldBlock: true,
108
+ events: [
109
+ { type: 'thought', text: canary.slice(0, 20) },
110
+ { type: 'text', text: canary.slice(20) },
111
+ ],
112
+ },
113
+ {
114
+ name: 'benign-theorem',
115
+ category: 'benign',
116
+ shouldBlock: false,
117
+ events: [{ type: 'text', text: 'Welcome to theorem proving and theory.' }],
118
+ },
119
+ {
120
+ name: 'benign-prefix-only',
121
+ category: 'benign',
122
+ shouldBlock: false,
123
+ events: [{ type: 'text', text: 'theo-' }],
124
+ },
125
+ {
126
+ name: 'benign-random-hex',
127
+ category: 'benign',
128
+ shouldBlock: false,
129
+ events: [{ type: 'text', text: 'theo-deadbeeffeedfacecafebabecafebabe' }],
130
+ },
131
+ {
132
+ name: 'benign-safe-reply',
133
+ category: 'benign',
134
+ shouldBlock: false,
135
+ events: [
136
+ { type: 'text', text: 'Hello! How can I help you today?' },
137
+ { type: 'tokens', tokens: { input: 1, output: 2, total: 3 } },
138
+ ],
139
+ },
140
+ ];
141
+ attacks.push(...allSplitPoints(canary));
142
+ return attacks;
143
+ }
144
+ /** Catalog metadata without event payloads (for docs / inventory). */
145
+ export function canaryEgressCatalog(canary) {
146
+ return buildCanaryEgressAttacks(canary).map(({ name, category, shouldBlock }) => ({
147
+ name,
148
+ category,
149
+ shouldBlock,
150
+ }));
151
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Inbound sanitize fuzz runner — exercises all sanitize channels against the corpus.
3
+ *
4
+ * @module
5
+ */
6
+ /**
7
+ * Run inbound adversarial fuzz against the corpus. Returns false when expected catches are missed.
8
+ */
9
+ export declare function runInboundGuardrailFuzz(options?: {
10
+ quiet?: boolean;
11
+ }): boolean;