export declare const ISSUE_BUCKETS: readonly ["framework-limitation", "instar-integration-gap", "generic-agent-mistake"];
export type IssueBucket = (typeof ISSUE_BUCKETS)[number];
export declare const ISSUE_SEVERITIES: readonly ["low", "medium", "high"];
export type IssueSeverity = (typeof ISSUE_SEVERITIES)[number];
export declare const ISSUE_STATUSES: readonly ["open", "spec'd", "fixed", "wont-fix"];
export type IssueStatus = (typeof ISSUE_STATUSES)[number];
export declare const PLAYBOOK_STATUSES: readonly ["none", "candidate", "extracted", "superseded"];
export type PlaybookStatus = (typeof PLAYBOOK_STATUSES)[number];
export interface FrameworkIssueLedgerOptions {
    dbPath: string;
    /** Override clock for deterministic tests. */
    now?: () => number;
}
/** What Stage B emits per observation (spec §5). */
export interface RecordObservationInput {
    framework: string;
    bucket: IssueBucket;
    title: string;
    severity?: IssueSeverity;
    /** Conservative auto-merge key (spec §13.3). Required — identifies the canonical issue. */
    dedupKey: string;
    /** Richer fingerprint for probable-dup review (spec §13.3). */
    signature?: string;
    /** OPAQUE reference only — path+line, log ref, PR#, sentinel-event id (spec §13.2/§17). */
    evidence?: string;
    observedVersion?: string;
    tickId?: string;
    /**
     * Collapses repeated observations of the same open issue within a fix-window
     * to one episode (spec §13.4). Defaults to the observed version so an unfixed
     * issue accrues at most one episode per version span.
     */
    episodeKey?: string;
    /** Forced primary when dual-tagged (spec §6). */
    bucketPrimary?: IssueBucket;
    relatedSpec?: string;
}
export interface RecordObservationResult {
    issueId: string;
    created: boolean;
    episodeRecorded: boolean;
    recurrenceCount: number;
    probableLoop: boolean;
}
export interface IssueRow {
    id: string;
    framework: string;
    bucket: IssueBucket;
    bucketPrimary: IssueBucket | null;
    title: string;
    severity: IssueSeverity;
    status: IssueStatus;
    dedupKey: string;
    signature: string | null;
    recurrenceCount: number;
    firstSeenVersion: string | null;
    lastSeenVersion: string | null;
    fixedInVersion: string | null;
    regressedFromIssueId: string | null;
    playbookStatus: PlaybookStatus;
    /** Non-Echo actor who attested the candidate→extracted promotion (§13.6). */
    promotedBy: string | null;
    wontFixReason: string | null;
    relatedSpec: string | null;
    probableLoop: boolean;
    createdAt: number;
    updatedAt: number;
    /** Derived at read (spec §13.4) — never stored, so a re-classification can't stale it. */
    generalizable: boolean;
    /** Derived at read: severityWeight × recurrenceCount × recency decay (spec §13.4). */
    impactScore: number;
}
export interface ListIssuesQuery {
    framework?: string;
    bucket?: IssueBucket;
    status?: IssueStatus;
    limit?: number;
}
export interface PlaybookQuery {
    targetFramework: string;
    limit?: number;
}
/** A single issue Stage-B forensics produced for a run (everything but the
 *  per-run framework/tickId, which `captureRun` supplies). */
export type ForensicFinding = Omit<RecordObservationInput, 'framework' | 'tickId'>;
export interface CaptureRunInput {
    framework: string;
    tickId?: string;
    findings: ForensicFinding[];
}
export interface CaptureRunResult {
    runId: string;
    framework: string;
    findingsCount: number;
    observationsWritten: number;
    newIssues: number;
    /** Previously-fixed issues whose signature/dedupKey matches a new finding —
     *  surfaced for human regression review, NOT auto-linked (spec §13.5). */
    regressionCandidates: Array<{
        findingDedupKey: string;
        candidateIssueId: string;
    }>;
}
export interface CaptureStats {
    totalRuns: number;
    totalObservationsWritten: number;
    lastRanAt: number | null;
    byFramework: Array<{
        framework: string;
        runs: number;
        observations: number;
        lastRanAt: number;
    }>;
}
export declare function scanForSecret(s: string): boolean;
export declare class FrameworkIssueLedger {
    private db;
    private now;
    constructor(opts: FrameworkIssueLedgerOptions);
    /** Stable list of frameworks the ledger has seen — for the route allowlist (spec §5/§17). */
    knownFrameworks(): string[];
    /**
     * Record one Stage-B observation. Finds-or-creates the canonical issue via
     * (framework, dedupKey); collapses the observation into a distinct episode via
     * episodeKey; increments the materialized recurrence_count only on a new
     * episode (spec §13.4). All writes run in a single transaction (CAS-safe: the
     * UNIQUE(issue_id, episode_key) index is the concurrency guard — a racing
     * duplicate episode insert fails and is counted as already-recorded).
     */
    recordObservation(input: RecordObservationInput): RecordObservationResult;
    /** Retention pruning — keep first N + last M observations per issue (spec §13.2). */
    private pruneObservations;
    /**
     * Single-writer CAS mutate of an issue's mutable fields (status, bucket,
     * playbookStatus, regression link, etc.). Follows CommitmentTracker's
     * read-modify-write-with-version-check pattern; enum fields are validated.
     * `wont-fix` requires a reason (spec §13.7).
     */
    updateIssue(issueId: string, patch: Partial<{
        status: IssueStatus;
        bucket: IssueBucket;
        bucketPrimary: IssueBucket | null;
        playbookStatus: PlaybookStatus;
        fixedInVersion: string | null;
        regressedFromIssueId: string | null;
        wontFixReason: string | null;
        relatedSpec: string | null;
    }>): IssueRow | null;
    /**
     * Promote an issue along the playbook lifecycle (spec §13.6):
     *   none → candidate → extracted → superseded.
     * `none → candidate` may be auto-suggested by Stage B (any actor). But
     * `candidate → extracted` — the step that puts a lesson into the reusable
     * onboarding checklist — REQUIRES a non-Echo attestation, so the playbook's
     * contents are never end-to-end under the proposing agent's control. The
     * attesting actor is recorded in `promoted_by`. Throws if Echo attempts the
     * `extracted` promotion itself.
     */
    promotePlaybook(issueId: string, target: PlaybookStatus, promotedBy: string): IssueRow | null;
    /**
     * Idempotent backfill of the §13.6 none→candidate auto-suggestion for issues
     * that were already terminal-resolved (fixed | wont-fix) and generalizable
     * BEFORE that auto-suggestion existed — their lessons were logged but stuck at
     * 'none', so the onboarding playbook never surfaced them. Promotes every such
     * row to 'candidate'; never touches candidate/extracted/superseded rows; never
     * promotes non-generalizable or non-terminal issues. Self-limiting (the WHERE
     * clause matches nothing after the first run) so it is safe to call on every
     * boot — which is how existing ledgers across the fleet pick up the seeding
     * without a dedicated PostUpdateMigrator entry. Returns the rows promoted.
     * candidate→extracted still requires a non-Echo attestation (promotePlaybook);
     * this only seeds candidates.
     */
    backfillPlaybookCandidates(): number;
    /** Bucket-distribution telemetry (spec §15) — surfaces attribution skew (a
     *  sudden spike in `generic-agent-mistake` is the "blame the mentee" tell). */
    observability(): {
        bucketDistribution: Record<IssueBucket, number>;
        leakSuspected: number;
        probableLoops: number;
        playbookExtracted: number;
    };
    /**
     * Suggest a regression link: a new issue whose signature/dedupKey matches a
     * previously-`fixed` issue is a candidate regression (spec §13.5). Returns the
     * matching fixed issues (does NOT auto-link — review decides).
     */
    suggestRegressions(input: {
        framework: string;
        dedupKey: string;
        signature?: string | null;
    }): IssueRow[];
    getIssue(id: string): IssueRow | null;
    /** Whether ANY framework's issue carries this dedup key. Used by the
     *  apprenticeship transcript-audit gate to verify a cycle's claimed
     *  locally-filed findings actually exist in this ledger. */
    hasDedupKey(dedupKey: string): boolean;
    listIssues(q?: ListIssuesQuery): IssueRow[];
    /**
     * The onboarding playbook for `targetFramework`: generalizable lessons from
     * PRIOR (other) frameworks, ranked by impactScore (spec §13.6). The playbook
     * for X is sourced from frameworks != X — never X's own issues.
     */
    playbook(q: PlaybookQuery): IssueRow[];
    observationCount(issueId: string): number;
    /**
     * Stage-B auto-capture (spec §5, §19.2). The single atomic entry point the
     * mentor tick calls after forensics: it writes every finding to the ledger
     * (no "remember to log" — zero-manual-capture) and ALWAYS logs the run to the
     * capture-funnel, even when `findings` is empty. That funnel row is what makes
     * an inert/broken writer visible: a run that produced zero observations is
     * recorded as a run, so "ran, found nothing" is distinguishable from "never
     * ran." Regression candidates are surfaced for review, never auto-linked
     * (spec §13.5 — promotion is not the writer's call).
     */
    captureRun(input: CaptureRunInput): CaptureRunResult;
    /** Capture-funnel observability (spec §5/§18). Surfaces ticks→observations so
     *  a writer that runs but never writes is visible. */
    captureStats(): CaptureStats;
    private rowToIssue;
    close(): void;
}
/** Clamp a list limit to 1..500 (spec §5/§17). */
export declare function clampLimit(raw: unknown): number;
//# sourceMappingURL=FrameworkIssueLedger.d.ts.map