diff --git a/src/data/militaryAwareness.test.mjs b/src/data/militaryAwareness.test.mjs index ab3f7af84..6da425cbf 100644 --- a/src/data/militaryAwareness.test.mjs +++ b/src/data/militaryAwareness.test.mjs @@ -2608,17 +2608,18 @@ test('the voice query snapshot is a detached copy of the rendered aircraft cohor assert.equal(first.cohorts.flights.provenance.enabled, true); assert.equal(first.cohorts.flights.items[0].icao24, 'nearby'); first.cohorts.flights.items[0].icao24 = 'mutated'; + first.cohorts.flights.items[0].position.x = 99; first.cohorts.flights.provenance.source = 'mutated'; - assert.equal( - militaryAwarenessLayer.getAircraftQuerySnapshot().cohorts.flights.items[0] - .icao24, - 'nearby', - ); - assert.notEqual( - militaryAwarenessLayer.getAircraftQuerySnapshot().cohorts.flights - .provenance.source, - 'mutated', - ); + first.cohorts.flights.provenance.stats.coverage = { radiusM: 1 }; + first.subject.position.x = 77; + const second = militaryAwarenessLayer.getAircraftQuerySnapshot(); + assert.equal(second.cohorts.flights.items[0].icao24, 'nearby'); + assert.equal(second.cohorts.flights.items[0].position.x, nearbyPosition.x); + assert.notEqual(second.cohorts.flights.provenance.source, 'mutated'); + assert.notDeepEqual(second.cohorts.flights.provenance.stats.coverage, { + radiusM: 1, + }); + assert.equal(second.subject.position.x, subjectPosition.x); } finally { runtime.restore(); restoreCollections(); diff --git a/src/layers/awareness/controls.js b/src/layers/awareness/controls.js index 5be2abfe1..38c273c05 100644 --- a/src/layers/awareness/controls.js +++ b/src/layers/awareness/controls.js @@ -105,23 +105,19 @@ export function createControls({ state: layerState, services, parts, source }) { truncated: !Number.isFinite(cohort.summary?.count) || cohort.summary?.truncated === true, - items: (cohort.summary?.navigationNearest || []).map((item) => ({ - ...item, - })), + // These records carry structured values such as Cartesian positions. + // A shallow spread would let a voice consumer mutate the retained + // Contacts cohort through the advertised read-only snapshot. + items: structuredClone(cohort.summary?.navigationNearest || []), source: cohort.source || null, provenance: cohort.provenance - ? { - ...cohort.provenance, - stats: cohort.provenance.stats - ? { ...cohort.provenance.stats } - : undefined, - } + ? structuredClone(cohort.provenance) : null, reason: cohort.summary?.reason || null, }; } return { - subject: { ...layerState.results.subject }, + subject: structuredClone(layerState.results.subject), evaluatedAt: layerState.results.evaluatedAt, radiusM: layerState.results.radiusM, cohorts,