Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions src/lib/advisories/presenter.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { describe, expect, it } from "vitest";

import {
assertNoBlocking,
BlockingAdvisoryError,
blockingAdvisories,
formatAdvisories,
} from "./presenter";
import type { Advisory } from "./types";

const warning: Advisory = {
id: "runtime_resources",
severity: "warning",
phase: "preflight.host",
title: "Runtime resources are low",
reason: "The sandbox build may stall.",
commands: ["colima start --cpu 4 --memory 8"],
docsUrl: "https://docs.example.test/runtime-resources",
resumeSafe: false,
kind: "manual",
};

const fatal: Advisory = {
id: "docker_unreachable",
severity: "fatal",
phase: "preflight.host",
title: "Docker is unreachable",
reason: "The daemon did not answer.",
resumeSafe: false,
};

describe("advisory presenter", () => {
it("formats deterministic console and JSON representations", () => {
expect(formatAdvisories([warning], "console")).toBe(
[
"[WARNING] Runtime resources are low (runtime_resources)",
" The sandbox build may stall.",
" Run: colima start --cpu 4 --memory 8",
" More: https://docs.example.test/runtime-resources",
].join("\n"),
);
expect(JSON.parse(formatAdvisories([warning], "json"))).toEqual([warning]);
});

it("classifies only fatal and blocking severities as blockers", () => {
expect(blockingAdvisories([warning, fatal])).toEqual([fatal]);
expect(() => assertNoBlocking([warning])).not.toThrow();
});

it("raises one structured error for all blockers", () => {
expect(() => assertNoBlocking([warning, fatal])).toThrow(BlockingAdvisoryError);
try {
assertNoBlocking([fatal]);
} catch (error) {
expect(error).toBeInstanceOf(BlockingAdvisoryError);
expect((error as BlockingAdvisoryError).advisories).toEqual([fatal]);
}
});
});
47 changes: 47 additions & 0 deletions src/lib/advisories/presenter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import type { Advisory } from "./types";

/** Supported pure rendering targets. */
export type AdvisoryFormat = "console" | "json";

/** Error raised when a caller attempts to continue past blocking advisories. */
export class BlockingAdvisoryError extends Error {
readonly advisories: readonly Advisory[];

constructor(advisories: readonly Advisory[]) {
super(`Blocked by ${advisories.length} advisory finding${advisories.length === 1 ? "" : "s"}.`);
this.name = "BlockingAdvisoryError";
this.advisories = advisories;
}
}

function formatConsoleAdvisory(advisory: Advisory): string {
const lines = [
`[${advisory.severity.toUpperCase()}] ${advisory.title} (${advisory.id})`,
` ${advisory.reason}`,
];
for (const command of advisory.commands ?? []) lines.push(` Run: ${command}`);
if (advisory.docsUrl) lines.push(` More: ${advisory.docsUrl}`);
return lines.join("\n");
}

/** Formats advisories without writing to stdout, stderr, or process state. */
export function formatAdvisories(advisories: readonly Advisory[], format: AdvisoryFormat): string {
if (format === "json") return JSON.stringify(advisories, null, 2);
return advisories.map(formatConsoleAdvisory).join("\n\n");
}

/** Returns the findings that prohibit the caller from continuing. */
export function blockingAdvisories(advisories: readonly Advisory[]): readonly Advisory[] {
return advisories.filter(
(advisory) => advisory.severity === "fatal" || advisory.severity === "blocking",
);
}

/** Throws one structured error when any fatal or blocking finding is present. */
export function assertNoBlocking(advisories: readonly Advisory[]): void {
const blocking = blockingAdvisories(advisories);
if (blocking.length > 0) throw new BlockingAdvisoryError(blocking);
}
33 changes: 33 additions & 0 deletions src/lib/advisories/registry.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { describe, expect, it } from "vitest";

import { ADVISORY_CHECKS, defineAdvisoryRegistry } from "./registry";
import type { AdvisoryCheck } from "./types";

function check(id: string): AdvisoryCheck<unknown> {
return {
id,
phase: "preflight.host",
severity: "info",
resumeSafe: true,
check: () => null,
};
}

describe("defineAdvisoryRegistry", () => {
it("preserves explicit order in an immutable registry", () => {
const registry = defineAdvisoryRegistry([check("first"), check("second")]);

expect(registry.map((entry) => entry.id)).toEqual(["first", "second"]);
expect(Object.isFrozen(registry)).toBe(true);
expect(ADVISORY_CHECKS).toEqual([]);
});

it("rejects duplicate stable IDs", () => {
expect(() => defineAdvisoryRegistry([check("duplicate"), check("duplicate")])).toThrow(
"Duplicate advisory check id 'duplicate'.",
);
});
});
19 changes: 19 additions & 0 deletions src/lib/advisories/registry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import type { AdvisoryCheck } from "./types";

/** Creates an explicit, immutable advisory registry with globally unique IDs. */
export function defineAdvisoryRegistry<Context>(
checks: readonly AdvisoryCheck<Context>[],
): readonly AdvisoryCheck<Context>[] {
const ids = new Set<string>();
for (const check of checks) {
if (ids.has(check.id)) throw new Error(`Duplicate advisory check id '${check.id}'.`);
ids.add(check.id);
}
return Object.freeze([...checks]);
}

/** Checks are imported and registered explicitly as migration slices land (#3213). */
export const ADVISORY_CHECKS = defineAdvisoryRegistry<unknown>([]);
129 changes: 129 additions & 0 deletions src/lib/advisories/runner.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import { describe, expect, it, vi } from "vitest";

import { runAdvisories } from "./runner";
import type { Advisory, AdvisoryCheck } from "./types";

function advisory(id: string, overrides: Partial<Advisory> = {}): Advisory {
return {
id,
severity: "warning",
phase: "preflight.host",
title: `Finding ${id}`,
reason: `Reason ${id}`,
resumeSafe: true,
...overrides,
};
}

function check(
id: string,
run: AdvisoryCheck<{ enabled: boolean }>["check"],
overrides: Partial<AdvisoryCheck<{ enabled: boolean }>> = {},
): AdvisoryCheck<{ enabled: boolean }> {
return {
id,
severity: "warning",
phase: "preflight.host",
resumeSafe: true,
check: run,
...overrides,
};
}

describe("runAdvisories", () => {
it("filters phases and skipped checks without evaluating them", () => {
const hostRun = vi.fn(() => advisory("host"));
const networkRun = vi.fn(() => advisory("network", { phase: "preflight.network" }));
const skippedRun = vi.fn(() => advisory("skipped"));

const result = runAdvisories(
[
check("host", hostRun),
check("network", networkRun, { phase: "preflight.network" }),
check("skipped", skippedRun, { skipIf: (context) => !context.enabled }),
],
{ enabled: false },
{ phase: "preflight.host" },
);

expect(result.advisories.map((item) => item.id)).toEqual(["host"]);
expect(result.executedCheckIds).toEqual(["host"]);
expect(result.results.get("skipped")).toBeNull();
expect(hostRun).toHaveBeenCalledOnce();
expect(networkRun).not.toHaveBeenCalled();
expect(skippedRun).not.toHaveBeenCalled();
});

it("reuses only resume-safe cached results", () => {
const safeRun = vi.fn(() => advisory("safe"));
const unsafeRun = vi.fn(() => advisory("unsafe", { resumeSafe: false }));
const cachedSafe = advisory("safe", { reason: "cached" });
const cachedUnsafe = advisory("unsafe", { reason: "stale", resumeSafe: false });

const result = runAdvisories(
[check("safe", safeRun), check("unsafe", unsafeRun, { resumeSafe: false })],
{ enabled: true },
{
resuming: true,
cachedResults: new Map([
["safe", cachedSafe],
["unsafe", cachedUnsafe],
]),
},
);

expect(result.reusedCheckIds).toEqual(["safe"]);
expect(result.executedCheckIds).toEqual(["unsafe"]);
expect(result.results.get("safe")).toBe(cachedSafe);
expect(result.results.get("unsafe")?.reason).toBe("Reason unsafe");
expect(safeRun).not.toHaveBeenCalled();
expect(unsafeRun).toHaveBeenCalledOnce();
});

it("suppresses presentation without discarding the evaluated result", () => {
const result = runAdvisories(
[check("hidden", () => advisory("hidden"))],
{ enabled: true },
{ suppressed: ["hidden"] },
);

expect(result.advisories).toEqual([]);
expect(result.results.get("hidden")?.id).toBe("hidden");
});

it("does not suppress fatal or blocking findings", () => {
const result = runAdvisories(
[
check("fatal", () => advisory("fatal", { severity: "fatal" }), {
severity: "fatal",
}),
check("blocking", () => advisory("blocking", { severity: "blocking" }), {
severity: "blocking",
}),
],
{ enabled: true },
{ suppressed: ["fatal", "blocking"] },
);

expect(result.advisories.map((item) => item.id)).toEqual(["fatal", "blocking"]);
});

it("rejects duplicate IDs before a second check can overwrite the first", () => {
expect(() =>
runAdvisories([check("duplicate", () => null), check("duplicate", () => null)], {
enabled: true,
}),
).toThrow("Duplicate advisory check id 'duplicate'.");
});

it("rejects advisory metadata that diverges from its check", () => {
expect(() =>
runAdvisories([check("stable-id", () => advisory("different-id", { severity: "fatal" }))], {
enabled: true,
}),
).toThrow("Advisory check 'stable-id' returned mismatched metadata");
});
});
102 changes: 102 additions & 0 deletions src/lib/advisories/runner.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

import type { Advisory, AdvisoryCheck, AdvisoryPhase } from "./types";

/** Selection, resume, cache, and presentation controls for one run. */
export interface RunAdvisoriesOptions {
phase?: AdvisoryPhase | readonly AdvisoryPhase[];
resuming?: boolean;
suppressed?: Iterable<string>;
cachedResults?: ReadonlyMap<string, Advisory | null>;
}

/** Structured output used to persist safe results across resume boundaries. */
export interface AdvisoryRunResult {
advisories: readonly Advisory[];
results: ReadonlyMap<string, Advisory | null>;
executedCheckIds: readonly string[];
reusedCheckIds: readonly string[];
}

function canSuppress(advisory: Advisory): boolean {
return advisory.severity !== "fatal" && advisory.severity !== "blocking";
}

function phaseSet(phase: RunAdvisoriesOptions["phase"]): ReadonlySet<AdvisoryPhase> | null {
if (!phase) return null;
return new Set(Array.isArray(phase) ? phase : [phase]);
}

function assertAdvisoryMatchesCheck<Context>(
check: AdvisoryCheck<Context>,
advisory: Advisory,
): void {
const mismatches: string[] = [];
if (advisory.id !== check.id) mismatches.push(`id '${advisory.id}'`);
if (advisory.phase !== check.phase) mismatches.push(`phase '${advisory.phase}'`);
if (advisory.severity !== check.severity) mismatches.push(`severity '${advisory.severity}'`);
if (advisory.resumeSafe !== check.resumeSafe) {
mismatches.push(`resumeSafe '${String(advisory.resumeSafe)}'`);
}
if (mismatches.length > 0) {
throw new Error(
`Advisory check '${check.id}' returned mismatched metadata: ${mismatches.join(", ")}.`,
);
}
}

/**
* Runs checks in registry order. During resume, cached results are reused only
* for checks that explicitly declare their prior verdict safe to reuse.
*/
export function runAdvisories<Context>(
checks: readonly AdvisoryCheck<Context>[],
context: Context,
options: RunAdvisoriesOptions = {},
): AdvisoryRunResult {
const phases = phaseSet(options.phase);
const suppressed = new Set(options.suppressed ?? []);
const results = new Map<string, Advisory | null>();
const advisories: Advisory[] = [];
const executedCheckIds: string[] = [];
const reusedCheckIds: string[] = [];
const seenIds = new Set<string>();

for (const check of checks) {
if (seenIds.has(check.id)) {
throw new Error(`Duplicate advisory check id '${check.id}'.`);
}
seenIds.add(check.id);

if (phases && !phases.has(check.phase)) continue;
if (check.skipIf?.(context)) {
results.set(check.id, null);
continue;
}

const canReuse =
options.resuming === true &&
check.resumeSafe &&
options.cachedResults?.has(check.id) === true;
const advisory = canReuse
? (options.cachedResults?.get(check.id) ?? null)
: check.check(context);

if (canReuse) reusedCheckIds.push(check.id);
else executedCheckIds.push(check.id);

if (advisory) assertAdvisoryMatchesCheck(check, advisory);
results.set(check.id, advisory);
if (advisory && (!suppressed.has(advisory.id) || !canSuppress(advisory))) {
advisories.push(advisory);
}
}

return {
advisories: Object.freeze(advisories),
results,
executedCheckIds: Object.freeze(executedCheckIds),
reusedCheckIds: Object.freeze(reusedCheckIds),
};
}
Loading