/**
 * JsonlFeedbackStore.ts — the DURABLE canonical FeedbackStore for the operated
 * instance (post-cutover authority under the migration spec's Option B: Echo's
 * instance keeps its OWN canonical copy; Portal never opens a write door).
 *
 * Backing format: newline-delimited JSON, one file per entity family
 * (`feedback.jsonl`, `clusters.jsonl`, `dispatches.jsonl`) under a caller-supplied
 * directory. The format is DELIBERATELY the same shape `PersistedShadowImportTarget`
 * writes (one full row per line, keyed by feedbackId/clusterId), so the proven AS-IS
 * import artifact seeds this store directly — the cutover import output IS the
 * canonical store, no translation step (fewer integrity risks at the one-way door).
 *
 * Mutation model: append-only log with LAST-WRITE-WINS on load. Every mutation
 * appends the FULL updated row; on construction all lines are folded into a Map by
 * id (later lines supersede earlier). A file with one row per entity — exactly what
 * the import produces — is therefore a valid, already-compact log. When superseded
 * lines pile up past a threshold the file is compacted at construction time via an
 * atomic temp+rename rewrite (crash mid-compaction leaves the original intact).
 *
 * JSONL (not SQLite) for the same reason as PersistedShadowImportTarget: dependency-
 * free, no native module, trivially inspectable, and append durability is a plain
 * appendFileSync. Volume is fleet feedback — low write rate; the import's measured
 * 148k-row artifact parses in well under a second.
 */
import type { FeedbackItem, Cluster } from '../processor/types.js';
import type { ReopenDecision } from '../processor/reopen.js';
import type { DispatchRecord } from '../dispatch/dispatch.js';
import type { FeedbackStore, FeedbackMetrics } from './FeedbackStore.js';
export declare class JsonlFeedbackStore implements FeedbackStore {
    private readonly dir;
    private readonly feedbackPath;
    private readonly clustersPath;
    private readonly dispatchesPath;
    private feedback;
    private clusters;
    private dispatches;
    private counts;
    constructor(dir: string);
    /**
     * Re-fold all three on-disk JSONL files into fresh in-memory state — the SAME
     * load the constructor runs. Use this when another PROCESS may have appended to
     * the shared store since this instance was built (e.g. the InboxDrainer, a
     * separate launchd process, appends `unprocessed` rows to `feedback.jsonl`
     * continuously; without a reload this in-memory Map is frozen at construction
     * time and the processing pass becomes a permanent no-op over everything
     * ingested after boot).
     *
     * Concurrency: this is a read-only re-fold and appends are atomic single-line
     * writes (appendFileSync). A reload that races a mid-append at worst skips one
     * torn trailing line (already handled by loadJsonl's torn-line skip); the next
     * reload picks up the completed line — identical to the constructor's load path.
     * The in-memory `counts` (FeedbackMetrics) are session-scoped tallies of THIS
     * instance's own mutations, not durable rows, so they are intentionally NOT
     * reset by a reload.
     */
    reload(): void;
    private loadAll;
    private loadAndMaybeCompact;
    private appendFeedback;
    private appendCluster;
    getUnprocessedFeedback(): FeedbackItem[];
    getActiveClusters(): Cluster[];
    getCluster(clusterId: string): Cluster | undefined;
    upsertClusterFromItem(clusterId: string, item: FeedbackItem): void;
    mergeIntoCluster(clusterId: string, _item: FeedbackItem): void;
    applyReopen(clusterId: string, decision: ReopenDecision): void;
    markProcessed(feedbackId: string, clusterId: string): void;
    hasFeedback(feedbackId: string): boolean;
    addFeedback(item: FeedbackItem): void;
    listDispatches(filter?: {
        since?: string;
        type?: string;
    }): DispatchRecord[];
    findDispatchByTitle(title: string): DispatchRecord | undefined;
    createDispatch(record: DispatchRecord): void;
    metrics(): FeedbackMetrics;
    /** Read-only size snapshot for the status surface. */
    sizes(): {
        feedback: number;
        clusters: number;
        dispatches: number;
    };
    /**
     * Read-only aggregate snapshot for `GET /feedback-factory/stats`. Pure read —
     * never mutates. `byStatus` buckets every feedback row by its `status`
     * (defaulting an absent status to 'unprocessed', mirroring getUnprocessedFeedback).
     * `lastWriteAt` is the latest content timestamp across all entities
     * (feedback.receivedAt, cluster.updatedAt/createdAt, dispatch.createdAt) — a
     * deterministic, content-derived high-water mark, null when the store is empty.
     */
    stats(): {
        total: number;
        byStatus: Record<string, number>;
        clusterCount: number;
        dispatchCount: number;
        lastWriteAt: string | null;
    };
}
//# sourceMappingURL=JsonlFeedbackStore.d.ts.map