refactor(ownership)!: consolidate legacy aliases

Migrate schema, installer, ASN.1, and tooling consumers to their owning definitions. Remove the redundant CI task entry and the deprecated Cutter command-local JSON flag.

BREAKING CHANGE: Replace Cutter --json=true or --json=false with --json-output=true or --json-output=false. Bare --json still controls CLI result formatting.
This commit is contained in:
morluto committed 2026-10-27 06:31:23 +08:00
1 parent 7c8eb63fcc
commit 567ea4b1e8
17 files changed
+73 -109

No files matched your search

+4 -4
View File
@@ -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 |
| --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-1
View File
@@ -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",
+2 -2
View File
@@ -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<LinuxHopperDownload> => {
download = (url: string): Promise<HopperDownload> => {
this.downloads.push(url);
return Promise.resolve({ ok: this.archiveOk, bytes: this.archive });
};
+1 -4
View File
@@ -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<LinuxHopperDownload>;
): Promise<HopperDownload>;
createTemporaryDirectory(): Promise<string>;
writeArchive(path: string, bytes: Uint8Array): Promise<void>;
installPackage(
@@ -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",
+1 -7
View File
@@ -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 },
),
@@ -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 = [
+8 -6
View File
@@ -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,
@@ -191,8 +191,6 @@ export const javaScriptRoutesViewSummarySchema = z.strictObject({
unknown: z.int().nonnegative(),
}),
});
export const javaScriptRoutesSourceReferenceSchema =
javaScriptRouteInventorySourceSchema;
type Request = z.output<typeof javaScriptRoutesViewRequestSchema>;
type Gap = z.infer<typeof javaScriptRouteGapSchema>;
@@ -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;
+1 -2
View File
@@ -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",
),
@@ -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<typeof applicationGraphEvidenceBaseSchema>;
const checkAuthorityState = (
+1 -2
View File
@@ -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()
-16
View File
@@ -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);
});
-9
View File
@@ -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;
+18 -17
View File
@@ -25,8 +25,6 @@ const OID_NAMES: Readonly<Record<string, string>> = {
"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 = <T>(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<typeof decodeName> | 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];
+21 -8
View File
@@ -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);
});
});