/** * Sentient Event Schema — Merkle-chained audit log for Tier-3 operations. * * Defines the `SentientEvent` TypeScript discriminated union covering all * 8 event kinds emitted during a Tier-3 governed execution pipeline run. * Provides {@link appendSentientEvent} (signs + appends) and * {@link querySentientEvents} (filters the log by kind / experimentId / time). * * ## Storage * * Events are stored as newline-delimited JSON (NDJSON) in * `/.cleo/audit/sentient-events.jsonl`. * Each event carries a `parentHash` linking back to the previous event, * forming a Merkle chain that makes insertion / deletion detectable. * * ## Signing * * `appendSentientEvent` calls {@link loadSigningIdentity} and signs the * canonical JSON bytes (all fields except `sig`) with the daemon's Ed25519 * private key before persisting. Callers supply an `AgentIdentity` directly * to keep key-loading outside the hot path. * * ## Event kinds * * | Kind | Written by | Purpose | * |------|-----------|---------| * | `baseline` | Daemon (pre-worktree) | Anchors metric snapshot before experiment starts | * | `sandbox.spawn` | Daemon | Records container start parameters | * | `patch.proposed` | Agent (via CLI inside container) | Patch summary from experiment agent | * | `verify` | Daemon | Per-gate evidence atom verification result | * | `sign` | Daemon | Final sign-off: all gates passed | * | `merge` | Daemon | FF-only merge committed to main | * | `abort` | Daemon | Experiment aborted (kill-switch or FF failure) | * | `revert` | Daemon (owner-triggered) | Squashed revert of prior merges | * * @see DESIGN.md §3 — Event Chain * @task T1022 */ import type { AgentIdentity } from 'llmtxt/identity'; /** All valid sentient event kind strings. */ export type SentientEventKind = 'baseline' | 'sandbox.spawn' | 'patch.proposed' | 'verify' | 'sign' | 'merge' | 'abort' | 'revert' | 'tsa_anchor'; /** * Payload for a `baseline` event. * * Written by the daemon before the experiment worktree is created. * `worktreeNotCreatedYet: true` is a structural assertion that the daemon * has not yet called `git worktree add` — preventing baseline-gaming where * the agent writes a slow baseline after the experiment is already underway. */ export interface BaselinePayload { /** The git commit SHA used as the baseline. */ commitSha: string; /** SHA-256 of the canonical baseline JSON (hex). */ baselineHash: string; /** Serialised metrics snapshot from `cleo bench --json` or similar. */ metricsJson: string; /** Structural assertion: worktree has NOT been created yet. */ worktreeNotCreatedYet: true; } /** * Payload for a `sandbox.spawn` event. * * Written by the daemon when it starts the `sentient-agent` container. */ export interface SandboxSpawnPayload { /** UUIDv4 identifying this experiment run. */ experimentId: string; /** Docker image tag used for the container. */ dockerImage: string; /** Absolute path to the git worktree the container operates on. */ worktreePath: string; /** Classification of the experiment (e.g. `"code-patch"`, `"refactor"`). */ experimentType: string; } /** * Payload for a `patch.proposed` event. * * Written by the experiment agent (inside the container) via `cleo sentient * sign` or equivalent CLI call. The daemon verifies this event came from a * trusted agent before proceeding to the merge ritual. */ export interface PatchProposedPayload { /** The CLEO task ID driving this experiment. */ taskId: string; /** List of relative file paths modified by the patch. */ patchFiles: string[]; /** Human-readable summary of the patch (≤ 500 chars). */ patchSummary: string; } /** * Payload for a `verify` event. * * Written once per acceptance gate. `passed: false` aborts the experiment. */ export interface VerifyPayload { /** The gate name (e.g. `"implemented"`, `"testsPassed"`, `"qaPassed"`). */ gate: string; /** Evidence atom strings supplied to `cleo verify`. */ evidenceAtoms: string[]; /** Whether this gate passed verification. */ passed: boolean; } /** * Payload for a `sign` event. * * Written by the daemon after all acceptance gates pass. This is the * point-of-no-return before the merge. */ export interface SignPayload { /** List of gate names that were verified. */ gates: string[]; /** True when every gate in `gates` passed. */ allPassed: boolean; } /** * Payload for a `merge` event. * * Written by the daemon after `git merge --ff-only` succeeds. */ export interface MergePayload { /** The new HEAD commit SHA after the merge. */ commitSha: string; /** Merge strategy used — always `"ff-only"` in Tier 3. */ mergeStrategy: 'ff-only'; /** The previous HEAD SHA before the merge. */ prevHeadSha: string; } /** * Payload for an `abort` event. * * Written when the experiment is aborted — either because the kill-switch * fired or because `git merge --ff-only` failed. */ export interface AbortPayload { /** Reason the experiment was aborted. */ abortReason: 'kill_switch' | 'ff_failed' | 'gate_failed' | 'error'; /** Merge ritual step number at which the abort occurred (1–10). */ abortAtStep: number; /** Whether the git worktree was successfully cleaned up. */ worktreeCleaned: boolean; } /** * Payload for a `revert` event. * * Written by the daemon when the owner issues `cleo revert --from `. * After this event, `globalPauseSet: true` blocks all Tier 1/2/3 ticks. */ export interface RevertPayload { /** The `receiptId` of the event to revert back to. */ fromReceiptId: string; /** The new squashed-revert commit SHA. */ revertCommitSha: string; /** The range of commit SHAs that were reverted. */ revertedRange: string[]; /** Whether the global kill-switch was set after the revert. */ globalPauseSet: true; } /** Base fields common to all sentient events. */ export interface SentientEventBase { /** Unique receipt identifier for this specific event (nanoid-style, 21 chars). */ receiptId: string; /** UUIDv4 linking all events for one experiment run. Empty string for `baseline`. */ experimentId: string; /** The Tier-2 accepted task driving this experiment. Empty for non-task events. */ taskId: string; /** SHA-256 of the previous event's canonical JSON bytes (Merkle link). Genesis = 64 zeros. */ parentHash: string; /** ISO-8601 UTC timestamp. */ timestamp: string; /** * Ed25519 signature over canonical JSON bytes of the event (all fields except `sig`). * Hex-encoded 64-byte signature (128 chars). */ sig: string; /** Hex-encoded 32-byte Ed25519 public key (64 chars) of the signer. */ pub: string; } /** Sentient event: baseline captured before worktree creation. */ export interface BaselineEvent extends SentientEventBase { kind: 'baseline'; payload: BaselinePayload; } /** Sentient event: sandbox container spawned. */ export interface SandboxSpawnEvent extends SentientEventBase { kind: 'sandbox.spawn'; payload: SandboxSpawnPayload; } /** Sentient event: patch proposed by the experiment agent. */ export interface PatchProposedEvent extends SentientEventBase { kind: 'patch.proposed'; payload: PatchProposedPayload; } /** Sentient event: acceptance gate verification result. */ export interface VerifyEvent extends SentientEventBase { kind: 'verify'; payload: VerifyPayload; } /** Sentient event: all gates signed off — ready to merge. */ export interface SignEvent extends SentientEventBase { kind: 'sign'; payload: SignPayload; } /** Sentient event: FF-only merge succeeded. */ export interface MergeEvent extends SentientEventBase { kind: 'merge'; payload: MergePayload; } /** Sentient event: experiment aborted. */ export interface AbortEvent extends SentientEventBase { kind: 'abort'; payload: AbortPayload; } /** Sentient event: owner-triggered revert. */ export interface RevertEvent extends SentientEventBase { kind: 'revert'; payload: RevertPayload; } /** Payload for `tsa_anchor` event — external RFC 3161 timestamp anchor. */ export interface TsaAnchorPayload { /** Receipt ID of the chain head at anchor time. */ chainHeadReceiptId: string; /** SHA-256 of the chain-head event's canonical JSON (hex). */ chainHeadHash: string; /** URL of the TSA queried. */ tsaUrl: string; /** Base64-encoded TSA TimeStampToken response bytes. */ tsaToken: string; /** ISO-8601 UTC timestamp of when the anchor was queried. */ anchoredAt: string; } /** Sentient event: daily RFC 3161 external timestamp anchor. */ export interface TsaAnchorEvent extends SentientEventBase { kind: 'tsa_anchor'; payload: TsaAnchorPayload; } /** * Discriminated union of all 8 sentient event kinds. * * Use the `kind` field to narrow to the specific event type. */ export type SentientEvent = BaselineEvent | SandboxSpawnEvent | PatchProposedEvent | VerifyEvent | SignEvent | MergeEvent | AbortEvent | RevertEvent | TsaAnchorEvent; /** * Input to {@link appendSentientEvent}. * * All fields except `receiptId`, `parentHash`, `timestamp`, `sig`, and `pub` * are caller-supplied. Those five are derived automatically during append. */ export type SentientEventInput = Omit; /** * Filter options for {@link querySentientEvents}. */ export interface SentientEventFilter { /** Only return events with this `kind`. */ kind?: SentientEventKind; /** Only return events with this `experimentId`. */ experimentId?: string; /** Only return events with `timestamp` after this ISO-8601 string. */ after?: string; /** Maximum number of events to return (default: no limit). */ limit?: number; } /** Path to the NDJSON event log (relative to projectRoot). */ export declare const SENTIENT_EVENTS_FILE = ".cleo/audit/sentient-events.jsonl"; /** * Append a signed sentient event to the project's audit log. * * Steps performed: * 1. Derive `receiptId` (random 21-char ID). * 2. Read the current chain tail to compute `parentHash`. * 3. Set `timestamp` to current UTC ISO-8601. * 4. Compute `sig` = Ed25519 signature over canonical JSON bytes. * 5. Append single-line JSON to `/.cleo/audit/sentient-events.jsonl`. * 6. Return the fully-constructed `SentientEvent`. * * The log is append-only; no existing entries are ever modified. * * @param projectRoot - Absolute path to the CLEO project root. * @param identity - Signing identity. Obtain via {@link loadSigningIdentity}. * @param input - Event data (all fields except those derived automatically). * @returns The fully constructed and persisted `SentientEvent`. * * @example * ```ts * import { appendSentientEvent } from '@cleocode/core/sentient/events.js'; * import { loadSigningIdentity } from '@cleocode/core/sentient/kms.js'; * * const identity = await loadSigningIdentity(projectRoot); * const event = await appendSentientEvent(projectRoot, identity, { * kind: 'baseline', * experimentId: '', * taskId: '', * payload: { commitSha, baselineHash, metricsJson, worktreeNotCreatedYet: true }, * }); * ``` */ export declare function appendSentientEvent(projectRoot: string, identity: AgentIdentity, input: SentientEventInput): Promise; /** * Query the sentient event log with optional filters. * * Reads the entire NDJSON log and returns matching events in chronological * order (the order they were appended). Malformed lines are silently skipped. * * @param projectRoot - Absolute path to the CLEO project root. * @param filter - Optional filter to narrow results. * @returns Array of matching `SentientEvent` objects, oldest first. * * @example * ```ts * // Get all baseline events for an experiment. * const events = await querySentientEvents(projectRoot, { * kind: 'baseline', * experimentId: 'exp-001', * }); * * // Get all events after a specific time. * const recent = await querySentientEvents(projectRoot, { * after: '2026-04-20T00:00:00Z', * }); * ``` */ export declare function querySentientEvents(projectRoot: string, filter?: SentientEventFilter): Promise; /** * Verify the Ed25519 signature on a single sentient event. * * @param event - The event to verify. * @returns `true` if the signature is valid. */ export declare function verifySentientEventSignature(event: SentientEvent): Promise; //# sourceMappingURL=events.d.ts.map