/**
 * BlockerLedger — the resolution-workflow + memory layer that COMPLETES
 * Principle 1 ("almost every blocker is a false blocker — work it through").
 *
 * The detection half already exists (deferral-detector hook, B16_UNVERIFIED_WALL,
 * B17_FALSE_BLOCKER). This ledger is the MISSING half: it turns a detected blocker
 * into a *gated pipeline* with structural evidence-of-work at every terminal, built
 * so the memory can NEVER become a deferral-laundromat.
 *
 * Pipeline (no state may be skipped — `advance` refuses an illegal transition):
 *   candidate → authority-checked → access-requested → dry-run → live-run → terminal
 *
 * Terminal states each require verified evidence-of-work:
 *   - `resolved`     — a real codified playbook (confined path, references the
 *                      blocker id, links a SUCCESSFUL live-run). Existence alone
 *                      never satisfies it.
 *   - `true-blocker` — the dangerous terminal, so the most gated. Requires a
 *                      closed-taxonomy reason + a recorded FAILED self-fetch/dry-run
 *                      attempt (no kind exempt) + an `access-requested` to the user
 *                      AFTER the failed attempt + passing the Tier-1 B17 authority.
 *                      Stored as a DECAYING HYPOTHESIS ("last verified <date>"),
 *                      re-tested on a cadence; re-settle needs NEW evidence.
 *
 * Signal vs Authority (docs/signal-vs-authority.md): the ledger RECORDS and
 * STRUCTURES; it never blocks an outbound message. The one judgment it carries —
 * the `true-blocker` settle — routes through the injected Tier-1 authority
 * (`settleAuthority`, default-wired to the B17 gate at the server), exactly as the
 * constitution requires. Brittle field-presence checks gate the *form* of evidence;
 * the *settle judgment* is the intelligent gate's.
 *
 * Concurrency: all mutations go through a single-writer CAS path (atomic temp-file
 * + rename), reusing the CommitmentTracker.mutate() pattern. File-JSON does not
 * exempt the ledger from the concurrency safety the rest of instar enforces.
 *
 * Spec: docs/specs/AUTONOMY-PRINCIPLES-ENFORCEMENT-SPEC.md (Piece 1).
 */
import type { SelfUnblockRunLoader } from './SelfUnblockChecklist.js';
/** The gated pipeline states. Order matters — `advance` walks them linearly. */
export declare const BLOCKER_PIPELINE: readonly BlockerNonTerminalState[];
export type BlockerNonTerminalState = 'candidate' | 'authority-checked' | 'access-requested' | 'dry-run' | 'live-run';
export type BlockerTerminalState = 'resolved' | 'true-blocker';
export type BlockerState = BlockerNonTerminalState | BlockerTerminalState;
/**
 * The ONLY legitimate true-blocker kinds (closed taxonomy — NOT free prose).
 * A reason that doesn't match is refused and the entry stays where it is.
 */
export declare const TRUE_BLOCKER_KINDS: readonly ["operator-only-secret", "operator-only-account", "legal-billing-authorization", "operator-judgment"];
export type TrueBlockerKind = (typeof TRUE_BLOCKER_KINDS)[number];
export interface BlockerTransition {
    from: BlockerState;
    to: BlockerState;
    at: string;
    /** Authenticated origin that performed the transition (session/operator id). */
    origin: string;
    note?: string;
}
export interface AuthorityCheckEvidence {
    /** Does the agent itself have the authority/access to clear this? */
    agentHasAuthority: boolean;
    /** Does the user hold the authority the agent lacks? */
    userHasAuthority: boolean;
    note: string;
}
export interface AccessRequestEvidence {
    /** A reference to the ACTUAL outbound message asking the user (e.g. a relay id). */
    messageRef: string;
    at: string;
}
export interface AttemptEvidence {
    /** A failed self-fetch (vault/gh/vercel/decrypt-miss) OR a failed dry-run. */
    type: 'self-fetch' | 'dry-run';
    at: string;
    /** What was tried and how it failed (untrusted free text — surfaced via envelope). */
    detail: string;
    succeeded: false;
}
export interface LiveRunEvidence {
    at: string;
    outcome: string;
    succeeded: boolean;
}
export interface ResolvedTerminal {
    kind: 'resolved';
    /** Confined, on-disk playbook path that references the blocker id + links live-run. */
    playbookPath: string;
    at: string;
}
export interface TrueBlockerTerminal {
    kind: 'true-blocker';
    reasonKind: TrueBlockerKind;
    /** Per-step rebuttal of why each pipeline stage failed (untrusted free text). */
    rebuttal: string;
    /** The MANDATORY recorded failed work-attempt (self-fetch for secret/account kinds). */
    failedAttempt: AttemptEvidence;
    /** Reference to the access-request made to the user, AFTER the failed attempt. */
    accessRequestRef: string;
    /** Hash of the B17 gate decision that AUTHORIZED this settle. */
    gateDecisionHash: string;
    at: string;
    /** D6: a settled true-blocker is a DECAYING hypothesis — reopened on this date. */
    recheckAfter: string;
    /** Consecutive re-settles with no new evidence — escalates to the user after N. */
    noEvidenceResettleCount: number;
}
export type BlockerTerminal = ResolvedTerminal | TrueBlockerTerminal;
export interface BlockerEntry {
    id: string;
    /** CAS version — bumped on every mutation. */
    version: number;
    state: BlockerState;
    /** The false-blocker framing that opened the entry (untrusted — surfaced via envelope). */
    detectedText: string;
    /** Authenticated origin that opened the entry. */
    origin: string;
    createdAt: string;
    updatedAt: string;
    history: BlockerTransition[];
    authorityCheck?: AuthorityCheckEvidence;
    accessRequest?: AccessRequestEvidence;
    dryRun?: AttemptEvidence;
    liveRun?: LiveRunEvidence;
    terminal?: BlockerTerminal;
    /** When this true-blocker was last reopened by the D6 re-walk job (if ever). */
    lastReopenedAt?: string;
    /**
     * Consecutive no-new-evidence re-settle ATTEMPTS since the last successful
     * (new-evidence) settle or reopen. Each refused no-evidence re-settle bumps this
     * (and is refused); at >= maxNoEvidenceResettles the anomaly escalates to the user.
     * A wall re-stamped without ever being re-tested is itself a signal.
     */
    noEvidenceResettleAttempts?: number;
}
/**
 * The injected Tier-1 authority for the `true-blocker` settle judgment.
 * Default-wired to the B17 gate at the server; tests inject a fake.
 * Returns allow/deny + a reason + a stable hash of the decision (for the audit line).
 */
export type SettleAuthority = (input: {
    entry: BlockerEntry;
    proposed: TrueBlockerTerminal;
}) => Promise<{
    allow: boolean;
    reason: string;
    decisionHash: string;
}>;
export interface BlockerLedgerOptions {
    /** Agent home / stateDir; the store lives at `<stateDir>/state/blocker-ledger.json`. */
    stateDir: string;
    /** The Tier-1 settle authority (B17). Optional — when absent, a true-blocker settle is refused. */
    settleAuthority?: SettleAuthority;
    /**
     * The Self-Unblock checklist run store ("Self-Unblock Before Escalating", §5.1).
     * OPTIONAL — when injected, the feature is ENABLED: a true-blocker settle then
     * REQUIRES a `selfUnblockRunId`, BlockerLedger LOADS + verifies the persisted
     * run, and DERIVES the `failedAttempt` from it (a caller-embedded failedAttempt
     * with no valid persisted run is HARD-rejected `missing_failed_attempt`). When
     * absent (dark default), the existing caller-supplied `failedAttempt` path is
     * unchanged. Spec: docs/specs/self-unblock-before-escalating.md.
     */
    selfUnblockRunStore?: SelfUnblockRunLoader;
    /** Move terminal entries older than this many days to the archive (default 30). */
    archiveAfterDays?: number;
    /** Default days until a settled true-blocker is reopened for a re-walk (default 30). */
    recheckAfterDays?: number;
    /** Consecutive no-new-evidence re-settles before escalating (default 2). */
    maxNoEvidenceResettles?: number;
    /** Max free-text length on any field, in chars (default 4000). */
    maxFreeTextChars?: number;
    /**
     * Confined directory roots a `resolved` playbook MUST live under (absolute or
     * stateDir-relative). Defaults to the agent's skills + playbook dirs.
     */
    confinedPlaybookRoots?: string[];
    /** Injectable clock for deterministic tests. */
    now?: () => Date;
}
export declare class BlockerLedgerError extends Error {
    readonly code: string;
    constructor(message: string, code: string);
}
/**
 * Wrap untrusted ledger free-text in a delimited, quoted envelope before it is
 * ever surfaced to an LLM (the D6 re-walk re-feeds it into context). NEVER
 * concatenate ledger text as instructions — it is DATA. Mirrors the
 * `<auto-learned-preference>` signal-envelope pattern.
 */
export declare function toLlmSafeEnvelope(text: string): string;
/** HTML-escape for the dashboard render path. */
export declare function escapeHtmlForDashboard(text: string): string;
export declare class BlockerLedger {
    private readonly storePath;
    private readonly archivePath;
    private readonly auditPath;
    private readonly settleAuthority?;
    /** When set, the Self-Unblock feature is ON: settle derives the failed attempt from a verified run. */
    private readonly selfUnblockRunStore?;
    private readonly archiveAfterDays;
    private readonly recheckAfterDays;
    private readonly maxNoEvidenceResettles;
    private readonly maxFreeText;
    private readonly confinedRoots;
    private readonly now;
    private readonly stateDir;
    private store;
    /** Serialize all mutations through one in-process queue (single-writer). */
    private mutateChain;
    constructor(opts: BlockerLedgerOptions);
    private loadStore;
    private saveStore;
    private iso;
    /**
     * Single-writer mutation funnel. Every mutation serializes through one
     * in-process chain (no two interleave a read-modify-write) — this IS the
     * single-writer guarantee. Each mutation reloads from disk first so a
     * cross-process write (e.g. a manual edit) is picked up rather than clobbered,
     * then applies + persists atomically (temp-file + rename).
     *
     * A rejected mutation does not poison the chain: the next mutation runs
     * regardless of the prior outcome.
     */
    private mutate;
    private applyOnce;
    private audit;
    private boundText;
    private find;
    /**
     * Open a `candidate` entry. Used directly by the deferral-detector auto-open
     * trigger (Structure > Willpower — the agent doesn't choose to log it).
     */
    open(input: {
        detectedText: string;
        origin: string;
    }): Promise<BlockerEntry>;
    list(opts?: {
        limit?: number;
        offset?: number;
        includeArchived?: boolean;
    }): {
        entries: BlockerEntry[];
        total: number;
    };
    get(id: string): BlockerEntry | undefined;
    /**
     * Advance through the linear non-terminal pipeline. REFUSES a transition that
     * skips a state — the work-the-blocker states ARE the feature; bypassing them
     * to a terminal is the exact avoidance Principle 1 exists to kill.
     */
    advance(id: string, input: {
        origin: string;
        authorityCheck?: AuthorityCheckEvidence;
        accessRequest?: AccessRequestEvidence;
        dryRun?: Omit<AttemptEvidence, 'type' | 'succeeded'> & {
            succeeded?: boolean;
        };
        liveRun?: LiveRunEvidence;
        note?: string;
    }): Promise<BlockerEntry>;
    /**
     * Settle to a terminal state with full evidence validation.
     *  - `resolved`: requires a confined, id-referencing playbook AND a successful live-run.
     *  - `true-blocker`: closed-taxonomy reason + failed-attempt rebuttal + post-attempt
     *    access-request + a PASS from the injected Tier-1 authority (B17).
     */
    settle(id: string, input: {
        origin: string;
        kind: 'resolved';
        playbookPath: string;
    } | {
        origin: string;
        kind: 'true-blocker';
        reasonKind: TrueBlockerKind;
        rebuttal: string;
        /**
         * The mandatory FAILED work-attempt. `at` defaults to now; it MUST precede
         * the access-request. When the Self-Unblock run store is configured, this
         * is DERIVED from the verified persisted run (a caller-embedded value with
         * no valid `selfUnblockRunId` is HARD-rejected) — see `selfUnblockRunId`.
         */
        failedAttempt?: {
            type: 'self-fetch' | 'dry-run';
            detail: string;
            at?: string;
        };
        /**
         * Reference to the persisted Self-Unblock checklist run that PRODUCED the
         * failed attempt ("Self-Unblock Before Escalating", §5.1). REQUIRED when the
         * run store is configured; ignored when it is not. BlockerLedger loads +
         * verifies the run and derives the `failedAttempt` from it.
         */
        selfUnblockRunId?: string;
        /** The access-request to the user — recorded AFTER the failed attempt. */
        accessRequest: {
            messageRef: string;
            at?: string;
        };
    }): Promise<BlockerEntry>;
    private settleTrueBlocker;
    /**
     * D6 re-walk: reopen a settled true-blocker to `candidate` when its recheck date
     * is due. The wall becomes a hypothesis again — re-settling requires NEW evidence.
     */
    reopenForRecheck(id: string): Promise<BlockerEntry>;
    /** Settled true-blockers whose recheck date is due (for the D6 job). */
    dueForRecheck(now?: Date): BlockerEntry[];
    /**
     * Move terminal entries older than the archive threshold to the archive file,
     * keeping the hot file bounded. Returns the number archived.
     */
    archiveOld(now?: Date): Promise<number>;
    private assertNotTerminal;
    /**
     * Verify a loaded Self-Unblock checklist run represents a GENUINE exhaustion
     * before its derived attempt can settle a true-blocker ("Self-Unblock Before
     * Escalating", §5.1). A run is valid iff it:
     *   - exists (the caller referenced a runId the runner actually produced),
     *   - is well-formed (a non-empty array of probe results + a completedAt),
     *   - matches the required attempt TYPE (a self-fetch-kind blocker needs a
     *     self-fetch run; a dry-run-kind needs a dry-run run),
     *   - is a genuine exhaustion: EVERY probe came up `holdsRelevantCred: false`
     *     (`exhausted: true`). A run that found a relevant credential is NOT an
     *     exhaustion — the agent should self-unblock with it, not escalate.
     *
     * Returns `{ ok: true, detail }` with a synthesized human summary of the probed
     * sources, or `{ ok: false, reason }`.
     */
    private verifySelfUnblockRun;
    /** Parse a caller-supplied ISO timestamp; return undefined if absent/invalid. */
    private normalizeIso;
    private computeRecheckAfter;
    /** Date.now/Math.random are unavailable in some contexts; derive jitter from the clock. */
    private deterministicJitter;
    private loadArchive;
    private saveArchive;
}
//# sourceMappingURL=BlockerLedger.d.ts.map