import type { IAuthoritySocketPaths } from './classes.authorityclient.js'; import type { IControllerAuthorityProjection, TControllerAccountsImportInventoryResult } from '../dist_ts_interfaces/index.js'; /** * How long one subscription attempt may stay unconnected, and how long the loop waits before the * next one. * * The grace window is what separates a daemon restart -- recovered by the client's own 250 ms * reconnect, at no cost -- from a daemon that is not there, which must stop being dialled. */ export interface IAuthoritySupervisionTimings { attemptGraceMs: number; retryBaseMs: number; retryMaxMs: number; } export interface IAuthorityStateOptions { /** * Where the daemon listens. Omitted means there is nothing to talk to: no client is built and no * socket is dialled. The endpoints are always supplied by the caller, never resolved here, so a * controller can never reach the host's real authority without having been given it. */ paths?: IAuthoritySocketPaths; /** Called after every projection change, so the controller can push one `accounts.changed`. */ onChange?: (projection: IControllerAuthorityProjection) => void; /** Supervision timings. The defaults suit a desktop daemon; a short-lived host may shorten them. */ timings?: Partial; } /** * The controller-owned view of the account authority. * * AGL holds exactly one of these and the browser holds none: a view reads the projection and never * tracks staleness itself, which is why a failed request cannot leave a screen locked. The * projection is credential-free by construction -- it carries the daemon's own management DTOs, * which the authority contract defines as safe for browser code. * * Connection supervision is deliberately split with the client. `AuthSwitchClient.subscribe` owns * one attempt: its long poll, its resync on an epoch change or a `resyncRequired` answer, and a * 250 ms reconnect while the daemon is briefly away. This class owns *whether there is an attempt * at all*: a host where the authority was never installed would otherwise reconnect four times a * second for the controller's whole life, so an attempt that has not connected within the grace * window is abandoned and retried on an exponential backoff instead. * * The published state is a named state, and it never flickers: * * | situation | published | * | --- | --- | * | before the first attempt has an outcome | `connecting` | * | the client's `initial` and `resync` statuses | unchanged | * | a schema-valid snapshot | `ready` | * | a connection lost or never made | `absent/not_running` | * | a snapshot shape this AGL cannot read | `incompatible/schema_unsupported`, until a new snapshot | * | every further retry | unchanged -- the last named state stays | */ export declare class AuthorityState { private readonly onChange?; private readonly configuredPaths?; private readonly timings; private client?; private projection; private started; private stopped; private announcedConnecting; private supervision?; private attempt?; private abandonTimer?; private retryTimer?; /** Ends the backoff wait early on `stop()`, so shutdown never waits out a 60 s timer. */ private retryWaitResolve?; private retryDelayMs; constructor(optionsArg?: IAuthorityStateOptions); getProjection(): IControllerAuthorityProjection; /** * The credential-free report of the legacy account stores this host still holds. * * It lives here rather than beside the handler because this class owns the one client and the one * verdict about the daemon: a read is attempted only while the projection is `ready`, and any * other state is answered by its own name instead of by a connection error. The read itself * writes nothing -- the authority opens those stores, hashes what it finds and answers with * identity claims -- and it takes the operation's signal, so a controller shutdown ends it * instead of waiting out the client's request timeout. */ readImportInventory(signalArg: AbortSignal): Promise; /** * Begins supervision and returns immediately. * * Controller startup never waits for the authority and never fails because of it: a host with no * daemon settles into the named `absent` state and keeps trying in the background, and a * controller built without endpoints settles there without opening anything at all. */ start(): void; /** Idempotent. Releases the subscription and both timers, and waits for the loop to finish. */ stop(): Promise; private superviseSubscription; /** Resolves `false` when the wait was cut short by `stop()`. The timer never holds the process. */ private waitBeforeRetry; /** * Ends a backoff wait exactly once. * * Clearing the timer alone would strand the supervision loop on a promise nobody resolves, and * `stop()` awaits that loop -- so the resolver and the timer are always released together. */ private endRetryWait; private applyStatus; /** * Names a connection AGL does not have, without ever overwriting a verdict about the daemon. * * `incompatible` is not a connection failure -- retrying cannot help and a view must keep saying * so -- therefore only a new snapshot leaves that state. */ private reportUnavailable; /** * Abandons an attempt that has not connected within the grace window. * * The window exists so a daemon restart recovers through the client's own 250 ms retry instead of * paying a backoff, while a daemon that is simply not there stops being dialled four times a * second for the controller's whole life. */ private armAbandonTimer; private clearAbandonTimer; private applySnapshot; private publish; }