From 5f3d1eaa2fcf5d05d11afc948a2b31b3737e1454 Mon Sep 17 00:00:00 2001 From: Said Date: Tue, 20 Oct 2026 00:43:47 +0100 Subject: [PATCH] fix(military): retire unavailable history and keep local trails --- docs/CURRENT-STATE.md | 2 ++ server/providers/aircraft/tracks.js | 21 +++++++++++++-------- src/layers/military/tracking.js | 2 +- src/sources/live/contract.test.mjs | 9 ++++----- src/sources/live/standalone.js | 22 ++-------------------- src/tooling/liveProviders.test.mjs | 18 ++++++++++++++++++ 6 files changed, 40 insertions(+), 34 deletions(-) diff --git a/docs/CURRENT-STATE.md b/docs/CURRENT-STATE.md index 55b7a63ab..70c741f79 100644 --- a/docs/CURRENT-STATE.md +++ b/docs/CURRENT-STATE.md @@ -1,5 +1,7 @@ # God's Eye View Current State +The standalone military source no longer requests the unavailable, undocumented ADSB.lol trace endpoint. Tracking retains its locally accumulated trail, and custom sources may still supply optional getTrack history. The old HTTP route validates the identifier then reports 503 history_unavailable without any upstream request, rather than forwarding a misleading 403. This does not restore historical coverage or download bulk daily archives. + Shared distance checks use `src/geoDistance.js` with `{ lat, lon }` points and the IUGG mean spherical Earth radius, 6371.0088 km. `greatCircleKm` is canonical; `greatCircleMeters` is its exact metre conversion. Missing, diff --git a/server/providers/aircraft/tracks.js b/server/providers/aircraft/tracks.js index 5f800ac59..3b0acd8d2 100644 --- a/server/providers/aircraft/tracks.js +++ b/server/providers/aircraft/tracks.js @@ -7,10 +7,9 @@ import { readCappedResponseText } from '../common/http.js'; * own credit bucket, 4 credits per call on the free tier). OAuth via the * shared coalesced token. 60s per-icao cache; 404/429 forwarded so the * client can fall back to its accumulated trail silently. - * /api/military/track?hex= — adsb.lol tar1090 readsb trace - * (undocumented but live; no browser CORS, hence this proxy). Up to ~24h - * of real history per aircraft. Treat as best-effort; data is ODbL — - * credit "adsb.lol (ODbL)" in the UI. + * /api/military/track?hex= — compatibility refusal for the retired, + * undocumented ADSB.lol trace route. The standalone military source uses + * locally accumulated trails; provider-supplied history remains optional. */ export function trackBackfillProxies() { const TRACK_CACHE_MS = 60000; @@ -107,10 +106,16 @@ export function trackBackfillProxies() { ); return; } - await proxyJson( - res, - `lol:${hex}`, - `https://adsb.lol/data/traces/${hex.slice(-2)}/trace_full_${hex}.json`, + res.statusCode = 503; + res.setHeader('Content-Type', 'application/json'); + res.setHeader('Cache-Control', 'no-store'); + res.setHeader('Retry-After', '1800'); + res.end( + JSON.stringify({ + error: + 'Military history unavailable; locally accumulated trails remain available', + code: 'history_unavailable', + }), ); } catch (error) { res.statusCode = 502; diff --git a/src/layers/military/tracking.js b/src/layers/military/tracking.js index 8a440a54f..3530a4877 100644 --- a/src/layers/military/tracking.js +++ b/src/layers/military/tracking.js @@ -593,7 +593,7 @@ export function createTracking({ } /** - * Fire-and-forget adsb.lol readsb trace backfill (PRD F2). Trace points are + * Fire-and-forget optional source history backfill (PRD F2). Trace points are * stride-thinned to the remaining vertex budget, then spliced strictly older * than the oldest seeded fix AHEAD of the locally accumulated fine segment, * capped at TRAIL_MAX_POINTS (newest kept). Any failure (404/timeout/ diff --git a/src/sources/live/contract.test.mjs b/src/sources/live/contract.test.mjs index 658db1596..b8b1787da 100644 --- a/src/sources/live/contract.test.mjs +++ b/src/sources/live/contract.test.mjs @@ -214,11 +214,10 @@ test('construction is inert; adapters preserve routes, viewport query, cache epo assert.equal(mil.observedAtMs, now - 60000); assert.equal(mil.records[0].positionTimeMs, now - 75000); assert.equal(mil.stale, true); - const track = await military.getTrack('abc123'); - assert.equal(track.complete, false); - assert.equal(track.records[0].observedAtMs, now - 50000); - assert.equal(track.records[0].baroAltitudeM, 304.8); - assert.equal(track.records[0].ellipsoidAltitudeM, null); + assert.equal(military.getTrack, undefined); + assert.ok( + requests.every(({ url }) => !url.startsWith('/api/military/track')), + ); }); test('cancellation after slow body parsing rejects even with a transport that ignores abort', async () => { diff --git a/src/sources/live/standalone.js b/src/sources/live/standalone.js index 55670c629..7311c492d 100644 --- a/src/sources/live/standalone.js +++ b/src/sources/live/standalone.js @@ -159,26 +159,8 @@ export function createMilitarySource({ status: response.status, }; }, - async getTrack(reference, { signal } = {}) { - const { response, payload } = await readResponse( - fetchImpl, - '/api/military/track?hex=' + encodeURIComponent(reference), - { signal }, - 'Military aircraft', - ); - if (!response.ok) throw httpError(response, 'Military aircraft'); - const baseTimeMs = epoch(payload?.timestamp, 1000); - return { - records: - baseTimeMs == null - ? [] - : normalizeAircraftTrack(payload?.trace, { - baseTimeMs, - readsb: true, - }), - complete: false, - }; - }, + // ADSB.lol does not expose a supported live-history endpoint. Omitting + // optional getTrack keeps the tracking layer on its accumulated trail. }; } diff --git a/src/tooling/liveProviders.test.mjs b/src/tooling/liveProviders.test.mjs index a60a37cea..488c2d40c 100644 --- a/src/tooling/liveProviders.test.mjs +++ b/src/tooling/liveProviders.test.mjs @@ -583,3 +583,21 @@ test('track backfill proxy returns 502 on an oversized upstream body and caches assert.equal(res2.statusCode, 502); assert.equal(callCount, 1); }); + +test('retired military history refuses without upstream I/O while validating identifiers', async (t) => { + t.mock.method(globalThis, 'fetch', async () => { + assert.fail('retired history must not contact the upstream'); + }); + const route = install(providers.trackBackfillProxies()); + for (const hex of ['abc123', 'abc456']) { + const response = await route('/api/military/track', '?hex=' + hex); + assert.equal(response.statusCode, 503); + assert.equal(JSON.parse(response.body).code, 'history_unavailable'); + assert.equal(response.headers['retry-after'], '1800'); + assert.equal(response.headers['cache-control'], 'no-store'); + } + assert.equal( + (await route('/api/military/track', '?hex=invalid')).statusCode, + 400, + ); +});