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

This commit is contained in:
Lumen Stage1 2026-09-23 18:58:21 -05:00
commit fcc18ddcc8
96 changed files with 8074 additions and 214 deletions

380
tests/unit/verifier.test.ts Normal file
View file

@ -0,0 +1,380 @@
/* eslint-disable @typescript-eslint/require-await, @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-unnecessary-type-assertion, @typescript-eslint/array-type, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access */
/** Stage 7 — pure package validation, ordering, replay protection, and quarantine. */
import { describe, expect, it } from "vitest";
import { canonicalJson } from "../../pipeline/canonical-json.js";
import { buildPackage } from "../../pipeline/package.js";
import { generateTestKeyPair, signManifest } from "../../pipeline/sign.js";
import { sha256Hex } from "../../pipeline/hash.js";
import { makeValidInput } from "../../pipeline/fixtures.js";
import { publicKeyFromDerBase64 } from "../../src/sync/verifier/ed25519.js";
import { verifyPackage } from "../../src/sync/verifier/package.js";
import type {
PackageFileProvider,
QuarantineRecord,
VerifyDependencies,
} from "../../src/sync/verifier/types.js";
import type { PackageSignature } from "../../src/data/festival-package/types.js";
const enc = (value: string) => new TextEncoder().encode(value);
function makeFixture() {
const keyPair = generateTestKeyPair();
const built = buildPackage(makeValidInput(), { signWith: keyPair });
if (!built.ok || !built.pkg.signature) throw new Error("fixture build failed");
const manifestBytes = enc(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 records: QuarantineRecord[] = [];
const provider: PackageFileProvider = {
listFiles: () => [...files.keys()],
getFile: async (name) => files.get(name),
};
const deps: VerifyDependencies = {
trustedKeys: new Map([
[keyPair.fingerprint, publicKeyFromDerBase64(keyPair.publicKeyDerBase64)],
]),
appVersion: "1.0.0",
supportedSchemaRange: [1],
quarantine: {
add: (record) => {
records.push(record);
},
},
};
return {
keyPair,
built: built.pkg,
signature: built.pkg.signature as PackageSignature,
manifestBytes,
files,
provider,
deps,
records,
};
}
function resign(manifest: unknown, keyPair: ReturnType<typeof generateTestKeyPair>) {
const bytes = enc(canonicalJson(manifest));
return { bytes, signature: signManifest(bytes, keyPair.privateKeyPem, keyPair.fingerprint) };
}
function withManifest(fixture: ReturnType<typeof makeFixture>, manifest: unknown) {
const signed = resign(manifest, fixture.keyPair);
return { ...fixture, manifestBytes: signed.bytes, signature: signed.signature };
}
function replaceSection(fixture: ReturnType<typeof makeFixture>, name: string, value: unknown) {
const bytes = enc(canonicalJson(value));
const sectionId = (
Object.keys(fixture.built.manifest.sections) as Array<
keyof typeof fixture.built.manifest.sections
>
).find((id) => fixture.built.manifest.sections[id].file === name);
if (!sectionId) throw new Error(`unknown section file ${name}`);
const entry = fixture.built.manifest.sections[sectionId];
const manifest = {
...fixture.built.manifest,
sections: {
...fixture.built.manifest.sections,
[sectionId]: { ...entry, bytes: bytes.length, sha256: sha256Hex(bytes) },
},
limits: {
totalBytes:
fixture.built.manifest.limits.totalBytes + bytes.length - fixture.files.get(name)!.length,
},
};
const changed = withManifest(fixture, manifest);
const files = new Map(fixture.files);
files.set(name, bytes);
return {
...changed,
files,
provider: {
listFiles: () => [...files.keys()],
getFile: async (file: string) => files.get(file),
},
};
}
function depsWithEvents(fixture: ReturnType<typeof makeFixture>, events: string[]) {
return {
...fixture.deps,
onGate: (gate: "signature" | "compatibility" | "files" | "schema") => events.push(gate),
};
}
async function verify(
fixture: ReturnType<typeof makeFixture>,
deps = fixture.deps,
signature: PackageSignature = fixture.signature as PackageSignature,
extras: {
readonly active?: { edition: string; packageVersion: number } | null;
readonly emergencyFloor?: unknown;
} = {},
) {
const input = {
manifestBytes: fixture.manifestBytes,
signature,
files: fixture.provider,
emergencyFloor: extras.emergencyFloor ?? fixture.built.emergencyFloor,
...(extras.active === undefined ? {} : { active: extras.active }),
};
return verifyPackage(input, deps);
}
describe("Stage 7 package verifier", () => {
it("accepts a valid signed package and does not quarantine it", async () => {
const fixture = makeFixture();
const result = await verify(fixture);
expect(result.ok).toBe(true);
expect(fixture.records).toHaveLength(0);
});
it("checks signature, compatibility, files, then schema in order", async () => {
const fixture = makeFixture();
const gates: string[] = [];
const result = await verify(fixture, depsWithEvents(fixture, gates));
expect(result.ok).toBe(true);
expect(gates).toEqual(["signature", "compatibility", "files", "schema"]);
});
it("rejects bad signatures without retrieving any package file", async () => {
const fixture = makeFixture();
let gets = 0;
const result = await verify(
{
...fixture,
provider: {
getFile: async () => {
gets++;
return undefined;
},
},
},
fixture.deps,
{ ...fixture.signature, signature: "invalid" } as PackageSignature,
);
expect(result).toMatchObject({ ok: false, code: "signature_mismatch" });
expect(gets).toBe(0);
expect(fixture.records).toHaveLength(1);
});
it("rejects malformed signatures and unknown keys before file access", async () => {
const fixture = makeFixture();
let gets = 0;
const input = {
...fixture,
provider: {
getFile: async () => {
gets++;
return undefined;
},
},
};
const malformed = await verify(input, fixture.deps, {
...fixture.signature,
signature: "",
} as PackageSignature);
expect(malformed.ok).toBe(false);
const unknown = await verify(input, fixture.deps, {
...fixture.signature,
publicKeyFingerprint: "sha256:unknown",
} as PackageSignature);
expect(unknown).toMatchObject({ ok: false, code: "unknown_fingerprint" });
expect(gets).toBe(0);
});
it("rejects a manifest hash mismatch and wrong manifest bytes", async () => {
const fixture = makeFixture();
const badHash = await verify(fixture, fixture.deps, {
...fixture.signature,
manifestSha256: "0".repeat(64),
} as PackageSignature);
expect(badHash).toMatchObject({ ok: false, code: "manifest_sha_mismatch" });
const wrongBytes = {
...fixture,
manifestBytes: enc(canonicalJson({ ...fixture.built.manifest, packageVersion: 99 })),
};
const wrong = await verify(wrongBytes);
expect(wrong).toMatchObject({ ok: false, code: "manifest_sha_mismatch" });
});
it.each([
[
"app",
{ appCompatibility: { minAppVersion: "9.0.0", maxAppVersion: null } },
"incompatible_app",
],
["schema", { schemaVersion: 99 }, "incompatible_app"],
["version", { packageVersion: 0 }, "malformed_manifest"],
["budget", { limits: { totalBytes: 41 * 1024 * 1024 } }, "budget_exceeded"],
] as const)("rejects %s before file retrieval", async (_name, change, code) => {
const fixture = makeFixture();
let gets = 0;
const changed = withManifest(fixture, { ...fixture.built.manifest, ...change });
const result = await verify(
{
...changed,
provider: {
getFile: async () => {
gets++;
return undefined;
},
},
},
changed.deps,
changed.signature,
);
expect(result).toMatchObject({ ok: false, code });
expect(gets).toBe(0);
});
it("rejects replayed and cross-edition packages before files", async () => {
const fixture = makeFixture();
const replay = await verify(fixture, fixture.deps, fixture.signature, {
active: {
edition: fixture.built.manifest.edition,
packageVersion: fixture.built.manifest.packageVersion,
},
});
expect(replay).toMatchObject({ ok: false });
const newer = withManifest(fixture, { ...fixture.built.manifest, packageVersion: 8 });
expect(
await verify({ ...newer, provider: fixture.provider }, { ...newer.deps }, newer.signature, {
active: {
edition: fixture.built.manifest.edition,
packageVersion: fixture.built.manifest.packageVersion,
},
}),
).toMatchObject({ ok: true });
const other = withManifest(fixture, { ...fixture.built.manifest, edition: "other-2026" });
expect(
await verify({ ...other, provider: fixture.provider }, { ...other.deps }, other.signature, {
active: {
edition: fixture.built.manifest.edition,
packageVersion: fixture.built.manifest.packageVersion,
},
}),
).toMatchObject({ ok: false, code: "incompatible_edition" });
});
it("rejects missing, size-mismatched, hash-mismatched, and unexpected files", async () => {
const fixture = makeFixture();
const missing = new Map(fixture.files);
missing.delete("schedule.json");
expect(
await verify({ ...fixture, provider: { getFile: async (name) => missing.get(name) } }),
).toMatchObject({ ok: false, code: "missing_file" });
const wrongSize = new Map(fixture.files);
wrongSize.set("schedule.json", enc("wrong"));
expect(
await verify({ ...fixture, provider: { getFile: async (name) => wrongSize.get(name) } }),
).toMatchObject({ ok: false, code: "size_mismatch" });
const wrongHash = new Map(fixture.files);
wrongHash.set("schedule.json", new Uint8Array(fixture.files.get("schedule.json")!));
const wrongSchedule = wrongHash.get("schedule.json");
if (wrongSchedule) wrongSchedule[0] = (wrongSchedule[0] ?? 0) ^ 1;
expect(
await verify({ ...fixture, provider: { getFile: async (name) => wrongHash.get(name) } }),
).toMatchObject({ ok: false, code: "hash_mismatch" });
const extra = new Map(fixture.files);
extra.set("unexpected.bin", enc("x"));
expect(
await verify({
...fixture,
provider: { listFiles: () => [...extra.keys()], getFile: async (name) => extra.get(name) },
}),
).toMatchObject({ ok: false, code: "unexpected_file" });
});
it("rejects malformed sections and invalid emergency floor after file integrity", async () => {
const fixture = makeFixture();
const schedule = JSON.parse(new TextDecoder().decode(fixture.files.get("schedule.json")));
schedule.events[0].dayKey = "1900-01-01";
const scheduleWithBadDay = replaceSection(fixture, "schedule.json", schedule);
const result = await verify(scheduleWithBadDay);
expect(result).toMatchObject({ ok: false, code: "malformed_manifest" });
const floor = await verify(fixture, fixture.deps, fixture.signature, {
emergencyFloor: { floor: true },
});
expect(floor).toMatchObject({ ok: false, code: "malformed_manifest" });
});
it("validates stable IDs, map POIs, and emergency section schema after integrity", async () => {
const fixture = makeFixture();
const schedule = JSON.parse(new TextDecoder().decode(fixture.files.get("schedule.json")));
schedule.events[0].id = "bad id";
expect(await verify(replaceSection(fixture, "schedule.json", schedule))).toMatchObject({
ok: false,
code: "malformed_manifest",
});
const map = JSON.parse(new TextDecoder().decode(fixture.files.get("map.json")));
map.pois[0].x = 2;
expect(await verify(replaceSection(fixture, "map.json", map))).toMatchObject({
ok: false,
code: "malformed_manifest",
});
const emergency = JSON.parse(new TextDecoder().decode(fixture.files.get("emergency.json")));
delete emergency.procedures;
expect(await verify(replaceSection(fixture, "emergency.json", emergency))).toMatchObject({
ok: false,
code: "malformed_manifest",
});
});
it("enforces downloaded map dimensions and the per-file ceiling", async () => {
const fixture = makeFixture();
const map = JSON.parse(new TextDecoder().decode(fixture.files.get("map.json")));
map.base.levels[0].width = 1601;
expect(await verify(replaceSection(fixture, "map.json", map))).toMatchObject({
ok: false,
code: "budget_exceeded",
});
const oversized = withManifest(fixture, {
...fixture.built.manifest,
sections: {
...fixture.built.manifest.sections,
emergency: {
...fixture.built.manifest.sections.emergency,
bytes: 7 * 1024 * 1024,
},
},
});
expect(await verify(oversized, oversized.deps, oversized.signature)).toMatchObject({
ok: false,
code: "malformed_manifest",
});
});
it("accepts unknown forward-compatible section fields after integrity", async () => {
const fixture = makeFixture();
const schedule = JSON.parse(new TextDecoder().decode(fixture.files.get("schedule.json")));
schedule.futureField = { version: 2, optional: true };
expect(await verify(replaceSection(fixture, "schedule.json", schedule))).toMatchObject({
ok: true,
});
});
it("quarantines failures without invoking activation or changing active state", async () => {
const fixture = makeFixture();
const active = {
edition: fixture.built.manifest.edition,
packageVersion: fixture.built.manifest.packageVersion,
};
const before = { ...active };
const result = await verify(
fixture,
fixture.deps,
{ ...fixture.signature, signature: "bad" } as PackageSignature,
{ active },
);
expect(result.ok).toBe(false);
expect(active).toEqual(before);
expect(fixture.records[0]).toMatchObject({
code: "signature_mismatch",
edition: null,
packageVersion: null,
});
});
});