/**
 * Host-callback isolation, one primitive.
 *
 * The invariant "a callback the HOST handed us must never take the engine down" was implemented by hand at
 * ~20 sites across 10 modules (observer taps, cascade/verify observability hooks, roster eviction sinks, the
 * retain ledger's eviction reporter, task-registry reap/quiescence/terminal notifiers, compaction observers,
 * the workflow event sink, the tool gate's deny observers). Every one of them was a correct swallow, but the
 * DISCLOSURE grade differed per site: the retain ledger counted its failures and offered them on a dedicated
 * channel, while the cascade and the roster store swallowed in total silence — so a deployment whose observer
 * is broken had no way to find out, and WHICH sites would tell it was a matter of which module it happened to
 * touch.
 *
 * This module is the single implementation those sites now share. Semantics:
 *  · SWALLOW — the callback's throw never reaches the engine's control flow (unchanged at every site);
 *  · COUNT — every failure is counted per site, readable for diagnostics ({@link SafeNotifier.failuresAt});
 *  · BOUNDED DISCLOSURE — the FIRST failure of each site is offered to an optional `onError`, later ones are
 *    counted only. A hot path whose sink fails on every call therefore reports once, never floods.
 *
 * WIRING STATUS (RB-473, batches 1/2a/2b landed 2026-08-02): the disclosure channel is now WIRED at the
 * owners a deployment can reach — TaskRegistry offers `notifierFailureCounts()` (read face; it is
 * constructed before any deployment sink exists), the delegation tool routes into `opts.onObserverError`
 * (site attached), auto-compaction / cascade / verify / roster-store take an `onNotifyError` option, the
 * tool gate surfaces failures as `observer.notify_failed` trace events, and the workflow options + the
 * SendMessage resume face take `onNotifyError`. Remaining unwired scopes (retain ledger `failuresAt`
 * read-face only, observer taps, misc subagent internals) disclose via counts.
 *
 * State discipline: no module-level mutable state. Counters live on the notifier instance the owner created
 * (per class instance, or per call for the run-scoped functions), so two runs — and two tests — can never see
 * each other's counts.
 *
 * WHY A THUNK and not `(fn, ...args)`: almost every site reads its callback off a host-owned object
 * (`config.onRung?.(info)`, `opts.onEvicted?.(e)`). `?.()` on a property calls it with THAT OBJECT as the
 * receiver, so a host that passed a method using `this` works today. Handing the extracted function value to
 * a helper would silently re-bind the receiver to `undefined` — an invisible behavior change at ~20 sites.
 * The thunk keeps the call expression (and its `?.`) at the site, byte-for-byte what it was.
 *
 * Scope note (deliberate parity): a callback typed `=> void` that instead returns a REJECTING promise is not
 * contained by {@link SafeNotifier.notify} — it is synchronous and never touches the return value, exactly
 * like the hand-written try/catch it replaced. Sites that genuinely await their callback use
 * {@link SafeNotifier.notifyAsync}.
 */
/** One contained failure, as offered to {@link SafeNotifierOptions.onError}. */
export interface SafeNotifyFailure {
    /** The owner-chosen site name (e.g. `"cascade.onRung"`) — a stable literal, never derived from host input. */
    readonly site: string;
    /** The thrown value, normalized to an `Error`. */
    readonly error: Error;
    /** This site's cumulative failure count including this one (always 1 for a disclosed failure). */
    readonly failures: number;
}
export interface SafeNotifierOptions {
    /**
     * Optional disclosure channel: called with the FIRST failure of each site (bounded — later failures of the
     * same site are counted only). A throw from `onError` itself is swallowed: the disclosure channel is host
     * code too, and it must not become a second fault source.
     */
    readonly onError?: (failure: SafeNotifyFailure) => void;
}
/** The sentence of a contained failure, for a consumer that puts it in front of a person. Total by the
 *  same rule as {@link containedError}: an error object handed in from elsewhere (a `lastErrorAt`
 *  reading, a rejection an adapter wrapped) may still carry a hostile `message`, and the disclosure a
 *  site composes from it must not be the thing that faults. */
export declare function safeErrorMessage(error: unknown): string;
export interface SafeNotifier {
    /**
     * Run one host-callback invocation, containing any throw. Returns `true` when it completed, `false` when it
     * threw. The thunk normally holds the original optional call (`() => config.onRung?.(info)`), so an absent
     * callback is a plain no-op that never touches the failure bookkeeping.
     */
    notify(run: () => void, site: string): boolean;
    /** {@link notify} for a site that AWAITS its callback — contains a rejection as well as a synchronous throw. */
    notifyAsync(run: () => unknown, site: string): Promise<boolean>;
    /** Total contained failures across all sites of this notifier. */
    readonly failures: number;
    /** Contained failures at one site (0 when that site never failed). */
    failuresAt(site: string): number;
    /** The most recent contained error at one site, if any. */
    lastErrorAt(site: string): Error | undefined;
    /** The sites that have failed at least once (diagnostics; empty on the healthy path). */
    failedSites(): readonly string[];
}
/**
 * Create an isolation scope for host callbacks. Cheap: the failure bookkeeping is allocated lazily, so a
 * notifier whose callbacks never throw costs one object and nothing per call beyond the `try`.
 */
export declare function createSafeNotifier(opts?: SafeNotifierOptions): SafeNotifier;
/**
 * Observe a `=> void` host callback's RETURN value for the async dialect: TypeScript accepts an
 * `async` function at a void seat, and a rejected one would escape {@link SafeNotifier.notify}
 * (which deliberately ignores return values — see the scope note above) as an unhandled rejection.
 * Call this with the callback's return value INSIDE the notify thunk: a thenable's rejection is
 * routed back through the same notifier/site, so async and sync failures share one count and one
 * bounded disclosure, and neither can fault the engine's control flow.
 */
export declare function observeThenableRejection(r: unknown, notifier: SafeNotifier, site: string): void;
