fix: allocate share-link layer tokens without collisions

This commit is contained in:
manjunath22466 committed 2026-09-24 16:25:55 +05:30
1 parent ce671ce500
commit 54735a9e6f
15 files changed
+1295 -20

No files matched your search

+131
View File
@@ -0,0 +1,131 @@
#!/usr/bin/env node
import { execFileSync } from 'node:child_process';
import path from 'node:path';
import { pathToFileURL } from 'node:url';
import {
LAYER_STATE_TOKEN_RESERVATIONS,
parseLayerStateTokenReservations,
validateLayerStateAllocations,
validateLayerStateRegistry,
} from '../src/data/layerState.js';
// The pre-ledger main revision had exactly the 28 pinned v2 registry entries.
// Accept only that source blob as the one-time bootstrap; any other published
// source without a ledger must fail closed rather than risk losing a token.
const PRE_LEDGER_LAYER_STATE_BLOB = 'e559a4c40191512dcefb83134958a3bfd4a98a69';
// This snapshot belongs to the verified published blob above, not to the PR's
// editable legacy map. Otherwise a candidate could redefine both its ledger
// and its comparison baseline before the first ledger reaches main.
export const PRE_LEDGER_LAYER_STATE_TOKENS = Object.freeze({
'ais-live-vessels': 'a',
'alpr-cameras': 'p',
'bhote-koshi-2026': 'h',
'bhote-koshi-locator': 'z',
bikeshare: 'b',
cctv: 'c',
directions: 'n',
earthquakes: 'e',
'fire-perimeters': '2',
flights: 'f',
'local-dams': 'q',
'local-datacenters': 'd',
'local-firms': 'w',
military: 'm',
'military-awareness': 'g',
'military-installations': 'i',
radio: 'r',
'recent-imagery': '1',
'rocket-launches': 'x',
satellites: 's',
'telegeography-submarine-cables': 'u',
traffic: 't',
transit: 'j',
'weather-cyclones': 'y',
'weather-lightning': 'l',
'weather-radar': 'v',
'weather-satellite': 'o',
wind: 'k',
});
const LEDGER_PATH = 'src/data/layerStateTokenReservations.json';
const SOURCE_PATH = 'src/data/layerState.js';
function git(args, cwd) {
return execFileSync('git', args, {
cwd,
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'pipe'],
}).trim();
}
/** Read every permanent assignment from a published Git revision. */
export function readPublishedLayerStateReservations(
baseRef,
cwd = process.cwd(),
) {
const commit = git(
['rev-parse', '--verify', '--end-of-options', `${baseRef}^{commit}`],
cwd,
);
const ledgerSpec = `${commit}:${LEDGER_PATH}`;
let hasLedger = true;
try {
git(['cat-file', '-e', ledgerSpec], cwd);
} catch {
hasLedger = false;
}
if (!hasLedger) {
const sourceBlob = git(['rev-parse', `${commit}:${SOURCE_PATH}`], cwd);
if (sourceBlob !== PRE_LEDGER_LAYER_STATE_BLOB) {
throw new Error(
'Published base has no recognizable layer-state token ledger',
);
}
return PRE_LEDGER_LAYER_STATE_TOKENS;
}
let rows;
try {
rows = JSON.parse(git(['show', ledgerSpec], cwd));
} catch (error) {
throw new Error(
`Cannot read published layer-state token ledger: ${error.message}`,
);
}
return parseLayerStateTokenReservations(rows);
}
export function checkLayerStateTokens(baseRef, cwd = process.cwd()) {
const base = readPublishedLayerStateReservations(baseRef, cwd);
validateLayerStateRegistry();
validateLayerStateAllocations(base, LAYER_STATE_TOKEN_RESERVATIONS);
return {
published: Object.keys(base).length,
added:
Object.keys(LAYER_STATE_TOKEN_RESERVATIONS).length -
Object.keys(base).length,
};
}
const invokedPath = process.argv[1]
? pathToFileURL(path.resolve(process.argv[1])).href
: '';
if (import.meta.url === invokedPath) {
const args = process.argv.slice(2);
const baseRef =
args[0] === '--base-ref' && args.length === 2 ? args[1] : null;
if (!baseRef || baseRef.startsWith('-')) {
console.error('Usage: npm run layer-token:check -- --base-ref origin/main');
process.exitCode = 1;
} else {
try {
const result = checkLayerStateTokens(baseRef);
console.log(
`Layer tokens valid against ${baseRef}: ${result.published} published, ` +
`${result.added} new.`,
);
} catch (error) {
console.error(`Layer token check failed: ${error.message}`);
process.exitCode = 1;
}
}
}
+5
View File
@@ -4,6 +4,11 @@
"scripts/format-scope.json",
"scripts/package-boundaries.json",
"scripts/format.mjs",
"scripts/next-layer-state-token.mjs",
"scripts/check-layer-state-tokens.mjs",
"scripts/qa-layer-token-twochar.mjs",
"src/data/layerStateTokenReservations.json",
"src/data/layerStateTokenLedger.test.mjs",
"scripts/check-package-boundaries.mjs",
"scripts/precompute-cctv-heights.mjs",
"src/tooling/precomputeCctvHeights.test.mjs",
+22
View File
@@ -0,0 +1,22 @@
#!/usr/bin/env node
import {
LAYER_STATE_TOKEN_RESERVATIONS,
nextLayerStateToken,
} from '../src/data/layerState.js';
const layerId = String(process.argv[2] || '').trim();
if (!/^[a-z0-9-]+$/.test(layerId)) {
console.error('Usage: npm run layer-token:next -- <layer-id>');
process.exitCode = 1;
} else if (Object.hasOwn(LAYER_STATE_TOKEN_RESERVATIONS, layerId)) {
console.error(
`${layerId} already owns token ${LAYER_STATE_TOKEN_RESERVATIONS[layerId]}`,
);
process.exitCode = 1;
} else {
console.log(`${layerId}: ${nextLayerStateToken()}`);
console.log('Order: free digits 0-9, then two-character base-36 00-zz.');
console.log('Re-run after rebasing onto the latest main before merge.');
}
+8
View File
@@ -507,6 +507,7 @@
"src/data/detectionPolicy.js",
"src/data/installationFeedback.js",
"src/data/layerState.js",
"src/data/layerStateTokenReservations.json",
"src/data/militaryAwarenessEngine.js",
"src/data/scenePick.js",
"src/keySetupCore.mjs",
@@ -746,6 +747,7 @@
"src/data/contactMatch.js",
"src/data/iconOrientation.js",
"src/data/layerState.js",
"src/data/layerStateTokenReservations.json",
"src/data/modelVisualAnchor.js",
"src/data/motionModel.js",
"src/data/renderAltitude.js",
@@ -787,6 +789,7 @@
"src/data/contactMatch.js",
"src/data/iconOrientation.js",
"src/data/layerState.js",
"src/data/layerStateTokenReservations.json",
"src/data/modelVisualAnchor.js",
"src/data/motionModel.js",
"src/data/renderAltitude.js",
@@ -823,6 +826,7 @@
"src/data/contactMatch.js",
"src/data/iconOrientation.js",
"src/data/layerState.js",
"src/data/layerStateTokenReservations.json",
"src/data/modelVisualAnchor.js",
"src/data/motionModel.js",
"src/data/renderAltitude.js",
@@ -1115,6 +1119,7 @@
"modules": [
"src/data/iconOrientation.js",
"src/data/layerState.js",
"src/data/layerStateTokenReservations.json",
"src/data/radioCountry.js",
"src/layers/radio/catalogModel.js",
"src/layers/radio/categories.js",
@@ -1383,6 +1388,7 @@
"src/data/issPass.js",
"src/data/labelArbiter.js",
"src/data/layerState.js",
"src/data/layerStateTokenReservations.json",
"src/data/lifecycle.js",
"src/data/localGeojson.js",
"src/data/localGeojsonCore.js",
@@ -2046,6 +2052,7 @@
"src/data/satellitePass.js",
"src/data/labelArbiter.js",
"src/data/layerState.js",
"src/data/layerStateTokenReservations.json",
"src/data/localGeojson.js",
"src/data/localGeojsonCore.js",
"src/data/localGeojsonLod.js",
@@ -2497,6 +2504,7 @@
"modules": [
"src/data/installationFeedback.js",
"src/data/layerState.js",
"src/data/layerStateTokenReservations.json",
"src/loadingFeedback.js",
"src/navigationPolicy.js",
"src/ui/shareRestoration.js",
+337
View File
@@ -0,0 +1,337 @@
#!/usr/bin/env node
/**
* End-to-end share/reload proof for the first two-character token. The QA
* layer and an exhausted-digit reservation scenario are injected into
* dev-server responses in this isolated browser only. Neither belongs in a
* production bundle or ledger.
*
* Run against a local Vite server: node scripts/qa-layer-token-twochar.mjs
*/
import assert from 'node:assert/strict';
import { readFile } from 'node:fs/promises';
import puppeteer from 'puppeteer';
import {
LAYER_STATE_TOKEN_RESERVATIONS,
nextLayerStateToken,
parseLayerStateTokenReservations,
validateLayerStateAllocations,
} from '../src/data/layerState.js';
const base = new URL(process.env.QA_BASE_URL || 'http://127.0.0.1:4173');
if (!['127.0.0.1', 'localhost'].includes(base.hostname))
throw new Error('The QA fixture requires a loopback-only dev server');
const origin = base.origin;
const fixtureId = 'qa-twochar-fixture';
const injections = { codec: 0, catalog: 0 };
const errors = [];
const reservations = JSON.parse(
await readFile(
new URL('../src/data/layerStateTokenReservations.json', import.meta.url),
'utf8',
),
);
assert.equal(
reservations.some(([, token]) => token === '00'),
false,
'00 must remain unallocated in the production ledger for this QA fixture',
);
const productionTokens = new Set(reservations.map(([, token]) => token));
const priorDigitRows = [...'0123456789']
.filter((digit) => !productionTokens.has(digit))
.map((digit) => [`qa-prior-digit-${digit}`, digit]);
const priorState = parseLayerStateTokenReservations([
...reservations,
...priorDigitRows,
]);
assert.equal(nextLayerStateToken(priorState), '00');
assert.equal(
validateLayerStateAllocations(
LAYER_STATE_TOKEN_RESERVATIONS,
parseLayerStateTokenReservations([
...reservations,
...priorDigitRows,
[fixtureId, '00'],
]),
),
true,
);
console.log(
'PASS: isolated fixture exhausts prior digits before allocating 00',
);
for (const source of [
'../src/data/layerState.js',
'../src/app/constructCatalog.js',
]) {
assert.equal(
(await readFile(new URL(source, import.meta.url), 'utf8')).includes(
fixtureId,
),
false,
`${fixtureId} must not exist in production source`,
);
}
function replaceOnce(source, from, to, label) {
const first = source.indexOf(from);
if (first < 0 || source.indexOf(from, first + from.length) >= 0)
throw new Error(`QA fixture anchor changed: ${label}`);
return source.replace(from, to);
}
function injectCodec(source) {
const fixtureRows = JSON.stringify([...priorDigitRows, [fixtureId, '00']]);
let result = replaceOnce(
source,
'parseLayerStateTokenReservations(reservationRows)',
`parseLayerStateTokenReservations([...reservationRows, ...${fixtureRows}])`,
'reservation',
);
result = replaceOnce(
result,
']);\n\nexport const REGISTERED_LAYER_IDS',
` Object.freeze({ id: '${fixtureId}', token: '00', disposition: 'enabled-only' }),\n]);\n\nexport const REGISTERED_LAYER_IDS`,
'registry',
);
injections.codec += 1;
return result;
}
function injectCatalog(source) {
const result = replaceOnce(
source,
' createBhoteKoshiEventLayer(),',
` {
id: '${fixtureId}',
name: 'Synthetic two-character token',
icon: '◆',
source: 'QA-only fixture',
async init() { return true; },
async enable() { return true; },
async update() { return true; },
async disable() { return true; },
async destroy() { return true; },
getStats() { return { count: 0 }; },
},
createBhoteKoshiEventLayer(),`,
'catalog',
);
injections.catalog += 1;
return result;
}
const browser = await puppeteer.launch({
headless: true,
args: [
'--no-sandbox',
'--disable-dev-shm-usage',
...(process.platform === 'darwin'
? ['--use-angle=metal', '--enable-gpu']
: ['--use-gl=angle', '--use-angle=swiftshader']),
],
});
async function newPage() {
const context = await browser.createBrowserContext();
const page = await context.newPage();
page.on('pageerror', (error) => errors.push(error.message));
await page.setViewport({ width: 1280, height: 800 });
await page.setCacheEnabled(false);
await page.evaluateOnNewDocument(() => {
window.__qaRestoreEvents = [];
window.addEventListener('gev:initial-share-restore-settled', (event) => {
window.__qaRestoreEvents.push(event.detail);
});
});
await page.setRequestInterception(true);
page.on('request', async (request) => {
try {
const url = new URL(request.url());
if (url.origin !== origin) return await request.abort();
const transform =
url.pathname === '/src/data/layerState.js'
? injectCodec
: url.pathname === '/src/app/constructCatalog.js'
? injectCatalog
: null;
if (!transform) return await request.continue();
const response = await fetch(request.url());
assert.equal(response.status, 200, `dev module ${url.pathname}`);
await request.respond({
status: 200,
contentType: 'application/javascript',
body: transform(await response.text()),
});
} catch (error) {
errors.push(String(error?.stack || error));
await request.abort().catch(() => {});
}
});
return { context, page };
}
async function load(page, url) {
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 60_000 });
await page.waitForFunction(
(id) => window.__godsEyeView?.dataManager?.layers?.has(id),
{ timeout: 60_000 },
fixtureId,
);
}
async function loadedState(page) {
return page.evaluate((id) => {
const manager = window.__godsEyeView.dataManager;
return {
enabled: manager.isEnabled(id),
flights: manager.isEnabled('flights'),
localStorage: localStorage.getItem('gev:layer-state:v2'),
restoreEvents: window.__qaRestoreEvents,
hash: location.hash,
};
}, fixtureId);
}
async function waitForRestore(page) {
await page.waitForFunction(() => window.__qaRestoreEvents.length > 0, {
timeout: 60_000,
});
return loadedState(page);
}
function withLayers(link, layers) {
const url = new URL(link);
const params = new URLSearchParams(url.hash.slice(1));
params.set('l', layers);
url.hash = params.toString();
return url.href;
}
const contexts = [];
try {
const sender = await newPage();
contexts.push(sender.context);
await load(sender.page, `${origin}/?welcome=0`);
const toggled = await sender.page.evaluate(
(id) =>
window.__godsEyeView.dataManager.setEnabled(id, true, { origin: 'user' }),
fixtureId,
);
assert.equal(toggled, true, 'synthetic layer enables through manager');
await sender.page.waitForFunction(
() => new URLSearchParams(location.hash.slice(1)).get('l') === '00',
{ timeout: 15_000 },
);
const sharedUrl = sender.page.url();
const authored = new URLSearchParams(new URL(sharedUrl).hash.slice(1));
assert.equal(authored.get('v'), '2');
assert.equal(authored.get('l'), '00');
assert.ok(authored.has('lat') && authored.has('lon'));
console.log('PASS: real user-origin enable encoded 00 in a full share URL');
const recipient = await newPage();
contexts.push(recipient.context);
await load(recipient.page, sharedUrl);
const restored = await waitForRestore(recipient.page);
assert.equal(restored.enabled, true);
assert.equal(restored.localStorage, null);
assert.equal(restored.restoreEvents.at(-1)?.status, 'settled');
assert.ok(
restored.restoreEvents
.at(-1)
?.layers?.some(
(layer) =>
layer.layerId === fixtureId &&
layer.targetEnabled === true &&
layer.succeeded === true,
),
'share coordinator restored the synthetic layer',
);
console.log(
'PASS: cold recipient reload restored the 00 layer without writing local preferences',
);
await recipient.page.reload({
waitUntil: 'domcontentloaded',
timeout: 60_000,
});
await recipient.page.waitForFunction(
(id) => window.__godsEyeView?.dataManager?.layers?.has(id),
{ timeout: 60_000 },
fixtureId,
);
const reloaded = await waitForRestore(recipient.page);
assert.equal(reloaded.enabled, true);
assert.equal(reloaded.localStorage, null);
assert.equal(new URLSearchParams(reloaded.hash.slice(1)).get('l'), '00');
console.log('PASS: a full recipient reload restores the same 00 layer state');
const disabled = await recipient.page.evaluate(
(id) =>
window.__godsEyeView.dataManager.setEnabled(id, false, {
origin: 'user',
}),
fixtureId,
);
assert.equal(disabled, true);
await recipient.page.waitForFunction(
() => new URLSearchParams(location.hash.slice(1)).get('l') === '',
{ timeout: 15_000 },
);
assert.equal((await loadedState(recipient.page)).enabled, false);
console.log('PASS: explicit OFF removes 00 from the authored share URL');
const legacy = await newPage();
contexts.push(legacy.context);
await load(legacy.page, withLayers(sharedUrl, 'f'));
const legacyState = await waitForRestore(legacy.page);
assert.equal(legacyState.flights, true);
assert.equal(legacyState.enabled, false);
assert.equal(legacyState.restoreEvents.at(-1)?.status, 'settled');
console.log('PASS: a legacy one-character token still restores');
const mixed = await newPage();
contexts.push(mixed.context);
await load(mixed.page, withLayers(sharedUrl, 'f.00'));
const mixedState = await waitForRestore(mixed.page);
assert.equal(mixedState.flights, true);
assert.equal(mixedState.enabled, true);
console.log('PASS: mixed legacy and two-character tokens restore together');
const malformed = await newPage();
contexts.push(malformed.context);
await load(malformed.page, withLayers(sharedUrl, 'f..00'));
const malformedState = await waitForRestore(malformed.page);
assert.equal(malformedState.flights, false);
assert.equal(malformedState.enabled, false);
assert.equal(malformedState.localStorage, null);
console.log(
'PASS: malformed layer list is rejected without partial restoration',
);
const repeated = await newPage();
contexts.push(repeated.context);
const repeatedUrl = new URL(withLayers(sharedUrl, 'f'));
const repeatedParams = new URLSearchParams(repeatedUrl.hash.slice(1));
repeatedParams.append('l', '00');
repeatedUrl.hash = repeatedParams.toString();
await load(repeated.page, repeatedUrl.href);
const repeatedState = await waitForRestore(repeated.page);
assert.equal(repeatedState.flights, false);
assert.equal(repeatedState.enabled, false);
assert.equal(repeatedState.localStorage, null);
console.log('PASS: repeated layer fields reject the entire share payload');
assert.ok(injections.codec >= contexts.length);
assert.ok(injections.catalog >= contexts.length);
assert.deepEqual(errors, []);
console.log(
'PASS: QA-only codec and catalog injections reached every isolated session',
);
console.log(
'Synthetic end-to-end evidence only; production 00 allocation remains pending.',
);
console.log('RESULT: 10 passed, 0 failed, 0 skipped');
} finally {
await Promise.all(contexts.map((context) => context.close().catch(() => {})));
await browser.close();
}