/** * Run lifecycle events through @hasna/events — bounded metadata and stable * pointers ONLY. * * The boundary is structural: buildRunLifecycleEvent starts from an allowlist * of fields (run_id, skill, version, status, timestamps, correlation_id, * attempt/lease pointers) and validateRunLifecycleEvent refuses anything else * before it can be emitted - unknown keys, nested payloads, and any value that * matches the credential patterns. Raw logs, artifact payloads, and inputs * cannot reach the event bus by construction, and the negative fixture proves * the guard fires rather than being asserted by prose. */ import { type EventEnvelope } from "@hasna/events"; import type { ServerRunRecord } from "../server/types.js"; export declare const RUN_LIFECYCLE_EVENT_TYPES: readonly ["skills.run.admitted", "skills.run.started", "skills.run.terminal", "skills.run.cancelled"]; export type RunLifecycleEventType = (typeof RUN_LIFECYCLE_EVENT_TYPES)[number]; /** * The full allowlist of data fields a run lifecycle event may carry. * * Every field is either a stable pointer or bounded metadata. Anything not in * this list is a payload violation; any value matching the redaction patterns * is a secret leak. Both checks run before emission. */ export declare const RUN_LIFECYCLE_EVENT_FIELDS: readonly ["run_id", "attempt_id", "lease_generation", "correlation_id", "skill", "version", "status", "at"]; export interface RunLifecycleEventData { run_id: string; attempt_id: string; lease_generation: number; correlation_id?: string; skill: string; version?: string; status: string; at: string; /** * @hasna/events constrains event data to Record. The index * signature is required to satisfy it; the RUNTIME allowlist is the guard * that actually bounds the payload (see RUN_LIFECYCLE_EVENT_FIELDS and * validateRunLifecycleEvent) - this type cannot be the fence, only the * shape of the legitimate payload. */ [key: string]: unknown; } /** Build a bounded lifecycle event envelope for a run state. Never carries inputs, logs, or payloads. */ export declare function buildRunLifecycleEvent(type: RunLifecycleEventType, run: Pick, options?: { version?: string; at?: string; source?: string; }): EventEnvelope; /** * Refuse any event payload that is not bounded metadata + stable pointers. * * Rejects, with EVENT_PAYLOAD_REJECTED: * - unknown top-level data keys (an input, a log array, an artifact body); * - non-scalar values outside the allowlist; * - any value whose text matches the credential patterns. */ export declare function validateRunLifecycleEvent(event: EventEnvelope): void; /** A sink for lifecycle events; swap in a recording sink in tests. */ export type RunEventSink = (event: EventEnvelope) => Promise; export interface RunEventEmitterOptions { /** Emission sink. Defaults to @hasna/events' EventsClient over a JsonEventsStore. */ sink?: RunEventSink; versionFor?: (run: Pick) => Promise; /** When false (default), a sink failure is reported but never fails the run transition. */ failOpen?: boolean; } export interface RunEventEmitter { emit(type: RunLifecycleEventType, run: ServerRunRecord): Promise; } /** * Emit one validated lifecycle event. * * Validation is mandatory and runs before the sink is touched; a payload that * fails validation throws EVEN when failOpen is set - a secret must never be * dropped into a fail-open hole. Sink failures (an unreachable events store) * are reported via the returned boolean / an optional error and do not fail * the run itself, because a run that succeeded is still a run that succeeded. */ export declare function createRunEventEmitter(options?: RunEventEmitterOptions): RunEventEmitter;