Checkpoint: current working state
Some checks failed
ci / check (push) Has been cancelled

This commit is contained in:
Lumen Stage1 2026-09-29 21:33:33 -05:00
commit e83946c207
57 changed files with 6257 additions and 775 deletions

View file

@ -0,0 +1,350 @@
/**
* @vitest-environment jsdom
* Stage 10 — Emergency three tiers: resolution matrix, Tier 2 rendering,
* demotion guidance, zero-IDB floor, loader, and boot wiring.
* Trace: IMPLEMENTATION-CONTRACT.md §14, SPIKE-06.
*/
/* eslint-disable @typescript-eslint/no-unsafe-call */
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
// @ts-expect-error fake-indexeddb types via exports fallback
import FDBFactory from "fake-indexeddb/lib/FDBFactory";
// @ts-expect-error fake-indexeddb types via exports fallback
import FDBKeyRange from "fake-indexeddb/lib/FDBKeyRange";
import { mount as boot } from "../../src/app/main.js";
import { loadDatasetEmergency } from "../../src/app/emergency.js";
import {
demotedBaseline,
isSupportedEmergencySchema,
narrowEmergencySection,
resolveEmergency,
visibleNotices,
} from "../../src/domain/emergency/logic.js";
import type { VerifiedEmergencyData } from "../../src/domain/emergency/types.js";
import type { EmergencySection } from "../../src/data/festival-package/types.js";
import { createEmergencyView } from "../../src/ui/views/emergency/emergency.js";
import { buildPackage } from "../../pipeline/package.js";
import { generateTestKeyPair } from "../../pipeline/sign.js";
import { makeValidInput } from "../../pipeline/fixtures.js";
import { canonicalJson } from "../../pipeline/canonical-json.js";
import { publicKeyFromDerBase64 } from "../../src/sync/verifier/ed25519.js";
import { verifyPackage } from "../../src/sync/verifier/package.js";
import { activateStagedPackage, stageVerifiedPackage } from "../../src/sync/activation.js";
import { openSystemDB } from "../../src/data/system-meta/store.js";
import { openSlotDB, writeSlotFile } from "../../src/data/slot/store.js";
import { DB } from "../../src/platform/idb/names.js";
const g = globalThis as unknown as Record<string, unknown>;
const NOW = 1_750_000_000_000;
function section(patch: Record<string, unknown> = {}): EmergencySection {
return {
section: "emergency",
emergencySchemaVersion: 1,
contentVersion: 2,
updatedAt: "2026-09-01T00:00:00.000Z",
services: {
emergencyNumber: "911",
security: { phone: "555-0100", location: "North gate" },
firstAid: { location: "Medical tent", hours: "10:00–22:00" },
},
locations: {
musterPoints: [{ id: "m1", name: "North field", poi: "poi-muster" }],
exits: [{ id: "e1", name: "Main gate", poi: "poi-exit" }],
aeds: [{ poi: "poi-aed" }],
},
address: { lines: ["1 Festival Way"], coordinates: { lat: 29.9, lon: -97.9 } },
procedures: [{ id: "medical", title: "Medical", steps: ["Call 911", "Find staff"] }],
notices: [
{
id: "n1",
severity: "info",
title: "Gates open",
body: ["Gates open at 9am"],
expiresAtUtc: null,
},
],
...patch,
};
}
function dataset(patch: Partial<VerifiedEmergencyData> = {}): VerifiedEmergencyData {
return {
section: section(),
packageVersion: 3,
generatedAt: "2026-08-20T00:00:00.000Z",
emergencySchemaVersion: 1,
...patch,
};
}
describe("resolveEmergency — tier matrix (SPIKE-06)", () => {
it("no dataset → baseline with no-dataset demotion", () => {
const model = resolveEmergency(undefined, NOW);
expect(model.source).toBe("baseline");
expect(model.demotion).toBe("no-dataset");
expect(model.provenance).toMatch(/^BASELINE v/);
expect(model.section).toBeNull();
});
it("valid dataset → dataset tier with merged floor fields and provenance", () => {
const sparse = section();
const { services: _dropServices, ...withoutServices } = sparse;
void _dropServices;
const model = resolveEmergency(dataset({ section: withoutServices as EmergencySection }), NOW);
expect(model.source).toBe("dataset");
expect(model.demotion).toBeNull();
// Floor-only services backfill the sparse section (defensive merge).
expect(model.section?.services.emergencyNumber).toBeTruthy();
expect(model.provenance).toContain("FESTIVAL DATA v3");
expect(model.provenance).toContain("generated 2026-08-20");
expect(model.provenance).toContain("updated 2026-09-01");
});
it("sparse sections merge floor fields instead of demoting", () => {
const sparse = section({ services: {}, procedures: [] });
const model = resolveEmergency(dataset({ section: sparse }), NOW);
expect(model.source).toBe("dataset");
expect(model.section?.services.emergencyNumber).toBeTruthy();
expect(model.section?.procedures.length).toBeGreaterThan(0);
});
it("unsupported schema → baseline with unsupported-schema demotion", () => {
const model = resolveEmergency(dataset({ emergencySchemaVersion: 99 }), NOW);
expect(model.source).toBe("baseline");
expect(model.demotion).toBe("unsupported-schema");
expect(isSupportedEmergencySchema(99)).toBe(false);
expect(isSupportedEmergencySchema(1)).toBe(true);
});
it("malformed section → baseline with malformed-section demotion", () => {
const badValues: unknown[] = [
"nope",
42,
null,
{},
{ section: "emergency" },
{ section: "emergency", emergencySchemaVersion: 0 },
{ section: "emergency", emergencySchemaVersion: "1" },
{ section: "emergency", emergencySchemaVersion: 1, services: "911" },
{
section: "emergency",
emergencySchemaVersion: 1,
procedures: [{ id: "x" }],
},
];
for (const bad of badValues) {
const model = resolveEmergency(dataset({ section: bad as EmergencySection }), NOW);
expect(model.source).toBe("baseline");
expect(model.demotion).toBe("malformed-section");
}
});
it("unknown future fields never throw (forward tolerance)", () => {
const future = {
...section(),
brandNewField: { deep: [1, 2, 3] },
services: { ...section().services, another: "x" },
};
const model = resolveEmergency(dataset({ section: future }), NOW);
expect(model.source).toBe("dataset");
});
it("expired notices hide; unexpiring and future notices show", () => {
const withNotices = section({
notices: [
{ id: "old", severity: "info", title: "Old", body: ["x"], expiresAtUtc: NOW - 1000 },
{ id: "live", severity: "urgent", title: "Live", body: ["y"], expiresAtUtc: NOW + 1000 },
{ id: "forever", severity: "info", title: "Forever", body: ["z"], expiresAtUtc: null },
],
});
const filtered = visibleNotices(withNotices, NOW);
expect(filtered.notices?.map((n) => n.id)).toEqual(["live", "forever"]);
const model = resolveEmergency(dataset({ section: withNotices }), NOW);
expect(model.section?.notices?.map((n) => n.id)).toEqual(["live", "forever"]);
});
it("demotedBaseline carries the reason with floor provenance", () => {
const model = demotedBaseline("unsupported-schema");
expect(model.source).toBe("baseline");
expect(model.demotion).toBe("unsupported-schema");
expect(model.provenance).toMatch(/^BASELINE v/);
});
});
describe("narrowEmergencySection", () => {
it("accepts a valid section, rejects everything else without throwing", () => {
expect(narrowEmergencySection(section())).toBeTruthy();
for (const bad of [undefined, null, 7, "x", [], {}, { section: "schedule" }]) {
expect(narrowEmergencySection(bad)).toBeNull();
}
});
});
describe("emergency view", () => {
it("floor tier renders number, dial link, procedures, and guidance", () => {
const view = createEmergencyView(undefined, NOW);
expect(view.querySelector("h1")?.textContent).toBe("Emergency");
expect(view.querySelector('a[href^="tel:"]')).toBeTruthy();
expect(view.textContent).toContain("BASELINE v");
expect(view.textContent).toContain("isn't on this device yet");
});
it("dataset tier renders locations with map handoffs and notices", () => {
const view = createEmergencyView(dataset(), NOW);
expect(view.textContent).toContain("FESTIVAL DATA v3");
expect(view.textContent).not.toContain("isn't on this device yet");
expect(view.textContent).toContain("North field");
expect(view.textContent).toContain("[info] Gates open");
const mapLinks = [...view.querySelectorAll('a[data-route="/map"]')];
expect(mapLinks.length).toBeGreaterThan(0);
expect(view.textContent).toContain("First-aid hours: 10:00–22:00");
});
it("each demotion reason renders its own guidance", () => {
for (const [reason, hint] of [
["no-dataset", "isn't on this device yet"],
["unsupported-schema", "newer app version"],
["malformed-section", "couldn't be read"],
] as const) {
const view = createEmergencyView(demotedBaseline(reason), NOW);
expect(view.textContent).toContain("BASELINE v");
expect(view.textContent).toContain(hint);
}
});
it("renders with zero storage access (Ring-0: no indexedDB, no caches)", () => {
const idx = g.indexedDB;
const cachesGlobal = (globalThis as Record<string, unknown>).caches;
// Ring-0 proof — storage must not exist here.
delete g.indexedDB;
delete (globalThis as Record<string, unknown>).caches;
try {
const view = createEmergencyView(undefined, NOW);
expect(view.querySelector("h1")?.textContent).toBe("Emergency");
expect(view.querySelector('a[href^="tel:"]')).toBeTruthy();
} finally {
g.indexedDB = idx;
if (cachesGlobal !== undefined) {
(globalThis as Record<string, unknown>).caches = cachesGlobal;
}
}
});
it("never renders raw HTML from content", () => {
const evil = section({ procedures: [{ id: "x", title: "<img src=x>", steps: ["<b>do</b>"] }] });
const view = createEmergencyView(dataset({ section: evil }), NOW);
expect(view.innerHTML).not.toContain("<img src=x>");
expect(view.innerHTML).not.toContain("<b>do</b>");
});
});
describe("dataset emergency loader", () => {
function deleteDb(name: string): Promise<void> {
return new Promise((resolve, reject) => {
const request = (g.indexedDB as IDBFactory).deleteDatabase(name);
request.onsuccess = () => {
resolve();
};
request.onerror = () => {
reject(request.error ?? new Error("delete failed"));
};
request.onblocked = () => {
resolve();
};
});
}
async function fixturePackage(version: number) {
const keyPair = generateTestKeyPair();
const built = buildPackage(makeValidInput({ packageVersion: version }), { signWith: keyPair });
if (!built.ok || !built.pkg.signature) throw new Error("fixture build failed");
const manifestBytes = new TextEncoder().encode(canonicalJson(built.pkg.manifest));
const files = new Map<string, Uint8Array>();
for (const [name, file] of built.pkg.files) files.set(name, file.canonicalBytes);
for (const asset of built.pkg.assets) files.set(asset.file, asset.bytesContent);
const result = await verifyPackage(
{
manifestBytes,
signature: built.pkg.signature,
files: { getFile: (name) => Promise.resolve(files.get(name)) },
emergencyFloor: built.pkg.emergencyFloor,
},
{
trustedKeys: new Map([
[keyPair.fingerprint, publicKeyFromDerBase64(keyPair.publicKeyDerBase64)],
]),
appVersion: "1.0.0",
supportedSchemaRange: [1],
},
);
if (!result.ok) throw new Error(`verify failed: ${result.reason}`);
return result;
}
beforeEach(async () => {
g.indexedDB = new FDBFactory() as unknown;
g.IDBKeyRange = FDBKeyRange as unknown;
await deleteDb(DB.SYSTEM);
await deleteDb(DB.SLOT_A);
await deleteDb(DB.SLOT_B);
});
afterEach(() => {
// Leave no polyfill behind for the zero-storage test above.
delete g.indexedDB;
});
it("fresh install → absent", async () => {
expect(await loadDatasetEmergency()).toEqual({ status: "absent" });
});
it("staged + activated fixture → ready with version detail", async () => {
const pkg = await fixturePackage(2);
const staged = await stageVerifiedPackage(pkg);
const activated = await activateStagedPackage(pkg, staged, "1.0.0");
expect(activated.ok).toBe(true);
const loaded = await loadDatasetEmergency();
expect(loaded.status).toBe("ready");
if (loaded.status !== "ready") throw new Error("unreachable");
expect(loaded.data.packageVersion).toBe(2);
expect(loaded.data.section.services.emergencyNumber).toBeTruthy();
expect(loaded.data.generatedAt).toBe(pkg.manifest.generatedAt);
});
it("malformed stored section → unusable malformed-section", async () => {
const system = await openSystemDB();
const { writeSystemMeta } = await import("../../src/data/system-meta/store.js");
const { INITIAL_SYSTEM_META } = await import("../../src/data/system-meta/types.js");
await writeSystemMeta(system, {
...INITIAL_SYSTEM_META,
activeSlot: "A",
activeEdition: "test-edition",
activePackageVersion: 1,
});
system.close();
const slot = await openSlotDB("A");
await writeSlotFile(slot, "emergency", {
bytes: 32,
sha256: "0".repeat(64),
json: { section: "emergency", services: {} },
});
slot.close();
expect(await loadDatasetEmergency()).toEqual({
status: "unusable",
reason: "malformed-section",
});
});
it("boot renders Tier 2 through the real path after staging", async () => {
const pkg = await fixturePackage(1);
const staged = await stageVerifiedPackage(pkg);
expect((await activateStagedPackage(pkg, staged, "1.0.0")).ok).toBe(true);
history.replaceState(null, "", "/emergency");
document.body.innerHTML = '<div id="app" aria-busy="true"></div>';
boot();
await vi.waitFor(() => {
expect(document.querySelector(".emergency-view")?.textContent).toContain("FESTIVAL DATA v1");
});
document.body.innerHTML = "";
});
});