@tailrace/core 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.
@@ -0,0 +1,401 @@
1
+ /**
2
+ * Public type surface for @tailrace/core.
3
+ *
4
+ * These types are the contract every integration builds against. They are specified
5
+ * by docs/policy-engine.md, docs/detection.md, and docs/vault.md - those docs are
6
+ * normative when a detail here is ambiguous.
7
+ */
8
+ /** What the policy engine decides to do with a detected span. */
9
+ type Action = "allow" | "mask" | "tokenize" | "block" | "review" | "detokenize";
10
+ /** Deterministic, Tier 0 secret classes. `block` on these cannot be relaxed to `allow`
11
+ * without `dangerouslyAllowSecrets` (docs/policy-engine.md §3.3). */
12
+ type SecretEntityClass = "api_key" | "jwt" | "private_key" | "high_entropy_secret" | "connection_string";
13
+ /** Structured PII, detected deterministically in Tier 0. */
14
+ type PiiEntityClass = "email" | "phone" | "credit_card" | "iban" | "ssn" | "ip_address" | "url_credentials";
15
+ /** Free-text PII, detected by the optional Tier 1 NER recognizer. */
16
+ type NerEntityClass = "person" | "location" | "organization";
17
+ /**
18
+ * Any entity class. The `string & {}` member keeps literal autocomplete for the known
19
+ * classes while still admitting user-defined classes from custom recognizers.
20
+ */
21
+ type EntityClass = SecretEntityClass | PiiEntityClass | NerEntityClass | (string & {});
22
+ /** The set of secret classes, as a runtime value, for the secrets-cannot-be-allowed rule. */
23
+ declare const SECRET_ENTITY_CLASSES: readonly SecretEntityClass[];
24
+ /** Structured PII classes (Tier 0). */
25
+ declare const PII_ENTITY_CLASSES: readonly PiiEntityClass[];
26
+ /** Free-text PII classes (Tier 1 NER). */
27
+ declare const NER_ENTITY_CLASSES: readonly NerEntityClass[];
28
+ /** A trust boundary a value is about to cross. */
29
+ type Boundary = {
30
+ kind: "model";
31
+ provider: string;
32
+ } | {
33
+ kind: "tool";
34
+ name: string;
35
+ direction: "in" | "out";
36
+ } | {
37
+ kind: "mcp";
38
+ server: string;
39
+ tool: string;
40
+ direction: "in" | "out";
41
+ } | {
42
+ kind: "telemetry";
43
+ } | {
44
+ kind: "egress";
45
+ sink: string;
46
+ };
47
+ /** Who is acting. `agent` is required ("default" if unset). */
48
+ interface Identity {
49
+ agent: string;
50
+ claims?: Record<string, string | string[]>;
51
+ }
52
+ /** A detected region of the input. Offsets are UTF-16 code units. */
53
+ interface Span {
54
+ entity: EntityClass;
55
+ /** Inclusive start offset (UTF-16 code units). */
56
+ start: number;
57
+ /** Exclusive end offset (UTF-16 code units). */
58
+ end: number;
59
+ /** 1.0 = validator-confirmed, 0.8 = pattern-only (docs/detection.md §1). */
60
+ confidence: number;
61
+ /** Id of the emitting recognizer. */
62
+ recognizer: string;
63
+ /** RFC 6901 JSON Pointer when the span came from an object leaf; absent for plain strings. */
64
+ path?: string;
65
+ }
66
+ /** A detection engine. Tier 0 recognizers MUST be synchronous. */
67
+ interface Recognizer {
68
+ id: string;
69
+ entities: EntityClass[];
70
+ tier: 0 | 1 | 2;
71
+ scan(text: string): Span[] | Promise<Span[]>;
72
+ }
73
+ /** One resolved policy outcome for one span. Never carries the raw value. */
74
+ interface Decision {
75
+ action: Action | "restore_miss";
76
+ entity: EntityClass;
77
+ boundary: Boundary;
78
+ identity: Identity;
79
+ /** Dotted path of the winning rule, e.g. `identities.support-agent.tools.crm.*`. */
80
+ rule: string;
81
+ /** JSON path + offsets, never the value. */
82
+ span: {
83
+ path: string;
84
+ start: number;
85
+ end: number;
86
+ };
87
+ /** SHA-256 of the raw value, hex - for audit correlation only. */
88
+ contentHash: string;
89
+ /**
90
+ * When {@link CheckOptions.applyBlockAs} remaps a `block` to another applied action,
91
+ * the resolved policy action stays `block` and this records what was applied.
92
+ */
93
+ appliedAs?: "mask";
94
+ }
95
+ /**
96
+ * Per-call options for {@link Tailrace.check}. Integration-only knobs; does not change
97
+ * policy resolution (docs/policy-engine.md §5).
98
+ */
99
+ interface CheckOptions {
100
+ /**
101
+ * When policy resolves to `block`, apply this action instead of throwing.
102
+ * Default: throw. Used by `@tailrace/ai-sdk` for `streamBlockBehavior: "redact"`.
103
+ */
104
+ applyBlockAs?: "mask";
105
+ /**
106
+ * Streaming hold-back (docs/vault.md §5). String inputs only.
107
+ * Detects+resolves on the full input once; applies only spans fully within the safe
108
+ * emit prefix; returns raw {@link CheckResult.remainder} to carry.
109
+ */
110
+ stream?: {
111
+ /** Trailing chars that may still grow into a match. */
112
+ holdback: number;
113
+ /** When true, flush the entire buffer (no hold-back). */
114
+ final?: boolean;
115
+ };
116
+ }
117
+ /** A per-entity rule; either a bare action or an action with modifiers. */
118
+ interface EntityRule {
119
+ action: Action;
120
+ /** Tokenization output shape (docs/vault.md §3). */
121
+ format?: "preserve" | "label";
122
+ /** Required to relax a secret class away from `block` (docs/policy-engine.md §3.3). */
123
+ dangerouslyAllowSecrets?: boolean;
124
+ }
125
+ type EntityRuleValue = Action | EntityRule;
126
+ /** A scope that maps entities and per-boundary overrides; reused at top level and per identity. */
127
+ interface PolicyScope {
128
+ entities?: Partial<Record<EntityClass, EntityRuleValue>>;
129
+ boundaries?: Record<string, {
130
+ entities?: Partial<Record<string, EntityRuleValue>>;
131
+ }>;
132
+ }
133
+ /** A full policy document (docs/policy-engine.md §2). */
134
+ interface PolicyDocument extends PolicyScope {
135
+ defaults?: {
136
+ action?: Action;
137
+ };
138
+ identities?: Record<string, PolicyScope>;
139
+ /** Global escape hatch for the secrets-cannot-be-allowed rule. */
140
+ dangerouslyAllowSecrets?: boolean;
141
+ }
142
+ /**
143
+ * Where a policy comes from. v0.1 ships `staticPolicy`; a hosted plane can slot in later
144
+ * without a core API change (docs/architecture.md §5).
145
+ */
146
+ interface PolicySource {
147
+ load(): Promise<PolicyDocument>;
148
+ subscribe?(cb: (p: PolicyDocument) => void): () => void;
149
+ }
150
+ /** A single stored token record. */
151
+ interface VaultRecord {
152
+ value: string;
153
+ entity: EntityClass;
154
+ }
155
+ interface VaultPutInput {
156
+ workflowId: string;
157
+ token: string;
158
+ entity: EntityClass;
159
+ value: string;
160
+ expiresAt?: number;
161
+ }
162
+ /** Reversible token storage (docs/vault.md §2). Values are stored encrypted at rest. */
163
+ interface Vault {
164
+ put(input: VaultPutInput): Promise<void>;
165
+ get(workflowId: string, token: string): Promise<VaultRecord | null>;
166
+ purge(workflowId: string): Promise<void>;
167
+ }
168
+ /** Minimal KV surface backing `kvVault`; structurally compatible with Cloudflare KV. */
169
+ interface KvStore {
170
+ get(key: string): Promise<string | null>;
171
+ put(key: string, value: string, opts?: {
172
+ expirationTtl?: number;
173
+ }): Promise<void>;
174
+ delete(key: string): Promise<void>;
175
+ }
176
+ /** One audit record emitted per check/restore. */
177
+ interface AuditEvent {
178
+ type: "check" | "restore";
179
+ workflowId: string;
180
+ timestamp: number;
181
+ decisions: Decision[];
182
+ }
183
+ /** A destination for audit events (console, JSONL, otel, custom). */
184
+ interface AuditSink {
185
+ emit(event: AuditEvent): void | Promise<void>;
186
+ }
187
+ /**
188
+ * Line writer for {@link jsonlSink}. Callers supply filesystem or remote adapters;
189
+ * core never touches the filesystem.
190
+ */
191
+ interface AuditWriter {
192
+ write(line: string): void | Promise<void>;
193
+ }
194
+ /** Master-key and TTL options for the built-in vaults. */
195
+ interface VaultOptions {
196
+ /** Master key; falls back to `TAILRACE_VAULT_KEY`, then a random per-process key. */
197
+ key?: string;
198
+ /** Default token TTL in seconds (docs/vault.md §2). */
199
+ ttlSeconds?: number;
200
+ }
201
+ /** Options for {@link createTailrace}. Every field has a sensible default. */
202
+ interface TailraceOptions {
203
+ policy?: PolicyDocument | PolicySource;
204
+ recognizers?: Recognizer[];
205
+ vault?: Vault | VaultOptions;
206
+ audit?: {
207
+ sinks?: AuditSink[];
208
+ };
209
+ /** Emit spans for private/reserved IP ranges (docs/detection.md §2). */
210
+ includePrivateIps?: boolean;
211
+ /** Notified with every decision, regardless of configured audit sinks. */
212
+ onDecision?: (decisions: Decision[]) => void;
213
+ }
214
+ type JsonValue = string | number | boolean | null | JsonValue[] | {
215
+ [k: string]: JsonValue;
216
+ };
217
+ type JsonObject = {
218
+ [k: string]: JsonValue;
219
+ };
220
+ interface CheckContext {
221
+ boundary: Boundary;
222
+ identity: Identity;
223
+ workflowId?: string;
224
+ }
225
+ interface CheckResult<T> {
226
+ output: T;
227
+ decisions: Decision[];
228
+ blocked: false;
229
+ /**
230
+ * Raw suffix held back when {@link CheckOptions.stream} is set on a string input.
231
+ * Absent for non-streaming checks.
232
+ */
233
+ remainder?: string;
234
+ }
235
+ /**
236
+ * The core primitive every integration calls. Integrations construct the right
237
+ * `Boundary`/`Identity` and translate errors; they contain no policy logic.
238
+ */
239
+ interface Tailrace {
240
+ /**
241
+ * Detect + resolve + apply. Throws {@link PolicyViolationError} on a `block`
242
+ * unless {@link CheckOptions.applyBlockAs} remaps the block.
243
+ */
244
+ check<T extends string | JsonObject>(input: T, ctx: CheckContext, options?: CheckOptions): Promise<CheckResult<T>>;
245
+ /** Restore tokens at a trusted `egress` boundary. Throws at any other boundary. */
246
+ restore<T extends string | JsonObject>(input: T, ctx: CheckContext): Promise<CheckResult<T>>;
247
+ }
248
+
249
+ /**
250
+ * Typed error taxonomy (docs/conventions.md §Error taxonomy).
251
+ *
252
+ * Every Tailrace error carries a stable string `code`. Error messages MUST NEVER
253
+ * contain a detected value - only entity classes, rule paths, and hashes. This is
254
+ * enforced by a test that greps thrown messages against fixture values.
255
+ */
256
+
257
+ /** Base class for every error thrown by Tailrace. */
258
+ declare class TailraceError extends Error {
259
+ /** Stable, machine-readable error code. */
260
+ readonly code: string;
261
+ constructor(code: string, message: string);
262
+ }
263
+ /**
264
+ * A `block` policy matched. Integrations translate this into their host framework's
265
+ * native failure mode. Carries the decisions that triggered the block; never a value.
266
+ */
267
+ declare class PolicyViolationError extends TailraceError {
268
+ readonly decisions: Decision[];
269
+ constructor(message: string, decisions: Decision[]);
270
+ }
271
+ /** A policy document failed schema validation. `path` points at the offending key. */
272
+ declare class PolicyValidationError extends TailraceError {
273
+ readonly path: string;
274
+ constructor(message: string, path: string);
275
+ }
276
+ /** An internal contract was breached, e.g. a restore requested at a non-egress boundary. */
277
+ declare class InvariantViolationError extends TailraceError {
278
+ constructor(message: string);
279
+ }
280
+ /** A vault adapter failed (storage, encryption, or lookup). */
281
+ declare class VaultError extends TailraceError {
282
+ constructor(message: string);
283
+ }
284
+ /** A recognizer threw while scanning. */
285
+ declare class RecognizerError extends TailraceError {
286
+ constructor(message: string);
287
+ }
288
+ /** A surface that is specified but not yet implemented in this milestone. */
289
+ declare class NotImplementedError extends TailraceError {
290
+ constructor(message: string);
291
+ }
292
+
293
+ /**
294
+ * Built-in audit sinks (console + JSONL writer).
295
+ */
296
+
297
+ /**
298
+ * Log each audit event as a single JSON line on the console.
299
+ *
300
+ * @example
301
+ * ```ts
302
+ * const gate = createTailrace({ audit: { sinks: [consoleSink()] } });
303
+ * ```
304
+ */
305
+ declare function consoleSink(): AuditSink;
306
+ /**
307
+ * Write each audit event as a JSON line via a caller-supplied {@link AuditWriter}.
308
+ * Core never touches the filesystem - pass a file/stream adapter from Node or the CLI.
309
+ *
310
+ * @example
311
+ * ```ts
312
+ * const lines: string[] = [];
313
+ * const sink = jsonlSink({ write: (line) => { lines.push(line); } });
314
+ * ```
315
+ */
316
+ declare function jsonlSink(writer: AuditWriter): AuditSink;
317
+
318
+ /**
319
+ * In-memory vault with TTL sweep. Default vault for createTailrace().
320
+ */
321
+
322
+ /**
323
+ * Create an in-memory vault. Values are encrypted at rest with the resolved master key.
324
+ *
325
+ * @example
326
+ * ```ts
327
+ * const gate = createTailrace({ vault: memoryVault({ key: "dev-only-key" }) });
328
+ * ```
329
+ */
330
+ declare function memoryVault(opts?: VaultOptions): Vault;
331
+
332
+ /**
333
+ * KV-backed vault (Cloudflare KV and compatible). Values encrypted at rest.
334
+ */
335
+
336
+ /**
337
+ * Create a vault backed by a minimal KV store.
338
+ *
339
+ * @example
340
+ * ```ts
341
+ * const gate = createTailrace({ vault: kvVault(env.MY_KV, { key: secret }) });
342
+ * ```
343
+ */
344
+ declare function kvVault(kv: KvStore, opts?: VaultOptions): Vault;
345
+
346
+ /**
347
+ * @tailrace/core - detection, policy engine, vault, and audit.
348
+ *
349
+ * Zero runtime dependencies; runs on Node 20+, Cloudflare Workers, and Vercel Edge.
350
+ * Public exports are intentionally short - every export is API forever
351
+ * (docs/architecture.md §6).
352
+ */
353
+
354
+ /**
355
+ * Create a gate. Zero-config by default: all secret classes `block`, common PII `tokenize`.
356
+ *
357
+ * @example
358
+ * ```ts
359
+ * const tailrace = createTailrace();
360
+ * const { output } = await tailrace.check(userInput, {
361
+ * boundary: { kind: "model", provider: "openai/gpt-4o" },
362
+ * identity: { agent: "default" },
363
+ * });
364
+ * ```
365
+ */
366
+ declare function createTailrace(options?: TailraceOptions): Tailrace;
367
+ /**
368
+ * Author and validate a policy document.
369
+ *
370
+ * @example
371
+ * ```ts
372
+ * const policy = definePolicy({ entities: { email: "tokenize" } });
373
+ * ```
374
+ */
375
+ declare function definePolicy(doc: PolicyDocument): PolicyDocument;
376
+ /**
377
+ * Define a custom recognizer.
378
+ *
379
+ * @example
380
+ * ```ts
381
+ * const employeeId = defineRecognizer({
382
+ * id: "employee-id",
383
+ * entities: ["employee_id"],
384
+ * tier: 0,
385
+ * scan: (text) => [],
386
+ * });
387
+ * ```
388
+ */
389
+ declare function defineRecognizer(recognizer: Recognizer): Recognizer;
390
+ /**
391
+ * Wrap a local policy document as a {@link PolicySource}. This is the default source and
392
+ * the shape a hosted policy plane implements later (docs/architecture.md §5).
393
+ *
394
+ * @example
395
+ * ```ts
396
+ * const source = staticPolicy(definePolicy({ entities: { email: "tokenize" } }));
397
+ * ```
398
+ */
399
+ declare function staticPolicy(doc: PolicyDocument): PolicySource;
400
+
401
+ export { type Action, type AuditEvent, type AuditSink, type AuditWriter, type Boundary, type CheckContext, type CheckOptions, type CheckResult, type Decision, type EntityClass, type EntityRule, type EntityRuleValue, type Identity, InvariantViolationError, type JsonObject, type JsonValue, type KvStore, NER_ENTITY_CLASSES, type NerEntityClass, NotImplementedError, PII_ENTITY_CLASSES, type PiiEntityClass, type PolicyDocument, type PolicyScope, type PolicySource, PolicyValidationError, PolicyViolationError, type Recognizer, RecognizerError, SECRET_ENTITY_CLASSES, type SecretEntityClass, type Span, type Tailrace, TailraceError, type TailraceOptions, type Vault, VaultError, type VaultOptions, type VaultPutInput, type VaultRecord, consoleSink, createTailrace, definePolicy, defineRecognizer, jsonlSink, kvVault, memoryVault, staticPolicy };