From 2947272233f94f697b185b61c6cf0cba90abe756 Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Wed, 26 Aug 2026 01:47:33 -0700 Subject: [PATCH] refactor(desktop): one canonical write shape for connection_id row stamping Reconcile #94901's API-layer row stamping with #94656's durable-owner persistence: extract lib/session-owner-stamp.ts as THE canonical stamp-untagged-rows write path (never clobbers an explicit owner, never stamps `local`) and re-express api/sessions' stampActiveConnectionOwner through it. #94656's writers (optimistic row from the captured owner route, mergeSessionPage carry, cache patch) are exact-owner writers and stay as-is; the helper's contract documents why it must not overwrite them. Credit: row-stamping concept from PR #94901 (joe-rodgers) and PR #95007 (weismanfamily); persistence shape from PR #94656 (Zeus-Deus). Co-authored-by: joe-rodgers <25499388+joe-rodgers@users.noreply.github.com> --- apps/desktop/src/api/sessions.ts | 12 +++------ apps/desktop/src/lib/session-owner-stamp.ts | 28 +++++++++++++++++++++ 2 files changed, 32 insertions(+), 8 deletions(-) create mode 100644 apps/desktop/src/lib/session-owner-stamp.ts diff --git a/apps/desktop/src/api/sessions.ts b/apps/desktop/src/api/sessions.ts index c89ce10756..3e51132d06 100644 --- a/apps/desktop/src/api/sessions.ts +++ b/apps/desktop/src/api/sessions.ts @@ -1,4 +1,5 @@ import { isMissingRestEndpoint } from '@/lib/gateway-rpc' +import { stampRowsWithOwningConnection } from '@/lib/session-owner-stamp' import { recordTranscriptTail } from '@/store/transcript-tail' import type { PaginatedSessions, @@ -37,16 +38,11 @@ function sessionScopeQuery(scope?: ProfileScope): string { * correctly know nothing about this Desktop-local registry id. Preserve an * explicit owner from a multi-source response; otherwise stamp the active * non-local source so a later resume cannot fall back to a same-named local - * profile. + * profile. Delegates to the canonical row-stamp helper so this stays the ONE + * write shape for connection_id on backend-returned rows. */ function stampActiveConnectionOwner(sessions: SessionInfo[]): SessionInfo[] { - const connectionId = getApiRequestConnection()?.trim() - - if (!connectionId || connectionId === 'local') { - return sessions - } - - return sessions.map(session => (session.connection_id ? session : { ...session, connection_id: connectionId })) + return stampRowsWithOwningConnection(sessions, getApiRequestConnection()) } /** diff --git a/apps/desktop/src/lib/session-owner-stamp.ts b/apps/desktop/src/lib/session-owner-stamp.ts new file mode 100644 index 0000000000..09a6b8a58d --- /dev/null +++ b/apps/desktop/src/lib/session-owner-stamp.ts @@ -0,0 +1,28 @@ +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 })) +}