/**
 * transitions.ts — TS port of the feedback-factory lifecycle state machine.
 *
 * Scar (a) (the evidence gate, processor side) + part of scar (c)/(d). Byte-exact
 * port of `V2_STATES` (:1000), `V2_TRANSITIONS` (:1009), `TRANSITION_GATES`
 * (:1028), `can_transition` (:1045), and `detect_cycling` (:1139) from the
 * reference `the-portal/.claude/scripts/feedback-processor.py`. These are the
 * pure decision functions: which lifecycle transitions are legal, what evidence
 * a terminal transition demands, the chronic circuit-breaker, and whether a
 * cluster is cycling. The DB-coupled drivers (cmd_transition, the version-anchored
 * half of can_transition_to_verified) are later increments — this module is the
 * pure, parity-testable core.
 *
 * The reason strings interpolate Python's `sorted(set)` list-repr; pyListRepr()
 * reproduces it (single-quoted, comma-space, alphabetical) so the parity harness
 * matches BOTH the `allowed` decision and the `reason` text byte-for-byte.
 */
/** Python V2_STATES (:1000) — set membership only; order irrelevant. */
export declare const V2_STATES: ReadonlySet<string>;
/** Python V2_TRANSITIONS (:1009) — allowed target states per current state. */
export declare const V2_TRANSITIONS: Readonly<Record<string, ReadonlySet<string>>>;
/** Python TRANSITION_GATES (:1028) — extra hard gates beyond state legality. */
export declare const TRANSITION_GATES: Readonly<Record<string, {
    required_context: string;
    error: string;
}>>;
export interface TransitionContext {
    dispatch_id?: string;
    recurrenceCount?: number;
    [k: string]: unknown;
}
/**
 * Port of Python `can_transition` (:1045). Returns `[allowed, reason]`.
 * Pure: no I/O. Mirrors the reference's check order exactly (invalid target →
 * unknown current → illegal transition → evidence gate → hard gate → chronic
 * circuit-breaker), so the first-failing reason matches the reference.
 */
export declare function canTransition(currentStatus: string, newStatus: string, justification?: string | null, context?: TransitionContext | null): [boolean, string];
/** Port of Python `detect_cycling` (:1139): fix_applied/new/investigating with recurrenceCount ≥ 2. */
export declare function detectCycling(cluster: {
    status?: string;
    recurrenceCount?: number;
}): boolean;
/**
 * V1_TO_V2_STATUS — legacy(v1)→canonical(v2) status projection. Byte-faithful to
 * Portal's `V1_TO_V2_STATUS` (feedback-processor.py:1035). Identity entries
 * (investigating/wontfix/duplicate are spelled the same in both vocabularies) are
 * listed verbatim so the map documents the full v1 vocabulary; only open/fixed/
 * resolved actually change under projection.
 *
 * NB `open` is the v1 birth-default literal (schema.prisma:1970 `@default("open")`),
 * NOT a standalone canonical state — it projects to `new`.
 */
export declare const V1_TO_V2_STATUS: Readonly<Record<string, string>>;
/**
 * TERMINAL_STATUSES — the canonical terminal set, evaluated on NORMALIZED status
 * (feedback-processor.py:379). A cluster whose normalized status is in this set has
 * reached a lifecycle end-state. Includes `legacy_closed`, a terminal-only legacy
 * literal that is not a member of {@link V2_STATES} and has no v1→v2 mapping (so it
 * passes through {@link normalizeStatus} unchanged and is matched here directly).
 */
export declare const TERMINAL_STATUSES: ReadonlySet<string>;
/**
 * Project a (possibly legacy-v1) status into the canonical v2 lifecycle space.
 * A status already in v2 — or any unrecognised value — passes through unchanged, so
 * this is idempotent and safe to apply to either side of a comparison without risk
 * of double-mapping.
 */
export declare function normalizeStatus(status: string): string;
/**
 * True iff the status, after normalization, is a canonical terminal state.
 * Normalize-BEFORE-check is load-bearing: a raw v1 `resolved` must read terminal,
 * which it only does once projected to `closed`. Checking the raw literal against
 * the v2 terminal set would let `resolved` slip through as non-terminal.
 */
export declare function isTerminalStatus(status: string): boolean;
//# sourceMappingURL=transitions.d.ts.map