@kindgi/compliance 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.
package/src/types.ts ADDED
@@ -0,0 +1,382 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (C) 2026 Kindgi Inc.
3
+
4
+ import type {
5
+ AgentId,
6
+ ComplianceEvidenceId,
7
+ FlowId,
8
+ ProjectId,
9
+ ProvenanceId,
10
+ RunId,
11
+ SigningKeyId,
12
+ TenantId,
13
+ Timestamp,
14
+ } from '@kindgi/types';
15
+
16
+ // ============ Wire types (schema-derived) ============
17
+
18
+ /**
19
+ * The built-in (known) compliance-relevant event classes. Listed as
20
+ * `examples` in `@kindgi/specs/compliance-evidence.schema.json`.
21
+ *
22
+ * Not exhaustive: evidence is a classifier lens over the audit stream,
23
+ * whose kinds are open (pack authors add their own), and a deployment's
24
+ * compliance classifier can mark any kind exportable. See `EvidenceKind`.
25
+ */
26
+ export const EVIDENCE_KINDS = [
27
+ 'access-event',
28
+ 'policy-decision',
29
+ 'config-change',
30
+ // `secret-rotation` is the coarse kind; emitters use the granular
31
+ // `secret-rotated` / `secret-rotation-started` / `secret-rotation-failed`
32
+ // kinds.
33
+ 'secret-rotation',
34
+ 'incident-event',
35
+ 'guardrail-violation',
36
+ 'eval-run',
37
+ 'approval-completed',
38
+ 'data-retention-action',
39
+ 'authn-event',
40
+ 'authz-decision',
41
+ 'model-invocation',
42
+ 'pack-install',
43
+ 'pack-upgrade',
44
+ 'pack-uninstall',
45
+ 'run-outcome',
46
+ // Env-binding lifecycle events. Symmetric with the secret-* kinds
47
+ // below; `env-*` events never carry the value in their payload.
48
+ 'env-resolved',
49
+ 'env-set',
50
+ 'env-deleted',
51
+ // Secret-binding lifecycle events. The framework NEVER copies the
52
+ // plaintext value into an evidence payload;
53
+ // payloads carry only metadata (name, version, scope, caller, errorCode).
54
+ 'secret-resolved',
55
+ 'secret-set',
56
+ 'secret-rotated',
57
+ 'secret-rotation-started',
58
+ 'secret-rotation-failed',
59
+ 'secret-revoked',
60
+ 'secret-hard-revoked',
61
+ // Human-in-the-loop approval decisions (exportable in the shipped
62
+ // compliance classifier).
63
+ 'hitl-decision',
64
+ ] as const;
65
+
66
+ /**
67
+ * An evidence kind: one of the built-in `EVIDENCE_KINDS`, or any other
68
+ * audit-event kind a deployment's classifier marks exportable. Consumers
69
+ * must tolerate kinds they don't know.
70
+ */
71
+ export type EvidenceKind = (typeof EVIDENCE_KINDS)[number] | (string & {});
72
+
73
+ export type ActorKind = 'user' | 'agent' | 'system' | 'admin' | 'external';
74
+
75
+ export interface EvidenceActor {
76
+ readonly kind: ActorKind;
77
+ readonly id?: string;
78
+ readonly ipAddress?: string;
79
+ readonly userAgent?: string;
80
+ }
81
+
82
+ export interface EvidenceSubject {
83
+ readonly kind: string;
84
+ readonly id?: string;
85
+ }
86
+
87
+ export type EvidenceOutcome = 'allowed' | 'denied' | 'succeeded' | 'failed' | 'escalated';
88
+
89
+ /**
90
+ * Reference back to the provenance DAG that produced this evidence.
91
+ * Same shape as `provenance.schema.json`'s runId + nodeId pair.
92
+ */
93
+ export interface ProvenanceRef {
94
+ readonly runId?: RunId;
95
+ readonly nodeId?: string;
96
+ /** For records linked to a persisted provenance record by id. */
97
+ readonly recordId?: ProvenanceId;
98
+ }
99
+
100
+ /** Ed25519 signature over the canonical serialisation of the evidence. */
101
+ export interface EvidenceSignature {
102
+ readonly algorithm: 'ed25519';
103
+ readonly keyId: string;
104
+ readonly value: string;
105
+ readonly signedAt: Timestamp;
106
+ }
107
+
108
+ /**
109
+ * The kind-specific payload lives here. `payload` always includes a
110
+ * `version` field, so consumers migrate older shapes to the current one
111
+ * on read instead of rewriting stored records.
112
+ */
113
+ export interface EvidencePayload {
114
+ readonly version: number;
115
+ readonly [key: string]: unknown;
116
+ }
117
+
118
+ /**
119
+ * One evidence record. Matches `@kindgi/specs/compliance-evidence.schema.json`
120
+ * modulo `payload` being an in-package-typed versioned document.
121
+ */
122
+ export interface Evidence {
123
+ readonly id: ComplianceEvidenceId;
124
+ readonly tenantId: TenantId;
125
+ /**
126
+ * Content-scope anchor — the project the underlying audit event
127
+ * belongs to. Absent for tenant-level events (e.g. authz decisions,
128
+ * admin actions before any project exists), mirroring the optional
129
+ * `AuditEvent.projectId`.
130
+ */
131
+ readonly projectId?: ProjectId;
132
+ readonly kind: EvidenceKind;
133
+ readonly timestamp: Timestamp;
134
+ readonly actor?: EvidenceActor;
135
+ readonly subject?: EvidenceSubject;
136
+ readonly outcome?: EvidenceOutcome;
137
+ readonly payload: EvidencePayload;
138
+ readonly provenanceRef?: ProvenanceRef;
139
+ readonly signature?: EvidenceSignature;
140
+ }
141
+
142
+ /**
143
+ * Alias exposing the public wire type under the framework-standard name.
144
+ * `ComplianceEvidence` and `Evidence` are the same type.
145
+ */
146
+ export type ComplianceEvidence = Evidence;
147
+
148
+ /**
149
+ * Optional signer. If a caller wires one up (typically backed by a
150
+ * signing key), every emitted record is signed for tamper detection.
151
+ * Absent signer = unsigned records, still valid but weaker audit story.
152
+ */
153
+ export type EvidenceSigner = (bytes: Uint8Array, tenantId: TenantId) => Promise<EvidenceSignature>;
154
+
155
+ /**
156
+ * Filter for `ComplianceProvider.list`. `tenantId` is required; the other
157
+ * fields are optional and compose as AND — with only `tenantId` set, it
158
+ * matches all evidence for the tenant.
159
+ */
160
+ export interface ListEvidenceFilter {
161
+ readonly tenantId: TenantId;
162
+ readonly kind?: EvidenceKind;
163
+ readonly runId?: RunId;
164
+ readonly since?: Date;
165
+ readonly until?: Date;
166
+ readonly limit?: number;
167
+ }
168
+
169
+ /**
170
+ * Pluggable provider — adapters forward evidence to an external
171
+ * compliance platform or SIEM. The core `emit` API stays constant;
172
+ * adapters translate the wire format.
173
+ */
174
+ export interface ComplianceProvider {
175
+ emit(evidence: EmitEvidenceInput): Promise<import('./errors.js').EmitResult>;
176
+ list(filter: ListEvidenceFilter): Promise<import('./errors.js').ListResult>;
177
+ describe(): { readonly name: string; readonly version: string };
178
+ }
179
+
180
+ /**
181
+ * Caller-supplied input to `emit`. The provider fills in `id` +
182
+ * `timestamp` + optional `signature`; everything else comes from the
183
+ * caller.
184
+ */
185
+ export interface EmitEvidenceInput {
186
+ readonly tenantId: TenantId;
187
+ readonly projectId: ProjectId;
188
+ readonly kind: EvidenceKind;
189
+ readonly payload: EvidencePayload;
190
+ readonly actor?: EvidenceActor;
191
+ readonly subject?: EvidenceSubject;
192
+ readonly outcome?: EvidenceOutcome;
193
+ readonly provenanceRef?: ProvenanceRef;
194
+ /** Override the record id (defaults to `randomUUID()`). */
195
+ readonly id?: ComplianceEvidenceId;
196
+ /** Override the timestamp (defaults to `now()`). Useful for backfills. */
197
+ readonly timestamp?: Timestamp;
198
+ }
199
+
200
+ // ============ Pipeline types (record-from-run, filter, page, bundle) ============
201
+
202
+ /**
203
+ * One model call summary attached to a run's evidence record. Redaction-
204
+ * safe by construction: no raw prompt text, no raw completion — only
205
+ * hashes + counts + timing + outcome. Callers that need raw content pass
206
+ * it via `RecordFromRunOptions.rawMessages` under the explicit opt-in
207
+ * (`RecordFromRunOptions.allowRawMessages: true`).
208
+ */
209
+ export interface ModelCallSummary {
210
+ readonly provider: string;
211
+ readonly model: string;
212
+ /** `<provider>/<model>@<date-or-version>` — matches provenance node convention. */
213
+ readonly modelVersion?: string;
214
+ /** SHA-256 (or similar) of the canonical prompt; caller-supplied. */
215
+ readonly promptHash?: string;
216
+ readonly inputTokens?: number;
217
+ readonly outputTokens?: number;
218
+ readonly durationMs?: number;
219
+ readonly outcome?: 'succeeded' | 'failed';
220
+ }
221
+
222
+ /**
223
+ * One tool invocation summary. Same redaction discipline as
224
+ * `ModelCallSummary` — args are hashed, not embedded.
225
+ */
226
+ export interface ToolInvocationSummary {
227
+ readonly toolId: string;
228
+ /** SHA-256 (or similar) of the canonical args; caller-supplied. */
229
+ readonly argsHash?: string;
230
+ readonly outcome?: 'succeeded' | 'failed';
231
+ readonly durationMs?: number;
232
+ }
233
+
234
+ /** One guardrail check outcome captured on the run. */
235
+ export interface GuardrailResultSummary {
236
+ readonly guardrailId: string;
237
+ readonly verdict: 'pass' | 'fail' | 'warn';
238
+ readonly severity?: 'low' | 'medium' | 'high' | 'critical';
239
+ /** Short human message; caller ensures no sensitive content. */
240
+ readonly message?: string;
241
+ }
242
+
243
+ /**
244
+ * Structured input to `generator.recordFromRun`. Represents the state of
245
+ * a terminated run that the pipeline turns into one evidence record.
246
+ *
247
+ * By construction this shape is redaction-safe: model prompts and tool
248
+ * args are hashes, guardrail messages are short strings without payload
249
+ * content.
250
+ */
251
+ export interface RunEvidenceContext {
252
+ readonly status: 'completed' | 'failed' | 'cancelled';
253
+ readonly startedAt?: Timestamp;
254
+ readonly endedAt?: Timestamp;
255
+ readonly agentId?: AgentId;
256
+ readonly flowId?: FlowId;
257
+ readonly flowVersion?: string;
258
+ readonly modelCalls?: readonly ModelCallSummary[];
259
+ readonly toolInvocations?: readonly ToolInvocationSummary[];
260
+ readonly guardrailResults?: readonly GuardrailResultSummary[];
261
+ /**
262
+ * Failure message from the terminal run. Callers redact this before
263
+ * passing — the generator does not scan it for secrets.
264
+ */
265
+ readonly failureMessage?: string;
266
+ /**
267
+ * Free-form extra fields the caller wants on the evidence payload.
268
+ * Additive; caller is responsible for redaction. NEVER put secrets or
269
+ * raw user-message content here — use `RecordFromRunOptions.rawMessages`
270
+ * for the latter (with the opt-in flag) so the redaction seam stays
271
+ * explicit.
272
+ */
273
+ readonly extra?: Readonly<Record<string, unknown>>;
274
+ }
275
+
276
+ /**
277
+ * Options for `generator.recordFromRun`. `tenantId`, `projectId`, `runId`,
278
+ * `kind` and `runContext` are required; everything else is optional, and
279
+ * the generator fills in `id` + `timestamp` when absent.
280
+ */
281
+ export interface RecordFromRunOptions {
282
+ readonly tenantId: TenantId;
283
+ /**
284
+ * Content-scope anchor. The generator
285
+ * threads this into `Evidence.projectId` on the emitted record.
286
+ */
287
+ readonly projectId: ProjectId;
288
+ readonly runId: RunId;
289
+ /**
290
+ * Compliance-relevant event class the caller wants this record filed
291
+ * under. The pipeline does NOT infer kind from the run outcome — the
292
+ * caller knows why the record is being emitted.
293
+ */
294
+ readonly kind: EvidenceKind;
295
+ readonly runContext: RunEvidenceContext;
296
+ readonly actor?: EvidenceActor;
297
+ readonly subject?: EvidenceSubject;
298
+ readonly outcome?: EvidenceOutcome;
299
+ /** Override the record id (defaults to `randomUUID()`). */
300
+ readonly id?: ComplianceEvidenceId;
301
+ /** Override the timestamp (defaults to `now()`). */
302
+ readonly timestamp?: Timestamp;
303
+ /** Optional provenance DAG back-reference. */
304
+ readonly provenanceRef?: ProvenanceRef;
305
+ /**
306
+ * When the tenant has explicitly opted in to raw-message retention,
307
+ * caller passes the raw messages here. They land verbatim under
308
+ * `payload.rawMessages`. Without this opt-in, raw content stays out
309
+ * of the record entirely.
310
+ */
311
+ readonly rawMessages?: readonly Readonly<Record<string, unknown>>[];
312
+ /**
313
+ * Must be `true` for `rawMessages` to be embedded — a belt-and-braces
314
+ * guard so a caller can't accidentally pass raw messages without an
315
+ * explicit opt-in acknowledgement.
316
+ */
317
+ readonly allowRawMessages?: boolean;
318
+ }
319
+
320
+ /**
321
+ * Filter shape shared by evidence listing and signed export
322
+ * (`ComplianceEvidenceGenerator.exportSigned`). `ComplianceProvider.list`
323
+ * takes `ListEvidenceFilter` instead. Every field is optional; an empty
324
+ * filter is "everything in the tenant". Composition is AND — all
325
+ * populated fields must match.
326
+ */
327
+ export interface EvidenceFilter {
328
+ readonly runId?: RunId;
329
+ readonly agentId?: AgentId;
330
+ readonly flowId?: FlowId;
331
+ readonly evidenceKind?: EvidenceKind;
332
+ /**
333
+ * Restrict to these kinds. An empty array matches nothing. Callers
334
+ * applying a classifier pass its exportable kinds here, so a signed
335
+ * bundle records exactly which kinds it covers.
336
+ */
337
+ readonly evidenceKinds?: readonly EvidenceKind[];
338
+ readonly from?: Timestamp;
339
+ readonly to?: Timestamp;
340
+ }
341
+
342
+ /** One page of evidence records from a cursor-paginated listing. */
343
+ export interface EvidencePage {
344
+ readonly data: readonly ComplianceEvidence[];
345
+ /** Absent when there are no more results after this page. */
346
+ readonly nextCursor?: string;
347
+ }
348
+
349
+ /**
350
+ * The signed export bundle envelope. Same shape family as the provenance
351
+ * signed export + audit-bundle envelope, so verifiers can reuse the exact
352
+ * same Ed25519 verification wrapper across all three surfaces.
353
+ */
354
+ export interface SignedEvidenceBundle {
355
+ readonly bundleSchemaVersion: '1.0.0';
356
+ readonly tenantId: TenantId;
357
+ /** Base64 of canonical JSON bytes — exactly what was signed. */
358
+ readonly bundle: string;
359
+ readonly algorithm: 'ed25519';
360
+ readonly signingKeyId: SigningKeyId;
361
+ /** Base64 of the Ed25519 signature bytes (64 bytes decoded). */
362
+ readonly signature: string;
363
+ /** PEM-encoded Ed25519 public key. */
364
+ readonly publicKey: string;
365
+ readonly canonicalization: 'sorted-key-json';
366
+ readonly exportedAt: Timestamp;
367
+ }
368
+
369
+ /**
370
+ * The body inside the signed bundle. Kept as a distinct exported type so
371
+ * downstream verifiers can type the decoded bytes without duplicating the
372
+ * shape.
373
+ */
374
+ export interface EvidenceBundleBody {
375
+ readonly bundleSchemaVersion: '1.0.0';
376
+ readonly tenantId: TenantId;
377
+ readonly filter: EvidenceFilter;
378
+ readonly records: readonly ComplianceEvidence[];
379
+ readonly exportedAt: Timestamp;
380
+ /** Count of records in the bundle. */
381
+ readonly recordCount: number;
382
+ }