/** * learning/behavior-capture.ts — User-input behavior capture (F-194, Phase B.3). * * Records structural-fingerprint-only observations about user prompts so * `worker-suggest.mjs` can adapt its routing without leaking PII. * * Per Q4 resolution: **no prompt text field of any kind is persisted.** * Each row carries only: * * { * fingerprint64: string; // sha256(canonicalizedPrompt).slice(0, 16) * workerId: string; // e.g. "mike", "linda", "todd", ... * accept: boolean; // user accepted the dispatch without editing * rejectReason?: string; // free-text reason the user rejected (no prompt) * timestamp: string; // ISO 8601 server-stamped * } * * The `prompt` / `promptRedacted` / `rawPrompt` field names are reserved * FORBIDDEN KEYS at the type level (see {@link FORBIDDEN_BEHAVIOR_KEYS}) * so a typo or a regression cannot accidentally start writing prompt * text to disk. `behavior-capture-drift.test.mjs` enforces the same * invariant at the file-system level. * * Why no prompt text at all: * - Operators with multi-tenant Bizar installs must not have other * users' prompts sitting on disk. * - The structural fingerprint is sufficient for the learner's * accept/reject correlation: two prompts that share a fingerprint * are observably equivalent and the learner treats them as one * signal. * - Removes the entire redaction layer (correction #11) — if the * sensitive bytes never reach disk, there is nothing to redact. * * Invariants: * - `fingerprint64` is exactly 16 hex chars (64 bits). * - `workerId` is a non-empty string. * - `timestamp` is server-stamped via `new Date().toISOString()`. * - `createFileBehaviorCapture` creates the parent dir with mode * `0o700` so the on-disk ledger stays operator-readable only. */ /** Mode applied to the parent dir of any file-backed capture. */ export declare const BEHAVIOR_DIR_MODE = 448; /** * Reserved FORBIDDEN field names. Any record that contains one of * these keys is rejected by `validateBehaviorRecord` so a regression * cannot accidentally start writing prompt text. */ export declare const FORBIDDEN_BEHAVIOR_KEYS: ReadonlyArray; /** Structural-fingerprint-only observation of one user-input event. */ export interface BehaviorRecord { /** sha256(canonicalizedPrompt).slice(0, 16) — 64-bit hex. */ readonly fingerprint64: string; /** Worker the user accepted or rejected (e.g. "mike", "linda"). */ readonly workerId: string; /** true = user accepted the dispatch without editing; false = rejected. */ readonly accept: boolean; /** Optional free-text reason the user rejected the dispatch. */ readonly rejectReason?: string; /** Server-stamped ISO 8601 timestamp. */ readonly timestamp: string; } /** * Compute the 64-bit hex fingerprint of a prompt. The prompt text is * canonicalized (collapsed whitespace) before hashing so two prompts * that differ only by trivial whitespace map to the same fingerprint. */ export declare function fingerprint64(prompt: string): string; /** Validate a BehaviorRecord. Throws on any forbidden key or bad shape. */ export declare function validateBehaviorRecord(record: unknown): asserts record is BehaviorRecord; /** Build a new BehaviorRecord. Server-stamps the timestamp. */ export declare function createBehaviorRecord({ fingerprint, workerId, accept, rejectReason, }: { fingerprint: string; workerId: string; accept: boolean; rejectReason?: string; }): BehaviorRecord; /** In-memory behavior capture — array-backed, useful for tests + workers. */ export interface BehaviorCapture { readonly append: (record: BehaviorRecord) => void; readonly list: () => ReadonlyArray; readonly size: () => number; } export declare function createInMemoryBehaviorCapture(): BehaviorCapture; /** File-backed behavior capture — append-only JSONL at mode 0o700. */ export interface FileBehaviorCapture extends BehaviorCapture { readonly path: string; } export declare function createFileBehaviorCapture({ filePath }: { filePath: string; }): FileBehaviorCapture; /** * Aggregate a list of BehaviorRecords into a compact summary suitable * for `additionalContext`. Returns `{ workerId: { accept: number, * reject: number, lastReason? } }`. No prompt text appears in the * summary; only counts + a single most-recent reject reason per worker. */ export interface WorkerBehaviorSummary { [workerId: string]: { accept: number; reject: number; lastRejectReason?: string; }; } export declare function summarizeBehavior(records: ReadonlyArray): WorkerBehaviorSummary; //# sourceMappingURL=behavior-capture.d.ts.map