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

View file

@ -0,0 +1,200 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/** Stage 9 — pull-only transport and verifier boundary. */
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { buildPackage } from "../../pipeline/package.js";
import { makeValidInput } from "../../pipeline/fixtures.js";
import { canonicalJson } from "../../pipeline/canonical-json.js";
import { generateTestKeyPair } from "../../pipeline/sign.js";
import { publicKeyFromDerBase64 } from "../../src/sync/verifier/ed25519.js";
import { HttpTransport } from "../../src/sync/transport/http.js";
import { TransportError } from "../../src/sync/transport/types.js";
import { pullCandidate } from "../../src/sync/pull.js";
function response(body: string | Uint8Array, status = 200): Response {
const bytes = typeof body === "string" ? new TextEncoder().encode(body) : body;
return {
ok: status >= 200 && status < 300,
status,
arrayBuffer: () => Promise.resolve(bytes.buffer as ArrayBuffer),
} as Response;
}
function inputUrl(input: RequestInfo | URL): string {
if (input instanceof URL) return input.toString();
if (typeof input === "string") return input;
return input.url;
}
function pointer(edition = "lumen-2026") {
return JSON.stringify({
edition,
packageVersion: 1,
manifestUrl: `/editions/${edition}/packages/1/manifest.json`,
generatedAt: "2026-08-30T12:00:00.000Z",
});
}
describe("Stage 9 HttpTransport", () => {
let fetchImpl: ReturnType<typeof vi.fn>;
let transport: HttpTransport;
beforeEach(() => {
fetchImpl = vi.fn();
transport = new HttpTransport("https://festival.example/", { fetchImpl });
Object.defineProperty(navigator, "onLine", { configurable: true, value: true });
});
afterEach(() => {
vi.restoreAllMocks();
});
it("reports availability from the browser online hint without making a request", () => {
expect(transport.isAvailable()).toBe(true);
Object.defineProperty(navigator, "onLine", { configurable: true, value: false });
expect(transport.isAvailable()).toBe(false);
expect(fetchImpl).not.toHaveBeenCalled();
});
it("retrieves and validates the edition pointer at the static-origin URL", async () => {
fetchImpl.mockResolvedValue(response(pointer()));
await expect(transport.fetchPointer("lumen-2026")).resolves.toMatchObject({
edition: "lumen-2026",
packageVersion: 1,
});
expect(inputUrl(fetchImpl.mock.calls[0]?.[0] as RequestInfo | URL)).toBe(
"https://festival.example/editions/lumen-2026/latest.json",
);
expect(fetchImpl.mock.calls[0]?.[1]).toEqual(
expect.objectContaining({ signal: expect.anything() }),
);
});
it("encodes pointer path segments and rejects cross-origin paths", async () => {
fetchImpl.mockResolvedValue(response(pointer("lumen/2026")));
await expect(transport.fetchPointer("lumen/2026")).resolves.toMatchObject({
edition: "lumen/2026",
});
expect(inputUrl(fetchImpl.mock.calls[0]?.[0] as RequestInfo | URL)).toBe(
"https://festival.example/editions/lumen%2F2026/latest.json",
);
await expect(transport.fetchBytes("https://other.example/file")).rejects.toMatchObject({
code: "malformed_response",
});
});
it.each([
[404, "missing_resource"],
[500, "http_error"],
] as const)("maps HTTP %s to %s", async (status, code) => {
fetchImpl.mockResolvedValue(response("failure", status));
await expect(transport.fetchBytes("/file")).rejects.toMatchObject({ code, status });
});
it("maps network failures, malformed pointers, timeout, and caller abort", async () => {
fetchImpl.mockRejectedValue(new TypeError("offline"));
await expect(transport.fetchBytes("/file")).rejects.toMatchObject({
code: "network_unavailable",
});
fetchImpl.mockResolvedValue(response("{}"));
await expect(transport.fetchPointer("lumen-2026")).rejects.toMatchObject({
code: "malformed_response",
});
fetchImpl.mockImplementation(
() =>
new Promise<Response>((_resolve, reject) => {
setTimeout(() => {
reject(new Error("request interrupted"));
}, 20);
}),
);
await expect(transport.fetchBytes("/slow", { timeoutMs: 1 })).rejects.toMatchObject({
code: "timeout",
});
const controller = new AbortController();
controller.abort();
await expect(transport.fetchBytes("/aborted", { signal: controller.signal })).rejects.toEqual(
expect.objectContaining({ code: "aborted" }),
);
});
});
describe("Stage 9 pull and verifier boundary", () => {
it("fetches pointer, manifest, and signature before protected files", async () => {
const keys = generateTestKeyPair();
const built = buildPackage(makeValidInput(), { signWith: keys });
if (!built.ok || !built.pkg.signature) throw new Error("fixture build failed");
const manifestBytes = new TextEncoder().encode(canonicalJson(built.pkg.manifest));
const calls: string[] = [];
const files = new Map<string, Uint8Array>();
files.set("/editions/lumen-2026/packages/1/manifest.json", manifestBytes);
files.set(
"/editions/lumen-2026/packages/1/signature.json",
new TextEncoder().encode(JSON.stringify(built.pkg.signature)),
);
for (const [name, file] of built.pkg.files)
files.set(`/editions/lumen-2026/packages/1/${name}`, file.canonicalBytes);
for (const asset of built.pkg.assets)
files.set(`/editions/lumen-2026/packages/1/${asset.file}`, asset.bytesContent);
const fetchImpl = vi.fn((input: RequestInfo | URL) => {
const url = inputUrl(input);
calls.push(url);
if (url.endsWith("/latest.json")) return Promise.resolve(response(pointer()));
const body = files.get(new URL(url).pathname);
return Promise.resolve(body ? response(body) : response("missing", 404));
});
const result = await pullCandidate(
new HttpTransport("https://festival.example/", { fetchImpl }),
"lumen-2026",
{
trustedKeys: new Map([[keys.fingerprint, publicKeyFromDerBase64(keys.publicKeyDerBase64)]]),
appVersion: "1.0.0",
supportedSchemaRange: [1],
},
);
expect(result?.result.ok).toBe(true);
expect(calls[0]).toMatch(/latest\.json$/);
expect(calls[1]).toMatch(/manifest\.json$/);
expect(calls[2]).toMatch(/signature\.json$/);
expect(
calls.indexOf("https://festival.example/editions/lumen-2026/packages/1/emergency.json"),
).toBeGreaterThan(2);
});
it("does not retrieve protected files when the signature gate fails", async () => {
const keys = generateTestKeyPair();
const built = buildPackage(makeValidInput(), { signWith: keys });
if (!built.ok || !built.pkg.signature) throw new Error("fixture build failed");
const manifestBytes = new TextEncoder().encode(canonicalJson(built.pkg.manifest));
const calls: string[] = [];
const fetchImpl = vi.fn((input: RequestInfo | URL) => {
const url = inputUrl(input);
calls.push(url);
if (url.endsWith("/latest.json")) return Promise.resolve(response(pointer()));
if (url.endsWith("/manifest.json")) return Promise.resolve(response(manifestBytes));
return Promise.resolve(
response(JSON.stringify({ ...built.pkg.signature, signature: "bad" })),
);
});
const result = await pullCandidate(
new HttpTransport("https://festival.example/", { fetchImpl }),
"lumen-2026",
{
trustedKeys: new Map([[keys.fingerprint, publicKeyFromDerBase64(keys.publicKeyDerBase64)]]),
appVersion: "1.0.0",
supportedSchemaRange: [1],
},
);
expect(result?.result).toMatchObject({ ok: false, code: "signature_mismatch" });
expect(calls).toHaveLength(3);
expect(calls.some((url) => /emergency|schedule|map|info|assets\.json/.test(url))).toBe(false);
});
it("has no activation, auth, push, background-sync, mesh, or IndexedDB dependency", async () => {
const source = await import("../../src/sync/pull.js");
expect(source).not.toHaveProperty("activateStagedPackage");
expect(TransportError).toBeDefined();
});
});