From 3248c54c1e5641893a150e7eedd9d35799d5f2b6 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 15 Aug 2026 12:07:07 +0000 Subject: [PATCH] fix(playlist): cover the generic-URL fallback in the multi-MAC portal ambiguity guard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The round-2 guard judged ambiguity over portal-shaped URLs only, while the portal picker falls back to a generic URL when no shaped one exists — two root-URL panels with several MACs still cross-paired through that fallback. The guard now inspects the exact pool the picker draws from. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01JE1De2dfyRZMYh4H2iGXb4 --- .../provider-import-detection.util.spec.ts | 20 ++++++++++++++ .../src/lib/provider-import-detection.util.ts | 26 ++++++++++++------- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/libs/shared/interfaces/src/lib/provider-import-detection.util.spec.ts b/libs/shared/interfaces/src/lib/provider-import-detection.util.spec.ts index 4e9e08f37..bad0e4477 100644 --- a/libs/shared/interfaces/src/lib/provider-import-detection.util.spec.ts +++ b/libs/shared/interfaces/src/lib/provider-import-detection.util.spec.ts @@ -699,6 +699,26 @@ describe('detectProviderImportCandidates', () => { } }); + it('treats several MACs with generic URLs on different hosts as ambiguous too', () => { + // Without portal-shaped URLs the picker falls back to a generic + // URL — the ambiguity guard must cover that pool as well, or two + // root-URL panels would still cross-pair through the fallback. + const candidates = detectProviderImportCandidates( + [ + 'http://panel-a.example.com', + 'MAC: 00:1A:79:AA:AA:AA', + 'http://panel-b.example.com', + 'MAC: 00:1A:79:BB:BB:BB', + ].join('\n') + ); + + const stalker = only(candidates, 'stalker'); + expect(stalker).toHaveLength(2); + for (const candidate of stalker) { + expect(candidate.portalUrl).toBeUndefined(); + } + }); + it('surfaces every MAC of a long multi-account list', () => { const macs = Array.from( { length: 6 }, diff --git a/libs/shared/interfaces/src/lib/provider-import-detection.util.ts b/libs/shared/interfaces/src/lib/provider-import-detection.util.ts index ddc7f2b03..326994822 100644 --- a/libs/shared/interfaces/src/lib/provider-import-detection.util.ts +++ b/libs/shared/interfaces/src/lib/provider-import-detection.util.ts @@ -115,18 +115,26 @@ export function detectProviderImportCandidates( // label belongs to — and a device ID submitted with the wrong MAC is // pinned by the portal permanently. Multi-MAC candidates therefore carry // portal + MAC only, and the user supplies identity per account. - // Several MACs meeting portal-shaped URLs on DIFFERENT hosts is two + // Several MACs meeting portal candidates on DIFFERENT hosts is two // accounts from two panels in one paste — there is no deterministic way // to say which MAC belongs to which portal, so those candidates carry the // MAC alone instead of prefilling half of them with the wrong panel. - // Same-host URL pairs (the Real/Panel lines of scanner dumps) stay - // unambiguous, as does one portal shared by a whole MAC list. - const stalkerHosts = new Set( - urls - .filter((url) => url.role === 'stalker') - .map((url) => url.parsed.hostname) - ); - const portalAmbiguous = macs.length > 1 && stalkerHosts.size > 1; + // The ambiguity is judged over the POOL `pickStalkerPortalUrl` would + // actually draw from: portal-shaped URLs when any exist, otherwise the + // generic-URL fallback — a guard on the shaped pool alone would let two + // root-URL panels slip through the fallback door. A single labeled + // "Portal:" line, same-host Real/Panel pairs, and one portal shared by a + // whole MAC list stay unambiguous. + const shapedPortals = urls.filter((url) => url.role === 'stalker'); + const portalPool = + shapedPortals.length > 0 + ? shapedPortals + : labeledHostUrl(labeled) !== undefined + ? [] + : urls.filter((url) => url.role === 'generic'); + const portalAmbiguous = + macs.length > 1 && + new Set(portalPool.map((url) => url.parsed.hostname)).size > 1; const portal = portalAmbiguous ? null : pickStalkerPortalUrl(urls, labeled);