@theoremjs/agents 0.3.4-next.484.g20d8b9d → 0.3.4-next.508.geda6db0

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 (68) hide show
  1. package/README.md +61 -51
  2. package/esm/mod.d.ts +2 -2
  3. package/esm/mod.js +1 -1
  4. package/esm/src/guardrails/credential-scan.d.ts +1 -1
  5. package/esm/src/guardrails/credential-scan.js +41 -18
  6. package/esm/src/guardrails/destinations.js +6 -2
  7. package/esm/src/guardrails/detect-at.d.ts +1 -3
  8. package/esm/src/guardrails/detect-at.js +6 -22
  9. package/esm/src/guardrails/egress-automata.js +203 -202
  10. package/esm/src/guardrails/egress-stream.js +10 -3
  11. package/esm/src/guardrails/egress-urls.js +4 -2
  12. package/esm/src/guardrails/injection-patterns.d.ts +3 -1
  13. package/esm/src/guardrails/injection-patterns.js +87 -4
  14. package/esm/src/guardrails/labels.d.ts +2 -2
  15. package/esm/src/guardrails/labels.js +7 -5
  16. package/esm/src/guardrails/mod.d.ts +1 -1
  17. package/esm/src/guardrails/normalize.js +4 -2
  18. package/esm/src/guardrails/prompt-echo.js +4 -2
  19. package/esm/src/guardrails/sensitive.d.ts +2 -2
  20. package/esm/src/guardrails/sensitive.js +1 -1
  21. package/esm/src/interface/profile-interface.js +13 -0
  22. package/esm/src/kernel/answer-tool.d.ts +14 -0
  23. package/esm/src/kernel/answer-tool.js +26 -0
  24. package/esm/src/kernel/engine/decision-references.d.ts +14 -0
  25. package/esm/src/kernel/engine/decision-references.js +55 -0
  26. package/esm/src/kernel/engine/decision.d.ts +3 -3
  27. package/esm/src/kernel/engine/decision.js +38 -22
  28. package/esm/src/kernel/engine/embedding.js +0 -2
  29. package/esm/src/kernel/engine/runner/gates.js +38 -4
  30. package/esm/src/kernel/engine/runner/steps.js +47 -10
  31. package/esm/src/kernel/engine/runner/stream.d.ts +14 -8
  32. package/esm/src/kernel/engine/runner/stream.js +25 -13
  33. package/esm/src/kernel/mod.d.ts +1 -1
  34. package/esm/src/kernel/mod.js +1 -1
  35. package/esm/src/kernel/profile-presence.js +12 -0
  36. package/esm/src/kernel/profile-scope.js +14 -14
  37. package/esm/src/kernel/protocols/binding.d.ts +3 -1
  38. package/esm/src/kernel/protocols/binding.js +6 -0
  39. package/esm/src/kernel/protocols/limits.d.ts +10 -11
  40. package/esm/src/kernel/protocols/limits.js +9 -23
  41. package/esm/src/kernel/protocols/runtime.d.ts +3 -2
  42. package/esm/src/kernel/protocols/runtime.js +1 -2
  43. package/esm/src/kernel/provider-budget.d.ts +2 -2
  44. package/esm/src/kernel/provider-budget.js +22 -12
  45. package/esm/src/kernel/provider-runtime.js +2 -2
  46. package/esm/src/kernel/registry/profiles.js +1 -1
  47. package/esm/src/kernel/registry/resolve.js +4 -0
  48. package/esm/src/kernel/registry/schemas.js +1 -1
  49. package/esm/src/kernel/schema.js +12 -2
  50. package/esm/src/kernel/types.d.ts +4 -2
  51. package/esm/src/observability/spans.js +7 -8
  52. package/esm/src/providers/embedding-limits.js +4 -4
  53. package/esm/src/providers/gemini/{embedding-protocol.d.ts → embeddings-protocol.d.ts} +3 -3
  54. package/esm/src/providers/gemini/{embedding-protocol.js → embeddings-protocol.js} +3 -3
  55. package/esm/src/providers/gemini/live-protocol.js +4 -1
  56. package/esm/src/providers/gemini/provider.js +2 -2
  57. package/esm/src/providers/local/embeddings-protocol.d.ts +7 -0
  58. package/esm/src/providers/local/{embedding-protocol.js → embeddings-protocol.js} +2 -2
  59. package/esm/src/providers/local/provider.js +2 -2
  60. package/esm/src/providers/mod.d.ts +3 -3
  61. package/esm/src/providers/mod.js +3 -3
  62. package/esm/src/providers/openrouter/decisions-protocol.js +11 -8
  63. package/esm/src/providers/openrouter/embeddings-protocol.d.ts +6 -0
  64. package/esm/src/providers/openrouter/{embedding-protocol.js → embeddings-protocol.js} +2 -2
  65. package/esm/src/providers/openrouter/provider.js +2 -2
  66. package/package.json +1 -1
  67. package/esm/src/providers/local/embedding-protocol.d.ts +0 -7
  68. package/esm/src/providers/openrouter/embedding-protocol.d.ts +0 -6
package/README.md CHANGED
@@ -57,7 +57,7 @@ Install `jsr:@theoremjs/agents` from JSR or `@theoremjs/agents` from npm.
57
57
 
58
58
  | You need to | Theorem gives you | Read |
59
59
  | :--- | :--- | :--- |
60
- | Describe the whole agent in one place | Six profile types, several models per profile, typed file inputs, and outputs that a schema checks | [Profile types](#profile-types) |
60
+ | Describe the whole agent in one place | A profile type for each kind of model call, several models per profile, typed file inputs, and outputs that a schema checks | [Profile types](#profile-types) |
61
61
  | Stop a prompt injection or a data leak | Input scanning by trust level, canary tokens, and egress checks that can send the model back to try again | [Guardrails](#guardrails) |
62
62
  | Let the model act, safely | One tool registry for function, HTTP, MCP, and provider tools, gated in layers, with OAuth 2.1 and PKCE built in | [Tools](#tools) |
63
63
  | Connect model servers | Protocols for Gemini, OpenRouter, TypeSafe, and OpenAI-compatible servers, and providers that you define | [Providers](#providers) |
@@ -66,8 +66,6 @@ Install `jsr:@theoremjs/agents` from JSR or `@theoremjs/agents` from npm.
66
66
 
67
67
  ---
68
68
 
69
- ---
70
-
71
69
  ## Quickstart
72
70
 
73
71
  Use these steps to create a support agent and run one turn.
@@ -178,22 +176,20 @@ Use the `type` field to choose what the agent does. The type sets the shape of t
178
176
  | `speech` | `runTurn` | text | audio (WAV, or MP3 on OpenRouter) | `geminiInteractions`, `openRouterOpenAi`, `openAi` |
179
177
  | `live` | `runSession` | realtime mic audio, camera frames, typed text | streamed audio, transcripts, tool calls | `geminiLive` |
180
178
  | `host` | `invokeTool`, `invokeToolStep`, `runTool`, `runToolStep` | tool calls from your own code, one at a time or as a step; the `run` pair gives how each call ended | guarded tool results | none; it never calls a model |
181
- | `decision` | `runDecision` | non-null JSON state plus declared questions | typed choices, probabilities, scores, or `noul` | `systemOne` |
179
+ | `decision` | `runDecision` | non-null JSON state plus declared questions | typed choices, probabilities, scores, boxes, or `noul` | `systemOne`, `openRouterDecisions` |
180
+ | `embedding` | `runEmbedding` | text, or text and media parts | one vector for each input | `geminiEmbeddings`, `openRouterEmbeddings`, `ollamaEmbeddings` |
182
181
 
183
182
  A protocol that you define can serve the same profile types. Its `endpoints` name the profile types that it serves.
184
183
 
185
- `defineProfile` rejects a field that does not belong to the type. Quota, canary, or egress on a `host` profile, attachments on `live`, and steering on `image` all fail at startup, not halfway through a turn.
184
+ `defineProfile` rejects a field that does not belong to the type, so the mistake fails at startup, not halfway through a turn.
185
+ For example, `live` takes no attachments and `image` takes no steering.
186
+ A `host` profile sets only `guardrails.detect` and `guardrails.network`.
186
187
 
187
188
 
188
189
  ---
189
190
 
190
191
  ## Architecture
191
192
 
192
- Tool handlers receive `ToolContext.operationId`, a kernel-issued ID for their action.
193
- Provider call IDs remain available for correlation. Approval clients preserve both IDs from the issued gate.
194
- These IDs distinguish actions. A host-owned execution coordinator retains their claims and outcomes.
195
- The React handler creates that coordinator from its atomic session store. Process restarts require durable shared storage.
196
-
197
193
  The runner is one deterministic path for one agent turn. A profile is a declaration that the host owns. Registered providers and their protocols supply model operations; `runSession` opens live sessions. Traces go to destinations that the host registers. There are no environment variables and no bundled database.
198
194
 
199
195
  ```text
@@ -226,7 +222,8 @@ Use `onStage` to act at a fixed point in the turn:
226
222
  - Apply a host limit at `pre_turn`.
227
223
  - Request confirmation at `pre_tool`.
228
224
  - Change a tool result at `post_tool`.
229
- A turn that stops early can resume. After a `gate` stop, call `invokeTool` with `resume`. After a cut-off reply, start a new `runTurn` with `continueFrom`.
225
+
226
+ A turn that stops early can resume. After a `gate` stop, call `invokeTool` with `resume`. After a cut-off reply, start a new `runTurn` with `continueFrom`.
230
227
 
231
228
  For a React interface, use the separate package [`@theoremjs/react`](https://github.com/masudl-hub/theoremai/blob/main/react/README.md). It builds on the headless projection `@theoremjs/agents/interface`. The agents package does not include React.
232
229
 
@@ -256,6 +253,18 @@ Every client-tool call uses the same gated pipeline. Provider-native tools execu
256
253
 
257
254
  A tool that must act as the signed-in user takes OAuth 2.1 with PKCE. The call becomes an auth gate, and the helpers in `@theoremjs/agents/kernel` finish the sign-in.
258
255
 
256
+ ### Run a tool once across workers
257
+
258
+ Each tool call gets an operation ID from the kernel, `ToolContext.operationId`. A `ToolExecutionCoordinator` that the host owns keeps a claim and an outcome for each ID, so a retried or resumed turn does not run the same action twice.
259
+ Pass it as `execution` on the `TurnRequest`. A tool that the page answers also needs `pageExecution`, a `PageToolExecution`.
260
+ Profile JSON and provider events cannot supply either one.
261
+
262
+ - **One process.** `createMemoryToolExecution` keeps claims and outcomes in memory. It refuses new operations after `maxOperations` (256 by default). Nothing survives a restart.
263
+ - **Several workers.** Write a coordinator over durable shared storage. The `@theoremjs/web` server handler builds one from its session store.
264
+ - **Replay.** Before you deliver a saved outcome on your own path, call `authorizeToolReplay`. It checks that the call is still allowed and may still be shown, and it runs no handler.
265
+
266
+ A claim that has no confirmed outcome (`ToolOperationUncertainty`) never becomes an approval again. Check the destination before you create a replacement action.
267
+
259
268
  ---
260
269
 
261
270
  ## Guardrails
@@ -268,10 +277,10 @@ Theorem scans each piece of text according to where it came from.
268
277
  | `assembled` | `TurnRequest.system`, built by your code per turn | Full scan |
269
278
  | `untrusted` | User text, history, slots, attachments, steering, stage injections | Full injection and sensitive-data scan |
270
279
 
271
- - **Coming in.** `detect` replaces prompt-injection phrasing, secrets, and personal data with a placeholder before the model sees them.
272
- - **Going out.** A **canary** is a secret token that Theorem hides in the system prompt. If the token appears in a reply, the prompt leaked, and the `canary_leak` detector stops the reply. The `prompt_leak` detector also stops a reply that repeats 12 words of the prompt. Both are set in `detect`, like every other detector. `egress` checks every outbound payload and can send the model back to repair its answer.
273
- - **Tool results.** Remote content is data. Theorem fences it, scans it, and refuses destructive calls after a remote read when `taint.afterRemoteRead` is set. `taint.remoteDestination` gates a call that sends to an address only remote content named.
274
- - **Network.** HTTP and MCP targets must pass `network.allowedHosts`. Theorem refuses private and metadata addresses by default.
280
+ - **Coming in.** `guardrails.detect` replaces prompt-injection phrasing, secrets, and personal data with a placeholder before the model sees them.
281
+ - **Going out.** A **canary** is a secret token that Theorem hides in the system prompt. If the token appears in a reply, the prompt leaked, and the `canary_leak` detector stops the reply. The `prompt_leak` detector also stops a reply that repeats 12 words of the prompt. Both are set in `guardrails.detect`, like every other detector. When a check blocks a reply, `guardrails.blockedReply` decides what happens: the model rewrites it (`retry`) or the user gets a refusal (`refuse`).
282
+ - **Tool results.** Remote content is data. Theorem fences it, scans it, and refuses destructive calls after a remote read when `guardrails.taint.afterRemoteRead` is set. `guardrails.taint.remoteDestination` gates a call that sends to an address only remote content named.
283
+ - **Network.** HTTP and MCP tools cannot reach private and metadata addresses by default. `guardrails.network.allowedHosts` names the hosts that may resolve to a private address, and `guardrails.network.allowPrivateNetworks` lifts the rule for local development.
275
284
 
276
285
  The checks fail closed: a payload that cannot be scanned counts as a block. The guardrails have an adversarial corpus, fuzzing, and mutation tests. The corpus ships as `@theoremjs/agents/guardrails/testing`.
277
286
 
@@ -283,9 +292,9 @@ The checks fail closed: a payload that cannot be scanned counts as a block. The
283
292
 
284
293
  ## Providers
285
294
 
286
- Host options can set finite `limits` for provider setup, credentials, transport, and normalized output.
287
- Defaults bound each operation to 120 seconds and each live connection opening to 30 seconds.
288
- Retries share the deadline and byte budget. Limits do not enter the profile.
295
+ A profile's model can set `limits`: the points at which Theorem stops its calls, such as a deadline or a byte cap.
296
+ The `limits` that you pass to `runTurn` override them, one field at a time.
297
+ Theorem sets none of its own, so a limit that nobody sets is the provider's own. Retries share the deadline and byte budget.
289
298
 
290
299
  A model call uses a **protocol**, a **provider** and a **binding**.
291
300
  The protocol is the code for one API shape. The provider is data: an address, the protocols that it serves, and a sign-in.
@@ -293,10 +302,10 @@ The binding under `models` names the protocol, the registered provider ID, and t
293
302
 
294
303
  | Provider | ID | Protocols |
295
304
  | :--- | :--- | :--- |
296
- | `geminiProvider` | `gemini` | `geminiInteractions` (text, image, speech), `geminiLive` (live) |
297
- | `openRouterProvider` | `openrouter` | `openRouterOpenAi` (text, speech), `openRouterImages` (image), `openRouterDecisions` (decision) |
305
+ | `geminiProvider` | `gemini` | `geminiInteractions` (text, image, speech), `geminiLive` (live), `geminiEmbeddings` (embedding) |
306
+ | `openRouterProvider` | `openrouter` | `openRouterOpenAi` (text, speech), `openRouterImages` (image), `openRouterDecisions` (decision), `openRouterEmbeddings` (embedding) |
298
307
  | `typesafeProvider` | `typesafe` | `systemOne` (decision) |
299
- | `localProvider` | `local` | `openAi` (text, speech), for a server such as Ollama |
308
+ | `localProvider` | `local` | `openAi` (text, speech), `ollamaEmbeddings` (embedding), for a server such as Ollama |
300
309
 
301
310
  Register providers before profiles. `registerProvider(provider, { id, connection, keySlot, fallbackKeySlot })` returns the registered provider.
302
311
  `registerProfile` refuses a binding whose provider does not serve its protocol, or whose protocol has no endpoint for the profile type.
@@ -378,29 +387,6 @@ Use the [Theorem docs](https://theorem.masudlewis.com/docs) for task procedures.
378
387
 
379
388
  ---
380
389
 
381
- For tool execution across several workers, pass a host-owned `ToolExecutionCoordinator` through the trusted request's `execution` option.
382
- The React server handler creates this service from its atomic session store.
383
- `ToolOperationIdentity` identifies the accepted invocation. `ToolOperationClaim` adds its worker token.
384
- `savedToolInvocationSchema` validates JSON dispatch evidence.
385
- `SavedToolInvocation` retains accepted request data and the final input used at dispatch.
386
- Use `toolInvocationHash` to compute the same canonical JSON digest as the coordinator.
387
- Use this evidence to check current resource access before you disclose a cached result.
388
- For browser effects, `PageToolExecution` issues a dispatch grant only after the claim.
389
- A page-answered tool needs a claim, so pass `execution` together with `pageExecution`.
390
- Use `createMemoryToolExecution` when one process serves the whole turn and nothing resumes after a restart.
391
- It keeps claims and outcomes in memory. Its `MemoryToolExecutionOptions` set `maxOperations` (256 by default), after which it refuses new operations.
392
- Write your own `ToolExecutionCoordinator` when a turn can resume on another worker.
393
- `PageToolDispatch` contains the grant ID and a pending `PageAnswer`. Host code supplies result delivery.
394
- The browser receives the final prepared arguments, then returns a result against that grant.
395
- Neither profile JSON nor a provider event can supply this service.
396
- `BeginToolOperationResult` reports whether a worker acquired the claim or an earlier operation already exists.
397
- `ToolOperationOutcome` retains the terminal event and a guarded `ModelToolResult` for replay.
398
- For a custom host replay path, call `authorizeToolReplay` before delivering a saved outcome.
399
- It checks current eligibility and disclosure without running handlers or preparation hooks.
400
- Its validator is `toolOperationOutcomeSchema`. `TerminalToolEvent` restricts the retained event to completion or failure.
401
- `ToolOperationUncertainty` records why a dispatched operation has no confirmed outcome.
402
- An unresolved claim never becomes an approval again. Check the destination before creating a replacement action.
403
-
404
390
  ## Public Entrypoints
405
391
 
406
392
  Import from the narrowest entrypoint that has what you need. Each row gives the JSR name and the npm name.
@@ -412,17 +398,20 @@ Import from the narrowest entrypoint that has what you need. Each row gives the
412
398
  | `jsr:@theoremjs/agents/providers` / `@theoremjs/agents/providers` | Protocols, first-party providers, provider definitions, sign-in helpers, and vault contracts. |
413
399
  | `jsr:@theoremjs/agents/guardrails` / `@theoremjs/agents/guardrails` | Sanitization, canary/egress gates, public error mapping, inbound injection/sensitive-data primitives. |
414
400
  | `jsr:@theoremjs/agents/guardrails/testing` / `@theoremjs/agents/guardrails/testing` | Adversarial corpus + fuzz helpers (test/harness only). |
401
+ | `jsr:@theoremjs/agents/guardrails/compile` / `@theoremjs/agents/guardrails/compile` | Compiles a host's own detector patterns (`compileDetect`) and says why a pattern is slow on hostile text (`slowPatternProblem`). It imports `refa`, so use it at build time or server startup, not in a Worker. |
415
402
  | `jsr:@theoremjs/agents/observability` / `@theoremjs/agents/observability` | Trace sinks, trace record helpers and OTLP/JSON export. |
416
403
  | `jsr:@theoremjs/agents/observability/jsonl` / `@theoremjs/agents/observability/jsonl` | Optional file sink (`jsonlSink`): daily rotating JSONL through `node:fs`, kept out of browser and Worker bundles. |
417
404
  | `jsr:@theoremjs/agents/observability/openinference` / `@theoremjs/agents/observability/openinference` | Optional OpenInference usage names (reasoning tokens, cost) for Phoenix. |
418
405
  | `jsr:@theoremjs/agents/observability/phoenix` / `@theoremjs/agents/observability/phoenix` | Optional: eval results as Phoenix span annotations. |
419
406
  | `jsr:@theoremjs/agents/host` / `@theoremjs/agents/host` | Optional Deno HTTP helpers (`json`, status mapping, cutout mint flush). |
420
407
  | `jsr:@theoremjs/agents/interface` / `@theoremjs/agents/interface` | Headless interface projection of a profile (transcript, composer, gates) that UI packages such as `@theoremjs/react` render. |
408
+ | `jsr:@theoremjs/agents/surface` / `@theoremjs/agents/surface` | Surfaces: the agent sees and works in a client's screen through two tools, `look` and `act`. Secrets reach the agent only as cards. |
421
409
  | `jsr:@theoremjs/agents/cli` / `@theoremjs/agents/cli` | Profile inspection and stress-test CLI (`agents` binary on npm). |
422
410
  | `jsr:@theoremjs/agents/presets` / `@theoremjs/agents/presets` | Optional convenience packs (`registerGooglePreset`, …). |
423
411
  | `jsr:@theoremjs/agents/presets/google` / `@theoremjs/agents/presets/google` | Google builtins (search/maps/urlContext/codeExecution) as tool-list entries. |
424
412
  | `jsr:@theoremjs/agents/presets/google/speech-voices` / `@theoremjs/agents/presets/google/speech-voices` | Gemini TTS voice names for `speech.voice` (`GOOGLE_SPEECH_VOICES`); no registry imports. |
425
413
  | `jsr:@theoremjs/agents/schema` / `@theoremjs/agents/schema` | Profile vocabulary and field catalog (closed unions, field metadata) for host UIs and docs; no Deno APIs. |
414
+ | `jsr:@theoremjs/agents/evals` / `@theoremjs/agents/evals` | Graders that read trace records and write their results as trace records through the same sink. |
426
415
 
427
416
  The studio is a separate package, `@theoremjs/studio`. It is never part of the kernel package. Install it beside the kernel to open your project's agents in a local studio: `npm install -D @theoremjs/studio`, then `npx theorem-studio`. It needs Deno. The same package shows the studio as a screen in your own app, with one server handler. Its README has both.
428
417
 
@@ -445,7 +434,7 @@ Named exports from the root barrel (same symbols hosts get from `@theoremjs/agen
445
434
  | --- | --- |
446
435
  | Guardrails errors | `ERROR_KINDS`, `ErrorKind`, `ErrorCopy`, `ErrorCopies`, `errorKindSchema`, `errorCopiesSchema`, `TheoremError`, `TheoremErrorOptions`, `errorKind`, `publicError`, `toErrorEvent`, `describeError`, `isAbortError`, `throwIfAborted` |
447
436
  | Network guardrails | `assertSafeUrl`, `fetchGuarded`, `dnsOverHttpsResolver`, `isLocalhostName`, `isPrivateOrLocalAddress`, `GuardedFetchOptions`, `ResolveHost`, `DnsOverHttpsOptions` |
448
- | Guardrail vocabulary | `AdvisoryLevel`, `TrustLevel`, `GuardrailStage`, `Severity`, `GuardrailHit`, `Verdict`, `GuardrailAction`, `GuardrailContext`, `GuardrailEvent`, `guardrailEventSchema`, `OutboundPayload`, `Provenance`, `ToolOrigin`, `ScanText`, `BlockedReplySpec`, `BlockedReplyOnBlock`, `ResolvedBlockedReply`, `UrlAllow`, `NameAllow`, `UrlDetector`, `ResolvedAllow`, `ProfileGuardrailsSpec`, `HostGuardrailsSpec`, `NetworkGuardrailSpec`, `QuotaGuardrailSpec`, `ResolvedGuardrailPolicy`, `SensitiveGroup`, `SensitiveSelection`, `SensitiveSwitches`, `GuardedToolText`, `TurnTaint`, `TaintGate`, `TaintGuardrailSpec`, `TurnDestinations`, `DestinationGate`, `DestinationVerdict`, `TRUST_LEVELS`, `GUARDRAIL_STAGES`, `SEVERITIES`, `TOOL_ORIGINS` |
437
+ | Guardrail vocabulary | `AdvisoryLevel`, `TrustLevel`, `GuardrailStage`, `Severity`, `GuardrailHit`, `Verdict`, `GuardrailAction`, `GuardrailContext`, `GuardrailEvent`, `guardrailEventSchema`, `OutboundPayload`, `Provenance`, `ToolOrigin`, `ScanText`, `BlockedReplySpec`, `BlockedReplyOnBlock`, `ResolvedBlockedReply`, `UrlAllow`, `NameAllow`, `UrlDetector`, `ResolvedAllow`, `ProfileGuardrailsSpec`, `HostGuardrailsSpec`, `NetworkGuardrailSpec`, `QuotaGuardrailSpec`, `ResolvedGuardrailPolicy`, `DecisionGuardrailsSpec`, `DecisionDisclosureEnforcer`, `DecisionDisclosureVerdict`, `SensitiveGroup`, `SensitiveSelection`, `SensitiveSwitches`, `GuardedToolText`, `TurnTaint`, `TaintGate`, `TaintGuardrailSpec`, `TurnDestinations`, `DestinationGate`, `DestinationVerdict`, `TRUST_LEVELS`, `GUARDRAIL_STAGES`, `SEVERITIES`, `TOOL_ORIGINS` |
449
438
  | Guardrail policy | `resolveGuardrailPolicy`, `BLOCKED_REPLY_ON_BLOCK`, `hitRules`, `EGRESS_RULES` |
450
439
  | Tool boundary | `guardToolResult`, `guardToolFailureText`, `inspectToolArguments`, `toolCallEvent`, `wrapToolData`, `isRemoteOrigin`, `composeToolText`, `checkTaintGate`, `recordTaint`, `isTainted`, `isSuspicious`, `checkDestinationGate`, `turnDestinations`, `addGivenDestinations`, `addHistoryDestinations`, `addRequestDestinations`, `addResultDestinations`, `DESTINATION_GATES`, `directives`, `advisoryLevel`, `DIRECTIVE_SIGNALS`, `Directive`, `DirectiveSignal`, `ADVISORY_LEVELS`, `TOOL_CLOSE`, `TOOL_ORIGINS`, `TAINT_GATES`, `InspectedToolArguments`, `textForScan`, `scanTextOf` |
451
440
  | Quota | `QuotaSlotStatus`, `clientIp`, `quotaBusy`, `quotaExhausted`, `releaseSlot`, `resetSlots`, `skipQuota`, `takeSlot` |
@@ -460,7 +449,8 @@ Named exports from the root barrel (same symbols hosts get from `@theoremjs/agen
460
449
  | Schema | `PROFILE_FIELDS`, `PROFILE_GRAPH`, `PROFILE_TYPES`, `PROFILE_ID_MAX_CHARS`, `PROFILE_HANDLE_MAX_CHARS`, `EXTRA_FIELDS`, `REQUEST_FIELDS`, `API_EXPORTS`, `fieldMeta`, `catalogPathFor`, `DYNAMIC_FIELD_PARENTS`, `spineFacetsForProfileType`, `profileGraphFacet`, `ProfileGraphFacet`, `ProfileGraphFacetId`, `ProfileGraphEditor`, `ProfileGraphRole`, `THINKING_LEVELS`, `KEY_SLOT_NAME`, `isKeySlotName`, `MEDIA_INPUT_KINDS`, `MEDIA_INPUT_KIND_VALUES`, `MEDIA_WILDCARDS`, `ATTACHMENT_ACCEPT_MIMES`, `IMAGE_ATTACHMENT_ACCEPT_MIMES`, `VOICE_ACCEPT_MIMES`, `SUMMARY_MODES`, `STREAM_MODES`, `SPEECH_AUDIO_FORMATS`, `COMPACTION_METERS`, `COMPACTION_OUTCOMES`, `COMPACTION_TIMINGS`, `CACHE_MODES`, `CACHE_TTLS`, `TURN_STAGES`, `TURN_INJECT_STAGES`, `TURN_STOP_KINDS`, `CONTINUE_STOP_KINDS`, `TOOL_GATE_KINDS`, `AWAITING_USER_INPUT_KINDS`, `AWAITING_USER_INPUT_STATUS`, `TOOL_LOAD_TIERS`, `TOOL_ACCESS`, `TOOL_PERMISSION`, `TOOL_TYPES`, `AUTH_UNAUTHENTICATED_POLICIES`, `HTTP_METHODS`, `STUDIO_AUTH_TYPES`, `TOOL_AUTH_TYPES`, `AuthUnauthenticatedPolicy`, `CustomToolType`, `HttpMethod`, `StudioAuthType`, `ToolAccess`, `ToolAuthType`, `ToolPermission`, `ToolType`, `LIVE_ACTIVITY_HANDLINGS`, `LIVE_START_SENSITIVITIES`, `LIVE_END_SENSITIVITIES` |
461
450
  | Scope | `KernelScope`, `createKernelScope`, `defaultKernelScope`, `KernelRegistry`, `createKernelRegistry` |
462
451
  | Profiles | `ProfileDefinition`, `ProfileDefinitionBase`, `TextProfileDefinition`, `ImageProfileDefinition`, `SpeechProfileDefinition`, `LiveProfileDefinition`, `HostProfileDefinition`, `DecisionProfileDefinition`, `EmbeddingProfileDefinition`, `ProfileRegistry`, `createProfileRegistry`, `clearProfiles`, `defineProfile`, `getProfile`, `hasProfile`, `listProfiles`, `registerProfile`, `registerProfiles`, `projectProfile`, `resolveTurn`, `pickModel` |
463
- | Tools | `ToolContext`, `GateReference`, `gateReferenceSchema`, `ToolRegistry`, `createToolRegistry`, `registerTool`, `registerTools`, `invokeTool`, `invokeToolStep`, `runTool`, `runToolStep`, `SettledToolCall`, `ToolCallResult`, `ToolStepCallResult`, `GATE_DECISIONS`, `GateDecision`, `askUserTool`, `registerHarnessTools`, `getTool`, `hasTool`, `requireTool`, `listTools`, `resetTools`, `formatToolResult`, `prepareTurnToolSnapshot`, `buildHttpToolTarget`, `executeHttpTool`, `executeMcpTool`, `parseMcpRpcResponse`, `isUnsupportedMcpProtocolError`, `MCP_PROTOCOL_VERSIONS`, `mcpRequestMeta`, `McpProtocolVersion`, `resolveToolAuth`, `credentialPlacement`, `withCredentialQuery`, `CredentialQuery`, `uncheckedOutput`, `UncheckedOutput` |
452
+ | Tools | `ToolContext`, `GateReference`, `gateReferenceSchema`, `ToolRegistry`, `createToolRegistry`, `registerTool`, `registerTools`, `invokeTool`, `invokeToolStep`, `runTool`, `runToolStep`, `SettledToolCall`, `ToolCallResult`, `ToolStepCallResult`, `GATE_DECISIONS`, `GateDecision`, `askUserTool`, `registerHarnessTools`, `getTool`, `hasTool`, `requireTool`, `listTools`, `resetTools`, `formatToolResult`, `prepareTurnToolSnapshot`, `buildHttpToolTarget`, `executeHttpTool`, `executeMcpTool`, `parseMcpRpcResponse`, `isUnsupportedMcpProtocolError`, `MCP_PROTOCOL_VERSIONS`, `mcpRequestMeta`, `McpProtocolVersion`, `resolveToolAuth`, `credentialPlacement`, `withCredentialQuery`, `CredentialQuery`, `uncheckedOutput`, `UncheckedOutput`, `McpRpcResponse`, `ModelToolResult`, `PageAnswer` |
453
+ | Tool execution | `ToolExecutionCoordinator`, `createMemoryToolExecution`, `MemoryToolExecutionOptions`, `authorizeToolReplay`, `BeginToolOperationResult`, `ToolOperationClaim`, `ToolOperationIdentity`, `ToolOperationOutcome`, `ToolOperationUncertainty`, `toolOperationOutcomeSchema`, `SavedToolInvocation`, `savedToolInvocationSchema`, `toolInvocationHash`, `TerminalToolEvent`, `PageToolDispatch`, `PageToolExecution` |
464
454
  | Structured | `SchemaRegistry`, `createSchemaRegistry`, `getStructured`, `registerStructured` |
465
455
  | Stop / resume | `ProfileTurnBehaviourSpec`, `MediaTurnBehaviourSpec`, `ProfileTurnResumptionSpec`, `TurnContinueFrom`, `TurnStop`, `TurnStopKind`, `ContinueStopKind`, `StepLimitMode`, `CONTINUE_STOP_KINDS`, `AUTO_CONTINUE_DELAY_MS`, `DEFAULT_ALLOW_CONTINUE`, `DEFAULT_AUTO_CONTINUE`, `GenerationStopError`, `isContinueStopKind`, `isGenerationStopError`, `isResumeableStop`, `isUserCancelledStop`, `profileAllowsSteering`, `profileAllowsInject`, `profileTurnResumption`, `shouldAutoContinue`, `turnStopFromClientStreamEnd`, `turnStopFromInteractionStatus`, `turnStopFromOpenAiFinishReason` |
466
456
  | Stages (target foundation) | `TURN_STAGES`, `TURN_INJECT_STAGES`, `STAGE_AFFORDANCES`, `STAGE_AFFORDANCE_MATRIX`, `TOOL_GATE_KINDS`, `AWAITING_USER_INPUT_KINDS`, `AWAITING_USER_INPUT_STATUS`, `applyStageResult`, `awaitingUserInputSchema`, `toolGateSchema`, `isTurnStage`, `isTurnInjectStage`, `isToolGateKind`, `isAwaitingUserInput`, `stageAllowsAffordance`, `stageEventFields`, `profileAllowsInject`, `StageAffordance`, `StageContext`, `StageResult`, `StageMutate`, `StageHandler`, `StageApplyInput`, `StageApplyOutput`, `StageApplyWarning`, `StageApplyWarningCode`, `StageEventExtra`, `AwaitingUserInput`, `ToolGate` — contract [`docs/contracts/stages.md`](https://github.com/masudl-hub/theoremai/blob/main/docs/contracts/stages.md) |
@@ -468,13 +458,14 @@ Named exports from the root barrel (same symbols hosts get from `@theoremjs/agen
468
458
  | Observability | `memorySink`, `noopSink`, `readTraceparent`, `writeTrace`, `buildRecord`, `traceRecordSchema`, `contentOf`, `inlineContent`, `toOtlpJson`, `startTrace`, `traceContent`, `traceBytes`, `traceJson`, `registerTraceDestination`, `requireTraceDestination`, `getTraceDestination`, `listTraceDestinationIds`, `clearTraceDestinations`, `isTraceSink`, `resolveTraceWriter`, `resolveObservabilityPolicy`, `traceSpanMeta`, `traceAttributeMeta`, `traceEventMeta`, `traceEventAttributeMeta`, `TRACE_ATTRIBUTE_GROUPS`, `TRACE_STATUS`, `TRACE_FIELDS`, `TRACE_SPAN_TYPES`, `TraceSpanMeta`, `TraceSpanType`, `TraceAttributeMeta`, `TraceEventMeta`, `TraceOptionMeta`, `TraceAttributeGroup`, `TraceValueFormat`, `TraceRecord`, `TraceSink`, `TraceWriteContext`, `TraceSpan`, `TraceSpanEvent`, `TraceSpanKind`, `TraceSpanLink`, `TraceSpanStatus`, `TraceAttributes`, `TraceAttributeValue`, `TraceContent`, `TraceBytes`, `TraceJson`, `TraceTree`, `SpanHandle`, `SpanOptions`, `SpanLinkInput`, `TraceClock`, `ProfileObservabilitySpec`, `ResolvedObservabilityPolicy`, `ResolvedTraceInclude`, `ResolvedTraceScrub`, `TraceIncludeSpec`, `TraceScrubSpec`, `ScrubSwitch`, `ResolvedScrubSwitch`, `OtlpTraceRequest`, `OtlpSpan`, `OtlpKeyValue`, `OtlpAnyValue` (file sink: `@theoremjs/agents/observability/jsonl` → `jsonlSink`, `JsonlSinkOptions`) |
469
459
  | Providers | `registerProvider`, `getProvider`, `listProviders`, `listProtocols`, `createProviderRegistry`, `defineProvider`, `Provider`, `ProviderDefinition`, `Registration`, `RegisteredProvider`, `ProviderRegistry`, `geminiProvider`, `openRouterProvider`, `typesafeProvider`, `localProvider` |
470
460
  | Provider sign-in | `bearer`, `header`, `query`, `Authorize`, `Signer`, `Outgoing`, `Serve`, `endpointUrlSchema` |
471
- | Protocols | `defineProtocol`, `extendProtocol`, `carriesTo`, `modelOptions`, `geminiInteractions`, `geminiLive`, `geminiEmbedding`, `openAi`, `openRouterOpenAi`, `openRouterImages`, `openRouterDecisions`, `openRouterEmbedding`, `ollamaEmbedding`, `systemOne`, `Protocol`, `ProtocolDefinition`, `ProtocolExtension`, `ExtensionBodyAt`, `ExtensionReader`, `ModelProfileType` |
461
+ | Protocols | `defineProtocol`, `extendProtocol`, `carriesTo`, `modelOptions`, `geminiInteractions`, `geminiLive`, `geminiEmbeddings`, `openAi`, `openRouterOpenAi`, `openRouterImages`, `openRouterDecisions`, `openRouterEmbeddings`, `ollamaEmbeddings`, `systemOne`, `Protocol`, `ProtocolDefinition`, `ProtocolExtension`, `ExtensionBodyAt`, `ExtensionReader`, `ModelProfileType` |
472
462
  | Protocol operations | `ProtocolContext`, `ProtocolOperations`, `ProtocolRequest`, `ProtocolSession`, `ProtocolSocket`, `ProtocolEvent`, `ProtocolContentEvent`, `SendInit`, `UpstreamFailure`, `Continuation`, `DecisionCall`, `EmbeddingCall`, `LiveToolResult`, `callProtocol`, `ProtocolCall`, `CallSetup` |
473
463
  | Model bindings | `ModelBinding`, `DecisionModelBinding`, `modelBindingSchema`, `decisionModelBindingSchema`, `commonModelSettingsSchema`, `keySlotSchema`, `checkBinding`, `keySlotsOf`, `RuntimeAbilities` |
474
464
  | Provider data | `JsonValue`, `JsonObject`, `jsonValueSchema`, `jsonObjectSchema`, `PriceTable`, `ModelPrice`, `Rates` |
475
465
  | Provider vault | `ProviderHostOptions`, `KeyVault`, `KeyCredential`, `CredentialContext`, `CredentialResolver`, `OpenWebSocket`, `plainWebSocket` |
476
466
  | Provider output | `ProviderCheckpoint`, `ProviderWarning`, `ProviderStateRebuilt`, `ProviderContinuationPolicy`, `providerCheckpointSchema`, `providerWarningSchema`, `providerContinuationSchema` |
477
- | Provider limits | `ProviderLimits`, `providerLimitsSchema`, `resolveProviderLimits` |
467
+ | Provider limits | `ProviderLimits`, `providerLimitsSchema` |
468
+ | Re-exported whole | Everything that `@theoremjs/agents/providers` and `@theoremjs/agents/presets` export. Their contracts list each name. |
478
469
 
479
470
  </details>
480
471
 
@@ -512,7 +503,7 @@ Four properties are invariant. Where a test or a lint checks one, the table name
512
503
  | P3 | No buried policy — behavioral defaults are declared profile-schema fields | `PROFILE_FIELDS` / schema |
513
504
  | P4 | Inert extras — optional entrypoints removable without behavior change | publish-bundle gate excludes `studio/` |
514
505
 
515
- Provider definition and registration validate without opening transport (`src/kernel/protocols/provider.ts`). A protocol never holds a key, a key slot, or a base URL. The kernel signs each request with the registered provider's sign-in. A protocol that extends another, as `openRouterDecisions` extends `systemOne`, has its own id, and a binding names the one its provider serves.
506
+ Provider definition and registration validate without opening transport (`src/kernel/protocols/provider.ts`). A protocol never holds a key, a key slot, or a base URL. The kernel signs each request with the registered provider's sign-in. A protocol that extends another, as `openRouterOpenAi` extends `openAi`, has its own id, and a binding names the one its provider serves.
516
507
 
517
508
  Domain rules, delivery policy, product copy, database access, and session memory belong in your application, not in Theorem.
518
509
 
@@ -534,10 +525,29 @@ MIT License. Copyright (c) ORCHID AI LLC.
534
525
  { "kind": "contract_test", "path": "tests/kernel/theorem.test.ts" }
535
526
  ]
536
527
  },
528
+ "Profile types": {
529
+ "supports": [
530
+ { "kind": "source", "path": "src/kernel/schema.ts" },
531
+ { "kind": "contract_test", "path": "tests/docs/readme.test.ts" }
532
+ ]
533
+ },
534
+ "Guardrails": {
535
+ "supports": [
536
+ { "kind": "source", "path": "src/guardrails/types.ts" },
537
+ { "kind": "contract_test", "path": "tests/docs/readme.test.ts" }
538
+ ]
539
+ },
540
+ "Providers": {
541
+ "supports": [
542
+ { "kind": "source", "path": "src/providers/mod.ts" },
543
+ { "kind": "contract_test", "path": "tests/docs/readme.test.ts" }
544
+ ]
545
+ },
537
546
  "Public Entrypoints": {
538
547
  "supports": [
539
- { "kind": "config", "path": "package.json" },
540
- { "kind": "contract_test", "path": "scripts/docs-truth/graph.test.mjs" }
548
+ { "kind": "config", "path": "deno.json" },
549
+ { "kind": "source", "path": "scripts/docs-truth/export-drift.mjs" },
550
+ { "kind": "contract_test", "path": "scripts/docs-truth/export-drift.test.mjs" }
541
551
  ]
542
552
  },
543
553
  "Package Boundary": {
package/esm/mod.d.ts CHANGED
@@ -14,7 +14,7 @@ export { guardrailFromHits, guardrailFromVerdict, guardrailTurnEvent, projectGua
14
14
  export { hitFromSpan, projectGuardrailEvent, } from './src/guardrails/hits.js';
15
15
  export type { ClientLabelKey, LabelKey, LabelOverrides, LabelParams, } from './src/guardrails/labels.js';
16
16
  export { CLIENT_LABEL_KEYS, LABEL_KEYS, labelDefault, labelText, overrideLabels, resetLabels, } from './src/guardrails/labels.js';
17
- export type { AdvisoryLevel, BlockedReplyOnBlock, BlockedReplySpec, CanaryGateResult, CanaryGateSession, CanaryStreamGate, DestinationGate, DestinationVerdict, Directive, DirectiveSignal, GivenUrls, GuardedToolText, GuardrailAction, GuardrailContext, GuardrailEvent, GuardrailHit, GuardrailStage, HostGuardrailsSpec, InspectedToolArguments, LiveHeldOutput, LiveOutboundBatchResult, LiveOutboundGateSession, NameAllow, NetworkGuardrailSpec, OutboundPayload, ProfileGuardrailsSpec, ProgressiveYieldGate, ProgressiveYieldGateOptions, ProgressiveYieldResult, Provenance, QuotaGuardrailSpec, ResolvedGuardrailPolicy, ScanText, SensitiveGroup, SensitiveSelection, SensitiveSwitches, Severity, TaintGate, TaintGuardrailSpec, ToolOrigin, TrustLevel, TurnDestinations, TurnTaint, UrlAllow, Verdict, } from './src/guardrails/mod.js';
17
+ export type { AdvisoryLevel, BlockedReplyOnBlock, BlockedReplySpec, CanaryGateResult, CanaryGateSession, CanaryStreamGate, DecisionDisclosureEnforcer, DecisionDisclosureVerdict, DecisionGuardrailsSpec, DestinationGate, DestinationVerdict, Directive, DirectiveSignal, GivenUrls, GuardedToolText, GuardrailAction, GuardrailContext, GuardrailEvent, GuardrailHit, GuardrailStage, HostGuardrailsSpec, InspectedToolArguments, LiveHeldOutput, LiveOutboundBatchResult, LiveOutboundGateSession, NameAllow, NetworkGuardrailSpec, OutboundPayload, ProfileGuardrailsSpec, ProgressiveYieldGate, ProgressiveYieldGateOptions, ProgressiveYieldResult, Provenance, QuotaGuardrailSpec, ResolvedGuardrailPolicy, ScanText, SensitiveGroup, SensitiveSelection, SensitiveSwitches, Severity, TaintGate, TaintGuardrailSpec, ToolOrigin, TrustLevel, TurnDestinations, TurnTaint, UrlAllow, Verdict, } from './src/guardrails/mod.js';
18
18
  export { ADVISORY_LEVELS, abortLiveOutboundTurn, addGivenDestinations, addHistoryDestinations, addRequestDestinations, addResultDestinations, BLOCKED_REPLY_ON_BLOCK, bindCanary, checkDestinationGate, checkTaintGate, composeToolText, createCanaryGateSession, createCanaryStreamGate, createLiveOutboundGateSession, createOutboundProgressiveGate, createProgressiveYieldGate, DESTINATION_GATES, DIRECTIVE_SIGNALS, directives, EGRESS_RULES, eventHasCanary, filterCanaryGatedEvents, finalizeLiveOutboundTurn, GUARDRAIL_STAGES, guardToolFailureText, guardToolResult, hitRules, inspectToolArguments, isRemoteOrigin, isSuspicious, isTainted, mintCanary, OMIT_CANARY, processLiveOutboundBatch, recordTaint, redactCanary, resolveGuardrailPolicy, SEVERITIES, scanTextForCanaryLeak, scanTextOf, TAINT_GATES, TOOL_CLOSE, TOOL_ORIGINS, TRUST_LEVELS, textForScan, toolCallEvent, turnDestinations, wrapToolData, wrapUserData, } from './src/guardrails/mod.js';
19
19
  export type { DnsOverHttpsOptions, GuardedFetchOptions, ResolveHost, } from './src/guardrails/network.js';
20
20
  export { assertSafeUrl, dnsOverHttpsResolver, fetchGuarded, isLocalhostName, isPrivateOrLocalAddress, } from './src/guardrails/network.js';
@@ -39,7 +39,7 @@ export type { ProfileGraphEditor, ProfileGraphFacet, ProfileGraphFacetId, Profil
39
39
  export { PROFILE_GRAPH, profileGraphFacet, spineFacetsForProfileType, } from './src/kernel/profile-graph.js';
40
40
  export type { RuntimeAbilities } from './src/kernel/protocols/binding.js';
41
41
  export { checkBinding, commonModelSettingsSchema, decisionModelBindingSchema, keySlotSchema, keySlotsOf, modelBindingSchema, } from './src/kernel/protocols/binding.js';
42
- export { providerLimitsSchema, resolveProviderLimits } from './src/kernel/protocols/limits.js';
42
+ export { providerLimitsSchema } from './src/kernel/protocols/limits.js';
43
43
  export type { CallSetup, ProtocolCall } from './src/kernel/protocols/runtime.js';
44
44
  export { callProtocol, plainWebSocket } from './src/kernel/protocols/runtime.js';
45
45
  export type { AttachmentFacts, AttachmentRules } from './src/kernel/registry/attachments.js';
package/esm/mod.js CHANGED
@@ -26,7 +26,7 @@ export { loadTokenEstimator, TOKEN_TEXT_ENCODING, } from './src/kernel/engine/to
26
26
  export { sumTokens } from './src/kernel/engine/usage.js';
27
27
  export { PROFILE_GRAPH, profileGraphFacet, spineFacetsForProfileType, } from './src/kernel/profile-graph.js';
28
28
  export { checkBinding, commonModelSettingsSchema, decisionModelBindingSchema, keySlotSchema, keySlotsOf, modelBindingSchema, } from './src/kernel/protocols/binding.js';
29
- export { providerLimitsSchema, resolveProviderLimits } from './src/kernel/protocols/limits.js';
29
+ export { providerLimitsSchema } from './src/kernel/protocols/limits.js';
30
30
  export { callProtocol, plainWebSocket } from './src/kernel/protocols/runtime.js';
31
31
  export { assertTurnAttachments, attachmentIssueCopy, attachmentIssues, attachmentIssueText, attachmentsRefused, base64Bytes, maxBytesForMime, requireMediaLimits, resolveMediaLimits, sanitizeCsvText, sanitizeTurnBlobs, } from './src/kernel/registry/attachments.js';
32
32
  export { clampThinkingLevel, clampThinkingLevelForApiId, mediaChannelForMime, mediaKindForMime, mimeAllowed, mimeEssence, modelEntryByApiId, requireModelBinding, } from './src/kernel/registry/catalog.js';
@@ -40,6 +40,6 @@ declare function shannonEntropy(data: string): number;
40
40
  /** The secrets `rules` find in `text`, each one's span. */
41
41
  declare function credentialSpans(text: string, rules: readonly CredentialRule[], global: CredentialAllowlist): RedactSpan[];
42
42
  /** Whether a match of `rule` at `at` of `text` is a credential gitleaks reports. */
43
- declare function credentialHit(rule: CredentialRule, global: CredentialAllowlist, match: string, text: string, at: number): boolean;
43
+ declare function credentialHit(rule: CredentialRule, global: CredentialAllowlist, match: string, text: string, at: number, lower?: string): boolean;
44
44
  export type { CredentialAllowlist, CredentialRule };
45
45
  export { credentialHit, credentialSpans, shannonEntropy };
@@ -78,18 +78,35 @@ function sha256Hex(text) {
78
78
  }
79
79
  return [...h].map((x) => x.toString(16).padStart(8, '0')).join('');
80
80
  }
81
- function allowed(list, read) {
82
- const target = read[list.target];
83
- const lower = read.secret.toLowerCase();
84
- return ((target !== '' && list.regexes.some((regex) => regex.test(target))) ||
85
- (target !== '' && list.hashes.length > 0 && list.hashes.includes(sha256Hex(target))) ||
86
- (lower !== '' && list.stopwords.some((word) => lower.includes(word))));
81
+ function scanOf(text) {
82
+ return { text, lines: new Map() };
83
+ }
84
+ function targetAllowed(list, target) {
85
+ return (target !== '' &&
86
+ (list.regexes.some((regex) => regex.test(target)) ||
87
+ (list.hashes.length > 0 && list.hashes.includes(sha256Hex(target)))));
87
88
  }
88
- /** The lines of `text` the stretch [start, end) is on. */
89
- function linesOf(text, start, end) {
90
- const from = text.lastIndexOf('\n', start - 1) + 1;
91
- const to = text.indexOf('\n', end);
92
- return text.slice(from, to < 0 ? text.length : to);
89
+ function allowed(list, read, scan) {
90
+ const lower = read.secret.toLowerCase();
91
+ if (lower !== '' && list.stopwords.some((word) => lower.includes(word)))
92
+ return true;
93
+ if (list.target !== 'line')
94
+ return targetAllowed(list, read[list.target]);
95
+ const from = scan.text.lastIndexOf('\n', read.start - 1) + 1;
96
+ const found = scan.text.indexOf('\n', read.end);
97
+ const to = found < 0 ? scan.text.length : found;
98
+ let answers = scan.lines.get(list);
99
+ if (!answers) {
100
+ answers = new Map();
101
+ scan.lines.set(list, answers);
102
+ }
103
+ const key = `${from}:${to}`;
104
+ let answer = answers.get(key);
105
+ if (answer === undefined) {
106
+ answer = targetAllowed(list, scan.text.slice(from, to));
107
+ answers.set(key, answer);
108
+ }
109
+ return answer;
93
110
  }
94
111
  /** Whether one of `rule`'s keywords is in `lower`, the text in lower case, by `end`. */
95
112
  function keywordsIn(rule, lower, end = lower.length) {
@@ -113,7 +130,7 @@ function finder(pattern) {
113
130
  * Where the secret in `rule`'s match at `at` of `text` is, or undefined when
114
131
  * gitleaks would let the match through. Keywords are not read here.
115
132
  */
116
- function credentialSecret(rule, global, text, match, at) {
133
+ function credentialSecret(rule, global, text, match, at, scan = scanOf(text)) {
117
134
  let lead = 0;
118
135
  while (match[lead] === '\n')
119
136
  lead++;
@@ -137,11 +154,12 @@ function credentialSecret(rule, global, text, match, at) {
137
154
  const read = {
138
155
  secret: text.slice(secret.start, secret.end),
139
156
  match: trimmed,
140
- line: linesOf(text, start, start + trimmed.length),
157
+ start,
158
+ end: start + trimmed.length,
141
159
  };
142
160
  if (rule.entropy !== undefined && shannonEntropy(read.secret) <= rule.entropy)
143
161
  return undefined;
144
- if (allowed(global, read) || rule.allowlists.some((list) => allowed(list, read))) {
162
+ if (allowed(global, read, scan) || rule.allowlists.some((list) => allowed(list, read, scan))) {
145
163
  return undefined;
146
164
  }
147
165
  return secret.end > secret.start ? secret : { start, end: start + trimmed.length };
@@ -149,6 +167,7 @@ function credentialSecret(rule, global, text, match, at) {
149
167
  /** The secrets `rules` find in `text`, each one's span. */
150
168
  function credentialSpans(text, rules, global) {
151
169
  const lower = text.toLowerCase();
170
+ const scan = scanOf(text);
152
171
  const spans = [];
153
172
  for (const rule of rules) {
154
173
  if (!keywordsIn(rule, lower))
@@ -157,7 +176,7 @@ function credentialSpans(text, rules, global) {
157
176
  for (const match of text.matchAll(pattern)) {
158
177
  if (match[0] === '' || !keywordsIn(rule, lower, match.index + match[0].length))
159
178
  continue;
160
- const secret = credentialSecret(rule, global, text, match[0], match.index);
179
+ const secret = credentialSecret(rule, global, text, match[0], match.index, scan);
161
180
  if (secret)
162
181
  spans.push({ ...secret, kind: 'sensitive' });
163
182
  }
@@ -165,8 +184,12 @@ function credentialSpans(text, rules, global) {
165
184
  return spans;
166
185
  }
167
186
  /** Whether a match of `rule` at `at` of `text` is a credential gitleaks reports. */
168
- function credentialHit(rule, global, match, text, at) {
169
- return (keywordsIn(rule, text.slice(0, at + match.length).toLowerCase()) &&
170
- credentialSecret(rule, global, text, match, at) !== undefined);
187
+ function credentialHit(rule, global, match, text, at, lower) {
188
+ const end = at + match.length;
189
+ // why: Keywords are ASCII, and only `İ` changes length when lowered, so a lowering as long as the text keeps every index.
190
+ const said = lower?.length === text.length
191
+ ? keywordsIn(rule, lower, end)
192
+ : keywordsIn(rule, text.slice(0, end).toLowerCase());
193
+ return said && credentialSecret(rule, global, text, match, at) !== undefined;
171
194
  }
172
195
  export { credentialHit, credentialSpans, shannonEntropy };
@@ -83,10 +83,14 @@ function hostOf(link) {
83
83
  }
84
84
  function destinationsIn(text) {
85
85
  const emails = emailsIn(text);
86
- let rest = text;
86
+ const pieces = [];
87
+ let at = 0;
87
88
  for (const { start, end } of emails) {
88
- rest = rest.slice(0, start) + ' '.repeat(end - start) + rest.slice(end);
89
+ pieces.push(text.slice(at, start), ' '.repeat(end - start));
90
+ at = end;
89
91
  }
92
+ pieces.push(text.slice(at));
93
+ const rest = pieces.join('');
90
94
  const found = [...emails];
91
95
  for (const match of [...rest.matchAll(LINK)]) {
92
96
  const host = hostOf(match[0]);
@@ -42,8 +42,6 @@ declare const SCOPED: readonly ["canary_leak", "prompt_leak"];
42
42
  declare function isScoped(detector: Detector): detector is (typeof SCOPED)[number];
43
43
  /** The host's own detectors that read `boundary`. */
44
44
  declare function hostAt(boundary: Boundary, detect: ResolvedDetect): HostDetector[];
45
- /** Refuses a text the person or host sent that is over `MAX_INBOUND_CHARS`; `where` names it for the log. */
46
- declare function refuseOverlong(text: string, where: string): void;
47
45
  /** Reads `text` as it crosses `boundary` under the profile's resolved matrix. */
48
46
  declare function detectAt(text: string, boundary: Boundary, detect: ResolvedDetect, scope?: DetectScope): Detection;
49
47
  /** The detectors that read `boundary`: every one not set to `ignore`. */
@@ -104,4 +102,4 @@ declare function boundaryReader(boundary: Boundary, detect: ResolvedDetect): Bou
104
102
  */
105
103
  declare function detectEvent(boundary: Boundary, found: Pick<Detection, 'action' | 'hits'>, provenance?: Provenance, stage?: GuardrailStage): GuardrailEvent | undefined;
106
104
  export type { BoundaryReader, Detection, DetectOutcome, DetectScope, Release, StoredSides, StreamRead, Stretch, };
107
- export { boundaryReader, detectAt, detectEvent, detectorsAt, detectReads, detectRelease, hostAt, isScoped, leakScopeOf, redactDetectors, refuseOverlong, scopeOf, storedHostSpans, storedSpans, stronger, };
105
+ export { boundaryReader, detectAt, detectEvent, detectorsAt, detectReads, detectRelease, hostAt, isScoped, leakScopeOf, redactDetectors, scopeOf, storedHostSpans, storedSpans, stronger, };
@@ -1,7 +1,7 @@
1
1
  // invariant: The one place a match becomes an action. Every boundary calls `detectAt`; nothing
2
2
  // else reads the matrix to decide what a match does.
3
3
  import { applySpans, spansFromPatterns } from '../observability/spans.js';
4
- import { INBOUND_BOUNDARIES, recordOf, TOOL_BOUNDARIES } from './boundaries.js';
4
+ import { recordOf, TOOL_BOUNDARIES } from './boundaries.js';
5
5
  import { canaryLeakRanges } from './canary.js';
6
6
  import { actionAt, DETECTORS, detects, } from './detectors.js';
7
7
  import { boundaryNote } from './egress.js';
@@ -13,7 +13,6 @@ import { promptEchoRanges } from './prompt-echo.js';
13
13
  import { DETECT_RULES, detectRule } from './rules.js';
14
14
  import { sensitiveSpans } from './sensitive.js';
15
15
  import { SENSITIVE_GROUPS } from './sensitive-groups.js';
16
- import { TheoremError } from './theorem-error.js';
17
16
  import { directives, mergedRanges } from './tool-directives.js';
18
17
  import { ownToolsWithout, toolLeakSpans } from './tool-leak.js';
19
18
  const STRENGTH = ['allow', 'flag', 'redact', 'block'];
@@ -208,25 +207,8 @@ function failedHit({ rule, label }, signal) {
208
207
  ...(signal === undefined ? {} : { signal }),
209
208
  };
210
209
  }
211
- /**
212
- * The longest single text the person or host may send, in characters: far over any model's
213
- * context, and under what the detectors read safely.
214
- */
215
- const MAX_INBOUND_CHARS = 1_000_000;
216
- const INBOUND = new Set(INBOUND_BOUNDARIES);
217
- /** Refuses a text the person or host sent that is over `MAX_INBOUND_CHARS`; `where` names it for the log. */
218
- function refuseOverlong(text, where) {
219
- if (text.length <= MAX_INBOUND_CHARS)
220
- return;
221
- // labels-exempt: internal diagnostic; the person reads `input.too_long`
222
- throw new TheoremError('input', `${where} text of ${text.length} characters is over ${MAX_INBOUND_CHARS}`, {
223
- copy: { key: 'input.too_long', params: { maxChars: MAX_INBOUND_CHARS } },
224
- });
225
- }
226
210
  /** Reads `text` as it crosses `boundary` under the profile's resolved matrix. */
227
211
  function detectAt(text, boundary, detect, scope = NO_SCOPE) {
228
- if (INBOUND.has(boundary))
229
- refuseOverlong(text, boundary);
230
212
  const hits = [];
231
213
  const redact = [];
232
214
  let action = 'allow';
@@ -242,7 +224,8 @@ function detectAt(text, boundary, detect, scope = NO_SCOPE) {
242
224
  for (const span of spans)
243
225
  hits.push(reader.hit(text, span));
244
226
  if (reader.chosen === 'redact')
245
- redact.push(...spans);
227
+ for (const span of spans)
228
+ redact.push(span);
246
229
  action = stronger(action, reader.chosen);
247
230
  }
248
231
  if (action === 'block')
@@ -382,7 +365,8 @@ function boundaryReader(boundary, detect) {
382
365
  return {
383
366
  read(text) {
384
367
  const detected = detectAt(text, boundary, detect);
385
- hits.push(...detected.hits);
368
+ for (const hit of detected.hits)
369
+ hits.push(hit);
386
370
  action = stronger(action, detected.action);
387
371
  return detected.text ?? '';
388
372
  },
@@ -431,4 +415,4 @@ function detectEvent(boundary, found, provenance, stage = BOUNDARY_STAGE[boundar
431
415
  ...(provenance ? { provenance } : {}),
432
416
  };
433
417
  }
434
- export { boundaryReader, detectAt, detectEvent, detectorsAt, detectReads, detectRelease, hostAt, isScoped, leakScopeOf, redactDetectors, refuseOverlong, scopeOf, storedHostSpans, storedSpans, stronger, };
418
+ export { boundaryReader, detectAt, detectEvent, detectorsAt, detectReads, detectRelease, hostAt, isScoped, leakScopeOf, redactDetectors, scopeOf, storedHostSpans, storedSpans, stronger, };