From 5d6dda1254875fcfadd8051b142a135c5b3e1475 Mon Sep 17 00:00:00 2001 From: Adamulek123 Date: Wed, 28 Oct 2026 17:08:49 +0100 Subject: [PATCH 1/9] perf(server): remember verified linked-environment routes --- apps/server/src/peer/PeerLinks.test.ts | 51 ++++++++++++++++++++++++++ apps/server/src/peer/PeerLinks.ts | 35 +++++++++++++++++- 2 files changed, 84 insertions(+), 2 deletions(-) diff --git a/apps/server/src/peer/PeerLinks.test.ts b/apps/server/src/peer/PeerLinks.test.ts index 5198fc443f768..48ef33682d644 100644 --- a/apps/server/src/peer/PeerLinks.test.ts +++ b/apps/server/src/peer/PeerLinks.test.ts @@ -329,6 +329,57 @@ const serveRaw = ( ), ); +it.effect( + "prefers a verified fallback briefly, rechecks identity, and retries the configured route", + () => + Effect.scoped( + Effect.gen(function* () { + const first = yield* serveApart(descriptorOf("environment-other", "Other")); + const b = yield* serveB(box); + const a = yield* makeA(laptop); + yield* a.links.link({ + url: b.url, + pairingCode: (yield* b.auth.issuePairingCredential()).credential, + access: "auto", + }); + const urls = JSON.stringify([first.url, b.url]); + yield* a.sql`UPDATE peer_environment_links SET urls_json = ${urls}`; + expect((yield* a.links.resolve(box.environmentId)).url).toBe(b.url); + yield* Ref.set(first.probes, 0); + yield* Ref.set(b.probes, 0); + yield* TestClock.adjust(Duration.seconds(30)); + expect((yield* a.links.resolve(box.environmentId)).url).toBe(b.url); + expect(yield* Ref.get(first.probes)).toBe(0); + expect(yield* Ref.get(b.probes)).toBe(1); + + // A recovered preferred address gets a turn, even under repeated demand. + yield* Ref.set(first.descriptor, box); + yield* TestClock.adjust(Duration.seconds(31)); + expect((yield* a.links.resolve(box.environmentId)).url).toBe(first.url); + expect(yield* Ref.get(first.probes)).toBe(1); + + // A remembered address that changes identity is skipped immediately. + yield* Ref.set(first.descriptor, descriptorOf("environment-other", "Other")); + expect((yield* a.links.resolve(box.environmentId)).url).toBe(b.url); + expect(yield* Ref.get(first.bearers)).toEqual([]); + + // Relinking restores the new configured order rather than the old preference. + yield* Ref.set(first.descriptor, box); + yield* a.links.link({ + url: first.url, + alternateUrls: [b.url], + pairingCode: (yield* first.auth.issuePairingCredential()).credential, + access: "auto", + }); + expect((yield* a.links.resolve(box.environmentId)).url).toBe(first.url); + yield* a.links.unlink(box.environmentId); + expect((yield* a.links.resolve(box.environmentId).pipe(Effect.flip)).reason).toBe( + "unknown_link", + ); + }), + ).pipe(Effect.provide(NodeHttpServer.layerTest)), +); + it.effect("never follows a peer's redirect with the pairing code", () => Effect.scoped( Effect.gen(function* () { diff --git a/apps/server/src/peer/PeerLinks.ts b/apps/server/src/peer/PeerLinks.ts index d2c8ef5fe8de3..d018bbec82539 100644 --- a/apps/server/src/peer/PeerLinks.ts +++ b/apps/server/src/peer/PeerLinks.ts @@ -194,6 +194,12 @@ const make = Effect.gen(function* () { const httpClient = yield* HttpClient.HttpClient; const environment = yield* ServerEnvironment.ServerEnvironment; const serverSettings = yield* ServerSettings.ServerSettingsService; + // Prefer a recently working route, without caching its identity or credentials. + // Retry the configured order after a minute so a recovered direct route wins again. + const preferredRoutes = new Map< + EnvironmentId, + { readonly url: string; readonly secretName: string; readonly until: number } + >(); const enabled = serverSettings.getSettings.pipe( Effect.map((settings) => settings.linkedEnvironmentsEnabled), Effect.orElseSucceed(() => false), @@ -528,6 +534,7 @@ const make = Effect.gen(function* () { if (Option.isSome(previous) && previous.value.secret_name !== secretName) { yield* secrets.remove(previous.value.secret_name).pipe(Effect.ignore); } + preferredRoutes.delete(peer.environmentId); return linked; }); @@ -593,6 +600,7 @@ const make = Effect.gen(function* () { for (const row of deleted) { yield* secrets.remove(row.secret_name).pipe(Effect.catch(failStorage)); } + preferredRoutes.delete(environmentId); return deleted.length > 0; }, ); @@ -607,7 +615,8 @@ const make = Effect.gen(function* () { }); } const link = yield* toLink(row.value); - if (DateTime.isLessThanOrEqualTo(link.expiresAt, yield* DateTime.now)) { + const now = yield* DateTime.now; + if (DateTime.isLessThanOrEqualTo(link.expiresAt, now)) { return yield* new PeerLinkError({ reason: "expired", message: `The link to ${link.label} expired. Link it again with a new pairing code from ${link.label}.`, @@ -622,9 +631,18 @@ const make = Effect.gen(function* () { } // The token goes only to an address that proves it is this peer, so a // reused LAN address or a moved hostname never receives it. + const preferred = preferredRoutes.get(environmentId); + const reuse = + preferred !== undefined && + preferred.secretName === row.value.secret_name && + preferred.until > DateTime.toEpochMillis(now) && + link.urls.includes(preferred.url); + const urls = reuse + ? [preferred.url, ...link.urls.filter((candidate) => candidate !== preferred.url)] + : link.urls; let fenced = true; let label = link.label; - const url = yield* Effect.findFirst(link.urls, (candidate) => + const url = yield* Effect.findFirst(urls, (candidate) => answersAs(candidate, environmentId).pipe( Effect.map((found) => { if (Option.isNone(found)) return false; @@ -648,6 +666,19 @@ const make = Effect.gen(function* () { message: `${link.label} runs a T3 Code version that cannot keep a linked agent's limits. Update it, then link again.`, }); } + if (!reuse || preferred.url !== url.value) { + preferredRoutes.delete(environmentId); + // Bound retained state even if links are changed outside this service. + if (preferredRoutes.size >= 256) { + const oldest = preferredRoutes.keys().next(); + if (!oldest.done) preferredRoutes.delete(oldest.value); + } + preferredRoutes.set(environmentId, { + url: url.value, + secretName: row.value.secret_name, + until: DateTime.toEpochMillis(now) + 60_000, + }); + } return { link: yield* withLabel(link, label), url: url.value, From f13294e1e284360fb5e7b9d974da85e0fbd96df9 Mon Sep 17 00:00:00 2001 From: Adamulek123 Date: Wed, 28 Oct 2026 17:17:24 +0100 Subject: [PATCH 2/9] perf(server): retain route preference only for fallbacks --- apps/server/src/peer/PeerLinks.test.ts | 9 +++++++ apps/server/src/peer/PeerLinks.ts | 34 +++++++++++++++----------- 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/apps/server/src/peer/PeerLinks.test.ts b/apps/server/src/peer/PeerLinks.test.ts index 48ef33682d644..cc99e4c54d7b7 100644 --- a/apps/server/src/peer/PeerLinks.test.ts +++ b/apps/server/src/peer/PeerLinks.test.ts @@ -363,6 +363,15 @@ it.effect( expect((yield* a.links.resolve(box.environmentId)).url).toBe(b.url); expect(yield* Ref.get(first.bearers)).toEqual([]); + // When the remembered fallback stops answering as this peer, primary recovery + // clears that preference so later calls do not retry the failed fallback. + yield* Ref.set(first.descriptor, box); + yield* Ref.set(b.descriptor, descriptorOf("environment-other", "Other")); + expect((yield* a.links.resolve(box.environmentId)).url).toBe(first.url); + yield* Ref.set(b.probes, 0); + expect((yield* a.links.resolve(box.environmentId)).url).toBe(first.url); + expect(yield* Ref.get(b.probes)).toBe(0); + // Relinking restores the new configured order rather than the old preference. yield* Ref.set(first.descriptor, box); yield* a.links.link({ diff --git a/apps/server/src/peer/PeerLinks.ts b/apps/server/src/peer/PeerLinks.ts index d018bbec82539..5022aef02f8b1 100644 --- a/apps/server/src/peer/PeerLinks.ts +++ b/apps/server/src/peer/PeerLinks.ts @@ -194,12 +194,14 @@ const make = Effect.gen(function* () { const httpClient = yield* HttpClient.HttpClient; const environment = yield* ServerEnvironment.ServerEnvironment; const serverSettings = yield* ServerSettings.ServerSettingsService; - // Prefer a recently working route, without caching its identity or credentials. + // Prefer a recently working fallback, without caching its identity or credentials. // Retry the configured order after a minute so a recovered direct route wins again. - const preferredRoutes = new Map< - EnvironmentId, - { readonly url: string; readonly secretName: string; readonly until: number } - >(); + let preferredRoutes: + | Map< + EnvironmentId, + { readonly url: string; readonly secretName: string; readonly until: number } + > + | undefined; const enabled = serverSettings.getSettings.pipe( Effect.map((settings) => settings.linkedEnvironmentsEnabled), Effect.orElseSucceed(() => false), @@ -534,7 +536,7 @@ const make = Effect.gen(function* () { if (Option.isSome(previous) && previous.value.secret_name !== secretName) { yield* secrets.remove(previous.value.secret_name).pipe(Effect.ignore); } - preferredRoutes.delete(peer.environmentId); + preferredRoutes?.delete(peer.environmentId); return linked; }); @@ -600,7 +602,7 @@ const make = Effect.gen(function* () { for (const row of deleted) { yield* secrets.remove(row.secret_name).pipe(Effect.catch(failStorage)); } - preferredRoutes.delete(environmentId); + preferredRoutes?.delete(environmentId); return deleted.length > 0; }, ); @@ -631,7 +633,7 @@ const make = Effect.gen(function* () { } // The token goes only to an address that proves it is this peer, so a // reused LAN address or a moved hostname never receives it. - const preferred = preferredRoutes.get(environmentId); + const preferred = link.urls.length > 1 ? preferredRoutes?.get(environmentId) : undefined; const reuse = preferred !== undefined && preferred.secretName === row.value.secret_name && @@ -666,14 +668,18 @@ const make = Effect.gen(function* () { message: `${link.label} runs a T3 Code version that cannot keep a linked agent's limits. Update it, then link again.`, }); } - if (!reuse || preferred.url !== url.value) { - preferredRoutes.delete(environmentId); + if (url.value === link.urls[0]) { + preferredRoutes?.delete(environmentId); + } else if (!reuse || preferred.url !== url.value) { + // Healthy primary routes need no preference state. + const routes = (preferredRoutes ??= new Map()); + routes.delete(environmentId); // Bound retained state even if links are changed outside this service. - if (preferredRoutes.size >= 256) { - const oldest = preferredRoutes.keys().next(); - if (!oldest.done) preferredRoutes.delete(oldest.value); + if (routes.size >= 256) { + const oldest = routes.keys().next(); + if (!oldest.done) routes.delete(oldest.value); } - preferredRoutes.set(environmentId, { + routes.set(environmentId, { url: url.value, secretName: row.value.secret_name, until: DateTime.toEpochMillis(now) + 60_000, From a43216988e7ed85c9dbac430f779dcb45c538a27 Mon Sep 17 00:00:00 2001 From: Adamulek123 Date: Wed, 28 Oct 2026 17:40:53 +0100 Subject: [PATCH 3/9] fix(server): start route preference after peer verification --- apps/server/src/peer/PeerLinks.test.ts | 38 ++++++++++++++++++++++++++ apps/server/src/peer/PeerLinks.ts | 3 +- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/apps/server/src/peer/PeerLinks.test.ts b/apps/server/src/peer/PeerLinks.test.ts index cc99e4c54d7b7..704ebbd5fb2bf 100644 --- a/apps/server/src/peer/PeerLinks.test.ts +++ b/apps/server/src/peer/PeerLinks.test.ts @@ -329,6 +329,44 @@ const serveRaw = ( ), ); +it.effect("starts the fallback preference window after route verification", () => + Effect.scoped( + Effect.gen(function* () { + const entered = yield* Deferred.make(); + const release = yield* Deferred.make(); + const probes = yield* Ref.make(0); + const slow = yield* serveRaw(() => + Ref.update(probes, (count) => count + 1).pipe( + Effect.andThen(Deferred.succeed(entered, undefined)), + Effect.andThen(Deferred.await(release)), + Effect.as(HttpServerResponse.empty({ status: 404 })), + ), + ); + const b = yield* serveB(box); + const a = yield* makeA(laptop); + yield* a.links.link({ + url: b.url, + pairingCode: (yield* b.auth.issuePairingCredential()).credential, + access: "auto", + }); + const urls = JSON.stringify([slow, b.url]); + yield* a.sql`UPDATE peer_environment_links SET urls_json = ${urls}`; + const resolving = yield* a.links.resolve(box.environmentId).pipe(Effect.forkScoped); + yield* Deferred.await(entered); + yield* TestClock.adjust(Duration.seconds(2)); + yield* Deferred.succeed(release, undefined); + expect((yield* Fiber.join(resolving)).url).toBe(b.url); + + yield* TestClock.adjust(Duration.seconds(59)); + expect((yield* a.links.resolve(box.environmentId)).url).toBe(b.url); + expect(yield* Ref.get(probes)).toBe(1); + yield* TestClock.adjust(Duration.seconds(2)); + expect((yield* a.links.resolve(box.environmentId)).url).toBe(b.url); + expect(yield* Ref.get(probes)).toBe(2); + }), + ).pipe(Effect.provide(NodeHttpServer.layerTest)), +); + it.effect( "prefers a verified fallback briefly, rechecks identity, and retries the configured route", () => diff --git a/apps/server/src/peer/PeerLinks.ts b/apps/server/src/peer/PeerLinks.ts index 5022aef02f8b1..bc0ce675470d8 100644 --- a/apps/server/src/peer/PeerLinks.ts +++ b/apps/server/src/peer/PeerLinks.ts @@ -671,6 +671,7 @@ const make = Effect.gen(function* () { if (url.value === link.urls[0]) { preferredRoutes?.delete(environmentId); } else if (!reuse || preferred.url !== url.value) { + const verifiedAt = yield* DateTime.now; // Healthy primary routes need no preference state. const routes = (preferredRoutes ??= new Map()); routes.delete(environmentId); @@ -682,7 +683,7 @@ const make = Effect.gen(function* () { routes.set(environmentId, { url: url.value, secretName: row.value.secret_name, - until: DateTime.toEpochMillis(now) + 60_000, + until: DateTime.toEpochMillis(verifiedAt) + 60_000, }); } return { From f1aab42c88dad8b9d36f7af5f8f95781fdc72026 Mon Sep 17 00:00:00 2001 From: Adamulek123 Date: Thu, 29 Oct 2026 08:02:05 +0100 Subject: [PATCH 4/9] perf(server): reuse verified fallback routes when listing linked environments --- apps/server/src/peer/PeerLinks.test.ts | 93 ++++++++++++++++++++++++++ apps/server/src/peer/PeerLinks.ts | 9 ++- 2 files changed, 101 insertions(+), 1 deletion(-) diff --git a/apps/server/src/peer/PeerLinks.test.ts b/apps/server/src/peer/PeerLinks.test.ts index 704ebbd5fb2bf..4feb0796607ed 100644 --- a/apps/server/src/peer/PeerLinks.test.ts +++ b/apps/server/src/peer/PeerLinks.test.ts @@ -427,6 +427,99 @@ it.effect( ).pipe(Effect.provide(NodeHttpServer.layerTest)), ); +it.effect("listing uses but does not renew a verified fallback and still checks identity", () => + Effect.scoped( + Effect.gen(function* () { + const first = yield* serveApart(descriptorOf("environment-other", "Other")); + const b = yield* serveB(box); + const a = yield* makeA(laptop); + yield* a.links.link({ + url: b.url, + pairingCode: (yield* b.auth.issuePairingCredential()).credential, + access: "auto", + }); + const urls = JSON.stringify([first.url, b.url]); + yield* a.sql`UPDATE peer_environment_links SET urls_json = ${urls}`; + expect((yield* a.links.resolve(box.environmentId)).url).toBe(b.url); + yield* Ref.set(first.probes, 0); + yield* Ref.set(b.probes, 0); + yield* TestClock.adjust(Duration.seconds(30)); + expect((yield* a.links.list).map((link) => link.status)).toEqual(["reachable"]); + expect(yield* Ref.get(first.probes)).toBe(0); + expect(yield* Ref.get(b.probes)).toBe(1); + + // Listing has not extended the original preference deadline. + yield* Ref.set(first.descriptor, box); + yield* TestClock.adjust(Duration.seconds(31)); + expect((yield* a.links.list).map((link) => link.status)).toEqual(["reachable"]); + expect(yield* Ref.get(first.probes)).toBe(1); + expect(yield* Ref.get(b.probes)).toBe(1); + + yield* Ref.set(first.descriptor, descriptorOf("environment-other", "Other")); + expect((yield* a.links.resolve(box.environmentId)).url).toBe(b.url); + yield* Ref.set(first.descriptor, box); + yield* Ref.set(b.descriptor, descriptorOf("environment-other", "Other")); + yield* Ref.set(first.probes, 0); + yield* Ref.set(b.probes, 0); + expect((yield* a.links.list).map((link) => link.status)).toEqual(["reachable"]); + expect(yield* Ref.get(b.probes)).toBe(1); + expect(yield* Ref.get(first.probes)).toBe(1); + expect(yield* Ref.get(b.bearers)).toEqual([]); + + // A removed address cannot be probed through an old preference. + const primaryOnly = JSON.stringify([first.url]); + yield* a.sql`UPDATE peer_environment_links SET urls_json = ${primaryOnly}`; + yield* Ref.set(b.probes, 0); + expect((yield* a.links.list).map((link) => link.status)).toEqual(["reachable"]); + expect(yield* Ref.get(b.probes)).toBe(0); + yield* a.links.unlink(box.environmentId); + expect(yield* a.links.list).toEqual([]); + }), + ).pipe(Effect.provide(NodeHttpServer.layerTest)), +); + +it.effect( + "cancelling a listing interrupts its preferred probe without trying another address", + () => + Effect.scoped( + Effect.gen(function* () { + const entered = yield* Deferred.make(); + const release = yield* Deferred.make(); + const hold = yield* Ref.make(false); + const preferred = yield* serveRaw(() => + Effect.gen(function* () { + if (yield* Ref.get(hold)) { + yield* Deferred.succeed(entered, undefined); + yield* Deferred.await(release); + } + return HttpServerResponse.jsonUnsafe(box); + }), + ); + const first = yield* serveApart(descriptorOf("environment-other", "Other")); + const b = yield* serveB(box); + const a = yield* makeA(laptop); + yield* a.links.link({ + url: b.url, + pairingCode: (yield* b.auth.issuePairingCredential()).credential, + access: "auto", + }); + const urls = JSON.stringify([first.url, preferred]); + yield* a.sql`UPDATE peer_environment_links SET urls_json = ${urls}`; + expect((yield* a.links.resolve(box.environmentId)).url).toBe(preferred); + yield* Ref.set(first.probes, 0); + yield* Ref.set(hold, true); + const listing = yield* a.links.list.pipe(Effect.forkScoped); + yield* Effect.gen(function* () { + yield* Deferred.await(entered); + expect(yield* Ref.get(first.probes)).toBe(0); + yield* Fiber.interrupt(listing); + }).pipe(Effect.ensuring(Deferred.succeed(release, undefined))); + yield* Ref.set(hold, false); + expect((yield* a.links.list).map((link) => link.status)).toEqual(["reachable"]); + }), + ).pipe(Effect.provide(NodeHttpServer.layerTest)), +); + it.effect("never follows a peer's redirect with the pairing code", () => Effect.scoped( Effect.gen(function* () { diff --git a/apps/server/src/peer/PeerLinks.ts b/apps/server/src/peer/PeerLinks.ts index bc0ce675470d8..e18daf881b55c 100644 --- a/apps/server/src/peer/PeerLinks.ts +++ b/apps/server/src/peer/PeerLinks.ts @@ -562,7 +562,14 @@ const make = Effect.gen(function* () { ): Effect.fn.Return { if (DateTime.isLessThanOrEqualTo(link.expiresAt, now)) return { ...link, status: "expired" }; let label = link.label; - const answered = yield* Effect.findFirst(link.urls, (url) => + const preferred = link.urls.length > 1 ? preferredRoutes?.get(link.environmentId) : undefined; + const urls = + preferred !== undefined && + preferred.until > DateTime.toEpochMillis(now) && + link.urls.includes(preferred.url) + ? [preferred.url, ...link.urls.filter((url) => url !== preferred.url)] + : link.urls; + const answered = yield* Effect.findFirst(urls, (url) => answersAs(url, link.environmentId).pipe( Effect.map((found) => { if (Option.isNone(found)) return false; From 9f7be1008701a1861145419707a20fb5c074bdc2 Mon Sep 17 00:00:00 2001 From: Adamulek123 Date: Thu, 29 Oct 2026 14:11:42 +0100 Subject: [PATCH 5/9] perf(server): discard failed route hints after another address recovers --- apps/server/src/peer/PeerLinks.test.ts | 41 ++++++++++++++++++++++++++ apps/server/src/peer/PeerLinks.ts | 8 +++++ 2 files changed, 49 insertions(+) diff --git a/apps/server/src/peer/PeerLinks.test.ts b/apps/server/src/peer/PeerLinks.test.ts index 4feb0796607ed..f2643058fd40e 100644 --- a/apps/server/src/peer/PeerLinks.test.ts +++ b/apps/server/src/peer/PeerLinks.test.ts @@ -466,6 +466,17 @@ it.effect("listing uses but does not renew a verified fallback and still checks expect(yield* Ref.get(first.probes)).toBe(1); expect(yield* Ref.get(b.bearers)).toEqual([]); + // Once listing verifies the configured primary, a failed fallback is + // no longer preferred on the next listing. + yield* Ref.set(b.probes, 0); + expect((yield* a.links.list).map((link) => link.status)).toEqual(["reachable"]); + expect(yield* Ref.get(b.probes)).toBe(0); + + yield* Ref.set(first.descriptor, descriptorOf("environment-other", "Other")); + yield* Ref.set(b.descriptor, box); + expect((yield* a.links.resolve(box.environmentId)).url).toBe(b.url); + yield* Ref.set(first.descriptor, box); + // A removed address cannot be probed through an old preference. const primaryOnly = JSON.stringify([first.url]); yield* a.sql`UPDATE peer_environment_links SET urls_json = ${primaryOnly}`; @@ -478,6 +489,35 @@ it.effect("listing uses but does not renew a verified fallback and still checks ).pipe(Effect.provide(NodeHttpServer.layerTest)), ); +it.effect("listing drops a failed preference when a middle address recovers", () => + Effect.scoped( + Effect.gen(function* () { + const first = yield* serveApart(descriptorOf("environment-other-a", "Other A")); + const middle = yield* serveApart(descriptorOf("environment-other-b", "Other B")); + const b = yield* serveB(box); + const a = yield* makeA(laptop); + yield* a.links.link({ + url: b.url, + pairingCode: (yield* b.auth.issuePairingCredential()).credential, + access: "auto", + }); + const urls = JSON.stringify([first.url, middle.url, b.url]); + yield* a.sql`UPDATE peer_environment_links SET urls_json = ${urls}`; + expect((yield* a.links.resolve(box.environmentId)).url).toBe(b.url); + yield* Ref.set(middle.descriptor, box); + yield* Ref.set(b.descriptor, descriptorOf("environment-other-c", "Other C")); + expect((yield* a.links.list).map((link) => link.status)).toEqual(["reachable"]); + yield* Ref.set(first.probes, 0); + yield* Ref.set(middle.probes, 0); + yield* Ref.set(b.probes, 0); + expect((yield* a.links.list).map((link) => link.status)).toEqual(["reachable"]); + expect(yield* Ref.get(first.probes)).toBe(1); + expect(yield* Ref.get(middle.probes)).toBe(1); + expect(yield* Ref.get(b.probes)).toBe(0); + }), + ).pipe(Effect.provide(NodeHttpServer.layerTest)), +); + it.effect( "cancelling a listing interrupts its preferred probe without trying another address", () => @@ -514,6 +554,7 @@ it.effect( expect(yield* Ref.get(first.probes)).toBe(0); yield* Fiber.interrupt(listing); }).pipe(Effect.ensuring(Deferred.succeed(release, undefined))); + expect(yield* Ref.get(first.probes)).toBe(0); yield* Ref.set(hold, false); expect((yield* a.links.list).map((link) => link.status)).toEqual(["reachable"]); }), diff --git a/apps/server/src/peer/PeerLinks.ts b/apps/server/src/peer/PeerLinks.ts index e18daf881b55c..76903bda4b9a3 100644 --- a/apps/server/src/peer/PeerLinks.ts +++ b/apps/server/src/peer/PeerLinks.ts @@ -578,6 +578,14 @@ const make = Effect.gen(function* () { }), ), ); + if ( + preferred !== undefined && + Option.isSome(answered) && + answered.value !== preferred.url && + preferredRoutes?.get(link.environmentId) === preferred + ) { + preferredRoutes.delete(link.environmentId); + } if (Option.isNone(answered)) return { ...link, status: "unreachable" }; return { ...(yield* withLabel(link, label)), status: "reachable" }; }); From 7c564d4754bc1556ad39e6a743fb96e29c9f7e99 Mon Sep 17 00:00:00 2001 From: Adamulek123 Date: Thu, 29 Oct 2026 15:11:08 +0100 Subject: [PATCH 6/9] perf(server): overlap slow status probes during route recovery --- apps/server/src/peer/PeerLinks.test.ts | 214 ++++++++++++++++++++++--- apps/server/src/peer/PeerLinks.ts | 56 +++++-- 2 files changed, 230 insertions(+), 40 deletions(-) diff --git a/apps/server/src/peer/PeerLinks.test.ts b/apps/server/src/peer/PeerLinks.test.ts index f2643058fd40e..c5dc78cc6b3f8 100644 --- a/apps/server/src/peer/PeerLinks.test.ts +++ b/apps/server/src/peer/PeerLinks.test.ts @@ -518,24 +518,109 @@ it.effect("listing drops a failed preference when a middle address recovers", () ).pipe(Effect.provide(NodeHttpServer.layerTest)), ); -it.effect( - "cancelling a listing interrupts its preferred probe without trying another address", - () => - Effect.scoped( +it.effect("cancelling a listing before fallback starts interrupts its preferred probe", () => + Effect.scoped( + Effect.gen(function* () { + const entered = yield* Deferred.make(); + const release = yield* Deferred.make(); + const hold = yield* Ref.make(false); + const preferred = yield* serveRaw(() => + Effect.gen(function* () { + if (yield* Ref.get(hold)) { + yield* Deferred.succeed(entered, undefined); + yield* Deferred.await(release); + } + return HttpServerResponse.jsonUnsafe(box); + }), + ); + const first = yield* serveApart(descriptorOf("environment-other", "Other")); + const b = yield* serveB(box); + const a = yield* makeA(laptop); + yield* a.links.link({ + url: b.url, + pairingCode: (yield* b.auth.issuePairingCredential()).credential, + access: "auto", + }); + const urls = JSON.stringify([first.url, preferred]); + yield* a.sql`UPDATE peer_environment_links SET urls_json = ${urls}`; + expect((yield* a.links.resolve(box.environmentId)).url).toBe(preferred); + yield* Ref.set(first.probes, 0); + yield* Ref.set(hold, true); + const listing = yield* a.links.list.pipe(Effect.forkScoped); + yield* Effect.gen(function* () { + yield* Deferred.await(entered); + expect(yield* Ref.get(first.probes)).toBe(0); + yield* Fiber.interrupt(listing); + }).pipe(Effect.ensuring(Deferred.succeed(release, undefined))); + expect(yield* Ref.get(first.probes)).toBe(0); + yield* Ref.set(hold, false); + expect((yield* a.links.list).map((link) => link.status)).toEqual(["reachable"]); + }), + ).pipe(Effect.provide(NodeHttpServer.layerTest)), +); + +it.effect.each([ + "fast-success", + "early-failure", + "hedged-success", + "slow-success", + "all-fail", + "cancel-before", + "cancel-after", + "replacement", +] as const)("status fallback probes are delayed, validated and cancelled (%s)", (scenario) => + Effect.gen(function* () { + const nativeFetch = yield* FetchHttpClient.Fetch; + const routes = new Set(); + const started = new Set(); + const aborted = new Set(); + let observing = false; + const fetch: typeof globalThis.fetch = (input, init) => { + const origin = new URL(String(input)).origin; + if (observing && routes.has(origin)) { + started.add(origin); + init?.signal?.addEventListener("abort", () => aborted.add(origin), { once: true }); + } + return nativeFetch(input, init); + }; + return yield* Effect.scoped( Effect.gen(function* () { - const entered = yield* Deferred.make(); - const release = yield* Deferred.make(); - const hold = yield* Ref.make(false); + const preferredEntered = yield* Deferred.make(); + const firstEntered = yield* Deferred.make(); + const thirdEntered = yield* Deferred.make(); + const releasePreferred = yield* Deferred.make(); + const releaseFirst = yield* Deferred.make(); + const preferredMode = yield* Ref.make<"ready" | "wrong" | "hold">("ready"); + const firstMode = yield* Ref.make<"ready" | "wrong" | "hold">("wrong"); + const thirdMode = yield* Ref.make<"ready" | "wrong">("wrong"); + const wrong = descriptorOf("environment-other", "Other"); const preferred = yield* serveRaw(() => Effect.gen(function* () { - if (yield* Ref.get(hold)) { - yield* Deferred.succeed(entered, undefined); - yield* Deferred.await(release); - } - return HttpServerResponse.jsonUnsafe(box); + const mode = yield* Ref.get(preferredMode); + if (observing) yield* Deferred.succeed(preferredEntered, undefined); + if (mode === "hold") yield* Deferred.await(releasePreferred); + return HttpServerResponse.jsonUnsafe(mode === "wrong" ? wrong : box); }), ); - const first = yield* serveApart(descriptorOf("environment-other", "Other")); + const first = yield* serveRaw(() => + Effect.gen(function* () { + const mode = yield* Ref.get(firstMode); + if (observing) yield* Deferred.succeed(firstEntered, undefined); + if (mode === "hold") yield* Deferred.await(releaseFirst); + return HttpServerResponse.jsonUnsafe(mode === "wrong" ? wrong : box); + }), + ); + const third = yield* serveRaw(() => + Effect.gen(function* () { + if (observing) yield* Deferred.succeed(thirdEntered, undefined); + return HttpServerResponse.jsonUnsafe( + (yield* Ref.get(thirdMode)) === "wrong" ? wrong : box, + ); + }), + ); + routes.add(preferred); + routes.add(first); + routes.add(third); const b = yield* serveB(box); const a = yield* makeA(laptop); yield* a.links.link({ @@ -543,22 +628,101 @@ it.effect( pairingCode: (yield* b.auth.issuePairingCredential()).credential, access: "auto", }); - const urls = JSON.stringify([first.url, preferred]); + const urls = JSON.stringify([first, third, preferred]); yield* a.sql`UPDATE peer_environment_links SET urls_json = ${urls}`; expect((yield* a.links.resolve(box.environmentId)).url).toBe(preferred); - yield* Ref.set(first.probes, 0); - yield* Ref.set(hold, true); - const listing = yield* a.links.list.pipe(Effect.forkScoped); + const cancelling = scenario === "cancel-before" || scenario === "cancel-after"; + yield* Ref.set( + preferredMode, + scenario === "early-failure" || scenario === "all-fail" + ? "wrong" + : scenario === "fast-success" + ? "ready" + : "hold", + ); + yield* Ref.set( + firstMode, + cancelling || scenario === "replacement" + ? "hold" + : scenario === "slow-success" || scenario === "all-fail" + ? "wrong" + : "ready", + ); + if (cancelling) yield* Ref.set(thirdMode, "ready"); + observing = true; yield* Effect.gen(function* () { - yield* Deferred.await(entered); - expect(yield* Ref.get(first.probes)).toBe(0); - yield* Fiber.interrupt(listing); - }).pipe(Effect.ensuring(Deferred.succeed(release, undefined))); - expect(yield* Ref.get(first.probes)).toBe(0); - yield* Ref.set(hold, false); - expect((yield* a.links.list).map((link) => link.status)).toEqual(["reachable"]); + let completed = false; + const listing = yield* a.links.list.pipe( + Effect.tap(() => + Effect.sync(() => { + completed = true; + }), + ), + Effect.forkScoped, + ); + yield* Deferred.await(preferredEntered); + if ( + scenario === "fast-success" || + scenario === "early-failure" || + scenario === "all-fail" + ) { + expect((yield* Fiber.join(listing)).map((link) => link.status)).toEqual([ + scenario === "all-fail" ? "unreachable" : "reachable", + ]); + if (scenario === "fast-success") { + yield* TestClock.adjust(Duration.seconds(1)); + expect([...started]).toEqual([preferred]); + } else expect(started.has(first)).toBe(true); + return; + } + if (scenario !== "cancel-before") { + yield* TestClock.adjust(Duration.millis(100)); + expect(started.has(first)).toBe(true); + yield* Deferred.await(firstEntered); + } + if (scenario === "replacement") { + yield* Ref.set(preferredMode, "wrong"); + yield* Ref.set(firstMode, "wrong"); + yield* Ref.set(thirdMode, "ready"); + expect((yield* a.links.resolve(box.environmentId)).url).toBe(third); + yield* Deferred.succeed(releaseFirst, undefined); + expect((yield* Fiber.join(listing)).map((link) => link.status)).toEqual(["reachable"]); + started.clear(); + expect((yield* a.links.resolve(box.environmentId)).url).toBe(third); + expect([...started]).toEqual([third]); + return; + } + if (cancelling) { + expect(started.has(third)).toBe(false); + yield* Fiber.interrupt(listing); + expect(aborted.has(preferred)).toBe(true); + if (scenario === "cancel-after") expect(aborted.has(first)).toBe(true); + yield* TestClock.adjust(Duration.seconds(1)); + expect(started.has(third)).toBe(false); + if (scenario === "cancel-before") expect(started.has(first)).toBe(false); + } else { + if (scenario === "slow-success") { + yield* Deferred.await(thirdEntered); + expect(completed).toBe(false); + yield* Deferred.succeed(releasePreferred, undefined); + } + expect((yield* Fiber.join(listing)).map((link) => link.status)).toEqual(["reachable"]); + if (scenario === "hedged-success") expect(aborted.has(preferred)).toBe(true); + } + }).pipe( + Effect.ensuring( + Effect.all([ + Deferred.succeed(releasePreferred, undefined), + Deferred.succeed(releaseFirst, undefined), + ]), + ), + ); }), - ).pipe(Effect.provide(NodeHttpServer.layerTest)), + ).pipe( + Effect.provide(NodeHttpServer.layerTest), + Effect.provideService(FetchHttpClient.Fetch, fetch), + ); + }), ); it.effect("never follows a peer's redirect with the pairing code", () => diff --git a/apps/server/src/peer/PeerLinks.ts b/apps/server/src/peer/PeerLinks.ts index 76903bda4b9a3..67ca77f30dc99 100644 --- a/apps/server/src/peer/PeerLinks.ts +++ b/apps/server/src/peer/PeerLinks.ts @@ -17,6 +17,7 @@ import * as Context from "effect/Context"; import * as Digest from "effect/crypto/Digest"; import * as SecureRandom from "effect/crypto/SecureRandom"; import * as DateTime from "effect/DateTime"; +import * as Deferred from "effect/Deferred"; import * as Duration from "effect/Duration"; import * as Effect from "effect/Effect"; import * as Base64Url from "effect/encoding/Base64Url"; @@ -45,6 +46,7 @@ const PeerHttpApi = HttpApi.make(EnvironmentHttpApi.identifier) /** The redirect registered for a link. Nothing ever listens there: the code comes back in the response. */ const LINK_REDIRECT_URI = "http://127.0.0.1/t3code-peer-link"; +const STATUS_FALLBACK_DELAY = Duration.millis(100); /** * How long a peer's descriptor may take before a route counts as unreachable. * A tailnet or relayed route to a machine that is waking up takes longer. @@ -556,38 +558,62 @@ const make = Effect.gen(function* () { WHERE environment_id = ${link.environmentId} `.pipe(Effect.ignoreCause({ log: true }), Effect.as({ ...link, label })); + const findStatusRoute = (urls: ReadonlyArray, environmentId: EnvironmentId) => + Effect.gen(function* () { + for (const url of urls) { + const found = yield* answersAs(url, environmentId); + if (Option.isSome(found)) return Option.some({ url, label: found.value.label }); + } + return Option.none(); + }); + const statusOf = Effect.fn("PeerLinks.statusOf")(function* ( link: PeerLink, now: DateTime.Utc, ): Effect.fn.Return { if (DateTime.isLessThanOrEqualTo(link.expiresAt, now)) return { ...link, status: "expired" }; - let label = link.label; const preferred = link.urls.length > 1 ? preferredRoutes?.get(link.environmentId) : undefined; - const urls = + const active = preferred !== undefined && preferred.until > DateTime.toEpochMillis(now) && link.urls.includes(preferred.url) - ? [preferred.url, ...link.urls.filter((url) => url !== preferred.url)] - : link.urls; - const answered = yield* Effect.findFirst(urls, (url) => - answersAs(url, link.environmentId).pipe( - Effect.map((found) => { - if (Option.isNone(found)) return false; - label = found.value.label; - return true; - }), - ), - ); + ? preferred + : undefined; + const answering = + active === undefined + ? findStatusRoute(link.urls, link.environmentId) + : Effect.gen(function* () { + const failed = yield* Deferred.make(); + const check = (urls: ReadonlyArray) => + findStatusRoute(urls, link.environmentId).pipe( + Effect.filterOrFail(Option.isSome), + Effect.map((found) => found.value), + ); + const preferredProbe = check([active.url]).pipe( + Effect.tapError(() => Deferred.succeed(failed, undefined)), + ); + // Fast preferred routes still need one request. Slow ones must not + // add their entire timeout before the configured routes can start. + const otherProbes = Deferred.await(failed).pipe( + Effect.timeoutOrElse({ + duration: STATUS_FALLBACK_DELAY, + orElse: () => Effect.void, + }), + Effect.andThen(check(link.urls.filter((url) => url !== active.url))), + ); + return yield* Effect.race(preferredProbe, otherProbes).pipe(Effect.option); + }); + const answered = yield* answering; if ( preferred !== undefined && Option.isSome(answered) && - answered.value !== preferred.url && + answered.value.url !== preferred.url && preferredRoutes?.get(link.environmentId) === preferred ) { preferredRoutes.delete(link.environmentId); } if (Option.isNone(answered)) return { ...link, status: "unreachable" }; - return { ...(yield* withLabel(link, label)), status: "reachable" }; + return { ...(yield* withLabel(link, answered.value.label)), status: "reachable" }; }); const list: PeerLinks["Service"]["list"] = Effect.gen(function* () { From e4ea812d4d135f03de899fc981c286559c723758 Mon Sep 17 00:00:00 2001 From: Adamulek123 Date: Thu, 29 Oct 2026 15:30:54 +0100 Subject: [PATCH 7/9] fix(server): ignore stale status hints after relinking --- apps/server/src/peer/PeerLinks.test.ts | 46 ++++++++++++++++++++++++++ apps/server/src/peer/PeerLinks.ts | 14 +++++--- 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/apps/server/src/peer/PeerLinks.test.ts b/apps/server/src/peer/PeerLinks.test.ts index c5dc78cc6b3f8..b99ceb75a64dc 100644 --- a/apps/server/src/peer/PeerLinks.test.ts +++ b/apps/server/src/peer/PeerLinks.test.ts @@ -489,6 +489,52 @@ it.effect("listing uses but does not renew a verified fallback and still checks ).pipe(Effect.provide(NodeHttpServer.layerTest)), ); +it.effect("listing ignores an old resolve's preference after relinking", () => + Effect.scoped( + Effect.gen(function* () { + const entered = yield* Deferred.make(); + const release = yield* Deferred.make(); + const probes = yield* Ref.make(0); + const fallback = yield* serveRaw(() => + Ref.update(probes, (count) => count + 1).pipe( + Effect.andThen(Deferred.succeed(entered, undefined)), + Effect.andThen(Deferred.await(release)), + Effect.as(HttpServerResponse.jsonUnsafe(box)), + ), + ); + const first = yield* serveApart(descriptorOf("environment-other", "Other")); + const b = yield* serveB(box); + const a = yield* makeA(laptop); + yield* a.links.link({ + url: b.url, + pairingCode: (yield* b.auth.issuePairingCredential()).credential, + access: "auto", + }); + const urls = JSON.stringify([first.url, fallback]); + yield* a.sql`UPDATE peer_environment_links SET urls_json = ${urls}`; + const resolving = yield* a.links.resolve(box.environmentId).pipe(Effect.forkScoped); + yield* Effect.gen(function* () { + yield* Deferred.await(entered); + yield* Ref.set(first.descriptor, box); + yield* a.links.link({ + url: first.url, + alternateUrls: [fallback], + pairingCode: (yield* first.auth.issuePairingCredential()).credential, + access: "auto", + }); + // The old resolve finishes after relinking cleared its preference. + yield* Deferred.succeed(release, undefined); + expect((yield* Fiber.join(resolving)).url).toBe(fallback); + yield* Ref.set(first.probes, 0); + yield* Ref.set(probes, 0); + expect((yield* a.links.list).map((link) => link.status)).toEqual(["reachable"]); + expect(yield* Ref.get(first.probes)).toBe(1); + expect(yield* Ref.get(probes)).toBe(0); + }).pipe(Effect.ensuring(Deferred.succeed(release, undefined))); + }), + ).pipe(Effect.provide(NodeHttpServer.layerTest)), +); + it.effect("listing drops a failed preference when a middle address recovers", () => Effect.scoped( Effect.gen(function* () { diff --git a/apps/server/src/peer/PeerLinks.ts b/apps/server/src/peer/PeerLinks.ts index 67ca77f30dc99..26f4ca21647d0 100644 --- a/apps/server/src/peer/PeerLinks.ts +++ b/apps/server/src/peer/PeerLinks.ts @@ -569,12 +569,14 @@ const make = Effect.gen(function* () { const statusOf = Effect.fn("PeerLinks.statusOf")(function* ( link: PeerLink, + secretName: string, now: DateTime.Utc, ): Effect.fn.Return { if (DateTime.isLessThanOrEqualTo(link.expiresAt, now)) return { ...link, status: "expired" }; const preferred = link.urls.length > 1 ? preferredRoutes?.get(link.environmentId) : undefined; const active = preferred !== undefined && + preferred.secretName === secretName && preferred.until > DateTime.toEpochMillis(now) && link.urls.includes(preferred.url) ? preferred @@ -617,11 +619,15 @@ const make = Effect.gen(function* () { }); const list: PeerLinks["Service"]["list"] = Effect.gen(function* () { - const links = yield* Effect.forEach(yield* readRows, toLink); + const links = yield* Effect.forEach(yield* readRows, (row) => + toLink(row).pipe(Effect.map((link) => ({ link, secretName: row.secret_name }))), + ); const now = yield* DateTime.now; - return yield* Effect.forEach(links, (link) => statusOf(link, now), { - concurrency: "unbounded", - }); + return yield* Effect.forEach( + links, + ({ link, secretName }) => statusOf(link, secretName, now), + { concurrency: "unbounded" }, + ); }); const get: PeerLinks["Service"]["get"] = (environmentId) => From 1550e2e7a0d01c509b28f951e67124858357d85b Mon Sep 17 00:00:00 2001 From: Adamulek123 Date: Mon, 9 Nov 2026 22:13:48 +0100 Subject: [PATCH 8/9] test(server): preserve peer names through route recovery --- apps/server/src/peer/PeerLinks.test.ts | 28 +++++++++++++++++++++----- apps/server/src/peer/PeerLinks.ts | 27 +++++++++++++------------ 2 files changed, 37 insertions(+), 18 deletions(-) diff --git a/apps/server/src/peer/PeerLinks.test.ts b/apps/server/src/peer/PeerLinks.test.ts index b99ceb75a64dc..3d568168dfed9 100644 --- a/apps/server/src/peer/PeerLinks.test.ts +++ b/apps/server/src/peer/PeerLinks.test.ts @@ -645,7 +645,11 @@ it.effect.each([ const mode = yield* Ref.get(preferredMode); if (observing) yield* Deferred.succeed(preferredEntered, undefined); if (mode === "hold") yield* Deferred.await(releasePreferred); - return HttpServerResponse.jsonUnsafe(mode === "wrong" ? wrong : box); + return HttpServerResponse.jsonUnsafe( + mode === "wrong" + ? wrong + : { ...box, label: observing ? "Preferred renamed" : box.label }, + ); }), ); const first = yield* serveRaw(() => @@ -653,14 +657,18 @@ it.effect.each([ const mode = yield* Ref.get(firstMode); if (observing) yield* Deferred.succeed(firstEntered, undefined); if (mode === "hold") yield* Deferred.await(releaseFirst); - return HttpServerResponse.jsonUnsafe(mode === "wrong" ? wrong : box); + return HttpServerResponse.jsonUnsafe( + mode === "wrong" ? wrong : { ...box, label: observing ? "First renamed" : box.label }, + ); }), ); const third = yield* serveRaw(() => Effect.gen(function* () { if (observing) yield* Deferred.succeed(thirdEntered, undefined); return HttpServerResponse.jsonUnsafe( - (yield* Ref.get(thirdMode)) === "wrong" ? wrong : box, + (yield* Ref.get(thirdMode)) === "wrong" + ? wrong + : { ...box, label: observing ? "Third renamed" : box.label }, ); }), ); @@ -712,9 +720,15 @@ it.effect.each([ scenario === "early-failure" || scenario === "all-fail" ) { - expect((yield* Fiber.join(listing)).map((link) => link.status)).toEqual([ + const listed = yield* Fiber.join(listing); + expect(listed.map((link) => link.status)).toEqual([ scenario === "all-fail" ? "unreachable" : "reachable", ]); + if (scenario !== "all-fail") { + const label = scenario === "fast-success" ? "Preferred renamed" : "First renamed"; + expect(listed[0]?.label).toBe(label); + expect(Option.getOrThrow(yield* a.links.get(box.environmentId)).label).toBe(label); + } if (scenario === "fast-success") { yield* TestClock.adjust(Duration.seconds(1)); expect([...started]).toEqual([preferred]); @@ -752,7 +766,11 @@ it.effect.each([ expect(completed).toBe(false); yield* Deferred.succeed(releasePreferred, undefined); } - expect((yield* Fiber.join(listing)).map((link) => link.status)).toEqual(["reachable"]); + const listed = yield* Fiber.join(listing); + expect(listed.map((link) => link.status)).toEqual(["reachable"]); + const label = scenario === "slow-success" ? "Preferred renamed" : "First renamed"; + expect(listed[0]?.label).toBe(label); + expect(Option.getOrThrow(yield* a.links.get(box.environmentId)).label).toBe(label); if (scenario === "hedged-success") expect(aborted.has(preferred)).toBe(true); } }).pipe( diff --git a/apps/server/src/peer/PeerLinks.ts b/apps/server/src/peer/PeerLinks.ts index 26f4ca21647d0..c3a1e681c820e 100644 --- a/apps/server/src/peer/PeerLinks.ts +++ b/apps/server/src/peer/PeerLinks.ts @@ -558,14 +558,17 @@ const make = Effect.gen(function* () { WHERE environment_id = ${link.environmentId} `.pipe(Effect.ignoreCause({ log: true }), Effect.as({ ...link, label })); - const findStatusRoute = (urls: ReadonlyArray, environmentId: EnvironmentId) => - Effect.gen(function* () { - for (const url of urls) { - const found = yield* answersAs(url, environmentId); - if (Option.isSome(found)) return Option.some({ url, label: found.value.label }); - } - return Option.none(); - }); + // Called within statusOf, which already owns the status-probe span. + const findStatusRoute = Effect.fnUntraced(function* ( + urls: ReadonlyArray, + environmentId: EnvironmentId, + ) { + for (const url of urls) { + const found = yield* answersAs(url, environmentId); + if (Option.isSome(found)) return Option.some({ url, label: found.value.label }); + } + return Option.none(); + }); const statusOf = Effect.fn("PeerLinks.statusOf")(function* ( link: PeerLink, @@ -623,11 +626,9 @@ const make = Effect.gen(function* () { toLink(row).pipe(Effect.map((link) => ({ link, secretName: row.secret_name }))), ); const now = yield* DateTime.now; - return yield* Effect.forEach( - links, - ({ link, secretName }) => statusOf(link, secretName, now), - { concurrency: "unbounded" }, - ); + return yield* Effect.forEach(links, ({ link, secretName }) => statusOf(link, secretName, now), { + concurrency: "unbounded", + }); }); const get: PeerLinks["Service"]["get"] = (environmentId) => From b7d7c40d8cac693a045a52f1bd5508d188f031dc Mon Sep 17 00:00:00 2001 From: Adamulek123 Date: Thu, 12 Nov 2026 00:08:55 +0100 Subject: [PATCH 9/9] test(server): keep route expiry fixture independent of descriptor retries --- apps/server/src/peer/PeerLinks.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/server/src/peer/PeerLinks.test.ts b/apps/server/src/peer/PeerLinks.test.ts index 421f3e433a204..319a6fb4a701c 100644 --- a/apps/server/src/peer/PeerLinks.test.ts +++ b/apps/server/src/peer/PeerLinks.test.ts @@ -340,7 +340,7 @@ it.effect("starts the fallback preference window after route verification", () = Ref.update(probes, (count) => count + 1).pipe( Effect.andThen(Deferred.succeed(entered, undefined)), Effect.andThen(Deferred.await(release)), - Effect.as(HttpServerResponse.empty({ status: 404 })), + Effect.as(HttpServerResponse.jsonUnsafe(descriptorOf("environment-other", "Other"))), ), ); const b = yield* serveB(box);