import { expect } from '@open-wc/testing'; import './nile-date-time-picker'; import '../nile-calendar'; import '../nile-input'; import type { Tr } from './nile-date-time-picker.test-helpers'; import { wallAt, fmtWall, makePicker, checkPayload, roundTrip, instantsFor, runtimeZones, transitionShape, selectAndApply, HOST_TZ, Failures, allTransitions, } from './nile-date-time-picker.test-helpers'; /* * Section K (adversarial audit), part 2 of 4: PHASE 4 (every DST transition). * Split out of nile-date-time-picker.test.ts so CI can run it on its own shard; * helpers shared across the split files live in nile-date-time-picker.test-helpers.ts. */ /* ========================================================================== PART 6 — PHASE 4/5: DST transitions, gaps, overlaps (was p4-dst.test.ts) ========================================================================== */ describe('PHASE 4 — every DST transition, found programmatically', () => { let transitions: Tr[] = []; let zonesWithDst: string[] = []; it('P4.0 discovers transitions across every runtime zone (2026)', () => { const zones = runtimeZones(); transitions = allTransitions(zones); zonesWithDst = Array.from(new Set(transitions.map(t => t.zone))); const fwd = transitions.filter(x => transitionShape(x.t, x.zone).kind === 'forward').length; // eslint-disable-next-line no-console console.warn(`[audit-p4] host=${HOST_TZ} zonesScanned=${zones.length} ` + `zonesWithTransitions=${zonesWithDst.length} transitions=${transitions.length} ` + `forward=${fwd} backward=${transitions.length - fwd}`); expect(transitions.length, 'must find real transitions').to.be.greaterThan(50); }).timeout(300000); it('P4.1 times around EVERY transition stay self-consistent and round-trip', async () => { const f = new Failures(); let probes = 0; for (const { zone, t } of transitions) { // instants around the transition -> the zone's own wall clock -> select for (const dt of [-2 * 3600000, -60000, 0, 60000, 2 * 3600000]) { const inst = t + dt; const w = wallAt(inst, zone); // skip if these digits are ambiguous — covered deliberately in PHASE 5 if (instantsFor(w, zone).length !== 1) continue; const el = await makePicker({}); const p = await selectAndApply(el, w, zone); const ctx = `tz=${zone} t=${t} dt=${dt / 1000}s want=${fmtWall(w)}`; f.add(ctx, checkPayload(p, zone)); if (p && p.unixEpoch !== null) { if (p.unixEpoch !== inst) { f.add(ctx, [{ code: 'P4_EPOCH_NE_INTENT', detail: `want=${inst} got=${p.unixEpoch} delta=${p.unixEpoch - inst}ms` }]); } f.add(ctx, (await roundTrip(p)).violations); } probes += 1; } } // eslint-disable-next-line no-console console.warn(`[audit-p4] around-transition probes=${probes} violations=${f.count}`); expect(f.count, `transition violations:${f.report()}`).to.equal(0); }).timeout(1800000); it('P4.2 GAP: a nonexistent wall clock still emits a payload describing its own epoch', async () => { const f = new Failures(); const notes: string[] = []; let n = 0; let before = 0; let after = 0; const afterZones: string[] = []; let posButBefore = 0; let negButAfter = 0; for (const { zone, t } of transitions) { const sh = transitionShape(t, zone); if (sh.kind !== 'forward') continue; const w = sh.insideWall; if (instantsFor(w, zone).length !== 0) continue; // must truly be a gap const el = await makePicker({}); const p = await selectAndApply(el, w, zone); const ctx = `GAP tz=${zone} want=${fmtWall(w)} delta=${sh.deltaMs / 60000}min`; // THE critical invariant: whatever it resolves to, the payload must be // internally truthful about its own epoch. f.add(ctx, checkPayload(p, zone)); if (p && p.unixEpoch !== null) { f.add(ctx, (await roundTrip(p)).violations); if (p.unixEpoch < t) { before += 1; if (sh.offBefore >= 0) posButBefore += 1; } else { after += 1; afterZones.push(zone); if (sh.offBefore < 0) negButAfter += 1; } if (n < 6) { notes.push(`${zone}: ${fmtWall(w)} -> epoch=${p.unixEpoch} value=${p.value} ` + `(lands ${p.unixEpoch < t ? 'BEFORE' : 'AFTER'} the gap)`); } } n += 1; } // eslint-disable-next-line no-console console.warn(`[audit-p4] gap probes=${n} violations=${f.count} ` + `LANDS_BEFORE=${before} LANDS_AFTER=${after} ` + `afterZonesSample=${afterZones.slice(0, 12).join(',')} ` + `SIGN_CORRELATION: positiveOffsetYetBefore=${posButBefore} negativeOffsetYetAfter=${negButAfter} ` + `(0/0 => direction is exactly the sign of the zone offset)` + `\n ${notes.join('\n ')}`); expect(f.count, `gap violations:${f.report()}`).to.equal(0); }).timeout(1800000); });