export interface MembershipFeedOpts { servers: string; space: string; /** DATA account public key — the CONNZ request + CONNECT/DISCONNECT event subjects pin this account. */ accountId: string; /** Scoped SYSTEM-account observer creds (conn A — CONNZ reader). Always a static string: it is * `rotation-renewed` ($SYS seed dies at `up`), so its only renewal is a system-account rotation + * broker restart — a new process, never a live reload. */ observerCreds: string; /** Scoped DATA-account read/write creds (conn B — members read + feed write): a literal string, or * a SOURCE (D5 slice 5 class 2) that reads them through the {@link SecretStore} seam — the manager * re-signs the SAME nkey into the store and the feed adopts it. May be async (a hosted `store.get`) * or sync (a local FS read / literal). A source-fed cred is renewed on a 75% timer that PROVES each * candidate on a disposable preflight before conn B ever presents it; conn B's authenticator only * ever presents the last BROKER-PROVEN generation, so an incidental reconnect can never carry an * unproven cred. A read that swaps the nkey fails loud — the feed's identity (inbox scope + * self-presence check) is pinned to the first read. */ rwCreds: string | (() => string | Promise); /** Safety reconcile interval (ms) — primary signal (no SUB/UNSUB event exists). Default 15000. */ intervalMs?: number; /** Connect/disconnect-event → re-poll debounce (ms); coalesces connect storms. Default 400. */ debounceMs?: number; /** Fan-out reply settle gap (ms): finish a CONNZ round this long after the last reply. Default 250. */ settleMs?: number; /** Fan-out hard cap (ms) per CONNZ round. Default 1500. */ maxWaitMs?: number; /** CONNZ per-server page size. Default 1024 (the server default). */ pageLimit?: number; /** Structured log sink (defaults to a `! membership:`-prefixed console.error). */ log?: (msg: string) => void; } export interface MembershipFeedHandle { /** Force an immediate reconcile (also used by tests). Never throws — errors are logged. */ poll(): Promise; /** Explicitly adopt a re-signed rw cred on conn B (D5 class-2 renewal): fetch from the source * (deadline-bounded), validate the identity pin, optional fingerprint match against the renewal * owner's `expected` generation, a DISPOSABLE PREFLIGHT proving the broker accepts the bytes, then * commit the proven cache and best-effort reconnect — returning the BROKER-ACCEPTED generation's * window (the resident conn-B swap is best-effort, not witnessed). Runs under the same single-flight * as the 75% timer, so the two can never interleave. THROWS when the source fetch fails * (missing/swapped cred), the fingerprint does not match `expected`, the broker refuses the * preflight, or the deadline elapses. Symmetric with the endpoint's {@link CotalEndpoint.reloadCreds}. */ reloadRwCreds(expected?: string): Promise<{ identity: string; iat?: number; exp?: number; }>; stop(): Promise; } /** Connect, wire the triggers + safety poll, and run an immediate first reconcile. * * Startup is TRANSACTIONAL. Conn A opens first, and every step after it can throw: an rw source that * rejects, credential bytes `idFromCreds` refuses, conn B's own dial (its pre-dial checkpoint refuses a * cred that is already expired, and the broker refuses one it will not authenticate), either KV open. * The first reconcile is NOT one of them: `poll()` wraps the whole loop in a catch that logs and has * zero rethrows, so a failing first reconcile resolves startup instead of rejecting it, and the * `await poll()` below cannot be the step that strands a connection. Before this change the only * `drain()` in this file was inside the handle's `stop()`, and a caller * that never receives the handle can never call it — so a reject left the observer connection open for * the life of the process. The rollback above now drains every connection it recorded before the * rejection propagates. * * Scope of the word TRANSACTIONAL, stated rather than assumed: the rollback drains CONNECTIONS, and * nothing else. It does not clear the rw renewal timer, the safety interval, or the two trigger * subscriptions, all of which are acquired below. What holds today is narrower than "unreachable": * four operations run after the timer arm, counting the ones that can reject or acquire rather than * every call expression. They are `connA.subscribe` twice, the `setInterval`, and `await poll()`. The * two subject helpers passed as subscribe arguments also execute; they format a string and return. * The only one of the four that awaits is `await poll()`, which swallows * its own failures, so the ordinary startup path never rejects down here. `connA.subscribe` on an * already-closed conn A is the one shape that still could, and it would leak the timer rather than a * connection. Left as a known gap rather than papered over: a step added below the arm that can * reject makes this incomplete, and the rollback then has to clear the timer too. */ export declare function startMembershipFeed(opts: MembershipFeedOpts): Promise; //# sourceMappingURL=membership-feed.d.ts.map