@kindgi/audit-events
npm install @kindgi/audit-events · source
Type contract for the Kindgi audit log. Defines the AuditEvent record every subsystem emits (authorization decisions, secret and env changes, run outcomes, guardrail violations, human-in-the-loop decisions) and AuditEventBinding, the tenant-scoped append / query / purge interface that storage adapters implement. Types only; this package has no runtime code.
Purpose
Section titled “Purpose”Give the whole system one audit substrate: one record shape, one write surface, one query surface. Compliance evidence is a classification lens over this stream (see @kindgi/compliance), not a second write path. Common fields (tenantId, projectId, timestamp, actor, correlationId, runId, …) are top-level so they can be indexed; kind-specific data lives in a versioned payload. The binding is caller-plugged: @kindgi/api takes one as auditEvents and mounts the audit routes over it, and the reference in-memory implementation lives in @kindgi/audit-events-inmemory.
Exports
Section titled “Exports”AuditEvent— one audit record:id— caller-supplied, unique per tenant.tenantId,projectId?(absent for tenant-level events),kind(open string, e.g.'authz-decision','secret-set','run-outcome'),timestamp.actor—<type>:<id>, e.g.user:u-1042,agent:support-triage,user:systemfor framework-internal actions.onBehalfOf?names the delegating party.correlationId?,runId?,agentId?,flowId?,outcome?.payload— versioned envelope{ v: 1, doc: … }; thedocshape depends onkind.
AuditEventBinding— the adapter interface:append(events)— batched write. Duplicate(tenantId, id)pairs are ignored, so retries are safe. Fails withPersistenceErrororAuditEventValidationError.query(input)— cursor-paginated read ordered by(timestamp, id); the cursor is opaque. Fails withPersistenceErrororInvalidCursorError.purge(input)— delete one kind's events older than a cutoff; returns the deleted count.describe()—{ name, version }.
AuditEventQueryInput—tenantId,filter?,cursor?,limit?.AuditEventFilter— AND-combined; absent fields match anything. Fields:id,kind,kinds(an empty array matches nothing),actor,onBehalfOf,runId,agentId,flowId,correlationId,outcome,payloadDoc(every entry must equal the same field ofpayload.doc), and inclusive ISOfrom/tobounds.AuditEventPage—dataplusnextCursor(absent on the last page).AuditEventPurgeInput(tenantId,kind,olderThan) andAuditEventPurgeResult(deleted).AuditEventError— union ofPersistenceError(persistence-error),InvalidCursorError(invalid-cursor), andAuditEventValidationError(invalid-event, with optional per-pathissues).
Example
Section titled “Example”import { randomUUID } from 'node:crypto';
import type { AuditEvent, AuditEventBinding } from '@kindgi/audit-events';import type { ProjectId, TenantId, Timestamp } from '@kindgi/types';
async function recordDecision( audit: AuditEventBinding, tenantId: TenantId, projectId: ProjectId, allowed: boolean,): Promise<void> { const event: AuditEvent = { id: randomUUID(), // unique per tenant; re-appending the same id is a no-op tenantId, projectId, kind: 'authz-decision', timestamp: new Date().toISOString() as Timestamp, actor: 'agent:support-triage', onBehalfOf: 'user:u-1042', outcome: allowed ? 'allowed' : 'denied', payload: { v: 1, doc: { action: 'tool:invoke', resource: 'crm.lookup' } }, }; const written = await audit.append([event]); if (written.kind === 'err') throw new Error(`${written.error.code}: ${written.error.message}`);}
// Page through every denial since `from`, oldest first.async function* denialsSince( audit: AuditEventBinding, tenantId: TenantId, from: string,): AsyncGenerator<AuditEvent> { let cursor: string | undefined; do { const page = await audit.query({ tenantId, filter: { kind: 'authz-decision', outcome: 'denied', from }, limit: 100, ...(cursor !== undefined && { cursor }), }); if (page.kind === 'err') throw new Error(`${page.error.code}: ${page.error.message}`); yield* page.value.data; cursor = page.value.nextCursor; } while (cursor !== undefined);}Non-goals
Section titled “Non-goals”- No storage implementation. Adapters implement
AuditEventBinding; the in-memory reference is@kindgi/audit-events-inmemory, and durable adapters are supplied by the Kindgi runtime. - No signing on the write path. Records are stored unsigned; signatures are computed over canonical bytes at export time by the compliance layer.
- No retention policy.
purgeremoves one kind older than a cutoff. Deciding when to call it, and exempting legal-hold kinds, belongs to the caller and the compliance classifier. - No closed set of kinds or payload schemas.
kindis an open string so packs can add their own; each kind defines its ownpayload.doc.
Related
Section titled “Related”@kindgi/audit-events-inmemory— referenceAuditEventBindingfor tests and local development.@kindgi/compliance— classifier and signed export over this stream;auditEventToEvidencemaps anAuditEventto an evidence record.@kindgi/api— accepts anAuditEventBindingasauditEvents.