import type { StalenessStatus } from './CartographerTree.js';
/**
 * Every detect failure path sets `refused: true` + one of these reasons so it
 * flows through the poller's `r.refused` branch and feeds the breaker — a
 * refusal is NEVER mistaken for "genuinely nothing to do". `detect-timeout` and
 * `detect-worker-start-failure` are set by the ENGINE (the worker can't report
 * its own timeout/start failure); the rest are set here.
 */
export type DetectRefusalReason = 'detect-index-too-large' | 'detect-index-unreadable' | 'detect-git-error' | 'detect-timeout' | 'detect-worker-start-failure';
export interface DetectInput {
    /** Absolute path to the cartographer index.json. */
    indexPath: string;
    /** Repo root the git plumbing runs against. */
    projectDir: string;
    /** Refuse (detect-index-too-large) above this on-disk byte size BEFORE parsing. */
    maxIndexBytes: number;
    /** Hard cap on the ordered candidate list this pass returns (maxNodesPerPass × headroom). */
    maxCandidates: number;
    /** Per-pass author budget — used for the anti-starvation front-bias cap (maxNodesPerPass/2). */
    maxNodesPerPass: number;
    /** A deferred dir at/above this defer count is force-promoted (anti-starvation). */
    maxDeferredPasses: number;
    /** How many oldest-fresh nodes to nominate for re-validation this pass. */
    revalidateSamplePerPass: number;
    /** Grace window for the never-authored-past-grace freshness split. */
    graceMs: number;
    /** Explicit git ls-tree maxBuffer (≥64MB) so a big tree never throws ENOBUFS. */
    gitMaxBuffer: number;
    /** Cap on the published stale sample written to the snapshot (the /stale surface). */
    snapshotSampleMax: number;
    /** Wall clock for the grace computation (injected for deterministic tests). */
    nowMs: number;
}
export interface DetectCounts {
    nodeCount: number;
    authoredCount: number;
    neverAuthored: number;
    stale: number;
    pathGone: number;
    generatedAt: string | null;
    headSha: string | null;
}
/** The full freshness aggregate (so `/cartographer/health` never recomputes it on the request thread). */
export interface DetectFreshness {
    nodeCount: number;
    authorableCount: number;
    freshCount: number;
    staleCount: number;
    neverAuthoredCount: number;
    neverAuthoredWithinGrace: number;
    neverAuthoredPastGrace: number;
    authorFailedCount: number;
    freshRatio: number;
    generatedAt: string | null;
}
export interface DetectResult {
    refused: boolean;
    refusalReason?: DetectRefusalReason;
    /** Ordered, BOUNDED (≤ maxCandidates) author candidates (deepest/anti-starvation first). */
    candidates: string[];
    /** Count of dirs whose `staleSincePass` was incremented + persisted this pass. */
    deferredApplied: number;
    counts: DetectCounts;
    freshness: DetectFreshness;
    /** Oldest-authored fresh node paths to re-validate (≤ revalidateSamplePerPass). */
    revalidationSample: string[];
    /** Bounded, secret-filtered stale sample for the /cartographer/stale snapshot. */
    staleSample: {
        path: string;
        status: StalenessStatus;
    }[];
    /** Total stale+never-authored candidates (the sample's `total` for truncation honesty). */
    staleTotal: number;
    /** Measured wall-clock of this detect (operators tune detectTimeoutMs from this). */
    durationMs: number;
    /** True when the index file did not exist (distinct from a refusal — boot scaffold builds it). */
    indexMissing?: boolean;
}
/** The detect-health enum carried on the snapshot + both read routes. */
export type LastDetectStatus = 'ok' | 'timeout' | 'worker-start-failed' | 'index-too-large' | 'index-unreadable' | 'git-error' | 'not-lease-holder';
/**
 * The small, per-host snapshot every /cartographer/* read route serves from —
 * so no request path ever parses the 67MB index or walks the tree. Written
 * atomically by the sweep engine after each detect; never committed (it is
 * per-host state under .instar/cartographer/).
 */
export interface CartographerSnapshot {
    generatedAt: string;
    headSha: string | null;
    counts: DetectCounts;
    freshness: DetectFreshness;
    staleSample: {
        path: string;
        status: StalenessStatus;
    }[];
    staleTotal: number;
    staleSampleTruncated: boolean;
    lastDetectStatus: LastDetectStatus;
    lastDetectAt: string;
    durationMs: number;
}
/** One authored-summary delta applied to the index in the single author-phase write. */
export interface IndexDelta {
    path: string;
    /** Present when the node's summary/codeHash changed (an author or fingerprint refresh). */
    summaryUpdatedAt?: string | null;
    codeHash?: string | null;
    /** Reset to 0 on a successful author. */
    staleSincePass?: number;
    /** Quarantine bookkeeping (failure path). */
    authorFailed?: boolean;
}
export interface ApplyDeltasInput {
    indexPath: string;
    maxIndexBytes: number;
    deltas: IndexDelta[];
}
/** Internal: instrumentation surfaced for tests (peak heap + node-file reads). */
export interface DetectInstrumentation {
    candidateHeapPeak: number;
    starvedHeapPeak: number;
    /** Always 0 — detect reads ZERO node files; the test asserts this. */
    nodeFileReads: number;
}
export declare function runDetect(input: DetectInput, instr?: DetectInstrumentation): DetectResult;
export declare function writeSnapshot(snapshotPath: string, snap: CartographerSnapshot): void;
export declare function readSnapshot(snapshotPath: string): CartographerSnapshot | null;
export declare function applyIndexDeltas(input: ApplyDeltasInput): {
    written: number;
    refused: boolean;
    refusalReason?: DetectRefusalReason;
};
//# sourceMappingURL=cartographerDetect.d.ts.map