From 0b1791d32ce8c3fb0b9b71d2c009e96cfbe9d8b6 Mon Sep 17 00:00:00 2001 From: sbc1-code <207095575+sbc1-code@users.noreply.github.com> Date: Thu, 22 Oct 2026 13:05:26 -0700 Subject: [PATCH] fix: preserve verified pedestrian routes when reports disappear --- CHANGELOG.md | 12 ++ DECISIONS.md | 10 ++ ROADMAP.md | 4 + docs/REFRESH-OPERATIONS.md | 57 ++++++++++ scripts/check-walk-drive-availability.mjs | 47 +++++++- scripts/fixtures/walk-drive-capability.json | 119 ++++++++++++++++++++ scripts/test-crossing-snapshot.mjs | 52 +++++++++ scripts/test-trust-state.mjs | 28 ++++- src/lib/crossingAvailability.js | 13 ++- 9 files changed, 337 insertions(+), 5 deletions(-) create mode 100644 scripts/fixtures/walk-drive-capability.json diff --git a/CHANGELOG.md b/CHANGELOG.md index cf53cfab8..bec90c810 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # CHANGELOG +## 2026-10-22 — Verified pedestrian route exceptions (local candidate, issue #59) + +- Keep the existing Progreso International Bridge (230901) and Santa Teresa + (240801) walk-or-drive pages and crossing-detail links when pedestrian reports + disappear. Capability is source-backed; current waits/closures still use the + unchanged report checks. No missing wait becomes zero or a recommendation. +- Add recorded-snapshot generator regression, exact-identity/zero/closure checks + and EN/ES keyboard/navigation/recovery coverage. Official sources, input + identities, evidence and remaining publication gates are in the refresh runbook. +- Local review candidate only. Other ports retain their existing report-based + behavior; #59 and the separate Search Console reconciliation remain open. + ## 2026-10-22 — Summary confidence released (PR #143) - Published source `a14654f32e0b83b11008a978d1fb085ff83db059` through existing diff --git a/DECISIONS.md b/DECISIONS.md index c382f972f..e2f7dccc9 100644 --- a/DECISIONS.md +++ b/DECISIONS.md @@ -1,5 +1,15 @@ # DECISIONS +## 2026-10-22 · Verified capability and current lane reports answer different questions + +The existence of a pedestrian comparison page must survive a missing current +report at a verified pedestrian crossing. Preserve Progreso International Bridge +230901 and Santa Teresa 240801 by exact port identity, backed by the official +sources in the refresh runbook. Do not infer Donna's capability from the Progreso +name, copy another crossing's report, or manufacture zero waits. Availability, +closures and freshness still gate recommendations. Keep other ports' existing +behavior until separately sourced; this two-port exception is not full curation. + ## 2026-10-22 · Deferred content must evaluate confidence when it actually opens A usable snapshot can expire while an optional chunk is downloading, while a diff --git a/ROADMAP.md b/ROADMAP.md index 5b597b0f8..32ae63f4f 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -67,6 +67,10 @@ cross-border experience; it is a product hypothesis, not a claim of demand. objects in each snapshot, so the inventory fluctuates as reports change. Keep this separate from document-language ownership; do not invent capability or expand routes without source evidence. + Issue #59 has a local, source-backed candidate preserving existing Progreso + International Bridge and Santa Teresa routes across missing reports. + Review/publication and other-port curation remain open; see the refresh runbook + for exact input identities, coverage and gates. - [x] Separate CBP lane report times from snapshot retrieval on dashboard and crossing detail cards. Preserve raw report text and unknown dates; release diff --git a/docs/REFRESH-OPERATIONS.md b/docs/REFRESH-OPERATIONS.md index a41e95131..c885101ce 100644 --- a/docs/REFRESH-OPERATIONS.md +++ b/docs/REFRESH-OPERATIONS.md @@ -1,5 +1,62 @@ # Public data refresh +## Walk-or-drive inventory candidate — 2026-10-22 UTC (issue #59, unreleased) + +- Review base: `a10706b5eb5ae48468e32c5fc21c657d63bd8153`, rebased cleanly + from initial base `42ca1fee`; incoming changes were collector inputs only. + Inspected current main, + existing worktrees, the updated issue and runbook before editing; no overlapping + open capability PR found. Preserve original 18 changed paths, prior worktrees + and newer collector inputs. The initial local stage performed no publication. + Review continuation authorizes one branch push and PR/CI, but no merge/deploy. +- Recorded `public/data/crossings.json` inputs, both 42 canonical crossings: + `e0bac72fd4d978dfbbec7f3c68d804025d3ac45c`, retrieved 03:52:35.415Z, + blob `3f6ea59a17f8edc620605c93fa3c7335cfc119e9`, versus + `5a681bad045eb4153830928021350844069cef98`, retrieved 04:52:36.981Z. + Real sitemap/prerender generators agree within each input, but pedestrian + canonicals drop **28 → 26**. Only `/walk-or-drive/progreso-progreso-international-bridge/` + and `/walk-or-drive/santa-teresa-santa-teresa-port-of-entry/` disappear when + their lane objects become null. Collector mapping of absent/N/A/closed reports + to null is not evidence of absent pedestrian infrastructure. +- Source review October 22 supports exactly two persistent positive exceptions, + by port number, without asserting current opening or changing waits: + **Progreso 230901**, [TxDOT 2021 guide, RGV-60/PDF194 and RGV-64/PDF198](https://ftp.txdot.gov/pub/txdot/move-texas-freight/studies/texas-mexico-border-crossings-guide-2021.pdf) + describes pedestrian infrastructure and northbound pedestrian processing; + the [bridge operator](https://texasmexicobridges.com/) also describes pedestrian + passage. **Santa Teresa 240801**, [City of El Paso, International Bridges, + Santa Teresa section](https://www.elpasotexas.gov/international-bridges), + explicitly includes pedestrians. These sources do not establish Donna 230902 + or neighboring ports' capability. No source hours are encoded as current status. +- Exact identity cross-check: [official CBP public feed](https://bwt.cbp.gov/api/bwtpublicmod) + returned HTTP200 at 20:13:25.210Z: 230901 = Progreso International Bridge, + 230902 = Donna International Bridge, 240801 = Santa Teresa Port of Entry. + These rows corroborate identity only; the dated capability sources do not + establish current operating hours, open lanes or wait availability. +- The shared predicate serves detail links, sitemap, prerender and legacy + aliases. Both recorded inputs now generate **28 canonicals**, identical across + sitemap/prerender. Missing reports still show unavailable; a real zero restores + comparison; closure overrides retained numbers. No wait parser, collector, + freshness threshold or generator implementation changes. +- Broader curation remains open. Six sampled snapshots show other report-based + differences, including Eagle Pass I, Stanton, Pharr, Anzalduas, Lukeville, + Donna, Rio Grande City and San Luis II. No new claim or removal for those ports. + Obtain official port-specific/directional evidence before adding positive or + negative entries. Missing reports alone cannot establish lack of pedestrian + service. #59 stays open; Search Console reconciliation is separately unverified. +- Initial local verification before rebase: fresh build PASS; entry **61.73 KiB**, CSS 19.09 KiB; + full npm test PASS (**133 unit tests, 402 browser navigations**), including both viewports and + EN/ES keyboard entry, canonical identity, missing/deleted reports, true zero, + closure and recovery. Regression runs real generators on recorded inputs in + temporary directories; source snapshots in this worktree are not overwritten. + No budget/audit gate or assertion was removed. +- Publication gates: review the two-port patch; integrate current main while + preserving collector inputs; require fresh build/full suite and exact-head CI + under unchanged **62 KiB**; obtain release authorization for this candidate + (the review/CI continuation still forbids merge/deploy); use the existing workflow once, then verify + manifest/source, both sitemap/prerender pages and EN/ES mobile/desktop links, + missing/zero/closure recovery on served assets. No new dependency, security/ + account setting, credential, ad, service, monitor or cost. + ## Summary confidence release — 2026-10-22 UTC (PR #143) - Reviewed head **84da4c8d29bc77940736fc6851962585d4f7e56c**, merged/source diff --git a/scripts/check-walk-drive-availability.mjs b/scripts/check-walk-drive-availability.mjs index 0d6125dd2..9cfbd7d49 100644 --- a/scripts/check-walk-drive-availability.mjs +++ b/scripts/check-walk-drive-availability.mjs @@ -47,9 +47,54 @@ export async function checkWalkDriveAvailability(page, context, baseUrl) { content = await page.locator('#root').innerText(); assert.ok(!/RIGHT NOW|Right now|Ahora mismo|0 min/.test(content), 'pending wait must not support a recommendation'); } + for (const lang of ['en', 'es']) { + await page.evaluate(value => localStorage.setItem('borderPulse_language', value), lang); + for (const [port, slug] of [ + ['230901', 'progreso-progreso-international-bridge'], + ['240801', 'santa-teresa-santa-teresa-port-of-entry'], + ]) { + const c = snapshot.crossings.find(row => row.port_number === port); + assert.ok(c, 'verified capability fixture must exist'); + c.port_status = 'Open'; + c.lanes = { passenger_standard: lane(60), pedestrian_standard: null, pedestrian_ready: null }; + const route = `/walk-or-drive/${slug}/`; + await page.goto(new URL(`/crossing/${slug}/`, baseUrl).href); + const link = page.locator(`#root a[href="${route}"]`); + await link.waitFor({ state: 'visible' }); + await link.focus(); + await link.press('Enter'); + await page.waitForURL(url => url.pathname === route); + const missing = page.getByRole('heading', { level: 1, name: lang === 'en' + ? `${c.name}: no current pedestrian wait reported` + : `${c.name}: sin espera peatonal actual reportada`, exact: true }); + await missing.waitFor(); + await page.waitForFunction(url => document.querySelector('link[rel="canonical"]')?.href === url, + `https://borderpulse.com${route}`); + assert.ok(!/0 min|RIGHT NOW|Ahora mismo/.test(await page.locator('#root').innerText()), 'capability supplies neither a zero nor a verdict'); + assert.ok(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth + 1), 'unavailable route fits viewport'); + c.lanes.pedestrian_standard = lane(0, 'no delay'); + await page.reload(); + await page.getByRole('heading', { name: lang === 'en' ? 'Walk (standard)' : 'A pie (estándar)', exact: true }).waitFor(); + assert.ok((await page.locator('#root').innerText()).includes('0 min'), 'a genuine zero restores the comparison'); + c.port_status = 'Closed'; + await page.reload(); + await page.getByRole('heading', { level: 1, name: lang === 'en' + ? `${c.name}: closed now` : `${c.name}: cerrado ahora`, exact: true }).waitFor(); + assert.ok(!(await page.locator('#root').innerText()).includes('0 min'), 'known capability does not override a closure'); + c.port_status = 'Open'; + delete c.lanes.pedestrian_standard; + delete c.lanes.pedestrian_ready; + await page.reload(); + await missing.waitFor(); + assert.ok(!(await page.locator('#root').innerText()).includes('0 min'), 'disappearing report removes the zero and comparison'); + await page.goBack(); + await page.waitForURL(url => url.pathname === `/crossing/${slug}/`); + await link.waitFor({ state: 'visible' }); + } + } } finally { await context.unroute(feedMatcher, feedHandler); await page.evaluate(() => localStorage.setItem('borderPulse_language', 'en')); } - console.log('[walk-drive] PASS: EN/ES closed, Back, reopened zero-wait and pending-report states'); + console.log('[walk-drive] PASS: EN/ES closed, Back, zero/pending and verified Progreso/Santa Teresa routes after missing/disappearing reports'); } diff --git a/scripts/fixtures/walk-drive-capability.json b/scripts/fixtures/walk-drive-capability.json new file mode 100644 index 000000000..5f808f625 --- /dev/null +++ b/scripts/fixtures/walk-drive-capability.json @@ -0,0 +1,119 @@ +[ + { + "sourceCommit": "e0bac72fd4d978dfbbec7f3c68d804025d3ac45c", + "sourceBlob": "3f6ea59a17f8edc620605c93fa3c7335cfc119e9", + "sourceSha256": "8fd1d66e254e9412e37dd7e0b1876f83f1402c4c97218fade2d6332de0c83665", + "fetched_at": "2026-10-22T03:52:35.415Z", + "crossings": [ + { + "port_number": "230901", + "name": "Progreso - Progreso International Bridge", + "port_status": "Open", + "lanes": { + "passenger_standard": { + "status": "delay", + "delay_minutes": 10, + "lanes_open": 2, + "update_time": "At 10:00 pm CDT" + }, + "passenger_sentri": null, + "passenger_ready": null, + "pedestrian_standard": { + "status": "no delay", + "delay_minutes": 5, + "lanes_open": 1, + "update_time": "At 10:00 pm CDT" + }, + "pedestrian_ready": null, + "commercial_standard": null, + "commercial_fast": null + } + }, + { + "port_number": "240801", + "name": "Santa Teresa - Santa Teresa Port of Entry", + "port_status": "Open", + "lanes": { + "passenger_standard": { + "status": "delay", + "delay_minutes": 40, + "lanes_open": 1, + "update_time": "At 9:00 pm MDT" + }, + "passenger_sentri": null, + "passenger_ready": { + "status": "delay", + "delay_minutes": 35, + "lanes_open": 1, + "update_time": "At 9:00 pm MDT" + }, + "pedestrian_standard": { + "status": "no delay", + "delay_minutes": 5, + "lanes_open": 1, + "update_time": "At 9:00 pm MDT" + }, + "pedestrian_ready": null, + "commercial_standard": { + "status": "delay", + "delay_minutes": 50, + "lanes_open": 1, + "update_time": "At 9:00 pm MDT" + }, + "commercial_fast": { + "status": "delay", + "delay_minutes": 45, + "lanes_open": 1, + "update_time": "At 9:00 pm MDT" + } + } + } + ] + }, + { + "sourceCommit": "5a681bad045eb4153830928021350844069cef98", + "sourceBlob": "e3eb7c086794d306c8e1b2f6a93fe01667adeaf7", + "sourceSha256": "b7c8e0a77fd42526e5b5868eaf56a2ac85811740e0b0b00e031b11cc1bcdc0ee", + "fetched_at": "2026-10-22T04:52:36.981Z", + "crossings": [ + { + "port_number": "230901", + "name": "Progreso - Progreso International Bridge", + "port_status": "Open", + "lanes": { + "passenger_standard": { + "status": "delay", + "delay_minutes": 10, + "lanes_open": 2, + "update_time": "At 11:00 pm CDT" + }, + "passenger_sentri": null, + "passenger_ready": { + "status": "no delay", + "delay_minutes": 5, + "lanes_open": 1, + "update_time": "At 11:00 pm CDT" + }, + "pedestrian_standard": null, + "pedestrian_ready": null, + "commercial_standard": null, + "commercial_fast": null + } + }, + { + "port_number": "240801", + "name": "Santa Teresa - Santa Teresa Port of Entry", + "port_status": "Open", + "lanes": { + "passenger_standard": null, + "passenger_sentri": null, + "passenger_ready": null, + "pedestrian_standard": null, + "pedestrian_ready": null, + "commercial_standard": null, + "commercial_fast": null + } + } + ] + } +] diff --git a/scripts/test-crossing-snapshot.mjs b/scripts/test-crossing-snapshot.mjs index b36bacb0e..75b7c1800 100644 --- a/scripts/test-crossing-snapshot.mjs +++ b/scripts/test-crossing-snapshot.mjs @@ -1,6 +1,10 @@ import test from 'node:test'; import assert from 'node:assert/strict'; import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import { execFileSync } from 'node:child_process'; +import { fileURLToPath } from 'node:url'; import { snapshotHtml, injectSnapshot } from './lib/crossing-snapshot-html.mjs'; import { crossingDisplayName, crossingSearchTerms, crossingSearchTitle } from '../src/lib/crossingNames.js'; @@ -73,3 +77,51 @@ test('built homepage and every crossing page contain their own useful visible sn } assert.doesNotMatch(fs.readFileSync('dist/privacy/index.html', 'utf8'), /data-prerender="crossing-snapshot"/); }); + +test('recorded pedestrian-report disappearance preserves the same sitemap and prerender canonicals', () => { + const root = fileURLToPath(new URL('../', import.meta.url)); + const temporary = fs.mkdtempSync(path.join(os.tmpdir(), 'bp-capability-build-')); + const snapshots = JSON.parse(fs.readFileSync(new URL('./fixtures/walk-drive-capability.json', import.meta.url))); + const expected = [ + 'https://borderpulse.com/walk-or-drive/progreso-progreso-international-bridge/', + 'https://borderpulse.com/walk-or-drive/santa-teresa-santa-teresa-port-of-entry/', + ]; + try { + for (const dir of ['src/lib', 'src/components/utils', 'scripts/lib']) { + fs.cpSync(path.join(root, dir), path.join(temporary, dir), { recursive: true }); + } + for (const file of ['scripts/build-sitemap.mjs', 'scripts/prerender.mjs']) { + fs.copyFileSync(path.join(root, file), path.join(temporary, file)); + } + fs.symlinkSync(path.join(root, 'node_modules'), path.join(temporary, 'node_modules')); + fs.writeFileSync(path.join(temporary, 'package.json'), '{"type":"module"}'); + fs.mkdirSync(path.join(temporary, 'public/data'), { recursive: true }); + const unavailable = { ...snapshots[1], crossings: snapshots[1].crossings.map(c => ({ ...c, port_status: 'Closed', lanes: {} })) }; + for (const snapshot of [...snapshots, unavailable]) { + fs.rmSync(path.join(temporary, 'dist'), { recursive: true, force: true }); + fs.mkdirSync(path.join(temporary, 'dist')); + fs.copyFileSync(path.join(root, 'index.html'), path.join(temporary, 'dist/index.html')); + // Donna is a different port; these two source-backed exceptions must not + // create a pedestrian route there merely because its name says Progreso. + const data = { ...snapshot, crossings: [...snapshot.crossings, + { port_number: '230902', name: 'Progreso - Donna International Bridge', lanes: {} }] }; + fs.writeFileSync(path.join(temporary, 'public/data/crossings.json'), JSON.stringify(data)); + for (const script of ['scripts/build-sitemap.mjs', 'scripts/prerender.mjs']) { + execFileSync(process.execPath, [script], { cwd: temporary, stdio: 'pipe' }); + } + const sitemap = [...fs.readFileSync(path.join(temporary, 'public/sitemap.xml'), 'utf8') + .matchAll(/([^<]+\/walk-or-drive\/[^<]+)<\/loc>/g)].map(m => m[1]).sort(); + assert.deepEqual(sitemap, expected, snapshot.sourceCommit); + const generated = fs.readdirSync(path.join(temporary, 'dist/walk-or-drive')).flatMap(slug => { + const html = fs.readFileSync(path.join(temporary, 'dist/walk-or-drive', slug, 'index.html'), 'utf8'); + if (/http-equiv="refresh"/.test(html)) return []; + const url = `https://borderpulse.com/walk-or-drive/${slug}/`; + assert.ok(html.includes(` { }); +test('verified pedestrian capability survives disappearing reports without inventing a wait', () => { + for (const port_number of ['230901', '240801']) { + for (const lanes of [undefined, {}, { pedestrian_standard: null, pedestrian_ready: null }, + { pedestrian_standard: { status: 'Update Pending', delay_minutes: null, lanes_open: 0 } }]) { + const crossing = { port_number, lanes }; + assert.equal(hasPedestrianLane(crossing), true); + assert.equal(availableLaneWait(crossing, 'pedestrian_standard'), null); + assert.equal(availableLaneWait(crossing, 'pedestrian_ready'), null); + } + const crossing = { port_number, lanes: { pedestrian_standard: { status: 'no delay', delay_minutes: 0, lanes_open: 1 } } }; + assert.equal(availableLaneWait(crossing, 'pedestrian_standard'), 0); + crossing.port_status = 'Closed'; + assert.equal(hasPedestrianLane(crossing), true, 'capability is not current availability'); + assert.equal(availableLaneWait(crossing, 'pedestrian_standard'), null); + } +}); + +test('capability exceptions use exact port identity, never a similar name or nearby crossing', () => { + for (const port_number of ['230902', '240601', 'unknown', undefined]) { + assert.equal(hasPedestrianLane({ port_number, name: 'Progreso - Santa Teresa', lanes: {} }), false); + } + assert.equal(hasPedestrianLane(null), false); + assert.equal(hasPedestrianLane({ port_number: '250201', lanes: { pedestrian_standard: { status: 'Update Pending' } } }), true, + 'unreviewed ports retain the existing lane-object behavior'); +}); + test('walk-or-drive ignores retained lane numbers at a closed crossing', () => { const crossing = { port_status: ' Closed ', lanes: { passenger_standard: { status: 'delay', delay_minutes: 15, lanes_open: 1 }, diff --git a/src/lib/crossingAvailability.js b/src/lib/crossingAvailability.js index 560075a37..886422dd9 100644 --- a/src/lib/crossingAvailability.js +++ b/src/lib/crossingAvailability.js @@ -1,8 +1,15 @@ -// Keep route generation aligned with the links shown on crossing pages. -// A lane object can legitimately be present while CBP reports "Update Pending"; -// the page should still exist and explain that no current wait is available. +// Verified pedestrian capability survives missing/closed CBP lane reports. +// Progreso: TxDOT 2021 Border Crossings Guide, RGV-60 and RGV-64. +// Santa Teresa: City of El Paso, International Bridges, Santa Teresa section. +// Source URLs, snapshot identities and the deliberately limited scope are in +// docs/REFRESH-OPERATIONS.md (issue #59). This does not assert current service. +const VERIFIED_PEDESTRIAN_PORTS = new Set(['230901', '240801']); + +// One rule for crossing-page links, sitemap and prerender. Other ports retain +// the existing report-based fallback until their capability is reviewed. export function hasPedestrianLane(crossing) { return Boolean( + VERIFIED_PEDESTRIAN_PORTS.has(crossing?.port_number) || crossing?.lanes?.pedestrian_standard || crossing?.lanes?.pedestrian_ready, );