From 97c50071ac96b21de43cb45badaf12f0d25c50c4 Mon Sep 17 00:00:00 2001 From: 4gray Date: Mon, 27 Jul 2026 10:23:44 +0200 Subject: [PATCH] feat(stalker): install discovered principals atomically --- .../stalker-base-identity-coordinator.spec.ts | 61 +++++++++---------- .../stalker-base-identity-coordinator.ts | 35 +++++++++-- 2 files changed, 60 insertions(+), 36 deletions(-) diff --git a/apps/electron-backend/src/app/services/stalker-session/stalker-base-identity-coordinator.spec.ts b/apps/electron-backend/src/app/services/stalker-session/stalker-base-identity-coordinator.spec.ts index 9938ea391..74dbbd5af 100644 --- a/apps/electron-backend/src/app/services/stalker-session/stalker-base-identity-coordinator.spec.ts +++ b/apps/electron-backend/src/app/services/stalker-session/stalker-base-identity-coordinator.spec.ts @@ -107,14 +107,11 @@ describe('StalkerBaseIdentityCoordinator', () => { } ); await Promise.resolve(); - const mutation = coordinator.runMutation( - 'principal-a', - async () => { - order.push('mutation-start'); - await mutationGate.promise; - order.push('mutation-end'); - } - ); + const mutation = coordinator.runMutation('principal-a', async () => { + order.push('mutation-start'); + await mutationGate.promise; + order.push('mutation-end'); + }); const laterReader = coordinator.runRead( 'principal-a', ready.snapshot.epoch + 1, @@ -128,11 +125,7 @@ describe('StalkerBaseIdentityCoordinator', () => { readerGate.resolve(); await reader; await Promise.resolve(); - expect(order).toEqual([ - 'reader-start', - 'reader-end', - 'mutation-start', - ]); + expect(order).toEqual(['reader-start', 'reader-end', 'mutation-start']); mutationGate.resolve(); await mutation; await laterReader; @@ -158,21 +151,13 @@ describe('StalkerBaseIdentityCoordinator', () => { const operation = jest.fn(); await expect( - coordinator.runRead( - 'principal-a', - first.snapshot.epoch, - operation - ) + coordinator.runRead('principal-a', first.snapshot.epoch, operation) ).resolves.toEqual({ kind: 'suspended', snapshot: { activePrincipal: 'principal-b', epoch: 2 }, }); await expect( - coordinator.runRead( - 'principal-b', - first.snapshot.epoch, - operation - ) + coordinator.runRead('principal-b', first.snapshot.epoch, operation) ).resolves.toEqual({ kind: 'suspended', snapshot: { activePrincipal: 'principal-b', epoch: 2 }, @@ -228,11 +213,7 @@ describe('StalkerBaseIdentityCoordinator', () => { expect(coordinator.snapshot).toEqual({ epoch: 2 }); await expect( - coordinator.runRead( - 'principal-a', - ready.snapshot.epoch, - jest.fn() - ) + coordinator.runRead('principal-a', ready.snapshot.epoch, jest.fn()) ).resolves.toEqual({ kind: 'suspended', snapshot: { epoch: 2 }, @@ -241,10 +222,7 @@ describe('StalkerBaseIdentityCoordinator', () => { it('can explicitly enter a no-active-principal transition epoch', async () => { const coordinator = new StalkerBaseIdentityCoordinator(); - await coordinator.runMutation( - 'principal-a', - async () => undefined - ); + await coordinator.runMutation('principal-a', async () => undefined); const transition = await coordinator.runMutation( undefined, @@ -256,4 +234,23 @@ describe('StalkerBaseIdentityCoordinator', () => { value: 'transition', }); }); + + it('installs a principal discovered inside one exclusive mutation without releasing the gate', async () => { + const coordinator = new StalkerBaseIdentityCoordinator(); + + const result = await coordinator.runDiscoveredPrincipalMutation( + async (epoch) => ({ + principal: 'discovered-principal', + value: `authenticated-${epoch}`, + }) + ); + + expect(result).toEqual({ + snapshot: { + activePrincipal: 'discovered-principal', + epoch: 1, + }, + value: 'authenticated-1', + }); + }); }); diff --git a/apps/electron-backend/src/app/services/stalker-session/stalker-base-identity-coordinator.ts b/apps/electron-backend/src/app/services/stalker-session/stalker-base-identity-coordinator.ts index f85a64be7..0c5cae480 100644 --- a/apps/electron-backend/src/app/services/stalker-session/stalker-base-identity-coordinator.ts +++ b/apps/electron-backend/src/app/services/stalker-session/stalker-base-identity-coordinator.ts @@ -21,6 +21,11 @@ export interface StalkerCoordinatedMutationResult { readonly value: T; } +export interface StalkerDiscoveredPrincipalMutationValue { + readonly principal: string; + readonly value: T; +} + interface ReadWaiter { readonly kind: 'read'; readonly resolve: () => void; @@ -107,6 +112,31 @@ export class StalkerBaseIdentityCoordinator { } } + async runDiscoveredPrincipalMutation( + operation: ( + mutationEpoch: number + ) => Promise> + ): Promise> { + await this.#acquireWrite(); + try { + if (this.#epoch >= Number.MAX_SAFE_INTEGER) { + throw new Error('stalker-coordinator-epoch-exhausted'); + } + this.#epoch += 1; + this.#activePrincipal = undefined; + + const result = await operation(this.#epoch); + validatePrincipal(result.principal); + this.#activePrincipal = result.principal; + return { + snapshot: this.snapshot, + value: result.value, + }; + } finally { + this.#releaseWrite(); + } + } + async #acquireRead(): Promise { if (!this.#writerActive && this.#waiters.length === 0) { this.#activeReaders += 1; @@ -178,10 +208,7 @@ export class StalkerBaseIdentityCoordinator { * single server-side token for the whole origin/MAC pair. */ export class StalkerBaseIdentityCoordinatorPool { - readonly #coordinators = new Map< - string, - StalkerBaseIdentityCoordinator - >(); + readonly #coordinators = new Map(); get size(): number { return this.#coordinators.size;