@kindgi/guardrails 0.0.0-bootstrap.0 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +78 -1
  3. package/dist/action-handler.d.ts +82 -0
  4. package/dist/action-handler.d.ts.map +1 -0
  5. package/dist/action-handler.js +120 -0
  6. package/dist/action-handler.js.map +1 -0
  7. package/dist/checks.d.ts +9 -0
  8. package/dist/checks.d.ts.map +1 -0
  9. package/dist/checks.js +235 -0
  10. package/dist/checks.js.map +1 -0
  11. package/dist/define-check.d.ts +93 -0
  12. package/dist/define-check.d.ts.map +1 -0
  13. package/dist/define-check.js +110 -0
  14. package/dist/define-check.js.map +1 -0
  15. package/dist/define.d.ts +27 -0
  16. package/dist/define.d.ts.map +1 -0
  17. package/dist/define.js +126 -0
  18. package/dist/define.js.map +1 -0
  19. package/dist/engine.d.ts +49 -0
  20. package/dist/engine.d.ts.map +1 -0
  21. package/dist/engine.js +198 -0
  22. package/dist/engine.js.map +1 -0
  23. package/dist/errors.d.ts +91 -0
  24. package/dist/errors.d.ts.map +1 -0
  25. package/dist/errors.js +4 -0
  26. package/dist/errors.js.map +1 -0
  27. package/dist/execution-strategy.d.ts +80 -0
  28. package/dist/execution-strategy.d.ts.map +1 -0
  29. package/dist/execution-strategy.js +96 -0
  30. package/dist/execution-strategy.js.map +1 -0
  31. package/dist/guardrail.schema.json +261 -0
  32. package/dist/index.d.ts +16 -0
  33. package/dist/index.d.ts.map +1 -0
  34. package/dist/index.js +11 -0
  35. package/dist/index.js.map +1 -0
  36. package/dist/judge.d.ts +31 -0
  37. package/dist/judge.d.ts.map +1 -0
  38. package/dist/judge.js +171 -0
  39. package/dist/judge.js.map +1 -0
  40. package/dist/types.d.ts +407 -0
  41. package/dist/types.d.ts.map +1 -0
  42. package/dist/types.js +11 -0
  43. package/dist/types.js.map +1 -0
  44. package/package.json +64 -4
  45. package/src/action-handler.ts +179 -0
  46. package/src/checks.ts +236 -0
  47. package/src/define-check.ts +207 -0
  48. package/src/define.ts +146 -0
  49. package/src/engine.ts +271 -0
  50. package/src/errors.ts +107 -0
  51. package/src/execution-strategy.ts +184 -0
  52. package/src/guardrail.schema.json +261 -0
  53. package/src/index.ts +79 -0
  54. package/src/judge.ts +221 -0
  55. package/src/types.ts +455 -0
package/src/types.ts ADDED
@@ -0,0 +1,455 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (C) 2026 Kindgi Inc.
3
+
4
+ import type {
5
+ Capability,
6
+ ModelProvider,
7
+ ProviderRegistry,
8
+ TenantPolicy,
9
+ } from '@kindgi/capabilities';
10
+ import type { ComplianceProvider } from '@kindgi/compliance';
11
+ import type {
12
+ AgentId,
13
+ FlowId,
14
+ GuardrailId,
15
+ ProjectId,
16
+ RunId,
17
+ TenantId,
18
+ Timestamp,
19
+ ToolId,
20
+ } from '@kindgi/types';
21
+
22
+ /**
23
+ * How a guardrail is checked. Open string — the engine dispatches
24
+ * on this value through an `ExecutionStrategyRegistry`. Built-in kinds
25
+ * are `'zero-llm' | 'llm-judge' | 'external'` (see
26
+ * `execution-strategy.ts`). Adapter packages register strategies for
27
+ * their own kinds without touching the engine — e.g. `'sandbox-code'`.
28
+ *
29
+ * The `BUILT_IN_GUARDRAIL_KINDS` constant enumerates the well-known values
30
+ * (the schema lists them as `examples`); the schema accepts any non-empty
31
+ * kind. `defineGuardrail` still requires a registered check of the same
32
+ * kind, and runtime dispatch uses the registered strategies, not this
33
+ * union.
34
+ */
35
+ export type GuardrailKind = string;
36
+
37
+ export const BUILT_IN_GUARDRAIL_KINDS = ['zero-llm', 'llm-judge', 'external'] as const;
38
+ export type BuiltInGuardrailKind = (typeof BUILT_IN_GUARDRAIL_KINDS)[number];
39
+
40
+ export type GuardrailSeverity = 'info' | 'warn' | 'error' | 'critical';
41
+
42
+ /**
43
+ * Actions the engine surfaces when a check fails. Open string — the
44
+ * caller (e.g. the agent runtime) matches on this to decide behavior,
45
+ * and an optional `ActionHandlerRegistry` (see `action-handler.ts`)
46
+ * lets adapters register new actions like `'hitl-review'` or
47
+ * `'redact-then-continue'`. Any non-empty name is accepted when the
48
+ * guardrail is defined; with `EvaluationBindings.actions` set, a name
49
+ * with no registered handler fails with `unknown-action` when the
50
+ * guardrail fires.
51
+ */
52
+ export type OnViolation = string;
53
+
54
+ export const BUILT_IN_ON_VIOLATIONS = [
55
+ 'halt',
56
+ 'retry',
57
+ 'escalate',
58
+ 'log-only',
59
+ 'compensate',
60
+ ] as const;
61
+ export type BuiltInOnViolation = (typeof BUILT_IN_ON_VIOLATIONS)[number];
62
+
63
+ export type ScopeWhen = 'always' | 'ci-only' | 'runtime-only';
64
+
65
+ export interface Action {
66
+ readonly 'on-violation': OnViolation;
67
+ readonly retry?: { readonly maxAttempts: number };
68
+ readonly escalateTo?: string;
69
+ readonly compensateWith?: string;
70
+ }
71
+
72
+ export interface Scope {
73
+ readonly when?: ScopeWhen;
74
+ readonly agents?: readonly AgentId[];
75
+ readonly flows?: readonly FlowId[];
76
+ readonly tenants?: readonly TenantId[];
77
+ }
78
+
79
+ export interface Budget {
80
+ readonly maxCostUsd?: number;
81
+ readonly maxLatencyMs?: number;
82
+ }
83
+
84
+ /**
85
+ * Isolation posture the runtime enforces around a check's handler. Same
86
+ * three tiers as `ToolManifest.sandbox` in `@kindgi/tools`.
87
+ *
88
+ * Duplicated here rather than imported from `@kindgi/tools` to keep
89
+ * this package free of a tools dependency. Keep the shape literally
90
+ * identical.
91
+ */
92
+ export type SandboxMode = 'none' | 'context-isolated' | 'strict';
93
+
94
+ /** Runtime resource caps enforced by the sandbox layer at check dispatch. */
95
+ export interface RuntimeLimits {
96
+ readonly memMB: number;
97
+ readonly cpuMs: number;
98
+ }
99
+
100
+ /** Network egress policy honored by the sandbox during a check. */
101
+ export type NetworkPolicy =
102
+ | { readonly kind: 'none' }
103
+ | { readonly kind: 'allowlist'; readonly hosts: readonly string[] }
104
+ | { readonly kind: 'unrestricted' };
105
+
106
+ /**
107
+ * JSON Schema for a typed `needs` slot. Author-time Zod is compiled to
108
+ * this at build (`z.toJSONSchema()`).
109
+ */
110
+ export type JsonSchema = Readonly<Record<string, unknown>>;
111
+
112
+ /**
113
+ * Discriminated typed-dependency declarations mirroring the Tool
114
+ * manifest's `TypedNeeds` shape (five slots); every slot is optional.
115
+ */
116
+ export interface TypedNeeds {
117
+ readonly env?: Readonly<Record<string, JsonSchema>>;
118
+ readonly secrets?: Readonly<Record<string, JsonSchema>>;
119
+ readonly config?: Readonly<Record<string, JsonSchema>>;
120
+ readonly capabilities?: readonly string[];
121
+ readonly bindings?: readonly string[];
122
+ }
123
+
124
+ /**
125
+ * Handler-artifact pointer for the guardrail's CHECK implementation.
126
+ * Same shape as Tool's `CodeArtifactRef` — discriminated on `kind`:
127
+ *
128
+ * - `'oci'` — production. Populated by the deploy pipeline.
129
+ * `modulePath` resolves inside the pinned
130
+ * `imageRef`; `artifactVersion` pins one deploy.
131
+ * - `'filesystem'` — dev-mode. Populated at dev-mode registration.
132
+ * `modulePath` is an absolute host path at the
133
+ * check module. Production servers SHOULD reject
134
+ * this variant.
135
+ *
136
+ * NOTE: `modulePath` points at the check handler bundle (the pack
137
+ * index's `checkModulePath`, see `@kindgi/handler-runtime`); the field
138
+ * name stays `modulePath` for wire symmetry with tools.
139
+ */
140
+ export type CodeArtifactRef =
141
+ | {
142
+ readonly kind: 'oci';
143
+ readonly imageRef: string;
144
+ readonly modulePath: string;
145
+ readonly artifactVersion: string;
146
+ }
147
+ | {
148
+ readonly kind: 'filesystem';
149
+ readonly modulePath: string;
150
+ };
151
+
152
+ /**
153
+ * A guardrail declaration. Same shape at runtime + in CI — one definition,
154
+ * two enforcement paths.
155
+ *
156
+ * `check` references a concrete check implementation:
157
+ * - For `zero-llm`: id of a check in the CheckRegistry (built-in or custom).
158
+ * - For `llm-judge`: id of a registered check of kind `llm-judge`
159
+ * (`defineGuardrail` resolves it); the judge itself is configured by
160
+ * `config` (`LlmJudgeConfig`) + `judgeCapabilities`.
161
+ * - For `external` and adapter kinds: an id the kind's strategy understands.
162
+ *
163
+ * `config` is check-specific — the check implementation validates it.
164
+ *
165
+ * ## Additive extensions
166
+ *
167
+ * `sandbox`, `limits`, `network`, `needsSpec`, `codeArtifactRef` are the
168
+ * same runtime-declaration extensions that ToolManifest carries.
169
+ * Guardrails are code-carrying primitives too (a check ships as a
170
+ * bundle at `checkModulePath` + `node_modules`), so every field applies
171
+ * as-is — none are excluded. The dispatch, SDK and deploy layers
172
+ * populate + honor them; consumers that don't know about them ignore the fields.
173
+ */
174
+ export interface Guardrail {
175
+ /**
176
+ * Globally-unique guardrail identifier. Convention:
177
+ * `<pack-id>.<guardrail-name>` (kebab-case, dot-namespaced). See the
178
+ * `GuardrailId` brand for the naming rule.
179
+ */
180
+ readonly id: GuardrailId;
181
+ /** Short human-readable label shown in violation UI + audit logs. */
182
+ readonly name?: string;
183
+ /** Prose describing what the guardrail guarantees + what happens on violation. Surfaced to reviewers when the guardrail fires. */
184
+ readonly description?: string;
185
+ /**
186
+ * `'zero-llm'` — pure function over the trace (default, fast,
187
+ * deterministic; the recommended kind for most safety rules).
188
+ * `'llm-judge'` — uses a model to score (opt-in, costs money;
189
+ * declare `budget` + `judgeCapabilities`). `'external'` — evaluated
190
+ * outside the engine by a caller-registered strategy (the built-in
191
+ * `external` strategy returns an `invalid-guardrail` error).
192
+ */
193
+ readonly kind: GuardrailKind;
194
+ /**
195
+ * Reference to the concrete check implementation. String id of a
196
+ * `RegisteredCheck` in the runtime `CheckRegistry` — either a
197
+ * built-in from `@kindgi/guardrails` (`BUILT_IN_CHECK_IDS`: `must-cite`,
198
+ * `never-call-tool`, `max-tool-calls`, `output-matches`, `tool-order`,
199
+ * `required-substring`, `forbidden-substring`) or a pack-authored
200
+ * check registered under its id (see `defineCheck`).
201
+ */
202
+ readonly check: string;
203
+ /**
204
+ * Check-specific configuration. Interpreted by the check
205
+ * implementation (validated by the check's `validateConfig`, e.g.
206
+ * derived from its `configSchema`, when `defineGuardrail` runs).
207
+ * Shape is opaque to the engine; e.g. `must-cite` reads
208
+ * `{minCitations?: number}`, `output-matches` reads `{pattern: string}`.
209
+ */
210
+ readonly config?: Readonly<Record<string, unknown>>;
211
+ /**
212
+ * What the framework does when this guardrail fires — `halt`
213
+ * (fail the run), `retry` (re-execute the step with a
214
+ * `maxAttempts` cap), `escalate` (route to HITL review),
215
+ * `log-only` (record but don't block), `compensate` (invoke a
216
+ * named compensation tool). `BUILT_IN_ON_VIOLATIONS` lists the
217
+ * built-in values; `OnViolation` itself is an open string.
218
+ */
219
+ readonly action: Action;
220
+ /**
221
+ * `'info'` / `'warn'` / `'error'` / `'critical'` — orthogonal to
222
+ * `action`. Severity is what LOGS + DASHBOARDS group by; action is
223
+ * what EXECUTION does. A `log-only` guardrail can still be
224
+ * `'critical'` — just doesn't halt.
225
+ */
226
+ readonly severity?: GuardrailSeverity;
227
+ /**
228
+ * When this guardrail applies. `{when: 'always'}` fires everywhere;
229
+ * `{when: 'ci-only'}` blocks CI but not runtime; `{when: 'runtime-only'}`
230
+ * enforces at runtime but not CI. Per-agent / per-flow selectors
231
+ * narrow further (e.g. `{agents: ['acme.support-agent']}`).
232
+ */
233
+ readonly scope?: Scope;
234
+ /**
235
+ * Cost + latency ceiling per guardrail invocation (relevant for
236
+ * `kind: 'llm-judge'` — zero-llm checks are free): `maxCostUsd` +
237
+ * `maxLatencyMs`. Declarative — not enforced by the runtime.
238
+ */
239
+ readonly budget?: Budget;
240
+ /**
241
+ * For `kind: 'llm-judge'` — capability declaration for the judge
242
+ * model, routed through `@kindgi/capabilities` under the tenant policy
243
+ * the caller passes as `EvaluationBindings.tenantPolicy`. Enables BYO
244
+ * judges. Ignored for zero-llm + external.
245
+ */
246
+ readonly judgeCapabilities?: Capability;
247
+ /** Isolation posture — see `SandboxMode`. */
248
+ readonly sandbox?: SandboxMode;
249
+ /** Memory + CPU caps enforced by the sandbox at check dispatch. */
250
+ readonly limits?: RuntimeLimits;
251
+ /** Network egress policy the sandbox honors while the check runs. */
252
+ readonly network?: NetworkPolicy;
253
+ /**
254
+ * Discriminated typed-dependency declarations (env / secrets / config /
255
+ * capabilities / bindings). See `TypedNeeds` for the shape.
256
+ */
257
+ readonly needsSpec?: TypedNeeds;
258
+ /**
259
+ * Pointer at the deploy-time OCI image + module path carrying the
260
+ * check handler bytes. Absent for in-process declarations.
261
+ */
262
+ readonly codeArtifactRef?: CodeArtifactRef;
263
+ }
264
+
265
+ /**
266
+ * Recorded events from a run that checks operate on. Callers materialize
267
+ * these from the run's recorded events, memory and provenance —
268
+ * guardrails doesn't read those directly (avoids a hard dep loop; keeps
269
+ * checks pure).
270
+ */
271
+ export interface ToolCallRecord {
272
+ readonly toolId: ToolId;
273
+ readonly toolName: string;
274
+ readonly arguments: Readonly<Record<string, unknown>>;
275
+ readonly at: Timestamp;
276
+ }
277
+
278
+ export interface ToolResultRecord {
279
+ readonly toolCallId: string;
280
+ readonly output: unknown;
281
+ readonly at: Timestamp;
282
+ }
283
+
284
+ export interface ModelCallRecord {
285
+ readonly providerId: string;
286
+ readonly model: string;
287
+ readonly promptTokens: number;
288
+ readonly completionTokens: number;
289
+ readonly at: Timestamp;
290
+ }
291
+
292
+ /**
293
+ * The materialised run trace a check reads. Callers assemble this at the
294
+ * boundary from the run's events / memory / provenance — the check
295
+ * itself is a pure function over this shape.
296
+ *
297
+ * Adapter checks that need data not typed here use
298
+ * `attributes: Record<string, unknown>` as the escape hatch.
299
+ */
300
+ export interface RunTrace {
301
+ readonly runId: RunId;
302
+ readonly tenantId: TenantId;
303
+ /**
304
+ * Content-scope anchor. Threaded to
305
+ * `bindings.compliance.emit()` when a violation surfaces so
306
+ * evidence records land under the same project as the run.
307
+ * Optional; when absent, the violation emit is skipped.
308
+ */
309
+ readonly projectId?: ProjectId;
310
+ readonly agentId?: AgentId;
311
+ readonly flowId?: FlowId;
312
+ /** Final assistant output text. Present when the run produced text. */
313
+ readonly output?: string;
314
+ readonly toolCalls: readonly ToolCallRecord[];
315
+ readonly toolResults: readonly ToolResultRecord[];
316
+ readonly modelCalls: readonly ModelCallRecord[];
317
+ /**
318
+ * The user input that opened the turn / flow invocation. Populated
319
+ * by the agent runtime (`@kindgi/agents`); optional for runs that
320
+ * have no user turn.
321
+ */
322
+ readonly userInput?: string;
323
+ /**
324
+ * Facts pulled by the agent's retrieval intents before the model
325
+ * call. Enables consistency guardrails (e.g. "every citation in
326
+ * output appears in retrieved.factIds").
327
+ */
328
+ readonly retrievedFactIds?: readonly string[];
329
+ /**
330
+ * Conversation id for multi-turn context. Undefined for one-shot
331
+ * runs that don't belong to a conversation.
332
+ */
333
+ readonly conversationId?: string;
334
+ /**
335
+ * 1-indexed turn number within the conversation. Undefined outside
336
+ * agent conversations.
337
+ */
338
+ readonly turnNumber?: number;
339
+ /** Cumulative USD cost for the turn / run. */
340
+ readonly totalCostUsd?: number;
341
+ /** Wall-clock duration in milliseconds. */
342
+ readonly durationMs?: number;
343
+ /**
344
+ * Whether this evaluation is happening in CI or at runtime. Determines
345
+ * which `scope.when` values apply.
346
+ */
347
+ readonly mode: 'ci' | 'runtime';
348
+ /** Free-form attributes checks may consult. Use for niche data not typed above. */
349
+ readonly attributes?: Readonly<Record<string, unknown>>;
350
+ }
351
+
352
+ /**
353
+ * Runtime bindings passed alongside the RunTrace so llm-judge guardrails
354
+ * can invoke a model and any guardrail can emit compliance evidence.
355
+ *
356
+ * All fields optional — a `zero-llm` guardrail needs none of them. Callers
357
+ * wire up only what they actually use.
358
+ */
359
+ export interface EvaluationBindings {
360
+ /** Required for `kind: 'llm-judge'` guardrails — resolves + invokes the judge. */
361
+ readonly providerRegistry?: ProviderRegistry;
362
+ /**
363
+ * Aborted when the caller stops waiting: the agent turn was
364
+ * cancelled, or ran past its wall-clock budget. A check that calls out
365
+ * (a judge model, a service) passes it on, so a slow call doesn't
366
+ * hold the turn; the llm-judge strategy passes it to the model call.
367
+ */
368
+ readonly abortSignal?: AbortSignal;
369
+ /**
370
+ * Optional — when present and the trace carries a `projectId`, every
371
+ * failed check emits a `guardrail-violation` compliance evidence
372
+ * record (passing checks emit nothing).
373
+ */
374
+ readonly compliance?: ComplianceProvider;
375
+ /**
376
+ * Override the judge model directly (bypasses the router). Useful for
377
+ * tests + hermetic pinning; production should route through the registry.
378
+ */
379
+ readonly judgeProvider?: ModelProvider;
380
+ /**
381
+ * The tenant's model-routing policy (provider / model allow and deny
382
+ * lists, `regionAllow`, caps). When present, llm-judge models are
383
+ * routed under it, and an explicit `judgeProvider` must satisfy it too
384
+ * — a judge never sends the run to a model the tenant doesn't allow.
385
+ */
386
+ readonly tenantPolicy?: TenantPolicy;
387
+ /**
388
+ * Optional execution-strategy registry. When present, the engine
389
+ * dispatches `guardrail.kind` through this registry instead of the
390
+ * built-in strategies (zero-llm / llm-judge / external). Callers extend
391
+ * by seeding built-ins + adapter strategies. See execution-strategy.ts.
392
+ */
393
+ readonly strategies?: import('./execution-strategy.js').ExecutionStrategyRegistry;
394
+ /**
395
+ * Optional action-handler registry. When present, the engine invokes
396
+ * the appropriate handler after evaluating each guardrail so custom
397
+ * actions (`'hitl-review'`, `'redact-then-continue'`, etc.) fire.
398
+ * Absent → the engine records the action in EvaluationResult but does
399
+ * not invoke a handler; the caller layer consults the action.
400
+ */
401
+ readonly actions?: import('./action-handler.js').ActionHandlerRegistry;
402
+ }
403
+
404
+ /** Result of a single check evaluation. */
405
+ export interface CheckResult {
406
+ readonly passed: boolean;
407
+ readonly reason?: string;
408
+ /** For llm-judge checks: the raw response text from the judge. */
409
+ readonly judgeResponse?: string;
410
+ /** Structured attributes attached to the result (evidence + logs). */
411
+ readonly attributes?: Readonly<Record<string, unknown>>;
412
+ }
413
+
414
+ /** Result of evaluating a guardrail against a trace, including action to take. */
415
+ export interface EvaluationResult {
416
+ readonly guardrailId: GuardrailId;
417
+ readonly result: CheckResult;
418
+ /** The action the caller should apply — pre-computed from `guardrail.action`. */
419
+ readonly action: OnViolation | 'noop';
420
+ readonly severity: GuardrailSeverity;
421
+ readonly at: Timestamp;
422
+ }
423
+
424
+ /**
425
+ * The signature checks implement. Pure function — same input yields same
426
+ * result. LLM-judge implementations are async and take bindings for
427
+ * capability routing.
428
+ */
429
+ export type CheckFunction = (
430
+ config: Readonly<Record<string, unknown>>,
431
+ trace: RunTrace,
432
+ bindings: EvaluationBindings,
433
+ ) => Promise<CheckResult>;
434
+
435
+ /**
436
+ * A registered check — has an id + optional config validator + evaluator.
437
+ * Config validation runs when the guardrail is defined; the evaluator
438
+ * runs at check time.
439
+ */
440
+ export interface RegisteredCheck {
441
+ readonly id: string;
442
+ readonly kind: GuardrailKind;
443
+ readonly evaluate: CheckFunction;
444
+ readonly validateConfig?: (config: unknown) => string | undefined;
445
+ }
446
+
447
+ /**
448
+ * Registry mapping check id → implementation. Built-in checks are
449
+ * pre-populated; consumers can register custom checks at pack init.
450
+ */
451
+ export interface CheckRegistry {
452
+ register(check: RegisteredCheck): void;
453
+ get(id: string): RegisteredCheck | undefined;
454
+ list(): readonly RegisteredCheck[];
455
+ }