import type { SessionInfo } from '@/types/hermes' /** * THE canonical write path for tagging backend-returned session rows with the * registry connection that owns them (the read counterpart is * `sessionOwnerRouteFromRow` in store/session-request-router). * * Every other `connection_id` writer works from an EXACT captured owner route * (the optimistic row in upsertOptimisticSession, the cache patch in * use-session-actions/utils, the mergeSessionPage carry) — those are * authoritative and this helper must never clobber them, so a row that * already names an owner is returned untouched. Only untagged rows served by * an active NON-local source get stamped: the gateway's HTTP APIs correctly * know nothing about Desktop-local registry ids, and an untagged remote row * would let a later resume fall back to a same-named local profile * ("session not found" on turn two). `local` is never stamped — a bare local * row already routes correctly and a `local` tag would only pin it against * the fail-closed owner resolution for no benefit. */ export function stampRowsWithOwningConnection( sessions: SessionInfo[], connectionId: null | string | undefined ): SessionInfo[] { const owner = String(connectionId ?? '').trim() if (!owner || owner === 'local') { return sessions } return sessions.map(session => (session.connection_id?.trim() ? session : { ...session, connection_id: owner })) } /** A durable backfill target: which backend store to stamp, expressed in the * same scope vocabulary every session API call uses. `connectionId === null` * means the primary/local backend's own store. */ export interface LegacyOwnerBackfillScope { connectionId: null | string profile: null | string } export interface LegacyOwnerBackfillTopology { /** Registry topology present (published registry, or the modern bridge). */ hasRegistryTopology: boolean /** Non-local registered connection ids from the registry snapshot. */ registryConnectionIds: string[] /** The source that served this page of rows: a registered connection id, * `'local'`/`null` for the primary pool, `undefined` when the caller * cannot name the serving source at all. */ servingConnectionId: null | string | undefined } /** * Decide whether enumeration under the CURRENT topology warrants the * one-shot legacy owner backfill (#94724), and against WHICH store — the * durable counterpart of `stampRowsWithOwningConnection`'s in-memory stamp. * * Pre-#95407 rows carry `profile_name = NULL` in state.db. The list * endpoints decorate outgoing rows with their serving profile, so the * Desktop cannot see which rows are legacy from a page — but the SERVER * knows exactly, and its backfill is idempotent and one-shot-per-row. The * desktop's job is only to pick the store whose owner is a single match: * the backend that serves an enumeration owns every row it serves. * * Fail-closed rules (returns null — rows stay NULL, and the read-only * stored-transcript path keeps their history reachable): * - no registry topology (nothing is broken; ambient owns every session); * - the serving source is a connection the registry does not know; * - the serving source cannot be named AND more than one registered * backend could own the store (multi-candidate — never guess). */ export function resolveLegacyOwnerBackfillScope( topology: LegacyOwnerBackfillTopology ): LegacyOwnerBackfillScope | null { if (!topology.hasRegistryTopology) { return null } const serving = topology.servingConnectionId if (serving === null || serving?.trim() === 'local') { // Primary pool rows live in the primary's own per-profile store — a // single known owner, stamped as that store's own serving profile. return { connectionId: null, profile: null } } const servingId = serving?.trim() ?? '' const registered = topology.registryConnectionIds.map(id => id.trim()).filter(id => id && id !== 'local') if (servingId) { // The backend that served the rows owns them. Only a REGISTERED source // is a durable owner; an unknown source id cannot be trusted to survive. return registered.includes(servingId) ? { connectionId: servingId, profile: null } : null } // Serving source unknown: single-match only when exactly one registered // backend exists. Two or more candidates would make the stamp a guess. return registered.length === 1 ? { connectionId: registered[0], profile: null } : null }