@kindgi/compliance 0.0.0-stage → 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/LICENSE +201 -0
- package/README.md +87 -2
- package/dist/classifier.d.ts +65 -0
- package/dist/classifier.d.ts.map +1 -0
- package/dist/classifier.js +4 -0
- package/dist/classifier.js.map +1 -0
- package/dist/errors.d.ts +93 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +4 -0
- package/dist/errors.js.map +1 -0
- package/dist/generator.d.ts +34 -0
- package/dist/generator.d.ts.map +1 -0
- package/dist/generator.js +31 -0
- package/dist/generator.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/resolve-emit.d.ts +75 -0
- package/dist/resolve-emit.d.ts.map +1 -0
- package/dist/resolve-emit.js +181 -0
- package/dist/resolve-emit.js.map +1 -0
- package/dist/types.d.ts +307 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +51 -0
- package/dist/types.js.map +1 -0
- package/package.json +50 -3
- package/src/classifier.ts +69 -0
- package/src/errors.ts +111 -0
- package/src/generator.ts +69 -0
- package/src/index.ts +68 -0
- package/src/resolve-emit.ts +256 -0
- package/src/types.ts +382 -0
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
|
+
}
|