diff --git a/docs/cli.md b/docs/cli.md index ce1cc38fb9..7d7f2823bb 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -985,10 +985,10 @@ Cutter when available. This is separate from `--format json`, which selects the CLI result representation; the global `--json` flag selects that same CLI representation. -Cutter still accepts deprecated `--json=true` and `--json=false` compatibility -values. When both forms are present, `--json-output` takes precedence in either -argument order. Bare `--json` retains its global meaning of selecting JSON CLI -output formatting. +Cutter no longer accepts command-local `--json=true` or `--json=false`. +Replace these with `--json-output=true` or `--json-output=false`, respectively. +Combine `--json-output` with bare `--json` when both Cutter command output and +the CLI result should use JSON; either argument order is accepted. | Status | Meaning | | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | diff --git a/package.json b/package.json index d5e3f0e1e5..c5d35afb1e 100644 --- a/package.json +++ b/package.json @@ -129,7 +129,6 @@ "check:ci:fast:tasks": "node scripts/run-turbo.mjs run typecheck lint:built format:check", "check:ci:deep": "node scripts/run-exclusive.mjs artifacts npm run check:ci:deep:tasks && npm run verify:architecture-guards && npm run verify:test-discovery && npm run test:python && npm run knip:production && npm run lint:typed", "check:ci:deep:tasks": "node scripts/run-turbo.mjs run knip metadata:check jscpd scan:todos", - "check:ci:tasks": "node scripts/run-turbo.mjs run typecheck lint:built format:check knip metadata:check jscpd scan:todos", "ci:static-context": "node scripts/ci/static-task-context.mjs", "build:cached": "node scripts/check-dependency-install.mjs && node scripts/run-exclusive.mjs artifacts node scripts/run-turbo.mjs run artifacts:skills", "build:termux": "node scripts/run-exclusive.mjs artifacts npm run build:termux:run", diff --git a/src/application/LinuxHopper.test.ts b/src/application/LinuxHopper.test.ts index b9a1f80624..fec5529dbb 100644 --- a/src/application/LinuxHopper.test.ts +++ b/src/application/LinuxHopper.test.ts @@ -13,12 +13,12 @@ import { LinuxHopperTemporaryCleanupOwner, systemLinuxHopperInstallHost, type LinuxDistribution, - type LinuxHopperDownload, type LinuxHopperInstallHost, type LinuxHopperLauncherStatus, type LinuxPackageInstallFailure, type LinuxPackageInstallOutcome, } from "./LinuxHopper.js"; +import type { HopperDownload } from "./HopperDownload.js"; import { err, ok } from "../domain/result.js"; import { type LinuxPackageFamily, @@ -74,7 +74,7 @@ class RecordingLinuxHost implements LinuxHopperInstallHost { Promise.resolve(this.distributionValue); release = (family: LinuxPackageFamily) => Promise.resolve(ok(releasesFor(new Uint8Array([1, 2, 3]))[family])); - download = (url: string): Promise => { + download = (url: string): Promise => { this.downloads.push(url); return Promise.resolve({ ok: this.archiveOk, bytes: this.archive }); }; diff --git a/src/application/LinuxHopper.ts b/src/application/LinuxHopper.ts index dc7e953b43..7df46cff03 100644 --- a/src/application/LinuxHopper.ts +++ b/src/application/LinuxHopper.ts @@ -119,9 +119,6 @@ export const supportsReaHost = ( distribution: LinuxDistribution | undefined, ): boolean => distribution?.packageFamily !== undefined; -/** Download response kept independent of Node's global fetch implementation. */ -export type LinuxHopperDownload = HopperDownload; - /** One failed package-manager command, with the output that explains it. */ export interface LinuxPackageInstallFailure { /** The executed executable and arguments, joined for display. */ @@ -159,7 +156,7 @@ export interface LinuxHopperInstallHost { download( url: string, options: { readonly signal?: AbortSignal }, - ): Promise; + ): Promise; createTemporaryDirectory(): Promise; writeArchive(path: string, bytes: Uint8Array): Promise; installPackage( diff --git a/src/application/environment/environmentBuild.ts b/src/application/environment/environmentBuild.ts index 65a37b89cf..097e7663f2 100644 --- a/src/application/environment/environmentBuild.ts +++ b/src/application/environment/environmentBuild.ts @@ -107,9 +107,6 @@ export interface EnvironmentBuildInput { readonly exclusiveDestination?: boolean | undefined; } -/** The report environment-build prints and export_environment_bundle returns. */ -export type { EnvironmentBuildReport }; - const captureOperations = new Set([ "inspect_web_network_capture", "capture_browser_scenario", diff --git a/src/cli/cutterCommands.ts b/src/cli/cutterCommands.ts index 68b818122e..b80461b145 100644 --- a/src/cli/cutterCommands.ts +++ b/src/cli/cutterCommands.ts @@ -60,12 +60,6 @@ export const registerCutterCommands = ( .describe( "Return command-specific JSON output when available (independent of CLI output formatting).", ), - json: z - .boolean() - .optional() - .describe( - "Deprecated compatibility input: use --json-output; pass --json=true or --json=false (bare --json formats CLI output).", - ), }), run: ({ args, options }) => withCommandCancellation((signal) => @@ -78,7 +72,7 @@ export const registerCutterCommands = ( session_id: args.sessionId, expected_generation: args.expectedGeneration, command: args.command, - json: options.jsonOutput ?? options.json ?? false, + json: options.jsonOutput ?? false, }, { signal }, ), diff --git a/src/contracts/native/binaryDiagnosticsToolContracts.ts b/src/contracts/native/binaryDiagnosticsToolContracts.ts index 3dbb820bad..eba10d04fa 100644 --- a/src/contracts/native/binaryDiagnosticsToolContracts.ts +++ b/src/contracts/native/binaryDiagnosticsToolContracts.ts @@ -12,7 +12,6 @@ import { peLoadConfigSchema, } from "../../domain/native/peLoadConfig.js"; import { inspectByteEntropyInputSchema } from "../../domain/native/byteEntropy.js"; -import { publicInputStrictObject } from "../../domain/publicInput.js"; import { byteEntropyOutputSchema } from "../toolOutputSchemaGroups.js"; import { inspectRecordedCrashInputSchema, @@ -65,13 +64,9 @@ export const inspectBinaryLayoutRequestSchema = export const inspectNativeExportsRequestSchema = inspectNativeExportsInputSchema.extend({ detail: analysisDetailSchema }); -/** - * Public `inspect_byte_entropy` input. The shared constructor runs before - * `detail` is added so the advertised input stays closed. - */ -export const inspectByteEntropyRequestSchema = publicInputStrictObject( - inspectByteEntropyInputSchema.shape, -).extend({ detail: analysisDetailSchema }); +/** Public byte-entropy input retains the producer schema's closed-object policy. */ +export const inspectByteEntropyRequestSchema = + inspectByteEntropyInputSchema.extend({ detail: analysisDetailSchema }); /** Offline binary diagnostics are independent of macOS process/UI providers. */ export const BINARY_DIAGNOSTICS_TOOL_CONTRACTS = [ diff --git a/src/domain/analysisView/analysisView.ts b/src/domain/analysisView/analysisView.ts index e20f3d3b90..a8e8420231 100644 --- a/src/domain/analysisView/analysisView.ts +++ b/src/domain/analysisView/analysisView.ts @@ -1,3 +1,8 @@ +import { + javaScriptRouteInventorySourceSchema, + javaScriptRouteInventorySchema, +} from "../javascript/javascriptRouteInventory.js"; +import { nativeFunctionMatchingContextSchema } from "../native/functionMatching.js"; import { nativeObservationViewRequestSchema, nativeContextViewRequestSchema, @@ -58,7 +63,6 @@ import { javaScriptRoutesViewRequestSchema, javaScriptRoutesViewItemsSchema, javaScriptRoutesViewSummarySchema, - javaScriptRoutesSourceReferenceSchema, projectJavaScriptRoutesView, } from "./javascriptRoutesView.js"; import { @@ -69,7 +73,6 @@ import { } from "./webBundleView.js"; import { webBundleAnalysisSchema } from "../webBundleAnalysis.js"; import { recordedBrowserOriginSchema } from "../browserObservation.js"; -import { javaScriptRouteInventorySchema } from "../javascript/javascriptRouteInventory.js"; import { sourceToBundleViewRequestSchema, sourceToBundleViewItemsSchema, @@ -122,7 +125,6 @@ import { projectNativeFunctionMatchingView, nativeFunctionMatchingViewRequestSchema, nativeFunctionMatchingViewItemsSchema, - nativeFunctionMatchingViewContextSchema, } from "./nativeFunctionMatchingView.js"; import { protobufViewRequestSchema, @@ -885,7 +887,7 @@ export const javaScriptRoutesViewResultSchema = z.strictObject({ inventory_capacity: javaScriptRouteInventorySchema.shape.capacity, capture_url: z.string().nullable(), items: javaScriptRoutesViewItemsSchema, - sources: z.array(javaScriptRoutesSourceReferenceSchema), + sources: z.array(javaScriptRouteInventorySourceSchema), summary: javaScriptRoutesViewSummarySchema.nullable(), byte_limited: z.boolean(), }); @@ -990,7 +992,7 @@ export const nativeFunctionMatchingSummaryViewResultSchema = z.strictObject({ ...summaryViewResultBase, artifact: z.null(), parent_operation: z.literal("match_native_functions"), - summary: nativeFunctionMatchingViewContextSchema, + summary: nativeFunctionMatchingContextSchema, }); /** Rows observed for one facet, or null when the provider declared that facet unavailable. */ @@ -1141,7 +1143,7 @@ export const analysisViewResultSchema = z.discriminatedUnion("kind", [ sourceToBundlePageViewResultSchema, z.strictObject({ ...viewResultBase, - ...nativeFunctionMatchingViewContextSchema.shape, + ...nativeFunctionMatchingContextSchema.shape, kind: z.literal("function-matches"), artifact: z.null(), view: nativeFunctionMatchingViewRequestSchema, diff --git a/src/domain/analysisView/javascriptRoutesView.ts b/src/domain/analysisView/javascriptRoutesView.ts index c2bab2d08b..4fa079c3dd 100644 --- a/src/domain/analysisView/javascriptRoutesView.ts +++ b/src/domain/analysisView/javascriptRoutesView.ts @@ -191,8 +191,6 @@ export const javaScriptRoutesViewSummarySchema = z.strictObject({ unknown: z.int().nonnegative(), }), }); -export const javaScriptRoutesSourceReferenceSchema = - javaScriptRouteInventorySourceSchema; type Request = z.output; type Gap = z.infer; diff --git a/src/domain/analysisView/nativeFunctionMatchingView.ts b/src/domain/analysisView/nativeFunctionMatchingView.ts index f4090dc4dc..e6870322aa 100644 --- a/src/domain/analysisView/nativeFunctionMatchingView.ts +++ b/src/domain/analysisView/nativeFunctionMatchingView.ts @@ -1,7 +1,6 @@ import { z } from "zod"; import { nativeFunctionMatchingSchema, - nativeFunctionMatchingContextSchema, nativeFunctionMatchSchema, nativeFunctionMatchMemberSchema, nativeMatchStateSchema, @@ -80,8 +79,6 @@ export const nativeFunctionMatchingViewItemsSchema = z.union([ }), ), ]); -export const nativeFunctionMatchingViewContextSchema = - nativeFunctionMatchingContextSchema; interface FunctionRow { readonly member: NativeFunctionMatching["matches"][number]["left"][number]; readonly match_id: string; diff --git a/src/domain/erlang/beam.ts b/src/domain/erlang/beam.ts index f5344e53dd..87170b6eaf 100644 --- a/src/domain/erlang/beam.ts +++ b/src/domain/erlang/beam.ts @@ -2,7 +2,6 @@ import { z } from "zod"; import { digestSchema } from "../digests.js"; import { absoluteLocalPathStringSchema } from "../localPath.js"; -import { publicInputStrictObject } from "../publicInput.js"; const uint = z.number().int().min(0).max(0xffffffff); const range = z.strictObject({ offset: uint, bytes: uint }); @@ -17,7 +16,7 @@ export const MAX_INSPECT_ERLANG_BEAM_ATOMS = 65536; export const MAX_INSPECT_ERLANG_BEAM_TABLE_ROWS = 65536; /** Complete bounded inspection of one Erlang BEAM IFF container. */ -export const inspectErlangBeamInputSchema = publicInputStrictObject({ +export const inspectErlangBeamInputSchema = z.strictObject({ path: absoluteLocalPathStringSchema.describe( "Absolute filesystem path to a BEAM file", ), diff --git a/src/domain/javascript/javascriptApplicationEvidenceSchemas.ts b/src/domain/javascript/javascriptApplicationEvidenceSchemas.ts index 203e1da82c..6ee5ac8bab 100644 --- a/src/domain/javascript/javascriptApplicationEvidenceSchemas.ts +++ b/src/domain/javascript/javascriptApplicationEvidenceSchemas.ts @@ -1,7 +1,6 @@ import { z } from "zod"; import { javascriptSourceRangeSchema } from "./javascriptSourceCoordinates.js"; -import { digestSchema } from "../digests.js"; -import { prefixedDigestSchema } from "../digests.js"; +import { digestSchema, prefixedDigestSchema } from "../digests.js"; const boundedTextSchema = z.string().min(1); const boundedKeySchema = z.string().min(1); @@ -139,14 +138,17 @@ const applicationLocationSchema = z.discriminatedUnion("kind", [ }), ]); -/** Explicit source-location availability for one graph fact. */ -const applicationLocationReferenceSchema = z.discriminatedUnion("available", [ - z.strictObject({ - available: z.literal(true), - value: applicationLocationSchema, - }), - unavailableFactSchema, -]); +/** Source-location availability and coordinates retained on each graph fact. */ +export const applicationGraphEvidenceLocationSchema = z.discriminatedUnion( + "available", + [ + z.strictObject({ + available: z.literal(true), + value: applicationLocationSchema, + }), + unavailableFactSchema, + ], +); /** Versioned extractor coordinates attached to every graph fact. */ const applicationExtractorSchema = z.strictObject({ @@ -242,7 +244,7 @@ const applicationGraphEvidenceBaseSchema = z.strictObject({ state: applicationFactStateSchema, confidence: applicationConfidenceSchema, artifact: applicationArtifactReferenceSchema, - location: applicationLocationReferenceSchema, + location: applicationGraphEvidenceLocationSchema, extractor: applicationExtractorSchema, coverage: applicationCoverageSchema, limitations: z.array(boundedTextSchema), @@ -253,10 +255,6 @@ const applicationGraphEvidenceBaseSchema = z.strictObject({ export const applicationGraphEvidenceContextSchema = applicationGraphEvidenceBaseSchema.omit({ location: true }); -/** Exact coordinates retained on each graph fact. */ -export const applicationGraphEvidenceLocationSchema = - applicationLocationReferenceSchema; - type EvidenceValue = z.infer; const checkAuthorityState = ( diff --git a/src/domain/native/byteEntropy.ts b/src/domain/native/byteEntropy.ts index 052d3fdd6e..c4f04082ec 100644 --- a/src/domain/native/byteEntropy.ts +++ b/src/domain/native/byteEntropy.ts @@ -1,6 +1,5 @@ import { z } from "zod"; import { localPathStringSchema } from "../localPath.js"; -import { publicInputStrictObject } from "../publicInput.js"; const offsetSchema = z .number() @@ -10,7 +9,7 @@ const offsetSchema = z const entropySchema = z.number().min(0).max(8); /** Select blocks relative to an explicit byte window; identity remains file-wide. */ -export const inspectByteEntropyInputSchema = publicInputStrictObject({ +export const inspectByteEntropyInputSchema = z.strictObject({ path: localPathStringSchema, block_size: z .number() diff --git a/src/domain/publicInput.test.ts b/src/domain/publicInput.test.ts deleted file mode 100644 index 04b46983bd..0000000000 --- a/src/domain/publicInput.test.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { expect, it } from "vitest"; -import { z } from "zod"; - -import { publicInputStrictObject } from "./publicInput.js"; - -it("rejects unknown keys on a public tool input", () => { - const schema = publicInputStrictObject({ - path: z.string(), - block_size: z.number().default(4096), - }); - expect(schema.parse({ path: "/file" })).toEqual({ - path: "/file", - block_size: 4096, - }); - expect(schema.safeParse({ path: "/file", extra: true }).success).toBe(false); -}); diff --git a/src/domain/publicInput.ts b/src/domain/publicInput.ts deleted file mode 100644 index 09093bc6ae..0000000000 --- a/src/domain/publicInput.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { z } from "zod"; - -/** - * Closed object schema for a public CLI or MCP tool input. - * - * Unknown keys fail here so public input policy has one owner. Result and - * internal objects stay on their own constructors. - */ -export const publicInputStrictObject = z.strictObject; diff --git a/src/native/pe/PeCertificateAsn1.ts b/src/native/pe/PeCertificateAsn1.ts index 4b3125e769..aaaca68450 100644 --- a/src/native/pe/PeCertificateAsn1.ts +++ b/src/native/pe/PeCertificateAsn1.ts @@ -25,8 +25,6 @@ const OID_NAMES: Readonly> = { "2.16.840.1.101.3.4.2.3": "sha512", }; -export type Asn1Node = Asn1BerNode; - export class PeCertificateAsn1Failure extends Error { constructor(message: string) { super(message); @@ -46,7 +44,7 @@ const required = (value: T | undefined, message: string): T => export const ASN1_NODE_BUDGET = 16_384; /** Decode one bounded ASN.1 TLV, including indefinite constructed BER values. */ -export const readAsn1 = (bytes: Buffer): Asn1Node => { +export const readAsn1 = (bytes: Buffer): Asn1BerNode => { try { return new Asn1BerReader(bytes, ASN1_NODE_BUDGET).read(); } catch (cause: unknown) { @@ -55,7 +53,7 @@ export const readAsn1 = (bytes: Buffer): Asn1Node => { } }; -const requireDefiniteEncoding = (node: Asn1Node, label: string): void => { +const requireDefiniteEncoding = (node: Asn1BerNode, label: string): void => { if (node.lengthForm === "indefinite") fail(`${label} must use definite DER length encoding.`); for (const child of node.children) requireDefiniteEncoding(child, label); @@ -165,7 +163,7 @@ const decodeUtf8 = (bytes: Buffer, budget: DecodedNameBudget): string => { }; const decodeDirectoryString = ( - node: Asn1Node, + node: Asn1BerNode, budget: DecodedNameBudget, ): string => { switch (node.tag) { @@ -211,18 +209,21 @@ const requireBitStringContent = (content: Buffer, label: string): void => { return fail(`${label} BIT STRING is truncated or has invalid unused bits.`); }; -const requireBitString = (node: Asn1Node | undefined, label: string): void => { +const requireBitString = ( + node: Asn1BerNode | undefined, + label: string, +): void => { if (node === undefined || node.tag !== 0x03) return fail(`${label} is not a BIT STRING.`); requireBitStringContent(node.content, label); }; -const sequence = (node: Asn1Node, label: string): readonly Asn1Node[] => { +const sequence = (node: Asn1BerNode, label: string): readonly Asn1BerNode[] => { if (node.tag !== 0x30) return fail(`Expected SEQUENCE for ${label}.`); return node.children; }; -const algorithmOid = (node: Asn1Node): string => { +const algorithmOid = (node: Asn1BerNode): string => { const children = sequence(node, "AlgorithmIdentifier"); const oid = children[0]; if (oid === undefined || oid.tag !== 0x06) @@ -237,7 +238,7 @@ interface Attribute { } const decodeName = ( - node: Asn1Node, + node: Asn1BerNode, budget: DecodedNameBudget, ): { readonly text: string; @@ -273,7 +274,7 @@ const decodeName = ( return { text, attributes }; }; -const validityTime = (node: Asn1Node, budget: DecodedNameBudget): string => { +const validityTime = (node: Asn1BerNode, budget: DecodedNameBudget): string => { if (node.tag !== 0x17 && node.tag !== 0x18) return fail("Validity instant is not UTCTime or GeneralizedTime."); // A zero byte becomes "\u0000" (six characters) in the hashed evidence JSON. @@ -292,7 +293,7 @@ export interface ParsedCertificate { readonly signature_algorithm_oid: string; } -const requireSubjectPublicKeyInfo = (node: Asn1Node): void => { +const requireSubjectPublicKeyInfo = (node: Asn1BerNode): void => { const children = sequence(node, "SubjectPublicKeyInfo"); const algorithm = children[0]; const key = children[1]; @@ -307,7 +308,7 @@ const requireSubjectPublicKeyInfo = (node: Asn1Node): void => { // issuerUniqueID [1], subjectUniqueID [2], and extensions [3] are optional // and ordered. Anything else is not a TBSCertificate field. const finishTbsCertificate = ( - children: readonly Asn1Node[], + children: readonly Asn1BerNode[], start: number, ): void => { let cursor = start; @@ -344,7 +345,7 @@ const finishTbsCertificate = ( /** Decode one X.509 Certificate SEQUENCE at a known file-relative DER node. */ export const parseCertificate = ( - node: Asn1Node, + node: Asn1BerNode, names: DecodedNameBudget = { remaining: MAX_DECODED_NAME_BYTES, teletexStringCompatibilityUsed: false, @@ -432,7 +433,7 @@ export interface ParsedSigner { } const signerIdentifier = ( - sid: Asn1Node, + sid: Asn1BerNode, names: DecodedNameBudget, ): { readonly issuer: ReturnType | null; @@ -471,7 +472,7 @@ const signerIdentifier = ( }; const parseSigner = ( - node: Asn1Node, + node: Asn1BerNode, names: DecodedNameBudget, ): ParsedSigner => { const children = sequence(node, "SignerInfo"); @@ -523,7 +524,7 @@ export interface ParsedSignedData { } const parseSignedData = ( - node: Asn1Node, + node: Asn1BerNode, names: DecodedNameBudget, ): ParsedSignedData => { const children = sequence(node, "SignedData"); @@ -569,7 +570,7 @@ const parseSignedData = ( }; /** Decode ContentInfo wrapping SignedData; other content types remain unsupported. */ -export const parsePkcs7SignedData = (root: Asn1Node): ParsedSignedData => { +export const parsePkcs7SignedData = (root: Asn1BerNode): ParsedSignedData => { const children = sequence(root, "ContentInfo"); const type = children[0]; const content = children[1]; diff --git a/tests/boundary/cli/cutterCommands.test.ts b/tests/boundary/cli/cutterCommands.test.ts index 1dcd2d5f49..c1db5b48ac 100644 --- a/tests/boundary/cli/cutterCommands.test.ts +++ b/tests/boundary/cli/cutterCommands.test.ts @@ -315,21 +315,34 @@ describe("Cutter CLI command output flags", () => { expect(() => JSON.parse(formatted.stdout)).not.toThrow(); for (const [flags, expected] of [ - [["--json=true"], true], - [["--json=false"], false], - [["--json=true", "--json-output=false"], false], - [["--json=false", "--json-output=true"], true], - [["--json-output=false", "--json=true"], false], - [["--json-output=true", "--json=false"], true], + [["--json-output=true"], true], + [["--json-output=false"], false], + [["--json", "--json-output=false"], false], + [["--json", "--json-output=true"], true], + [["--json-output=false", "--json"], false], + [["--json-output=true", "--json"], true], ] as const) { - const compatible = await serveCli( + const selected = await serveCli( ["cutter-command", SESSION_ID, "0", "pdi", ...flags], { REA_CUTTER_BRIDGE_DIR: directory }, ); - expect(compatible.exitCode).toBe(0); + expect(selected.exitCode).toBe(0); expect(commandJsonModes.at(-1)).toBe(expected); + if (flags.some((flag) => flag === "--json")) + expect(() => JSON.parse(selected.stdout)).not.toThrow(); } + const commandsBeforeRejectedFlags = commandJsonModes.length; + for (const flag of ["--json=true", "--json=false"]) { + const rejected = await serveCli( + ["cutter-command", SESSION_ID, "0", "pdi", flag], + { REA_CUTTER_BRIDGE_DIR: directory }, + ); + expect(rejected.exitCode).toBe(1); + expect(rejected.stdout).toContain("Unknown flag: --json"); + } + expect(commandJsonModes).toHaveLength(commandsBeforeRejectedFlags); + await assertRealVerifierJsonInspection(directory, commandJsonModes); }); });