feat(stalker): install discovered principals atomically

This commit is contained in:
4gray committed 2026-07-27 10:23:44 +02:00
1 parent 79729c4dfc
commit 97c50071ac
2 files changed
+60 -36

No files matched your search

@@ -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',
});
});
});
@@ -21,6 +21,11 @@ export interface StalkerCoordinatedMutationResult<T> {
readonly value: T;
}
export interface StalkerDiscoveredPrincipalMutationValue<T> {
readonly principal: string;
readonly value: T;
}
interface ReadWaiter {
readonly kind: 'read';
readonly resolve: () => void;
@@ -107,6 +112,31 @@ export class StalkerBaseIdentityCoordinator {
}
}
async runDiscoveredPrincipalMutation<T>(
operation: (
mutationEpoch: number
) => Promise<StalkerDiscoveredPrincipalMutationValue<T>>
): Promise<StalkerCoordinatedMutationResult<T>> {
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<void> {
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<string, StalkerBaseIdentityCoordinator>();
get size(): number {
return this.#coordinators.size;