/**
 * HumanAsDetectorLog — treats every human-caught coherence break as a
 * first-class diagnostic signal about which automated layer FAILED.
 *
 * The insight (Dawn's 2026-04-26 Lesson, "Justin Pointing Things Out =
 * Guardian Failure"): when a human has to surface something wrong — a stale
 * claim, a contradiction, a state incoherence — that is not a normal input to
 * be quietly fixed. It is evidence that some monitor/gate/guardian that was
 * supposed to catch it didn't. Logging these over time produces a heat map of
 * "where the human is doing the system's job," which tells you which automated
 * layers are dead weight and which coverage gaps are real.
 *
 * Instar already has CoherenceMonitor, CoherenceGate, and DegradationReporter —
 * but all of those watch the system's OWN state. None of them treat a human
 * correction as a signal. This closes that gap.
 *
 * Pattern mirrors DegradationReporter: singleton, configure() at startup,
 * append-only JSONL persistence, best-effort and never throws into the caller.
 *
 * Usage:
 *   const log = HumanAsDetectorLog.getInstance();
 *   log.configure({ stateDir, agentName });
 *   // On every inbound human message:
 *   log.observe({ text, topicId, messageId, source: 'telegram' });
 *
 * Ported from Dawn's human-as-detector.jsonl infrastructure.
 */
/** A detected human-as-detector signal. */
export interface HumanDetectorSignal {
    /** ISO timestamp when the signal was recorded */
    ts: string;
    /** Where the correction arrived from (telegram, slack, lifeline, ...) */
    source: string;
    /** Topic/channel id, if known */
    topicId: number | null;
    /** Message id, if known */
    messageId: number | null;
    /** Coarse classification of what kind of break the human surfaced */
    category: HumanDetectorCategory;
    /** Which automated layer should plausibly have caught this first */
    suspectedFailedLayer: string;
    /** Confidence the message really is a human-caught coherence break */
    confidence: 'low' | 'medium' | 'high';
    /** Why the classifier fired (the signal phrases it matched) */
    matchedSignals: string[];
    /** Short preview of the human's message (truncated) */
    messagePreview: string;
}
export type HumanDetectorCategory = 'factual-correction' | 'staleness' | 'contradiction' | 'source-of-truth-drift' | 'repeat-ask' | 'meta-failure' | 'preference' | 'frustration';
/**
 * Categories that feed the Correction & Preference Learning Sentinel rather
 * than the guardian-failure heat map. They are tagged here (single source of
 * truth) and excluded from {@link HumanAsDetectorLog.summarizeByLayer}. The
 * correction loop keys on these to decide whether a message is a learning
 * candidate. Spec §3.2.
 */
export declare const LEARNING_ONLY_CATEGORIES: ReadonlySet<HumanDetectorCategory>;
/** Deterministic-weight at/above which a learning signal is "full confidence"
 *  for the analyzer's code-determined provenance filter (spec §3.5). A lone
 *  weak rule (weight < this) surfaces but never alone admits a record. */
export declare const LEARNING_DETERMINISTIC_THRESHOLD = 3;
export declare class HumanAsDetectorLog {
    private static instance;
    private stateDir;
    private agentName;
    /** In-memory ring of recent signals (for health/summary without disk reads) */
    private recent;
    private constructor();
    static getInstance(): HumanAsDetectorLog;
    /** Reset singleton for testing. */
    static resetForTesting(): void;
    configure(opts: {
        stateDir: string;
        agentName?: string;
    }): void;
    /**
     * Classify a piece of human text WITHOUT recording it. Pure, deterministic,
     * no I/O — this is the unit-testable core.
     *
     * Returns null if the text doesn't look like a coherence-break correction.
     */
    classify(text: string): {
        category: HumanDetectorCategory;
        suspectedFailedLayer: string;
        confidence: 'low' | 'medium' | 'high';
        matchedSignals: string[];
        /** Total Layer-0 weight — the CODE-determined provenance the correction
         *  analyzer keys on (spec §3.5). Stable across phrasings of the same rule. */
        deterministicWeight: number;
        /** When the top signal is a learning-only family (preference|frustration),
         *  this is that category; null otherwise. The correction loop reads this to
         *  decide whether a message is a learning candidate (spec §3.2). */
        learningKind: 'preference' | 'frustration' | null;
    } | null;
    /**
     * Observe an inbound human message. If it looks like a coherence-break
     * correction, record it. Returns the recorded signal, or null if the message
     * wasn't a correction. Never throws.
     */
    observe(input: {
        text: string;
        source: string;
        topicId?: number | null;
        messageId?: number | null;
    }): HumanDetectorSignal | null;
    /** Recent in-memory signals (for health checks / summary endpoint). */
    getRecent(): HumanDetectorSignal[];
    private driftSampled;
    private driftMismatches;
    /** Record one drift-canary sample outcome. `mismatch` = the LLM judged the
     *  un-classified message to actually be a correction (a regex miss). */
    recordDriftSample(mismatch: boolean): void;
    /** Drift-canary counters (sampled / mismatches / miss-rate). Read-only. */
    getDriftCanary(): {
        sampled: number;
        mismatches: number;
        missRate: number;
    };
    /**
     * Summarize signals grouped by suspected failed layer — the heat map of
     * "where the human is doing the system's job."
     */
    summarizeByLayer(): Array<{
        layer: string;
        count: number;
        categories: string[];
    }>;
    private persist;
    /**
     * Best-effort: repopulate the in-memory ring from the last 200 persisted
     * records. The heat map (summarizeByLayer + recent) reads only the ring, so
     * without this it would silently empty on every restart even though the full
     * history is on disk. Persisted records carry no messagePreview (kept off
     * disk by persist()), so hydrated entries have an empty preview — the byLayer
     * counts, which are the point of the heat map, are fully restored.
     */
    private hydrateFromDisk;
}
/** Minimal shape of an inbound message-logged entry the wiring reads. */
export interface InboundMessageEntry {
    fromUser?: boolean;
    text?: string;
    topicId?: number | null;
    messageId?: number | null;
}
/**
 * The gating decision used where the log is chained onto a message adapter's
 * onMessageLogged: observe ONLY inbound HUMAN messages that carry text. Agent
 * messages and empty entries are skipped. Extracted from the server wiring so
 * the inbound-human gate is unit-testable (wiring-integrity), not buried inline.
 */
export declare function observeInboundMessage(log: HumanAsDetectorLog, entry: InboundMessageEntry, source?: string): HumanDetectorSignal | null;
//# sourceMappingURL=HumanAsDetectorLog.d.ts.map