Files
callstack__agent-device/scripts/fuzz/report.ts
devin-ai-integration[bot] 006c4cadc9 test: nightly parser fuzz lane — parser input fails as typed AppErrors, never hangs (#1414) (#1438)
* test: nightly parser fuzz lane with typed-AppError invariant (#1414)

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(fuzz): run envelope, artifact promotion, and harness self-check tests (#1414)

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(fuzz): shared scheduled-lane envelope on every terminal path, watchdog after ready (#1414)

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(fuzz): envelope for malformed options; add scheduled-lane health consumer (#1414, #1430)

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(lanes): actions:read scope, terminal error envelope, first-due grace (#1414, #1430)

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(lanes): anchor first-run grace to schedule registration, use exec helper in tests (#1414, #1430)

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(lanes): portable POSIX pickaxe pattern for schedule registration (#1414, #1430)

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* refactor(fuzz): fast-check generators over the shared hazard list, drop the bundled lane-health work (#1414)

- Strip scripts/scheduled-lane/* and scheduled-lane-health.yml: that watcher is #1430's own
  deliverable and collides with PR #1439's implementation of the same lane. What this lane owes
  (a per-run envelope) moves into scripts/fuzz/envelope.ts.
- Rebase onto #1437 and rebuild the generator layer on fast-check: cases come from arbitraries
  sharing SELECTOR_VALUE_HAZARDS with the property suite, and counterexamples are shrunk, so a
  failure names a minimal input plus fast-check's seed/path instead of a 20k-char random string.
- Route harness.test.ts into the serialized subprocess-stub project.
- Drop the AGENT_DEVICE_FUZZ_STARTUP_DELAY_MS test seam: the ready handshake is now proven by a
  case budget far below real worker startup.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(fuzz): replay the regression corpus through the worker watchdog (#1414)

A promoted hang case used to wedge the unit job until the CI timeout, because corpus replay called
checkCase in-process. It now goes through the same worker-backed watchdog the nightly lane uses, so
such a case fails against a 5s per-case budget; the file moves to the serialized subprocess-stub
project with the rest of the worker-driven fuzz tests.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(fuzz): let the watchdog outlive vitest's default case timeout (#1414)

A wedged parser was surfacing as a bare 'Test timed out in 5000ms' instead of the named hang:
failure that says which input wedged, because the file's vitest timeout was shorter than the
watchdog budget times the number of replayed cases.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(fuzz): complete drift provenance in the lane envelope (#1414)

configHash now covers every input that decides what a seed generates (generate.ts and the shared property arbitraries, not just the arbitraries/targets/invariant), and tool records fast-check's installed version. A generation-loop edit or a fast-check upgrade previously changed the case set while the envelope looked unchanged. A test recomputes the hash with each input omitted so a future omission fails.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: Michał Pierzchała <thymikee@gmail.com>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-07-28 11:29:25 +02:00

62 lines
2.5 KiB
TypeScript

// Failure reporting for the parser fuzz lane (#1414).
//
// A nightly failure has to be actionable from the run log alone, so every failing case is
// written as an artifact and printed with two copy-pasteable commands: one that reproduces it
// and one that promotes it into the checked-in corpus the unit lane replays.
import fs from 'node:fs';
import path from 'node:path';
import { appendToCorpus, type CorpusEntry } from './corpus.ts';
import { describeFailure, type FuzzFailure } from './invariant.ts';
export type ReportedFailure = FuzzFailure & { artifact?: string };
function writeArtifact(artifactDir: string, failure: FuzzFailure, ordinal: number): string {
fs.mkdirSync(artifactDir, { recursive: true });
const file = path.join(artifactDir, `${failure.target}-${failure.kind}-${ordinal}.json`);
fs.writeFileSync(file, `${JSON.stringify(failure, null, 2)}\n`);
return file;
}
function corpusEntryFor(failure: FuzzFailure): CorpusEntry {
return {
target: failure.target,
input: failure.input,
note: `${failure.kind}: ${failure.detail}`,
};
}
/** Writes an artifact per failure and prints repro + promote commands. */
export function reportFailures(
failures: readonly FuzzFailure[],
artifactDir: string,
): { reported: ReportedFailure[]; reproCommands: string[] } {
if (failures.length === 0) return { reported: [], reproCommands: [] };
process.stdout.write(`\n${failures.length} invariant violation(s):\n`);
const reported: ReportedFailure[] = [];
const reproCommands: string[] = [];
failures.forEach((failure, ordinal) => {
const artifact = writeArtifact(artifactDir, failure, ordinal);
const repro = `pnpm fuzz:parsers --input-file ${artifact}`;
reproCommands.push(repro);
reported.push({ ...failure, artifact });
process.stdout.write(`\n${describeFailure(failure)}\n`);
process.stdout.write(` input: ${JSON.stringify(failure.input)}\n`);
process.stdout.write(` repro: ${repro}\n`);
process.stdout.write(` promote: ${repro} --append-corpus\n`);
});
return { reported, reproCommands };
}
/** Appends failing cases to the checked-in corpus, printing what was added. */
export function promoteFailures(failures: readonly FuzzFailure[]): CorpusEntry[] {
if (failures.length === 0) return [];
const added = appendToCorpus(failures.map(corpusEntryFor));
process.stdout.write(
added.length === 0
? '\nRegression corpus already contains every failing case.\n'
: `\nAppended ${added.length} case(s) to the regression corpus.\n`,
);
return added;
}