/**
 * ID-prefix migration window — read-side compat for legacy `thd_*` IDs.
 *
 * Consumers that touch raw legacy IDs (filesystem migrator, wire decoders,
 * CLI imports) route through {@link acceptLegacyThreadId} so the warning
 * signal is structured rather than ad-hoc console output (Convention #18).
 *
 * | Window state | Reader accepts      | Writer emits | Legacy read behaviour         |
 * |--------------|---------------------|--------------|-------------------------------|
 * | OPEN (now)   | `thd_*` AND `prj_*` | `prj_*` only | emits `MigrationWarning` once |
 * | CLOSED       | `prj_*` only        | `prj_*` only | rejects `StalePrefixError`    |
 *
 * The {@link WINDOW_OPEN} constant is the single switch that flips this
 * module from soft-accept to hard-reject. Convention #0: no silent
 * long-lived compat — the window is explicit and fails closed when closed.
 */
import type { ProjectId } from '../../types/session/ids.js';
/**
 * Structured event emitted when the reader accepts a legacy `thd_*` ID.
 *
 * Shape is stable across the 0.2.x window — platform consumers can wire
 * this into their observability pipeline (metrics, audit log) without
 * string parsing (Convention #18).
 */
export interface MigrationWarning {
    kind: 'id_prefix_legacy_read';
    legacyId: string;
    normalizedId: ProjectId;
    at: Date;
    emittedOncePerProcess: true;
}
/** Sink contract — one `emit` method so callers can swap implementations. */
export interface MigrationWarningSink {
    emit(warning: MigrationWarning): void;
}
/**
 * Default sink used when consumers do not inject one. Drops warnings on the
 * floor — migration still runs; observers just lose the signal. Convention
 * #5 deny-by-default applies to behaviour, not telemetry; missing a warning
 * sink is not a failure mode.
 */
export declare const NOOP_MIGRATION_WARNING_SINK: MigrationWarningSink;
/**
 * Raised when the reader encounters a string that is neither a valid
 * `prj_*` nor `thd_*` prefix. In the 0.3.x window the legacy branch will
 * also throw this error — flip {@link WINDOW_OPEN} to `false` to cut over.
 */
export declare class StalePrefixError extends Error {
    readonly details: {
        rawId: string;
        kind: 'thd_rejected' | 'unknown_prefix';
    };
    constructor(details: {
        rawId: string;
        kind: 'thd_rejected' | 'unknown_prefix';
    });
}
/**
 * 0.2.x: compat window OPEN — `thd_*` coerces to `prj_*` with a warning.
 * 0.3.x: flip to `false` — legacy branch becomes throw-only.
 */
export declare const WINDOW_OPEN = true;
/**
 * Accept-on-read for legacy `thd_*` IDs during the 0.2.x window.
 *
 * Contract:
 *  - `prj_*` inputs return as-is; no warning emitted.
 *  - `thd_*` inputs normalize to `prj_<suffix>` and emit a single
 *    {@link MigrationWarning} per distinct input string per process.
 *  - Everything else throws {@link StalePrefixError}.
 *  - When {@link WINDOW_OPEN} flips to `false`, the `thd_*` branch throws
 *    too — single knob, single commit.
 *
 * Testing hook: {@link __resetSeenLegacyForTests} clears the dedup Set so
 * each test starts with a clean emission history.
 */
export declare function acceptLegacyThreadId(raw: string, sink: MigrationWarningSink): ProjectId;
/**
 * Writer guard: reject emission of `thd_*` at encode time. Phase 1's
 * {@link generateProjectId} already emits `prj_*` only; this helper exists
 * so write paths that handle raw strings (e.g. synthesis during filesystem
 * migration) can fail fast on accidental legacy re-emission. Convention #0:
 * no silent back-sliding to the old prefix.
 */
export declare function rejectLegacyPrefix(id: string): void;
/**
 * Test-only: clear the process-level dedup Set so subsequent
 * {@link acceptLegacyThreadId} calls emit warnings again. Production code
 * must never call this — the whole point is one warning per distinct
 * legacy id per process.
 */
export declare function __resetSeenLegacyForTests(): void;
//# sourceMappingURL=id-prefix.d.ts.map