feat(tess): wire durable interaction surfaces
All checks were successful
ci/woodpecker/pr/ci Pipeline was successful
All checks were successful
ci/woodpecker/pr/ci Pipeline was successful
This commit is contained in:
@@ -64,6 +64,23 @@ describe('DurableSessionCoordinator', () => {
|
||||
expect(recovered.handoffs).toMatchObject([{ handoffId: 'handoff-1', status: 'pending' }]);
|
||||
});
|
||||
|
||||
it('rebinds a recovered runtime while preserving the immutable conversation owner scope', async () => {
|
||||
const coordinator = new DurableSessionCoordinator(new InMemoryDurableSessionStore());
|
||||
await coordinator.create(IDENTITY);
|
||||
await coordinator.create({
|
||||
...IDENTITY,
|
||||
providerId: 'fleet-next',
|
||||
runtimeSessionId: 'nova-next',
|
||||
});
|
||||
|
||||
await expect(coordinator.snapshot(IDENTITY.sessionId)).resolves.toMatchObject({
|
||||
identity: { ...IDENTITY, providerId: 'fleet-next', runtimeSessionId: 'nova-next' },
|
||||
});
|
||||
await expect(coordinator.create({ ...IDENTITY, ownerId: 'other-owner' })).rejects.toThrow(
|
||||
/identity conflict/,
|
||||
);
|
||||
});
|
||||
|
||||
it('deduplicates duplicate ingress and never reprocesses an inbox record after restart or compaction', async () => {
|
||||
const store = new InMemoryDurableSessionStore();
|
||||
const firstProcess = new DurableSessionCoordinator(store);
|
||||
|
||||
@@ -256,9 +256,11 @@ export class InMemoryDurableSessionStore implements DurableSessionStore {
|
||||
async create(identity: DurableSessionIdentity): Promise<void> {
|
||||
const existing = this.sessions.get(identity.sessionId);
|
||||
if (existing) {
|
||||
if (!identitiesEqual(existing.identity, identity)) {
|
||||
if (!sameEnrollmentScope(existing.identity, identity)) {
|
||||
throw new Error(`Durable Tess session identity conflict: ${identity.sessionId}`);
|
||||
}
|
||||
existing.identity.providerId = identity.providerId;
|
||||
existing.identity.runtimeSessionId = identity.runtimeSessionId;
|
||||
return;
|
||||
}
|
||||
this.sessions.set(identity.sessionId, {
|
||||
@@ -416,14 +418,12 @@ export class InMemoryDurableSessionStore implements DurableSessionStore {
|
||||
}
|
||||
}
|
||||
|
||||
function identitiesEqual(left: DurableSessionIdentity, right: DurableSessionIdentity): boolean {
|
||||
function sameEnrollmentScope(left: DurableSessionIdentity, right: DurableSessionIdentity): boolean {
|
||||
return (
|
||||
left.agentName === right.agentName &&
|
||||
left.sessionId === right.sessionId &&
|
||||
left.tenantId === right.tenantId &&
|
||||
left.ownerId === right.ownerId &&
|
||||
left.providerId === right.providerId &&
|
||||
left.runtimeSessionId === right.runtimeSessionId
|
||||
left.ownerId === right.ownerId
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user