From bac8a115cdd5c6c3e2e6edb9b220d306b1f0fa63 Mon Sep 17 00:00:00 2001 From: x-labb <154824493+x-labb@users.noreply.github.com> Date: Mon, 26 Oct 2026 23:59:36 +0100 Subject: [PATCH] fix(cli): report the byte-entropy provider id in cleanup diagnostics inspect-byte-entropy passed a hard-coded "rea-byte-entropy" to the shared command finalizer, so a failed descriptor close named a provider that differs from the Evidence provider ("byte-entropy"). Use BYTE_ENTROPY_PROVIDER.id so both come from one owner. Fixes #7279 Co-Authored-By: gensokyo-dev Co-Authored-By: Claude Opus 5.5 --- src/cli/binaryDiagnosticsCommands.ts | 3 +- .../byteEntropyCleanupCli.test.ts | 103 ++++++++++++++++++ 2 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 tests/process-global/byteEntropyCleanupCli.test.ts diff --git a/src/cli/binaryDiagnosticsCommands.ts b/src/cli/binaryDiagnosticsCommands.ts index 18657da2a3..0d55fdb1ae 100644 --- a/src/cli/binaryDiagnosticsCommands.ts +++ b/src/cli/binaryDiagnosticsCommands.ts @@ -48,6 +48,7 @@ import { inspectKernelBuildConfigInputSchema } from "../domain/native/kernelBuil import { projectAnalysisError } from "../domain/analysisErrorProjection.js"; import { finishCliEvidenceCommand } from "./cliEvidenceCallerPath.js"; import { withEvidenceCallerInputPath } from "../domain/evidence.js"; +import { BYTE_ENTROPY_PROVIDER } from "../domain/investigationProviders.js"; import { localPathStringSchema } from "../domain/localPath.js"; import { absoluteCallerPath } from "../filesystem/callerPath.js"; import { @@ -476,7 +477,7 @@ export const registerBinaryDiagnosticsCommands = ( { signal }, ), entropy, - "rea-byte-entropy", + BYTE_ENTROPY_PROVIDER.id, "inspect_byte_entropy", signal, ).then(projectCliAnalysisResult); diff --git a/tests/process-global/byteEntropyCleanupCli.test.ts b/tests/process-global/byteEntropyCleanupCli.test.ts new file mode 100644 index 0000000000..2ba5d69ff2 --- /dev/null +++ b/tests/process-global/byteEntropyCleanupCli.test.ts @@ -0,0 +1,103 @@ +import { writeFile } from "node:fs/promises"; +import { join } from "node:path"; +import { afterEach, expect, it, vi } from "vitest"; + +const closeState = vi.hoisted(() => ({ + path: "", + failures: 0, + attempts: 0, + release: [] as Array<() => Promise>, +})); + +vi.mock("node:fs/promises", async (importOriginal) => { + const fs = await importOriginal(); + return { + ...fs, + open: async (...args: Parameters) => { + const handle = await fs.open(...args); + if (args[0] !== closeState.path) return handle; + const close = handle.close.bind(handle); + closeState.release.push(close); + handle.close = async () => { + closeState.attempts++; + if (closeState.failures > 0) { + closeState.failures--; + throw Object.assign(new Error("injected descriptor close failure"), { + code: "EIO", + }); + } + await close(); + }; + return handle; + }, + }; +}); + +import { createCli } from "../../src/cli.js"; +import { CLI_COMMANDS } from "../../src/cliCommandNames.js"; +import { BYTE_ENTROPY_PROVIDER } from "../../src/domain/investigationProviders.js"; +import { createTestTempDirectory } from "../fixtures/temporaryDirectory.js"; + +const exitCode = process.exitCode; +afterEach(async () => { + closeState.path = ""; + closeState.failures = 0; + closeState.attempts = 0; + process.exitCode = exitCode; + // Injected failures leave the real descriptor open; release it before the + // fixture directory is removed. + await Promise.all(closeState.release.splice(0).map((close) => close())); +}); + +const inspect = async (path: string): Promise => { + let stdout = ""; + await createCli({}).serve( + [CLI_COMMANDS.inspectByteEntropy, path, "--format", "json"], + { + env: {}, + stdout: (text) => { + stdout += text; + }, + exit: () => undefined, + }, + ); + return JSON.parse(stdout); +}; + +it.each([ + { closeFails: true, attempts: 2 }, + { closeFails: false, attempts: 1 }, +])( + "names the Evidence provider in cleanup diagnostics (close fails: $closeFails)", + async ({ closeFails, attempts }) => { + const directory = await createTestTempDirectory("rea-entropy-cleanup-cli-"); + const path = join(directory, "entropy-input.bin"); + await writeFile(path, "entropy fixture\n".repeat(32)); + closeState.path = path; + closeState.failures = closeFails ? Number.MAX_SAFE_INTEGER : 0; + + const output = await inspect(path); + + expect(closeState.attempts).toBe(attempts); + if (!closeFails) { + expect(output).toMatchObject({ + provider: { id: BYTE_ENTROPY_PROVIDER.id }, + }); + expect(output).not.toHaveProperty("code"); + return; + } + expect(output).toMatchObject({ + code: "cleanup_incomplete", + details: { + cleanup: "incomplete", + diagnostics: { + cleanup_error: { + code: "cleanup_incomplete", + details: { provider_id: BYTE_ENTROPY_PROVIDER.id }, + }, + }, + }, + }); + expect(process.exitCode).toBe(1); + }, +);