/**
 * TopicProfileStore — sticky per-topic execution profile (framework / model /
 * thinking-mode), TOPIC-PROFILE-SPEC §4/§5.1.
 *
 * Modeled on TopicFrameworksStore for the file/atomic-write shape, with the
 * deliberate departures the spec's review required:
 *
 *  - Single-writer serialized `mutate(topicKey, patch)` (the CommitmentTracker
 *    CAS pattern), NOT a load→overwrite-whole-object `set`. The profile has
 *    three independently-writable axes — a whole-object rewrite would clobber
 *    a concurrently-set field. `mutate` does a read-modify-write under a
 *    per-topic process-local lock, merging only the changed fields.
 *  - In-memory cache is AUTHORITATIVE for reads. The file is read once at
 *    boot and on explicit external-change invalidation only — per-spawn
 *    resolution is O(1) (§5.1).
 *  - Durability precedes acknowledgment: `mutate` resolves only after a flush
 *    containing its write has durably landed (tmp+rename completed). A failed
 *    flush REFUSES the write out loud (throws FlushRefusedError) AND ROLLS
 *    BACK the in-memory mutation to the last durably-flushed snapshot — on a
 *    failed COALESCED flush, every waiter whose write is not yet durable is
 *    refused and rolled back together (§5.1, rounds 5-7).
 *  - Queued flush waiters COALESCE: a mutate arriving while a flush is in
 *    flight is satisfied by a single trailing flush of the latest cache
 *    snapshot — a batch of N writes costs O(1-2) flushes (§5.1, round 6).
 *  - The undo snapshot ({ current, previous }) is durable in the same file;
 *    `previous` shifts once per delta-carrying disclosure — the ORCHESTRATION
 *    layer signals the shift via opts.shiftPrevious (it owns the §8 burst /
 *    coalescing-window accounting); the store never guesses the cadence.
 *  - Legacy `state/topic-frameworks.json` is a ONE-DIRECTIONAL read-only seed
 *    (never overwrites a profile entry) + a profile-store-written mirror so a
 *    binary rollback still reads current framework data (§5.1).
 *  - The dry-run shadow (`intendedProfile`, §14) is a sibling field that
 *    resolution NEVER reads; it has its own lifecycle (flip-clear, skew fates).
 *  - §10.4 parked intended-but-unhealthy profiles + breaker counter live on
 *    the entry so a revert can re-offer the pin and a new operator pin can
 *    atomically supersede the parked state in the same mutate.
 */
import type { IntelligenceFramework } from './intelligenceProviderFactory.js';
import { type EffortLevel, type EscalationOverride, type ProfileModelTier, type ProfileValidationError, type ThinkingMode } from './topicProfileValidation.js';
/** §4 — the Topic Profile object. Every axis independently nullable. */
export interface TopicProfile {
    framework?: IntelligenceFramework | null;
    /** Explicit BASELINE model id — mutually exclusive with modelTier. */
    model?: string | null;
    /** BASELINE tier pin — mutually exclusive with model. */
    modelTier?: ProfileModelTier | null;
    /** §9 — does the heavy-work ultra mandate still apply? Default 'inherit'. */
    escalationOverride?: EscalationOverride | null;
    thinkingMode?: ThinkingMode | null;
    /**
     * Claude Code `--effort` launch level (low|medium|high|xhigh|max), passed
     * verbatim as `--effort <level>` at spawn — a DIRECT CLI-flag pin, distinct
     * from the cross-framework `thinkingMode` abstraction. No-op on non-claude
     * frameworks.
     */
    effort?: EffortLevel | null;
    /** ISO timestamp, stamped server-side. */
    updatedAt: string;
    /** VERIFIED operator principal (or 'api-token' / 'system:*'), stamped server-side. */
    updatedBy: string;
}
/** §14 — dry-run shadow intent. Resolution NEVER reads this. */
export interface IntendedProfileShadow {
    fields: Omit<TopicProfile, 'updatedAt' | 'updatedBy'>;
    recordedAt: string;
    recordedBy: string;
}
/** §10.4 — a reverted profile retained as intended-but-unhealthy. */
export interface ParkedProfile {
    profile: TopicProfile;
    reason: string;
    parkedAt: string;
}
export interface TopicProfileEntry {
    current: TopicProfile | null;
    /** Undo target — the profile the operator last saw disclosed (§8/R7-4). */
    previous: TopicProfile | null;
    intendedProfile: IntendedProfileShadow | null;
    parked: ParkedProfile | null;
    /** §10.4 consecutive attributable spawn-failure counter. */
    breakerCount: number;
}
export declare class FlushRefusedError extends Error {
    constructor(cause: unknown);
}
export declare class ProfileLockTimeoutError extends Error {
    constructor(topicKey: string);
}
export declare class ProfileValidationRefusal extends Error {
    readonly validation: ProfileValidationError;
    constructor(validation: ProfileValidationError);
}
export interface MutateOptions {
    /**
     * Shift current→previous before applying the patch. The orchestration
     * layer sets this on the FIRST write of a disclosed burst / coalescing
     * window (§5.1 once-per-delta-carrying-disclosure cadence).
     */
    shiftPrevious?: boolean;
    /** WRITE-phase lock-acquisition timeout (ms). Default 5000. */
    lockTimeoutMs?: number;
}
export interface MutateResult {
    /** False when the patch produced no effective field change. */
    changed: boolean;
    entry: TopicProfileEntry;
    /** True when this mutate cleared a §10.4 parked profile (supersession). */
    supersededParked: boolean;
}
export interface ReplaceResult {
    /** False when the arriving entry equals local current (no effective delta). */
    delta: boolean;
    entry: TopicProfileEntry;
    /** §14 skew arm (ii): a populated local shadow retained against a shadowless REPLACE. */
    retainedLocalShadow: boolean;
    /** §14 skew arm (i): an arriving shadow discarded because this machine is not in dry-run. */
    discardedArrivingShadow: boolean;
    /**
     * §5.3/§10.2 receiving-machine revalidation: fields of the ARRIVING entry
     * that failed the closed-enum/framework-compat clamp on this host and fell
     * to the default (null) for that field. Empty when revalidation was not
     * requested or everything validated. The caller owns the one-line
     * disclosure per dropped field (aggregated per §5.3).
     */
    droppedFields: ProfileValidationError[];
}
export interface ReplaceRevalidationOptions {
    /**
     * The framework an arriving model id validates against when the arriving
     * entry does not itself carry a valid framework (§10.2 framework-compat).
     */
    fallbackFramework: IntelligenceFramework;
}
export interface ReplaceOptions {
    /**
     * §5.3 (mandatory at the transfer-apply and §12 restore-apply doors): run
     * the full §10.2 closed-enum + framework-compat clamp over the ARRIVING
     * entry BEFORE persist — never trusted because a peer sent it. A failing
     * field falls to the default (null) for that field and is reported in
     * `droppedFields` for the caller's one-line disclosure. The installed-CLI
     * arm of §5.3 revalidation stays at the resolution boundary
     * (TopicProfileResolver.isLaunchable) so a valid pin is RETAINED — it
     * falls back at launch with the same disclosure, and recovers when the
     * CLI appears. Omitted = no revalidation (trusted local callers only).
     */
    revalidate?: ReplaceRevalidationOptions;
}
export interface TopicProfileStoreOptions {
    /** Absolute path to state/topic-profiles.json. */
    stateFilePath: string;
    /** Absolute path to the legacy state/topic-frameworks.json (seed + mirror). */
    legacyFrameworksPath?: string;
    /** Whether THIS machine is in dry-run (governs arriving-shadow fate, §14). */
    isDryRun?: () => boolean;
    now?: () => Date;
    /** Optional audit sink (rollback-window reconcile + boot events, §12). */
    audit?: (event: Record<string, unknown>) => void;
}
export declare class TopicProfileStore {
    private readonly stateFilePath;
    private readonly legacyFrameworksPath;
    private readonly isDryRun;
    private readonly now;
    /** AUTHORITATIVE in-memory cache (§5.1). */
    private topics;
    /** Deep snapshot of the last durably-flushed topics map (rollback target). */
    private lastFlushed;
    private mirrorGeneratedAt;
    /** Per-topic process-local locks (promise chains). */
    private locks;
    /** Store-wide serialized flush queue (§5.1 round-4/6). */
    private flushInFlight;
    private trailingFlush;
    constructor(options: TopicProfileStoreOptions);
    private readonly audit;
    /** Entry for a topic, or null. Cache read — never touches disk. */
    get(topicKey: number | string): TopicProfileEntry | null;
    /** The live profile for a topic (resolution input). Never the shadow. */
    resolve(topicKey: number | string): TopicProfile | null;
    /** Undo target (§10.3). Null when nothing to undo yet. */
    previousFor(topicKey: number | string): TopicProfile | null;
    /** Admin/migration only (§5.1) — read surfaces use get(). */
    all(): Record<string, TopicProfileEntry>;
    /**
     * Run `fn` holding the topic's process-local lock. The §8 RESPAWN phase and
     * escalation swaps serialize through this same lock; `mutate` uses it for
     * the WRITE phase. `timeoutMs` bounds ACQUISITION (a wedged holder causes a
     * ProfileLockTimeoutError, not an unbounded queue); the holder itself is
     * bounded by the caller (§8 RESPAWN-phase TTL).
     */
    withTopicLock<T>(topicKey: number | string, fn: () => Promise<T> | T, timeoutMs?: number): Promise<T>;
    /**
     * Field-merged single-writer write. `null` field values CLEAR the field;
     * `undefined` fields are untouched. Enforces the §4 model/modelTier hard
     * mutual exclusion against the MERGE RESULT. Resolves only after the write
     * is durable; throws FlushRefusedError (with rollback) when it is not.
     */
    mutate(topicKey: number | string, patch: Partial<Omit<TopicProfile, 'updatedAt'>> & {
        updatedBy: string;
    }, opts?: MutateOptions): Promise<MutateResult>;
    /**
     * Wholesale per-topic REPLACE — NOT a field-merge (§5.3 round-3: a merge
     * would skip nulls and resurrect a pin the operator cleared remotely).
     * `previous` is pinned to the receiving machine's pre-replace `current`
     * (undo means "back to what this machine had") — EXCEPT when the REPLACE
     * produces no effective delta (A→B→A round-trip, duplicate retried pull):
     * then `previous` must NOT shift (§5.1 round-10).
     *
     * Arriving-shadow fate under regime/timing skew (§14 round-13):
     *  (i) a NON-dry-run receiver discards the arriving shadow;
     *  (ii) an arriving entry with NO shadow never destroys a populated local
     *       shadow on a still-dry-run receiver — it is retained.
     */
    replaceEntry(topicKey: number | string, arriving: {
        current: TopicProfile | null;
        intendedProfile?: IntendedProfileShadow | null;
    }, opts?: ReplaceOptions): Promise<ReplaceResult>;
    /** Record a dry-run intent. Never read by resolution. */
    setShadow(topicKey: number | string, fields: IntendedProfileShadow['fields'], recordedBy: string): Promise<void>;
    /**
     * The dryRun true→false flip clears EVERY topic's shadow — intents are
     * NEVER promoted, at the flip or ever (§14 round-12). Returns the cleared
     * intents so the caller can surface the ONE coalesced expired-intents
     * notice.
     */
    clearAllShadows(): Promise<Array<{
        topicKey: string;
        shadow: IntendedProfileShadow;
    }>>;
    /** Park the current profile as intended-but-unhealthy and revert. */
    parkAndRevert(topicKey: number | string, reason: string, revertTo: TopicProfile | null): Promise<void>;
    /** The parked pin, for the §5.2(b) re-apply recovery write. */
    parkedFor(topicKey: number | string): ParkedProfile | null;
    /** Clear the parked state (after a successful re-apply). */
    clearParked(topicKey: number | string): Promise<void>;
    /** Increment the attributable spawn-failure counter; returns the new count. */
    incrementBreaker(topicKey: number | string): Promise<number>;
    /** §10.4 — the counter resets on any successful spawn. */
    resetBreaker(topicKey: number | string): Promise<void>;
    /**
     * Explicit cache invalidation — the restore route fires this after a
     * snapshot restore lands on disk (the file is otherwise server-owned
     * single-writer; external edits reconcile only here or at boot).
     */
    invalidateAndReload(): void;
    private loadFromDisk;
    /**
     * Boot persistence + mirror self-heal (§12). Seeded/reconciled entries are
     * flushed so `topic-profiles.json` exists from first boot, and a mirror
     * that diverges from the profile store is regenerated (the flush path does
     * both). Best-effort and async — boot itself never refuses; the cache is
     * authoritative and the seed is reconstructible at every boot.
     */
    private bootFinalize;
    /**
     * One-directional read-only seed from the legacy framework store: seed the
     * `framework` field for any topic ABSENT from the profile store. Never
     * overwrites — EXCEPT the §12 rollback-window reconcile: when the legacy
     * file's `updatedAt` differs from the last mirror-write stamp, the legacy
     * file was written by something other than this store's mirror (a rolled-
     * back binary's live `/route` writes), so topics whose framework VALUE
     * differs re-seed the framework arm only, audited. `previous: null` on
     * seeds (§5.1 — undo with no snapshot is refused plainly upstream).
     */
    private seedFromLegacy;
    /**
     * Serialize flushes store-wide. A flush snapshots the authoritative cache
     * AT FLUSH TIME, so a later-completing flush can never persist an older
     * snapshot. Writes arriving mid-flight coalesce onto ONE trailing flush.
     * Failure: every undurable waiter is refused + the cache rolls back to the
     * last durably-flushed snapshot BEFORE the refusals fire.
     */
    private flushDurably;
    private writeFileAtomic;
    /**
     * §5.1/§12 — the legacy file becomes a read-only mirror regenerated from
     * the profile store (sole writer = this store), so a binary rollback to
     * the prior release still reads current framework data. Best-effort,
     * SECOND (profile file is the source of truth and flushes first), and only
     * rewritten when the framework view actually changed.
     */
    private lastMirrorView;
    private regenerateMirror;
    /** The mirror-generation stamp (§12 rollback-window reconcile predicate). */
    getMirrorGeneratedAt(): string | undefined;
    private entryFor;
}
/**
 * §5.3/§10.2 — clamp an ARRIVING (peer-asserted, untrusted) profile through
 * the closed enums, field by field. Unlike the write-surface refusal
 * (first-error, profile unchanged), transfer-apply degrades PER FIELD: a
 * failing field falls to null (the default for that field at resolution)
 * and is reported for the one-line disclosure; valid sibling fields are
 * kept. Provenance (updatedAt/updatedBy) travels verbatim — a malformed
 * provenance pair means the entry is not a well-formed profile at all and
 * is treated as absent (current: null), reported.
 *
 * The arriving model id validates against the arriving entry's OWN
 * framework when that framework is enum-valid, else `fallbackFramework`
 * (§10.2 framework-compat — a model incompatible with the framework falls).
 * A clamped result holding BOTH model and modelTier drops both (§4 hard
 * mutual exclusion — picking a winner silently is the named anti-pattern).
 */
export declare function revalidateArrivingProfile(arriving: TopicProfile | null, fallbackFramework: IntelligenceFramework): {
    profile: TopicProfile | null;
    dropped: ProfileValidationError[];
};
/** §5.3 — the dry-run shadow travels on the pull and is revalidated the same way. */
export declare function revalidateArrivingShadow(arriving: IntendedProfileShadow, fallbackFramework: IntelligenceFramework): {
    shadow: IntendedProfileShadow | null;
    dropped: ProfileValidationError[];
};
/** Field-level profile equality over the five profile axes (delta detection). */
export declare function profilesEqual(a: TopicProfile | null, b: TopicProfile | null): boolean;
//# sourceMappingURL=TopicProfileStore.d.ts.map