refactor: use canonical implementations and types directly

Remove forwarding wrappers and shadow aliases, migrate callers and verification scripts to their owners, and share one exhaustive semantic resource-bound projection.
This commit is contained in:
morluto
2026-10-09 23:35:19 +08:00
parent 3283a45d09
commit 765e809e36
119 changed files with 577 additions and 629 deletions
+1 -1
View File
@@ -44,7 +44,7 @@ See [docs/testing.md](docs/testing.md) for test scopes and verification lanes an
## Configuration & Environment Variables
Configuration is parsed and validated by `src/config.ts` and `src/config/`. Keep user-facing setup and provider configuration in [README.md](README.md) and the relevant guide under `docs/`; do not maintain a second environment-variable catalogue here.
Configuration is parsed and validated by `src/config/`. Keep user-facing setup and provider configuration in [README.md](README.md) and the relevant guide under `docs/`; do not maintain a second environment-variable catalogue here.
## Coding Style & Naming Conventions
+1 -1
View File
@@ -58,7 +58,7 @@ graph TD
end
subgraph "Configuration"
CONFIG["src/config.ts + src/config/<br/>Zod-validated env parsing<br/>provider settings and origins"]
CONFIG["src/config/<br/>Zod-validated env parsing<br/>provider settings and origins"]
end
subgraph "Tests"
+2 -2
View File
@@ -4,7 +4,7 @@ import assert from "node:assert/strict";
import { constants } from "node:buffer";
import { createHash } from "node:crypto";
import { canonicalDigest } from "../../../dist/domain/comparisonSemantics.js";
import { digestCanonicalValue } from "../../../dist/domain/canonicalDigest.js";
import {
createEvidence,
parseEvidence,
@@ -23,7 +23,7 @@ for (let index = 0; index < count; index += 1) {
expected.update(encodedLeaf);
}
expected.update("]}");
assert.equal(canonicalDigest(value), expected.digest("hex"));
assert.equal(digestCanonicalValue(value), expected.digest("hex"));
const evidence = createEvidence(
undefined,
+2 -2
View File
@@ -1,4 +1,4 @@
import { canonicalDigest } from "../domain/comparisonSemantics.js";
import { digestCanonicalValue } from "../domain/canonicalDigest.js";
import type { ProviderIdentity } from "./AnalysisProvider.js";
import type { BrowserScenario } from "../domain/browserScenario.js";
import type { BrowserScenarioCapture } from "../domain/browserScenarioCapture.js";
@@ -13,7 +13,7 @@ const browserScenarioParameters = (
scenario: BrowserScenario,
): EvidenceObservation["parameters"] => ({
...jsonObjectSchema.parse(scenario),
scenario_sha256: canonicalDigest(scenario, "Browser scenario"),
scenario_sha256: digestCanonicalValue(scenario, "Browser scenario"),
});
/** Create Evidence without retaining resolved scenario secret values. */
+2 -19
View File
@@ -5,7 +5,8 @@ import { silentLogger } from "../logger.js";
import type { Logger } from "pino";
import type { DirectAnalysisDependencies } from "./DirectAnalysisDependencies.js";
const runSessionStatus = async (
/** Read target-free provider status and operation availability. */
export const runSessionStatus = async (
dependencies: Pick<
DirectAnalysisDependencies,
"createBinarySession" | "readConfiguration"
@@ -26,21 +27,3 @@ const runSessionStatus = async (
await session.close();
}
};
/** List binary-session provider candidates and auxiliary operation availability. */
export const runProviderStatus = (
dependencies: Pick<
DirectAnalysisDependencies,
"createBinarySession" | "readConfiguration"
>,
logger: Logger = silentLogger,
) => runSessionStatus(dependencies, logger);
/** List binary-session operation descriptors; this is not the full MCP catalog. */
export const runCapabilityStatus = (
dependencies: Pick<
DirectAnalysisDependencies,
"createBinarySession" | "readConfiguration"
>,
logger: Logger = silentLogger,
) => runSessionStatus(dependencies, logger);
+1 -3
View File
@@ -26,13 +26,11 @@ import type {
} from "./SetupTypes.js";
import type { SetupClient } from "./SupportedClients.js";
const defaultCommand = (): readonly string[] => npxRegistrationCommand();
/** Configure one supported client's native stdio MCP registration shape. */
export const configureClientConfiguration = (
client: SetupClient,
environment: SetupProviderEnvironment = {},
command: readonly string[] = defaultCommand(),
command: readonly string[] = npxRegistrationCommand(),
): Promise<ClientConfigurationResult> => {
if (client.format === undefined || client.format === "unsupported")
return Promise.resolve({ status: "failed", reason: "readback" });
@@ -1,4 +1,4 @@
import { canonicalDigest } from "../../domain/comparisonSemantics.js";
import { digestCanonicalValue } from "../../domain/canonicalDigest.js";
import type { ProviderIdentity } from "../AnalysisProvider.js";
import type { Evidence, EvidenceObservation } from "../../domain/evidence.js";
import { createEvidence } from "../../domain/evidence.js";
@@ -44,6 +44,6 @@ const parameters = (
const scenario = jsonObjectSchema.parse(input);
return {
...scenario,
scenario_sha256: canonicalDigest(scenario, "Electron scenario"),
scenario_sha256: digestCanonicalValue(scenario, "Electron scenario"),
};
};
@@ -1,4 +1,4 @@
import { compareCodePoints } from "../../domain/canonicalOrdering.js";
import { compareUnicodeCodePoints } from "../../domain/unicodeCodePointOrder.js";
import type { ElectronIpcFinding } from "../../domain/javascript/electronStaticAnalysisTypes.js";
import type { ElectronBoundarySummary } from "../../domain/javascript/javascriptApplicationAnalysis.js";
import type { JavaScriptModuleArtifactAnalysis } from "./JavaScriptArtifactAnalysisTypes.js";
@@ -37,7 +37,7 @@ export const collectElectronIpcRecords = (
finding,
})),
)
.sort((left, right) => compareCodePoints(left.key, right.key));
.sort((left, right) => compareUnicodeCodePoints(left.key, right.key));
/** Match only exact literal channels and retain ambiguous matches separately. */
export const classifyElectronIpcPairings = (
@@ -26,13 +26,11 @@ import {
partialApplicationCoverage,
} from "../../domain/javascript/javascriptApplicationEvidenceSchemas.js";
import type { JavaScriptSemanticResourceLimit } from "../../domain/javascript/javascriptSemanticValueTypes.js";
import { semanticCoverageResourceLimits } from "../../domain/javascript/javascriptSemanticCoverage.js";
import {
SEMANTIC_EXPRESSION_DEPTH_LIMIT,
SEMANTIC_PRIMITIVE_CANDIDATE_LIMIT,
SEMANTIC_PRIMITIVE_JSON_BYTES_LIMIT,
semanticResourceLimitReason,
} from "../../domain/javascript/javascriptSemanticResourceLimits.js";
semanticCoverageResourceLimits,
semanticResourceLimitCoverage,
} from "../../domain/javascript/javascriptSemanticCoverage.js";
import { semanticResourceLimitReason } from "../../domain/javascript/javascriptSemanticResourceLimits.js";
import {
addJavaScriptArtifactContainers,
addJavaScriptArtifactFiles,
@@ -138,7 +136,7 @@ const graphCoverage = (context: JavaScriptArtifactGraphContext) => {
return partialApplicationCoverage([], null);
if (resourceLimits.length > 0)
return partialApplicationCoverage(
resourceLimits.map(applicationGraphResourceLimit),
semanticResourceLimitCoverage(resourceLimits),
null,
);
if (unknownGap) return partialApplicationCoverage([], null);
@@ -158,24 +156,6 @@ const semanticResourceLimits = (
),
].sort();
const applicationGraphResourceLimit = (
resourceLimit: JavaScriptSemanticResourceLimit,
) => ({
name: `javascript_semantic_${resourceLimit.replaceAll("-", "_")}`,
value:
resourceLimit === "primitive-candidates"
? SEMANTIC_PRIMITIVE_CANDIDATE_LIMIT
: resourceLimit === "primitive-bytes"
? SEMANTIC_PRIMITIVE_JSON_BYTES_LIMIT
: SEMANTIC_EXPRESSION_DEPTH_LIMIT,
unit:
resourceLimit === "expression-depth"
? ("depth" as const)
: resourceLimit === "primitive-bytes"
? ("bytes" as const)
: ("items" as const),
});
const graphLimitations = (
context: JavaScriptArtifactGraphContext,
coverage: "complete" | "partial" | "unknown" | "unavailable",
@@ -1,4 +1,4 @@
import { compareCodePoints } from "../../domain/canonicalOrdering.js";
import { compareUnicodeCodePoints } from "../../domain/unicodeCodePointOrder.js";
import type { JavaScriptJsonModuleObservation } from "./JavaScriptArtifactAnalysisTypes.js";
import type { JavaScriptArtifactFile } from "../../domain/javascript/javascriptArtifactFiles.js";
@@ -31,7 +31,7 @@ export const analyzeJavaScriptJsonModule = (
}
const keys =
typeof value === "object" && value !== null && !Array.isArray(value)
? Object.keys(value).sort(compareCodePoints)
? Object.keys(value).sort(compareUnicodeCodePoints)
: [];
const retained = keys;
return {
@@ -1,18 +1,16 @@
import { posix } from "node:path";
import { compareCodePoints } from "../../domain/canonicalOrdering.js";
import { compareUnicodeCodePoints } from "../../domain/unicodeCodePointOrder.js";
import type { ApplicationNode } from "../../domain/javascript/javascriptApplicationGraphSchemas.js";
import {
completeApplicationCoverage,
partialApplicationCoverage,
} from "../../domain/javascript/javascriptApplicationEvidenceSchemas.js";
import { semanticCoverageResourceLimits } from "../../domain/javascript/javascriptSemanticCoverage.js";
import {
SEMANTIC_EXPRESSION_DEPTH_LIMIT,
SEMANTIC_PRIMITIVE_CANDIDATE_LIMIT,
SEMANTIC_PRIMITIVE_JSON_BYTES_LIMIT,
semanticResourceLimitReason,
} from "../../domain/javascript/javascriptSemanticResourceLimits.js";
semanticCoverageResourceLimits,
semanticResourceLimitCoverage,
} from "../../domain/javascript/javascriptSemanticCoverage.js";
import { semanticResourceLimitReason } from "../../domain/javascript/javascriptSemanticResourceLimits.js";
import type {
JavaScriptModuleOrigin,
JavaScriptSemanticModuleLink,
@@ -81,7 +79,7 @@ export const addJavaScriptSourceModules = (
.flatMap(({ exportedName }) =>
exportedName === null ? [] : [exportedName],
)
.sort(compareCodePoints);
.sort(compareUnicodeCodePoints);
const retainedExports = [...new Set(exports)];
const source = context.accumulator.addNode({
kind: "javascript-module",
@@ -408,21 +406,7 @@ const semanticCoverage = (
if (semantic.ir.coverage.status === "complete" && resourceLimits.length === 0)
return completeApplicationCoverage();
return partialApplicationCoverage(
resourceLimits.map((resourceLimit) => ({
name: `javascript_semantic_${resourceLimit.replaceAll("-", "_")}`,
value:
resourceLimit === "primitive-candidates"
? SEMANTIC_PRIMITIVE_CANDIDATE_LIMIT
: resourceLimit === "primitive-bytes"
? SEMANTIC_PRIMITIVE_JSON_BYTES_LIMIT
: SEMANTIC_EXPRESSION_DEPTH_LIMIT,
unit:
resourceLimit === "expression-depth"
? ("depth" as const)
: resourceLimit === "primitive-bytes"
? ("bytes" as const)
: ("items" as const),
})),
semanticResourceLimitCoverage(resourceLimits),
semantic.ir.coverage.omittedCount,
);
};
@@ -1,4 +1,4 @@
import { compareCodePoints } from "../../domain/canonicalOrdering.js";
import { compareUnicodeCodePoints } from "../../domain/unicodeCodePointOrder.js";
import type { JavaScriptSemanticGraphNode } from "../../domain/javascript/javascriptSemanticGraphSchemas.js";
import { JAVASCRIPT_SEMANTIC_RELATION_FAMILIES } from "../../domain/javascript/javascriptSemanticGraphSchemas.js";
import type { JavaScriptSemanticIr } from "../../domain/javascript/javascriptSemanticIr.js";
@@ -65,7 +65,7 @@ export const createSemanticCallableOwnerLookup = (
const rank =
best === undefined
? 1
: compareCodePoints(candidate.priority, best.priority) ||
: compareUnicodeCodePoints(candidate.priority, best.priority) ||
best.order - candidate.order;
if (node !== undefined && rank > 0) {
best = candidate;
@@ -1,10 +1,12 @@
import {
semanticContainer,
semanticPropertyPointer,
type JavaScriptSemanticSlot,
} from "../../domain/javascript/javascriptSemanticSlots.js";
import type { JavaScriptSemanticBinding } from "../../domain/javascript/javascriptSemanticIr.js";
import type { JavaScriptSemanticValue } from "../../domain/javascript/javascriptSemanticValueTypes.js";
import type {
JavaScriptSemanticProperty,
JavaScriptSemanticValue,
} from "../../domain/javascript/javascriptSemanticValueTypes.js";
import { createJavaScriptSemanticGraphUnknown } from "../../domain/javascript/javascriptSemanticGraph.js";
import {
retainSemanticGraphNode,
@@ -155,7 +157,7 @@ export const semanticPropertySlot = (
context: SemanticFlowProjectionContext,
objectBindingId: string,
path: readonly string[],
fact: JavaScriptSemanticSlot,
fact: JavaScriptSemanticProperty,
) =>
retainSemanticGraphNode(context.state, context.file, {
kind: "property-slot",
+1 -3
View File
@@ -9,8 +9,6 @@ import { ARTIFACT_ANALYSIS_OPERATIONS } from "../contracts/artifactToolContracts
export const ARTIFACT_PROVIDER_IDENTITY: ProviderIdentity = Object.freeze(
ARTIFACT_GRAPH_PROVIDER,
);
const IDENTITY = ARTIFACT_PROVIDER_IDENTITY;
/** Declare artifact coverage without constructing readers or acquiring resources. */
export const artifactCapabilities = (
platform: NodeJS.Platform = process.platform,
@@ -18,7 +16,7 @@ export const artifactCapabilities = (
Object.freeze(
ARTIFACT_ANALYSIS_OPERATIONS.map((operation) => {
const common = {
provider: IDENTITY,
provider: ARTIFACT_PROVIDER_IDENTITY,
operation,
effects: Object.freeze({
mutatesArtifact: false,
+2 -2
View File
@@ -3,7 +3,7 @@ import { lstat, open, opendir, realpath } from "node:fs/promises";
import { isAbsolute, join, relative, sep } from "node:path";
import type { Readable } from "node:stream";
import { compareCodePoints } from "../domain/canonicalOrdering.js";
import { compareUnicodeCodePoints } from "../domain/unicodeCodePointOrder.js";
import {
ArtifactReaderFailure,
@@ -49,7 +49,7 @@ export class DirectoryArtifactReader implements ArtifactReader {
await withDirectoryIoContext("inspect directory", directory, () =>
assertContainedDirectory(root, directory),
);
children.sort(compareCodePoints);
children.sort(compareUnicodeCodePoints);
const directories: string[] = [];
for (const name of children) {
abortIfNeeded(signal);
@@ -1,7 +1,7 @@
import { lstat, realpath } from "node:fs/promises";
import { compareCodePoints } from "../../domain/canonicalOrdering.js";
import { canonicalDigest } from "../../domain/comparisonSemantics.js";
import { compareUnicodeCodePoints } from "../../domain/unicodeCodePointOrder.js";
import { digestCanonicalValue } from "../../domain/canonicalDigest.js";
import { AsarArtifactReader } from "../AsarArtifactReader.js";
import {
ArtifactPathRegistry,
@@ -178,7 +178,7 @@ const materializeSelection = async ({
await reader.close();
readerClosed = true;
extracted.sort((left, right) =>
compareCodePoints(left.relative_path, right.relative_path),
compareUnicodeCodePoints(left.relative_path, right.relative_path),
);
const result = createExtractionResult(
input,
@@ -245,15 +245,15 @@ const createExtractionResult = (
source_manifest_id: inventory.manifest.manifest_id,
selected_occurrence_ids: selected
.map(({ occurrence }) => occurrence.occurrence_id)
.sort(compareCodePoints),
files_sha256: canonicalDigest(extracted, "Artifact"),
.sort(compareUnicodeCodePoints),
files_sha256: digestCanonicalValue(extracted, "Artifact"),
output_root_alias: "$OUTPUT_ROOT" as const,
};
return artifactExtractionResultSchema.parse({
manifest: inventory.manifest,
extraction_manifest: {
...extractionSemantic,
extraction_id: `aex_${canonicalDigest(extractionSemantic, "Artifact")}`,
extraction_id: `aex_${digestCanonicalValue(extractionSemantic, "Artifact")}`,
},
output_root: input.outputRoot,
artifacts: extracted,
@@ -1,5 +1,5 @@
import { compareCodePoints } from "../../domain/canonicalOrdering.js";
import { canonicalDigest } from "../../domain/comparisonSemantics.js";
import { compareUnicodeCodePoints } from "../../domain/unicodeCodePointOrder.js";
import { digestCanonicalValue } from "../../domain/canonicalDigest.js";
import {
artifactEdgeId,
artifactIdForContent,
@@ -52,7 +52,7 @@ export const createOccurrence = (
path: string,
parent: string | null,
): MutableOccurrence => ({
occurrence_id: `occ_${canonicalDigest({ path, kind: entry.kind }, "Artifact")}`,
occurrence_id: `occ_${digestCanonicalValue({ path, kind: entry.kind }, "Artifact")}`,
artifact_id: null,
parent_occurrence_id: parent,
logical_path: path,
@@ -80,8 +80,6 @@ export const createOccurrence = (
});
/** Compare exact child names independently of host locale and traversal order. */
export const compareDirectoryChildNames = compareCodePoints;
export const materializeDirectoryNodes = (
occurrences: MutableOccurrence[],
nodes: Map<string, ArtifactNode>,
@@ -107,9 +105,9 @@ export const materializeDirectoryNodes = (
artifact_id,
entry_kind,
}))
.sort((left, right) => compareDirectoryChildNames(left.name, right.name));
.sort((left, right) => compareUnicodeCodePoints(left.name, right.name));
const node = createArtifactNode({
sha256: canonicalDigest({ kind: "directory", children }, "Artifact"),
sha256: digestCanonicalValue({ kind: "directory", children }, "Artifact"),
size: 0,
format: "directory",
contentState: "virtual",
@@ -131,7 +129,7 @@ export const createRootNode = (input: {
createArtifactNode({
sha256:
input.digest?.sha256 ??
canonicalDigest(
digestCanonicalValue(
{
kind: "directory-root",
children: input.occurrences
@@ -140,7 +138,7 @@ export const createRootNode = (input: {
artifact_id,
}))
.sort((left, right) =>
compareDirectoryChildNames(left.logical_path, right.logical_path),
compareUnicodeCodePoints(left.logical_path, right.logical_path),
),
},
"Artifact",
+6 -4
View File
@@ -1,5 +1,5 @@
import type { ArtifactInventorySnapshot } from "../../domain/artifactInventorySnapshot.js";
import { compareCodePoints } from "../../domain/canonicalOrdering.js";
import { compareUnicodeCodePoints } from "../../domain/unicodeCodePointOrder.js";
import { createReadStream } from "node:fs";
import { lstat } from "node:fs/promises";
@@ -253,18 +253,20 @@ const verifyRootDigest = async (
const sortNodes = (nodes: ArtifactNode[]): ArtifactNode[] =>
nodes.sort((left, right) =>
compareCodePoints(left.artifact_id, right.artifact_id),
compareUnicodeCodePoints(left.artifact_id, right.artifact_id),
);
const sortOccurrences = (
occurrences: MutableOccurrence[],
): MutableOccurrence[] =>
occurrences.sort((left, right) =>
compareCodePoints(left.logical_path, right.logical_path),
compareUnicodeCodePoints(left.logical_path, right.logical_path),
);
const sortEdges = <T extends { edge_id: string }>(edges: T[]): T[] =>
edges.sort((left, right) => compareCodePoints(left.edge_id, right.edge_id));
edges.sort((left, right) =>
compareUnicodeCodePoints(left.edge_id, right.edge_id),
);
const buildManifest = ({
rootNode,
@@ -2,7 +2,7 @@ import { createHash } from "node:crypto";
import { isAbsolute } from "node:path";
import type { Readable } from "node:stream";
import { compareCodePoints } from "../../domain/canonicalOrdering.js";
import { compareUnicodeCodePoints } from "../../domain/unicodeCodePointOrder.js";
import { AsarArtifactReader } from "../AsarArtifactReader.js";
import {
ArtifactPathRegistry,
@@ -84,13 +84,13 @@ export const readJavaScriptArtifactFiles = async (
};
await visitReader(reader, "", snapshot.manifest.root_sha256, context);
const files = context.files.sort((left, right) =>
compareCodePoints(left.path, right.path),
compareUnicodeCodePoints(left.path, right.path),
);
assertExpectedFilesWereVisited(expected, files);
return {
files,
containers: context.containers.sort((left, right) =>
compareCodePoints(left.path, right.path),
compareUnicodeCodePoints(left.path, right.path),
),
text_bytes_read: context.textBytes,
invalid_utf8_files: context.invalidUtf8,
+4 -3
View File
@@ -67,13 +67,12 @@ import { captureCdpScreenshot } from "./CdpScreenshot.js";
import { comparePngScreenshots } from "./PngVisualDiff.js";
import { CDP_BROWSER_PROVIDER_IDENTITY } from "./providerIdentities.js";
const IDENTITY = CDP_BROWSER_PROVIDER_IDENTITY;
const CLEANUP_DOMAINS = ["Network", "Debugger", "Runtime", "Page"] as const;
/** Passive browser observation through a selected user-owned CDP endpoint. */
export class CdpBrowserProvider implements BrowserObservationPort {
identity(): ProviderIdentity {
return IDENTITY;
return CDP_BROWSER_PROVIDER_IDENTITY;
}
async listTargets(
@@ -440,4 +439,6 @@ const providerError = (
? new BrowserObservationError(operation, cause.reason, { cause })
: cause instanceof AnalysisError
? cause
: new ProviderAdapterError(IDENTITY.id, operation, { cause });
: new ProviderAdapterError(CDP_BROWSER_PROVIDER_IDENTITY.id, operation, {
cause,
});
+3 -3
View File
@@ -4,7 +4,7 @@ import canonicalize from "canonicalize";
import type { InspectWebPageInput } from "../domain/browserObservation.js";
import type { WebPageInspection } from "../domain/browserObservationSchemas.js";
import { compareCodePoints } from "../domain/canonicalOrdering.js";
import { compareUnicodeCodePoints } from "../domain/unicodeCodePointOrder.js";
import { CdpConnection } from "./CdpConnection.js";
import { optionalCdpCommand } from "./CdpOptionalCommand.js";
import {
@@ -85,7 +85,7 @@ export const captureStorageFingerprints = async ({
await addIndexedDb(run, origin, indexedDbNames);
await addCaches(run, caches);
state.items.sort((left, right) =>
compareCodePoints(
compareUnicodeCodePoints(
`${left.scope}:${left.identity_sha256}`,
`${right.scope}:${right.identity_sha256}`,
),
@@ -447,7 +447,7 @@ const stableValue = (value: unknown): unknown => {
return Object.fromEntries(
Object.entries(record)
.filter(([key]) => key !== "objectId" && key !== "customPreview")
.sort(([left], [right]) => compareCodePoints(left, right))
.sort(([left], [right]) => compareUnicodeCodePoints(left, right))
.map(([key, item]) => [key, stableValue(item)]),
);
};
+2 -2
View File
@@ -1,4 +1,4 @@
import { canonicalDigest } from "../domain/comparisonSemantics.js";
import { digestCanonicalValue } from "../domain/canonicalDigest.js";
import type { ProgressReporter } from "../application/ProgressReporter.js";
import type {
ElectronPageInspection,
@@ -289,7 +289,7 @@ const captureResources = async (
? null
: Math.max(0, numberValue(resource.contentSize) ?? 0),
};
const resourceKey = `electron_resource_${canonicalDigest(item, "CDP capture")}`;
const resourceKey = `electron_resource_${digestCanonicalValue(item, "CDP capture")}`;
if (seen.has(resourceKey)) continue;
seen.add(resourceKey);
resources.push({
+4 -4
View File
@@ -30,12 +30,10 @@ import { inspectCdpElectronPage } from "./CdpElectronInspection.js";
import { authorizedElectronFile } from "./ElectronFileScope.js";
import { CDP_ELECTRON_PROVIDER_IDENTITY } from "./providerIdentities.js";
const IDENTITY = CDP_ELECTRON_PROVIDER_IDENTITY;
/** Passive Electron provider for local file pages exposed by loopback CDP. */
export class CdpElectronProvider implements ElectronObservationPort {
identity(): ProviderIdentity {
return IDENTITY;
return CDP_ELECTRON_PROVIDER_IDENTITY;
}
async listTargets(
@@ -169,4 +167,6 @@ const providerError = (
? new BrowserObservationError(operation, cause.reason, { cause })
: cause instanceof AnalysisError
? cause
: new ProviderAdapterError(IDENTITY.id, operation, { cause });
: new ProviderAdapterError(CDP_ELECTRON_PROVIDER_IDENTITY.id, operation, {
cause,
});
+2 -2
View File
@@ -2,7 +2,7 @@ import type {
ElectronPageInspection,
InspectElectronPageInput,
} from "../domain/javascript/electronObservation.js";
import { canonicalDigest } from "../domain/comparisonSemantics.js";
import { digestCanonicalValue } from "../domain/canonicalDigest.js";
import { createWebTextArtifact } from "../domain/webContentArtifact.js";
import type { CdpConnection } from "./CdpConnection.js";
import { CdpCaptureCompleteness } from "./CdpCaptureCompleteness.js";
@@ -49,7 +49,7 @@ export const captureElectronScripts = async (
is_module: script.isModule,
language: script.language,
};
const scriptKey = `electron_script_${canonicalDigest(identity, "CDP capture")}`;
const scriptKey = `electron_script_${digestCanonicalValue(identity, "CDP capture")}`;
if (seen.has(scriptKey)) continue;
seen.add(scriptKey);
total += 1;
+3 -14
View File
@@ -22,10 +22,7 @@ export const registerUtilityCommands = (
cli: CliInstance,
logger: Logger,
environment: Readonly<Record<string, string | undefined>>,
analysis: Pick<
DirectAnalysis,
"runProviderAnalysis" | "runProviderStatus" | "runCapabilityStatus"
>,
analysis: Pick<DirectAnalysis, "runProviderAnalysis" | "runSessionStatus">,
): void => {
const { runProviderAnalysis } = analysis;
registerCapabilityCommands(cli, logger, analysis);
@@ -129,10 +126,7 @@ const invalidNativeUiScenarioInput = (
const registerCapabilityCommands = (
cli: CliInstance,
logger: Logger,
{
runProviderStatus,
runCapabilityStatus,
}: Pick<DirectAnalysis, "runProviderStatus" | "runCapabilityStatus">,
{ runSessionStatus }: Pick<DirectAnalysis, "runSessionStatus">,
): void => {
for (const command of [
CLI_COMMANDS.capabilities,
@@ -143,12 +137,7 @@ const registerCapabilityCommands = (
command === "capabilities"
? "List provider capabilities and side effects"
: "List configured analysis providers",
run: () =>
logCliCommand(logger, command, () =>
command === CLI_COMMANDS.providers
? runProviderStatus(logger)
: runCapabilityStatus(logger),
),
run: () => logCliCommand(logger, command, () => runSessionStatus(logger)),
});
}
};
+2 -6
View File
@@ -5,10 +5,7 @@ import {
runProviderAnalysis as executeProviderAnalysis,
runManagedProviderExecution as executeManagedProvider,
} from "../application/DirectAnalysis.js";
import {
runCapabilityStatus as executeCapabilityStatus,
runProviderStatus as executeProviderStatus,
} from "../application/DirectAnalysisStatus.js";
import { runSessionStatus as executeSessionStatus } from "../application/DirectAnalysisStatus.js";
import type { DirectAnalysisDependencies } from "../application/DirectAnalysisDependencies.js";
import { createBinarySession, createManagedBinarySession } from "./binary.js";
@@ -26,8 +23,7 @@ export const createDirectAnalysis = (
return {
runDirectAnalysis: executeDirectAnalysis.bind(undefined, dependencies),
runProviderAnalysis: executeProviderAnalysis.bind(undefined, dependencies),
runProviderStatus: executeProviderStatus.bind(undefined, dependencies),
runCapabilityStatus: executeCapabilityStatus.bind(undefined, dependencies),
runSessionStatus: executeSessionStatus.bind(undefined, dependencies),
};
};
+5 -5
View File
@@ -1,11 +1,11 @@
import { canonicalDigest } from "../domain/comparisonSemantics.js";
import { digestCanonicalValue } from "../domain/canonicalDigest.js";
import { createEvidence } from "../domain/evidence.js";
import { jsonValueSchema } from "../domain/jsonValue.js";
const inventory = (digit: string) => {
const sha = digit.repeat(64);
const artifactId = `art_${canonicalDigest({ sha256: sha }, "Artifact example")}`;
const occurrenceId = `occ_${canonicalDigest({ root: artifactId }, "Artifact example")}`;
const artifactId = `art_${digestCanonicalValue({ sha256: sha }, "Artifact example")}`;
const occurrenceId = `occ_${digestCanonicalValue({ root: artifactId }, "Artifact example")}`;
const nodes = [
{
artifact_id: artifactId,
@@ -36,7 +36,7 @@ const inventory = (digit: string) => {
limitations: [],
},
];
const graphSha256 = canonicalDigest(
const graphSha256 = digestCanonicalValue(
{
nodes,
occurrences,
@@ -47,7 +47,7 @@ const inventory = (digit: string) => {
);
return jsonValueSchema.parse({
manifest: {
manifest_id: `agm_${canonicalDigest(
manifest_id: `agm_${digestCanonicalValue(
{
root_artifact_id: artifactId,
graph_sha256: graphSha256,
@@ -110,7 +110,8 @@ const semanticGraph = createJavaScriptSemanticGraph({
limitations: ["Semantic relations are unavailable in this minimal example."],
});
const staticEvidence = createEvidence(
/** Minimal valid static application Evidence for graph workflow examples. */
export const JAVASCRIPT_APPLICATION_EVIDENCE_EXAMPLE = createEvidence(
{ path: inputPath, sha256: applicationSha256, format: "directory" },
{
id: "rea-javascript-application",
@@ -171,9 +172,6 @@ const staticEvidence = createEvidence(
},
);
/** Minimal valid static application Evidence for graph workflow examples. */
export const JAVASCRIPT_APPLICATION_EVIDENCE_EXAMPLE = staticEvidence;
const runtimeEvidence = createEvidence(
undefined,
{
@@ -238,7 +236,7 @@ const runtimeEvidence = createEvidence(
/** Compact valid Evidence pair used by the public reconciliation contract. */
export const JAVASCRIPT_RUNTIME_RECONCILIATION_EXAMPLE = {
static_layers: [{ analysis: staticEvidence }],
static_layers: [{ analysis: JAVASCRIPT_APPLICATION_EVIDENCE_EXAMPLE }],
runtime_observations: [runtimeEvidence],
};
@@ -1,18 +1,16 @@
import { z } from "zod";
import { isAbsolute } from "node:path";
import type { ToolContract } from "../toolContractTypes.js";
import { managedOutputSchemas } from "../toolOutputSchemaGroups.js";
import { toolContractMetadata } from "../toolEffects.js";
import { requireOutputSchema } from "../toolOutputSchemaPrimitives.js";
import {
isAbsoluteLocalPath,
localPathStringSchema,
} from "../../domain/localPath.js";
import { localPathStringSchema } from "../../domain/localPath.js";
/** Exact caller boundary for execution-free PE/CLI triage and identity. */
export const managedTargetInputSchema = z.strictObject({
path: localPathStringSchema
.refine(isAbsoluteLocalPath, {
.refine(isAbsolute, {
message:
"path must be an absolute local filesystem path (for example /tmp/Example.dll or C:\\Example.dll)",
})
@@ -1,11 +1,11 @@
import { canonicalDigest } from "../domain/comparisonSemantics.js";
import { digestCanonicalValue } from "../domain/canonicalDigest.js";
import { createEvidence } from "../domain/evidence.js";
import { jsonValueSchema } from "../domain/jsonValue.js";
const inventory = (format: "apk" | "ipa" | "directory", digit: string) => {
const sha = digit.repeat(64);
const artifactId = `art_${canonicalDigest({ sha256: sha }, "Artifact inventory")}`;
const occurrenceId = `occ_${canonicalDigest(
const artifactId = `art_${digestCanonicalValue({ sha256: sha }, "Artifact inventory")}`;
const occurrenceId = `occ_${digestCanonicalValue(
{ root: artifactId },
"Artifact inventory",
)}`;
@@ -39,7 +39,7 @@ const inventory = (format: "apk" | "ipa" | "directory", digit: string) => {
limitations: [],
},
];
const graphSha256 = canonicalDigest(
const graphSha256 = digestCanonicalValue(
{
nodes,
occurrences,
@@ -50,7 +50,7 @@ const inventory = (format: "apk" | "ipa" | "directory", digit: string) => {
);
return jsonValueSchema.parse({
manifest: {
manifest_id: `agm_${canonicalDigest(
manifest_id: `agm_${digestCanonicalValue(
{
root_artifact_id: artifactId,
graph_sha256: graphSha256,
+4 -6
View File
@@ -1,14 +1,12 @@
import { isAbsolute } from "node:path";
import { executableFormatHintSchema } from "../domain/dosCom.js";
import {
isAbsoluteLocalPath,
localPathStringSchema,
} from "../domain/localPath.js";
import { localPathStringSchema } from "../domain/localPath.js";
import { z } from "zod";
import { analysisProviderSelectorSchema } from "./providerSelection.js";
/** Caller-supplied analysis snapshot path; must name its destination absolutely. */
const snapshotPathSchema = localPathStringSchema
.refine(isAbsoluteLocalPath, {
.refine(isAbsolute, {
message:
"snapshot_path must be an absolute local filesystem path (for example /tmp/rea/analysis.json or C:\\rea\\analysis.json)",
})
@@ -18,7 +16,7 @@ const snapshotPathSchema = localPathStringSchema
/** Caller-supplied analysis target path; must name the target absolutely. */
const binaryTargetPathSchema = localPathStringSchema
.refine(isAbsoluteLocalPath, {
.refine(isAbsolute, {
message:
"path must be an absolute local filesystem path (for example /tmp/fixture.bin or C:\\analysis\\fixture.bin)",
})
+3 -5
View File
@@ -1,9 +1,7 @@
import { z } from "zod";
import { isAbsolute } from "node:path";
import {
isAbsoluteLocalPath,
localPathStringSchema,
} from "../domain/localPath.js";
import { localPathStringSchema } from "../domain/localPath.js";
/** Optional expectations used to compare the live session with its caller. */
export const binarySessionInputSchema = z.strictObject({
@@ -13,7 +11,7 @@ export const binarySessionInputSchema = z.strictObject({
.regex(/^[a-f0-9]{64}$/u)
.optional(),
expected_server_path: localPathStringSchema
.refine(isAbsoluteLocalPath, {
.refine(isAbsolute, {
message:
"expected_server_path must be an absolute local filesystem path (for example /opt/rea/dist/main.js)",
})
+3 -5
View File
@@ -1,9 +1,7 @@
import { z } from "zod";
import {
isAbsoluteLocalPath,
localPathStringSchema,
} from "../domain/localPath.js";
import { isAbsolute } from "node:path";
import { localPathStringSchema } from "../domain/localPath.js";
import { artifactComparisonInputSchema } from "../domain/artifactComparison.js";
import { bundleComparisonInputSchema } from "../domain/bundleComparison.js";
import { callPathInputSchema } from "../domain/callPathSchemas.js";
@@ -57,7 +55,7 @@ const session = <
/** Session-owned Evidence bundle export options. */
export const exportEvidenceBundleInputSchema = z.strictObject({
path: localPathStringSchema
.refine(isAbsoluteLocalPath, {
.refine(isAbsolute, {
message:
"path must be an absolute local filesystem path (for example /tmp/rea/evidence.json or C:\\rea\\evidence.json)",
})
+3 -5
View File
@@ -1,9 +1,7 @@
import { z } from "zod";
import { isAbsolute } from "node:path";
import {
isAbsoluteLocalPath,
localPathStringSchema,
} from "../domain/localPath.js";
import { localPathStringSchema } from "../domain/localPath.js";
import { processTraceSpecificationSchema } from "../domain/process/processTraceSpecification.js";
import { evidenceSchema } from "../domain/evidence.js";
@@ -26,7 +24,7 @@ export const addressContextInputSchema = z.strictObject({
/** Session-owned Evidence bundle import options. */
export const importEvidenceBundleInputSchema = z.strictObject({
path: localPathStringSchema
.refine(isAbsoluteLocalPath, {
.refine(isAbsolute, {
message:
"path must be an absolute local filesystem path (for example /tmp/evidence.json or C:\\rea\\evidence.json)",
})
+8 -8
View File
@@ -3,8 +3,8 @@ import { createHash } from "node:crypto";
import { describe, expect, it } from "vitest";
import { projectAppleApplication } from "./appleApplication.js";
import { compareCodePoints } from "../canonicalOrdering.js";
import { canonicalDigest } from "../comparisonSemantics.js";
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
import { digestCanonicalValue } from "../canonicalDigest.js";
import { createEvidence, type Evidence } from "../evidence.js";
import { jsonValueSchema } from "../jsonValue.js";
@@ -20,7 +20,7 @@ interface FixtureEntry {
const sha = (text: string): string =>
createHash("sha256").update(text).digest("hex");
const artifactId = (sha256: string): string =>
`art_${canonicalDigest({ sha256 }, "Artifact inventory")}`;
`art_${digestCanonicalValue({ sha256 }, "Artifact inventory")}`;
/** Build one complete, content-addressed inventory page for pure projection tests. */
const inventoryEvidence = (
@@ -57,8 +57,8 @@ const inventoryEvidence = (
) => ({
occurrence_id:
path === "."
? `occ_${canonicalDigest({ root: rootId }, "Artifact inventory")}`
: `occ_${canonicalDigest(
? `occ_${digestCanonicalValue({ root: rootId }, "Artifact inventory")}`
: `occ_${digestCanonicalValue(
{ root_artifact_id: rootId, logical_path: path, entry_kind: kind },
"Artifact inventory",
)}`,
@@ -97,9 +97,9 @@ const inventoryEvidence = (
left.artifact_id.localeCompare(right.artifact_id),
);
const sortedOccurrences = occurrences.sort((left, right) =>
compareCodePoints(left.logical_path, right.logical_path),
compareUnicodeCodePoints(left.logical_path, right.logical_path),
);
const graphSha256 = canonicalDigest(
const graphSha256 = digestCanonicalValue(
{
nodes: sortedNodes,
occurrences: sortedOccurrences,
@@ -122,7 +122,7 @@ const inventoryEvidence = (
authority: "shipped-artifact",
result: jsonValueSchema.parse({
manifest: {
manifest_id: `agm_${canonicalDigest(
manifest_id: `agm_${digestCanonicalValue(
{ root_artifact_id: rootId, graph_sha256: graphSha256 },
"Artifact inventory",
)}`,
+4 -4
View File
@@ -1,4 +1,4 @@
import { compareCodePoints } from "./canonicalOrdering.js";
import { compareUnicodeCodePoints } from "./unicodeCodePointOrder.js";
import { z } from "zod";
import {
@@ -147,7 +147,7 @@ export const compareArtifacts = (
right_manifest_id: right.inventory.manifest.manifest_id,
summary,
changes,
limitations: [...new Set(limitations)].sort(compareCodePoints),
limitations: [...new Set(limitations)].sort(compareUnicodeCodePoints),
});
};
@@ -171,7 +171,7 @@ const compareOccurrences = (
const rightRelations = relationsByPath(right);
const paths = [
...new Set([...leftByPath.keys(), ...rightByPath.keys()]),
].sort(compareCodePoints);
].sort(compareUnicodeCodePoints);
const output: ArtifactChange[] = [];
for (const path of paths) {
const leftOccurrence = leftByPath.get(path);
@@ -370,7 +370,7 @@ const relationsByPath = (
}
for (const values of output.values())
values.sort((left, right) =>
compareCodePoints(canonicalJson(left), canonicalJson(right)),
compareUnicodeCodePoints(canonicalJson(left), canonicalJson(right)),
);
return output;
};
+8 -8
View File
@@ -1,4 +1,4 @@
import { canonicalDigest } from "./comparisonSemantics.js";
import { digestCanonicalValue } from "./canonicalDigest.js";
import type {
ArtifactEdge,
ArtifactNode,
@@ -8,7 +8,7 @@ import type {
/** Content-address an artifact independently of where it was observed. */
export const artifactIdForContent = (sha256: string): string =>
`art_${canonicalDigest({ sha256 }, "Artifact")}`;
`art_${digestCanonicalValue({ sha256 }, "Artifact")}`;
/** Identify an occurrence by its root and exact logical location. */
export const occurrenceIdForLocation = (input: {
@@ -17,8 +17,8 @@ export const occurrenceIdForLocation = (input: {
readonly entryKind: ArtifactOccurrence["entry_kind"];
}): string =>
input.logicalPath === "."
? `occ_${canonicalDigest({ root: input.rootArtifactId }, "Artifact")}`
: `occ_${canonicalDigest(
? `occ_${digestCanonicalValue({ root: input.rootArtifactId }, "Artifact")}`
: `occ_${digestCanonicalValue(
{
root_artifact_id: input.rootArtifactId,
logical_path: input.logicalPath,
@@ -34,7 +34,7 @@ export const artifactGraphDigest = (input: {
readonly edges: readonly ArtifactEdge[];
readonly contradictions: readonly IntegrityContradiction[];
}): string =>
canonicalDigest(
digestCanonicalValue(
{
nodes: input.nodes,
occurrences: input.occurrences,
@@ -49,7 +49,7 @@ export const artifactManifestId = (
rootArtifactId: string,
graphSha256: string,
): string =>
`agm_${canonicalDigest(
`agm_${digestCanonicalValue(
{ root_artifact_id: rootArtifactId, graph_sha256: graphSha256 },
"Artifact",
)}`;
@@ -61,7 +61,7 @@ export const artifactContradictionId = (input: {
readonly declaredSha256: string;
readonly observedSha256: string;
}): string =>
`ic_${canonicalDigest(
`ic_${digestCanonicalValue(
{
root_artifact_id: input.rootArtifactId,
logical_path: input.logicalPath,
@@ -81,4 +81,4 @@ export const artifactEdgeId = (
| "occurrence_id"
| "logical_path"
>,
): string => `edge_${canonicalDigest(semantic, "Artifact")}`;
): string => `edge_${digestCanonicalValue(semantic, "Artifact")}`;
+8 -8
View File
@@ -4,7 +4,7 @@ import {
artifactInventoryResultSchema,
type ArtifactInventoryResult,
} from "./artifactGraph.js";
import { canonicalDigest } from "./comparisonSemantics.js";
import { digestCanonicalValue } from "./canonicalDigest.js";
import { uniqueSorted } from "./canonicalOrdering.js";
import { evidenceSchema, parseEvidence, type Evidence } from "./evidence.js";
import { jsonValueSchema } from "./jsonValue.js";
@@ -170,7 +170,7 @@ export const createArtifactInspection = (
},
substeps: [
{
substep_id: `ais_${canonicalDigest(
substep_id: `ais_${digestCanonicalValue(
{
operation: "inventory_artifact",
evidence_id: inventoryEvidence.evidence_id,
@@ -211,7 +211,7 @@ export const createArtifactInspection = (
};
return artifactInspectionResultSchema.parse({
...semantic,
inspection_id: `ai_${canonicalDigest(semantic, "Artifact inspection")}`,
inspection_id: `ai_${digestCanonicalValue(semantic, "Artifact inspection")}`,
});
};
@@ -258,7 +258,7 @@ const observation = (
evidence_id: evidenceId,
};
return {
observation_id: `aio_${canonicalDigest(semantic, "Artifact inspection")}`,
observation_id: `aio_${digestCanonicalValue(semantic, "Artifact inspection")}`,
...semantic,
};
};
@@ -277,7 +277,7 @@ const allRelationships = (
evidence_id: evidenceId,
};
return {
relationship_id: `air_${canonicalDigest(semantic, "Artifact inspection")}`,
relationship_id: `air_${digestCanonicalValue(semantic, "Artifact inspection")}`,
...semantic,
};
});
@@ -305,7 +305,7 @@ const allHypotheses = (
"Format classification proposes a bounded follow-up; it is not semantic proof.",
};
return {
hypothesis_id: `aih_${canonicalDigest(semantic, "Artifact inspection")}`,
hypothesis_id: `aih_${digestCanonicalValue(semantic, "Artifact inspection")}`,
...semantic,
};
});
@@ -324,7 +324,7 @@ const contradictions = (
evidence_id: evidenceId,
};
return {
contradiction_id: `aic_${canonicalDigest(semantic, "Artifact inspection")}`,
contradiction_id: `aic_${digestCanonicalValue(semantic, "Artifact inspection")}`,
...semantic,
};
});
@@ -418,7 +418,7 @@ const branch = (
evidence_id: evidenceId,
};
return {
branch_id: `aib_${canonicalDigest(semantic, "Artifact inspection")}`,
branch_id: `aib_${digestCanonicalValue(semantic, "Artifact inspection")}`,
...semantic,
};
};
+5 -5
View File
@@ -5,7 +5,7 @@ import {
type ArtifactOccurrence,
type IntegrityContradiction,
} from "./artifactGraph.js";
import { compareCodePoints } from "./canonicalOrdering.js";
import { compareUnicodeCodePoints } from "./unicodeCodePointOrder.js";
import { canonicalJson } from "./comparisonSemantics.js";
import {
artifactContradictionId,
@@ -163,13 +163,13 @@ const orderedInventory = (
},
): InventorySet => {
const nodes = [...values.nodes.values()].sort((left, right) =>
compareCodePoints(left.artifact_id, right.artifact_id),
compareUnicodeCodePoints(left.artifact_id, right.artifact_id),
);
const occurrences = [...values.occurrences.values()].sort((left, right) =>
compareCodePoints(left.logical_path, right.logical_path),
compareUnicodeCodePoints(left.logical_path, right.logical_path),
);
const edges = [...values.edges.values()].sort((left, right) =>
compareCodePoints(left.edge_id, right.edge_id),
compareUnicodeCodePoints(left.edge_id, right.edge_id),
);
return {
manifest: first.manifest,
@@ -179,7 +179,7 @@ const orderedInventory = (
integrityContradictions: first.integrity_contradictions,
limitations: [
...new Set(pages.flatMap(({ inventory }) => inventory.limitations)),
].sort(compareCodePoints),
].sort(compareUnicodeCodePoints),
complete:
nodes.length === first.manifest.node_count &&
occurrences.length === first.manifest.occurrence_count &&
+3 -3
View File
@@ -1,4 +1,4 @@
import { canonicalDigest } from "./comparisonSemantics.js";
import { digestCanonicalValue } from "./canonicalDigest.js";
import type {
BrowserScenarioArtifactKind,
@@ -30,7 +30,7 @@ export const commitBrowserScenarioNormalization = (
const commitment = { built_in_rules: [...BUILT_IN_RULES], rules };
return {
...commitment,
sha256: canonicalDigest(commitment, "Browser scenario"),
sha256: digestCanonicalValue(commitment, "Browser scenario"),
};
};
@@ -40,7 +40,7 @@ export const digestNormalizedScenarioValue = (
artifact: BrowserScenarioArtifactKind,
rules: readonly BrowserScenarioNormalizationRule[],
): string =>
canonicalDigest(normalize(value, artifact, rules), "Browser scenario");
digestCanonicalValue(normalize(value, artifact, rules), "Browser scenario");
const normalize = (
value: unknown,
+6 -8
View File
@@ -1,8 +1,9 @@
import { z } from "zod";
import { isAbsolute } from "node:path";
import { IDENTIFIER_PATTERN } from "./stringPatterns.js";
import { isAbsoluteLocalPath, localPathStringSchema } from "./localPath.js";
import { localPathStringSchema } from "./localPath.js";
import { browserNetworkContentSelectionSchema } from "./browserNetworkEvidence.js";
import {
@@ -12,13 +13,10 @@ import {
export const scenarioIdentifierSchema = z.string().regex(IDENTIFIER_PATTERN);
const browserExecutablePathSchema = localPathStringSchema.refine(
isAbsoluteLocalPath,
{
message:
"executable_path must be an absolute local filesystem path (for example /opt/chromium/chrome or C:\\chromium\\chrome.exe)",
},
);
const browserExecutablePathSchema = localPathStringSchema.refine(isAbsolute, {
message:
"executable_path must be an absolute local filesystem path (for example /opt/chromium/chrome or C:\\chromium\\chrome.exe)",
});
const browserScenarioBaseUrlSchema = z
.string()
+3 -5
View File
@@ -157,9 +157,9 @@ const parseSnapshots = (
const snapshots = new Map<string, FunctionSnapshot>();
for (const group of groups) {
const snapshot = parseFunctionEvidence(group);
const address = normalizeAddress(snapshot.dossier.procedure.address);
const address = parseCallPathAddress(snapshot.dossier.procedure.address);
for (const callee of snapshot.dossier.callees)
normalizeAddress(callee.address);
parseCallPathAddress(callee.address);
if (snapshots.has(address))
throw new TypeError(`Duplicate function Evidence for ${address}`);
snapshots.set(address, snapshot);
@@ -196,7 +196,7 @@ const createGraph = (
for (const [address, snapshot] of snapshots) {
graph.mergeNode(address);
for (const callee of snapshot.dossier.callees) {
const calleeAddress = normalizeAddress(callee.address);
const calleeAddress = parseCallPathAddress(callee.address);
graph.mergeNode(calleeAddress);
graph.mergeDirectedEdge(address, calleeAddress);
}
@@ -371,5 +371,3 @@ const uniqueEvidence = (snapshots: Iterable<FunctionSnapshot>): string[] =>
[
...new Set([...snapshots].flatMap((snapshot) => snapshotLinks(snapshot))),
].sort((left, right) => left.localeCompare(right));
const normalizeAddress = (input: string): string => parseCallPathAddress(input);
+1 -15
View File
@@ -1,20 +1,6 @@
import { compareUnicodeCodePoints } from "./unicodeCodePointOrder.js";
/**
* Canonical code-point ordering shared across domain and application layers.
*
* Compares strings by Unicode code point so graph ordering, fingerprints, and
* deterministic tie-breaks stay stable regardless of locale. Prefer this over
* `String.prototype.localeCompare`, which is locale-dependent.
*
* Ordering by code point differs from ordering by UTF-16 code unit for astral
* code points (above U+FFFF), whose lead surrogate is numerically below the
* BMP tail U+E000-U+FFFF.
*/
export const compareCodePoints = (left: string, right: string): number =>
compareUnicodeCodePoints(left, right);
/** Deduplicate and sort strings with canonical code-point ordering. */
export const uniqueSorted = <Value extends string>(
values: readonly Value[],
): Value[] => [...new Set(values)].sort(compareCodePoints);
): Value[] => [...new Set(values)].sort(compareUnicodeCodePoints);
-10
View File
@@ -1,7 +1,5 @@
import canonicalize from "canonicalize";
import { digestCanonicalValue } from "./canonicalDigest.js";
/** Whether an inventory can support a claim that an item is absent. */
export const absenceClaimable = (coverage: {
readonly status: string;
@@ -12,14 +10,6 @@ export const absenceClaimable = (coverage: {
coverage.truncated !== true &&
(coverage.omitted_count === undefined || coverage.omitted_count === 0);
/** SHA-256 of canonical JSON for stable, provider-neutral comparison IDs. */
export const canonicalDigest = (
value: unknown,
context = "Comparison",
): string => {
return digestCanonicalValue(value, context);
};
/** Canonical JSON used for deterministic tie breaking and stable digests. */
export const canonicalJson = (
value: unknown,
+4 -3
View File
@@ -1,13 +1,14 @@
import { z } from "zod";
import { isAbsolute } from "node:path";
import { digestSchema } from "../digests.js";
import { jsonValueSchema } from "../jsonValue.js";
import { isAbsoluteLocalPath, localPathStringSchema } from "../localPath.js";
import { localPathStringSchema } from "../localPath.js";
const bytes = z.number().int().min(0).max(Number.MAX_SAFE_INTEGER);
const range = z.strictObject({ offset: bytes, length: bytes.positive() });
const target = {
path: localPathStringSchema
.refine(isAbsoluteLocalPath, {
.refine(isAbsolute, {
message:
"path must be an absolute local filesystem path (for example /tmp/firmware.bin or C:\\firmware\\firmware.bin)",
})
@@ -22,7 +23,7 @@ export const firmwareInputSchemas = {
extract_firmware: z.strictObject({
...target,
output_directory: localPathStringSchema
.refine(isAbsoluteLocalPath, {
.refine(isAbsolute, {
message: "output_directory must be an absolute local filesystem path",
})
.describe("Absolute, absent output directory; parent must exist"),
+6 -5
View File
@@ -5,8 +5,9 @@ import { enhancedInputSchemas } from "../contracts/enhancedInputs.js";
import { functionComparisonResultSchema } from "./functionComparisonSchemas.js";
import { createEvidence, type Evidence } from "./evidence.js";
import { functionDossierSchema } from "./hopperValues.js";
import { canonicalDigest, canonicalJson } from "./comparisonSemantics.js";
import { compareCodePoints } from "./canonicalOrdering.js";
import { canonicalJson } from "./comparisonSemantics.js";
import { digestCanonicalValue } from "./canonicalDigest.js";
import { compareUnicodeCodePoints } from "./unicodeCodePointOrder.js";
import { jsonValueSchema } from "./jsonValue.js";
import { compareFunctions } from "./functionComparison.js";
@@ -228,7 +229,7 @@ describe("function collection collation ties", () => {
const canonicalProjection = callees
.map(({ name }) => ({ direction: "out", name }))
.sort((left, right) =>
compareCodePoints(canonicalJson(left), canonicalJson(right)),
compareUnicodeCodePoints(canonicalJson(left), canonicalJson(right)),
);
const result = compareFunctions(
observe("b", calling(callees)),
@@ -239,8 +240,8 @@ describe("function collection collation ties", () => {
);
expect(calls).toMatchObject({
status: "unchanged",
left_digest: canonicalDigest(canonicalProjection),
right_digest: canonicalDigest(canonicalProjection),
left_digest: digestCanonicalValue(canonicalProjection),
right_digest: digestCanonicalValue(canonicalProjection),
});
});
});
@@ -1,5 +1,5 @@
import { canonicalJson } from "./comparisonSemantics.js";
import { compareCodePoints } from "./canonicalOrdering.js";
import { compareUnicodeCodePoints } from "./unicodeCodePointOrder.js";
import type { FunctionSnapshot } from "./functionDossierEvidence.js";
import type { FunctionComparisonResult } from "./functionComparisonSchemas.js";
@@ -179,7 +179,7 @@ export const sorted = (values: readonly unknown[]): readonly unknown[] =>
value,
json: canonicalJson(value, "Function normalization"),
}))
.sort((left, right) => compareCodePoints(left.json, right.json))
.sort((left, right) => compareUnicodeCodePoints(left.json, right.json))
.map(({ value }) => value);
export const isAutoName = (name: string): boolean =>
+2 -2
View File
@@ -1,4 +1,4 @@
import { canonicalDigest } from "./comparisonSemantics.js";
import { digestCanonicalValue } from "./canonicalDigest.js";
import type {
DimensionName,
FunctionComparisonResult,
@@ -87,4 +87,4 @@ export const summarize = (dimensions: readonly FunctionDimension[]) => ({
});
const digest = (value: unknown): string =>
canonicalDigest(value, "Function comparison");
digestCanonicalValue(value, "Function comparison");
@@ -1,8 +1,9 @@
import { z } from "zod";
import { isAbsolute } from "node:path";
import { isAbsoluteLocalPath, localPathStringSchema } from "../localPath.js";
import { localPathStringSchema } from "../localPath.js";
const pathInputSchema = localPathStringSchema.refine(isAbsoluteLocalPath, {
const pathInputSchema = localPathStringSchema.refine(isAbsolute, {
message:
"Electron executable, application, and root paths must be absolute local filesystem paths (for example /Applications/Electron.app/Contents/MacOS/Electron)",
});
@@ -15,7 +15,7 @@ import {
electronStaticValue,
objectProperty,
} from "./electronStaticAnalysisValues.js";
import { compareCodePoints } from "../canonicalOrdering.js";
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
import { addLocatedFinding } from "./javascriptStaticAnalysisFindings.js";
import {
argumentNode,
@@ -150,7 +150,9 @@ const collectWebPreferences = (
if (value.status === "dynamic") unknown += 1;
preferences.push({ name, value });
}
preferences.sort((left, right) => compareCodePoints(left.name, right.name));
preferences.sort((left, right) =>
compareUnicodeCodePoints(left.name, right.name),
);
return {
status: "object-literal",
preferences,
@@ -1,6 +1,6 @@
import * as t from "@babel/types";
import { compareCodePoints } from "../canonicalOrdering.js";
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
import { stripQueryAndFragment } from "../artifactPathSyntax.js";
import type { ElectronNativeAddonBindingFinding } from "./electronStaticAnalysisTypes.js";
import { addLocatedFinding } from "./javascriptStaticAnalysisFindings.js";
@@ -137,7 +137,7 @@ const addBinding = (input: NativeBindingInput): void => {
const { context, node, specifier, kind } = input;
const unique = [
...new Set(input.members.filter((member) => member !== "")),
].sort(compareCodePoints);
].sort(compareUnicodeCodePoints);
const members = unique.length === 0 ? ["*"] : unique;
addLocatedFinding(context, {
collection: context.accumulator.nativeAddonBindings,
@@ -1,6 +1,6 @@
import * as t from "@babel/types";
import { compareCodePoints } from "../canonicalOrdering.js";
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
import type { ElectronStaticValue } from "./electronStaticAnalysisTypes.js";
import {
readExactJavaScriptLiteral,
@@ -105,7 +105,7 @@ export const collectContextBridgeMembers = (
unknown: 0,
};
collectMembersAt(node, "", 0, state);
const members = [...new Set(state.members)].sort(compareCodePoints);
const members = [...new Set(state.members)].sort(compareUnicodeCodePoints);
return {
status: "object-literal",
members,
@@ -9,7 +9,8 @@ import type {
ApplicationNode,
} from "./javascriptApplicationGraphSchemas.js";
import type { ApplicationGraphEvidence } from "./javascriptApplicationEvidenceSchemas.js";
import { compareCodePoints, uniqueSorted } from "../canonicalOrdering.js";
import { uniqueSorted } from "../canonicalOrdering.js";
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
import type { ApplicationVersionComparisonItem } from "./javascriptApplicationVersionComparisonSchemas.js";
interface ChangeGraphInput {
@@ -218,7 +219,7 @@ const sourceCoverageLimits = (input: ChangeGraphInput) =>
(limit) => [`${limit.name}\0${limit.value}\0${limit.unit}`, limit],
),
).values(),
].sort((left, right) => compareCodePoints(left.name, right.name));
].sort((left, right) => compareUnicodeCodePoints(left.name, right.name));
const combineOmittedCounts = (left: number | null, right: number | null) =>
left === null || right === null ? null : left + right;
@@ -238,7 +239,7 @@ const mergeNodes = (nodes: readonly ApplicationNode[]): ApplicationNode[] => {
.map((observation) => [observation.observation_id, observation]),
).values(),
].sort((left, right) =>
compareCodePoints(left.observation_id, right.observation_id),
compareUnicodeCodePoints(left.observation_id, right.observation_id),
);
return createJavaScriptApplicationNode({
kind: first.kind,
@@ -250,11 +251,11 @@ const mergeNodes = (nodes: readonly ApplicationNode[]): ApplicationNode[] => {
});
});
return merged.sort((left, right) =>
compareCodePoints(left.node_id, right.node_id),
compareUnicodeCodePoints(left.node_id, right.node_id),
);
};
const uniqueEdges = (edges: readonly ApplicationEdge[]): ApplicationEdge[] =>
[...new Map(edges.map((edge) => [edge.edge_id, edge])).values()].sort(
(left, right) => compareCodePoints(left.edge_id, right.edge_id),
(left, right) => compareUnicodeCodePoints(left.edge_id, right.edge_id),
);
@@ -1,6 +1,8 @@
import { z } from "zod";
import { canonicalDigest, canonicalJson } from "../comparisonSemantics.js";
import { compareCodePoints, uniqueSorted } from "../canonicalOrdering.js";
import { canonicalJson } from "../comparisonSemantics.js";
import { digestCanonicalValue } from "../canonicalDigest.js";
import { uniqueSorted } from "../canonicalOrdering.js";
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
import { freezeOwnedJsonSnapshotSteps } from "../immutableJson.js";
import type {
@@ -37,7 +39,7 @@ const normalizeLimits = (
limits: ApplicationGraphEvidence["coverage"]["limits"],
): ApplicationGraphEvidence["coverage"]["limits"] =>
[...new Map(limits.map((limit) => [limitKey(limit), limit])).values()].sort(
(left, right) => compareCodePoints(limitKey(left), limitKey(right)),
(left, right) => compareUnicodeCodePoints(limitKey(left), limitKey(right)),
);
const normalizeEvidence = (
@@ -65,7 +67,7 @@ const nodeSemantic = (node: Pick<ApplicationNode, "kind" | "identity">) => ({
});
const nodeId = (node: Pick<ApplicationNode, "kind" | "identity">): string =>
`jag_node_${canonicalDigest(nodeSemantic(node), "JavaScript Application Graph")}`;
`jag_node_${digestCanonicalValue(nodeSemantic(node), "JavaScript Application Graph")}`;
const observationSemantic = (
nodeIdentifier: string,
@@ -82,14 +84,14 @@ const observationId = (
"observation_id"
>,
): string =>
`jag_observation_${canonicalDigest(
`jag_observation_${digestCanonicalValue(
observationSemantic(nodeIdentifier, observation),
)}`;
type EdgeSemantic = Omit<ApplicationEdge, "edge_id">;
const edgeId = (edge: EdgeSemantic): string =>
`jag_edge_${canonicalDigest(edge, "JavaScript Application Graph")}`;
`jag_edge_${digestCanonicalValue(edge, "JavaScript Application Graph")}`;
const sortedUniqueIssue = (
values: readonly string[],
@@ -98,7 +100,9 @@ const sortedUniqueIssue = (
context: z.RefinementCtx,
): void => {
for (let index = 1; index < values.length; index += 1) {
if (compareCodePoints(values[index - 1] ?? "", values[index] ?? "") < 0)
if (
compareUnicodeCodePoints(values[index - 1] ?? "", values[index] ?? "") < 0
)
continue;
context.addIssue({
code: "custom",
@@ -394,7 +398,7 @@ const checkGraphInvariants = (
const { graph_id: identifier, ...semantic } = graph;
if (
identifier !==
`jag_${canonicalDigest(semantic, "JavaScript Application Graph")}`
`jag_${digestCanonicalValue(semantic, "JavaScript Application Graph")}`
)
context.addIssue({
code: "custom",
@@ -432,7 +436,7 @@ export const createJavaScriptApplicationNode = (
});
})
.sort((left, right) =>
compareCodePoints(left.observation_id, right.observation_id),
compareUnicodeCodePoints(left.observation_id, right.observation_id),
);
return applicationNodeSchema.parse({
node_id: identifier,
@@ -467,10 +471,10 @@ export const createJavaScriptApplicationGraph = (
...parsed,
root_node_ids: uniqueSorted(parsed.root_node_ids),
nodes: [...parsed.nodes].sort((left, right) =>
compareCodePoints(left.node_id, right.node_id),
compareUnicodeCodePoints(left.node_id, right.node_id),
),
edges: [...parsed.edges].sort((left, right) =>
compareCodePoints(left.edge_id, right.edge_id),
compareUnicodeCodePoints(left.edge_id, right.edge_id),
),
coverage: {
...parsed.coverage,
@@ -480,7 +484,7 @@ export const createJavaScriptApplicationGraph = (
};
return javascriptApplicationGraphSchema.parse({
...semantic,
graph_id: `jag_${canonicalDigest(semantic, "JavaScript Application Graph")}`,
graph_id: `jag_${digestCanonicalValue(semantic, "JavaScript Application Graph")}`,
});
};
@@ -517,7 +521,7 @@ export const parseJavaScriptApplicationGraph = (
export const computeJavaScriptApplicationGraphSha256 = (
input: unknown,
): string =>
canonicalDigest(
digestCanonicalValue(
parseJavaScriptApplicationGraph(input),
"JavaScript Application Graph",
);
@@ -1,4 +1,4 @@
import { canonicalDigest } from "../comparisonSemantics.js";
import { digestCanonicalValue } from "../canonicalDigest.js";
import { uniqueSorted } from "../canonicalOrdering.js";
import type { Evidence } from "../evidence.js";
import { buildJavaScriptApplicationChangeGraph } from "./javascriptApplicationChangeGraph.js";
@@ -78,7 +78,7 @@ export const compareJavaScriptApplicationVersions = (
};
return applicationVersionComparisonResultSchema.parse({
...semantic,
comparison_id: `javc_${canonicalDigest(semantic, "Application version comparison")}`,
comparison_id: `javc_${digestCanonicalValue(semantic, "Application version comparison")}`,
});
};
@@ -39,7 +39,6 @@ export const compareApplicationVersionsInputSchema = z
}
});
const emptyCandidateNodesSchema = emptyArraySchema;
const candidateNodesSchema = z.tuple([nodeIdSchema]).rest(nodeIdSchema);
const exactMatchSchema = z.strictObject({
status: z.literal("matched"),
@@ -49,22 +48,22 @@ const exactMatchSchema = z.strictObject({
"exact-module-source-digest",
]),
confidence: z.literal("exact"),
candidate_left_node_ids: emptyCandidateNodesSchema,
candidate_right_node_ids: emptyCandidateNodesSchema,
candidate_left_node_ids: emptyArraySchema,
candidate_right_node_ids: emptyArraySchema,
});
const sourceMapMatchSchema = z.strictObject({
status: z.literal("matched"),
basis: z.literal("source-map-identity"),
confidence: z.literal("high"),
candidate_left_node_ids: emptyCandidateNodesSchema,
candidate_right_node_ids: emptyCandidateNodesSchema,
candidate_left_node_ids: emptyArraySchema,
candidate_right_node_ids: emptyArraySchema,
});
const inferredMatchSchema = z.strictObject({
status: z.literal("matched"),
basis: z.enum(["structural-fingerprint", "semantic-key"]),
confidence: z.literal("medium"),
candidate_left_node_ids: emptyCandidateNodesSchema,
candidate_right_node_ids: emptyCandidateNodesSchema,
candidate_left_node_ids: emptyArraySchema,
candidate_right_node_ids: emptyArraySchema,
});
const matchedItemMatchSchema = z.union([
exactMatchSchema,
@@ -75,14 +74,14 @@ const unmatchedItemMatchSchema = z.strictObject({
status: z.literal("unmatched"),
basis: z.literal("none"),
confidence: z.literal("unknown"),
candidate_left_node_ids: emptyCandidateNodesSchema,
candidate_right_node_ids: emptyCandidateNodesSchema,
candidate_left_node_ids: emptyArraySchema,
candidate_right_node_ids: emptyArraySchema,
});
const leftAmbiguousMatchSchema = z.strictObject({
status: z.literal("ambiguous"),
basis: z.literal("none"),
confidence: z.literal("unknown"),
candidate_left_node_ids: emptyCandidateNodesSchema,
candidate_left_node_ids: emptyArraySchema,
candidate_right_node_ids: candidateNodesSchema,
});
const rightAmbiguousMatchSchema = z.strictObject({
@@ -90,7 +89,7 @@ const rightAmbiguousMatchSchema = z.strictObject({
basis: z.literal("none"),
confidence: z.literal("unknown"),
candidate_left_node_ids: candidateNodesSchema,
candidate_right_node_ids: emptyCandidateNodesSchema,
candidate_right_node_ids: emptyArraySchema,
});
const comparisonItemContextShape = {
@@ -1,9 +1,6 @@
import {
absenceClaimable,
canonicalDigest,
canonicalJson,
} from "../comparisonSemantics.js";
import { compareCodePoints } from "../canonicalOrdering.js";
import { absenceClaimable, canonicalJson } from "../comparisonSemantics.js";
import { digestCanonicalValue } from "../canonicalDigest.js";
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
import type { Evidence } from "../evidence.js";
import type {
ApplicationEdge,
@@ -96,7 +93,9 @@ export const classifyJavaScriptApplicationVersions = (
...matching.pairs.map((pair) => matchedItem(pair, fullContext)),
...leftItems,
...rightItems,
].sort((left, right) => compareCodePoints(left.item_id, right.item_id)),
].sort((left, right) =>
compareUnicodeCodePoints(left.item_id, right.item_id),
),
};
};
@@ -326,7 +325,7 @@ const locations = (node: ApplicationNode) =>
evidence.location.available ? evidence.location.value : evidence.location,
)
.sort((left, right) =>
compareCodePoints(canonicalJson(left), canonicalJson(right)),
compareUnicodeCodePoints(canonicalJson(left), canonicalJson(right)),
);
const properties = (node: ApplicationNode) =>
@@ -337,7 +336,7 @@ const properties = (node: ApplicationNode) =>
source_map_reference,
}))
.sort((left, right) =>
compareCodePoints(canonicalJson(left), canonicalJson(right)),
compareUnicodeCodePoints(canonicalJson(left), canonicalJson(right)),
);
const relationshipSignature = (
@@ -371,7 +370,7 @@ const relationshipSignature = (
];
return [];
});
return canonicalJson(signatures.sort(compareCodePoints));
return canonicalJson(signatures.sort(compareUnicodeCodePoints));
};
interface EdgeSignatureContext {
@@ -400,7 +399,7 @@ const itemEvidenceLinks = (
...nativeLinks(left, context.leftNativeEvidence),
...nativeLinks(right, context.rightNativeEvidence),
]),
].sort(compareCodePoints);
].sort(compareUnicodeCodePoints);
const nativeLinks = (
node: ApplicationNode | undefined,
@@ -445,7 +444,7 @@ const itemWithId = <Semantic extends ComparisonItemSemantic>(
semantic: Semantic,
): Semantic & { readonly item_id: string } => ({
...semantic,
item_id: `javc_item_${canonicalDigest(semantic, "Application version item")}`,
item_id: `javc_item_${digestCanonicalValue(semantic, "Application version item")}`,
});
const stringProperty = (node: ApplicationNode, key: string): string | null => {
@@ -1,5 +1,5 @@
import { canonicalJson } from "../comparisonSemantics.js";
import { compareCodePoints } from "../canonicalOrdering.js";
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
import type { ApplicationNode } from "./javascriptApplicationGraphSchemas.js";
import type { JsonValue } from "../jsonValue.js";
@@ -85,7 +85,7 @@ export const matchJavaScriptApplicationVersions = (
const rightIds = new Set(remainingRight.map(({ node_id: id }) => id));
return {
pairs: pairs.sort((a, b) =>
compareCodePoints(
compareUnicodeCodePoints(
`${a.left.node_id}\0${a.right.node_id}`,
`${b.left.node_id}\0${b.right.node_id}`,
),
@@ -109,7 +109,7 @@ interface TierContext {
const applyTier = (context: TierContext): void => {
const leftByKey = nodesByKey(context.left.values(), context.tier.key);
const rightByKey = nodesByKey(context.right.values(), context.tier.key);
for (const key of [...leftByKey.keys()].sort(compareCodePoints)) {
for (const key of [...leftByKey.keys()].sort(compareUnicodeCodePoints)) {
const leftGroup = leftByKey.get(key) ?? [];
const rightGroup = rightByKey.get(key) ?? [];
if (rightGroup.length === 0) continue;
@@ -174,7 +174,7 @@ const filteredCandidates = (
[...candidates.entries()].flatMap(([nodeId, values]) => {
const filtered = [...values]
.filter((value) => retained.has(value))
.sort(compareCodePoints);
.sort(compareUnicodeCodePoints);
return filtered.length === 0 ? [] : [[nodeId, filtered] as const];
}),
);
@@ -309,10 +309,10 @@ const stringList = (value: unknown): string[] =>
Array.isArray(value)
? value
.filter((item): item is string => typeof item === "string")
.sort(compareCodePoints)
.sort(compareUnicodeCodePoints)
: [];
const sortedNodes = (nodes: Iterable<ApplicationNode>): ApplicationNode[] =>
[...nodes].sort((left, right) =>
compareCodePoints(left.node_id, right.node_id),
compareUnicodeCodePoints(left.node_id, right.node_id),
);
@@ -4,7 +4,7 @@ import * as t from "@babel/types";
import { traverseJavaScriptAst } from "./javascriptSemanticTraversal.js";
import { compareCodePoints } from "../canonicalOrdering.js";
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
import { propertyName } from "./javascriptAstValues.js";
import { stringValue } from "./javascriptStaticAnalysisHelpers.js";
@@ -37,7 +37,7 @@ export const collectJavaScriptExports = (node: t.Node): StaticExports => {
},
});
return {
values: [...exports].sort(compareCodePoints),
values: [...exports].sort(compareUnicodeCodePoints),
};
};
@@ -1,4 +1,5 @@
import { compareCodePoints, uniqueSorted } from "../canonicalOrdering.js";
import { uniqueSorted } from "../canonicalOrdering.js";
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
import {
canonicalExportShapeValue,
digestExportShapeValue,
@@ -107,7 +108,7 @@ const sortChanges = (
}))
.sort(
(left, right) =>
compareCodePoints(left.sortKey, right.sortKey) ||
compareUnicodeCodePoints(left.sortKey, right.sortKey) ||
left.index - right.index,
)
.map(({ change }) => change);
@@ -7,7 +7,6 @@ const evidenceIdSchema = prefixedDigestSchema("ev");
const graphIdSchema = prefixedDigestSchema("jag");
const nodeIdSchema = prefixedDigestSchema("jag_node");
const textSchema = z.string().min(1);
const selectorTextSchema = textSchema;
const semanticPrimitiveSchema = z.union([
z.string(),
z.number().finite(),
@@ -59,10 +58,10 @@ export const compareJavaScriptExportShapesInputSchema = z
.strictObject({
left: evidenceSchema,
right: evidenceSchema,
left_module_path: selectorTextSchema,
left_export_name: selectorTextSchema,
right_module_path: selectorTextSchema,
right_export_name: selectorTextSchema,
left_module_path: textSchema,
left_export_name: textSchema,
right_module_path: textSchema,
right_export_name: textSchema,
})
.superRefine((input, context) => {
if (input.left.evidence_id === input.right.evidence_id)
@@ -151,8 +150,8 @@ const projectedReturnShapeSchema = z
export const projectedExportReturnShapesSchema = z
.strictObject({
semantic_role: z.literal("export-return-shapes"),
module_path: selectorTextSchema,
exported_name: selectorTextSchema,
module_path: textSchema,
exported_name: textSchema,
callable_id: textSchema,
callable_kind: z.enum(["function", "class", "method"]),
static_return_shapes: z.array(projectedReturnShapeSchema),
@@ -179,8 +178,8 @@ export const projectedExportReturnShapesSchema = z
const exportCandidateSchema = z.strictObject({
node_id: nodeIdSchema,
module_path: selectorTextSchema,
export_name: selectorTextSchema,
module_path: textSchema,
export_name: textSchema,
matches_requested_module: z.boolean(),
matches_requested_export: z.boolean(),
});
@@ -188,8 +187,8 @@ const exportCandidateSchema = z.strictObject({
const selectorResultShape = {
evidence_id: evidenceIdSchema,
graph_id: graphIdSchema,
requested_module_path: selectorTextSchema,
requested_export_name: selectorTextSchema,
requested_module_path: textSchema,
requested_export_name: textSchema,
candidates: z.array(exportCandidateSchema),
omitted_candidates: z.number().int().min(0),
};
@@ -238,7 +237,8 @@ const propertyPresenceSchema = z.enum([
"unknown-coverage",
]);
const comparisonChangeSchema = z.strictObject({
/** One change row; presence is required even when values stay unknown. */
export const javaScriptExportShapeComparisonChangeSchema = z.strictObject({
change_id: prefixedDigestSchema("jesc_change"),
status: z.enum(["added", "removed", "changed", "unknown"]),
path: jsonPointerSchema,
@@ -255,10 +255,6 @@ const comparisonChangeSchema = z.strictObject({
limitations: z.array(textSchema),
});
/** One change row; presence is required even when values stay unknown. */
export const javaScriptExportShapeComparisonChangeSchema =
comparisonChangeSchema;
const inventoryPropertyCoverageSchema = z.strictObject({
path: jsonPointerSchema,
status: z.enum(["complete", "partial"]),
@@ -286,7 +282,7 @@ export const javaScriptExportShapeComparisonResultSchema = z.strictObject({
changed: z.number().int().min(0),
unknown: z.number().int().min(0),
}),
changes: z.array(comparisonChangeSchema),
changes: z.array(javaScriptExportShapeComparisonChangeSchema),
coverage: z.strictObject({
status: z.enum(["complete-within-inputs", "partial", "truncated"]),
left_graph_status: z.enum([
@@ -326,7 +322,7 @@ export type JavaScriptExportShapeComparisonResult = z.output<
typeof javaScriptExportShapeComparisonResultSchema
>;
export type JavaScriptExportShapeComparisonChange = z.output<
typeof comparisonChangeSchema
typeof javaScriptExportShapeComparisonChangeSchema
>;
export type JavaScriptExportShapePropertyInventory = z.output<
typeof propertyInventorySchema
@@ -5,7 +5,7 @@ import {
type JavaScriptExportShapeComparisonResult,
type ProjectedExportReturnShapes,
} from "./javascriptExportShapeComparisonSchemas.js";
import { compareCodePoints } from "../canonicalOrdering.js";
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
type Shape = ProjectedExportReturnShapes["static_return_shapes"][number];
type SelectorResult = JavaScriptExportShapeComparisonResult["left"];
@@ -196,7 +196,7 @@ const exportCandidates = (
}
}
return [...candidates.values()].sort((left, right) =>
compareCodePoints(candidateKey(left), candidateKey(right)),
compareUnicodeCodePoints(candidateKey(left), candidateKey(right)),
);
};
@@ -7,7 +7,8 @@ import type {
JavaScriptExportShapeComparisonResult,
JavaScriptExportShapePropertyInventory,
} from "./javascriptExportShapeComparisonSchemas.js";
import { compareCodePoints, uniqueSorted } from "../canonicalOrdering.js";
import { uniqueSorted } from "../canonicalOrdering.js";
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
import type {
SelectedJavaScriptExport,
RetainedJavaScriptExportShapes,
@@ -252,7 +253,7 @@ const compareDiscriminants = (
left: Discriminant,
right: Discriminant,
): number =>
compareCodePoints(
compareUnicodeCodePoints(
canonicalExportShapeValue(left),
canonicalExportShapeValue(right),
);
@@ -1,4 +1,4 @@
import { compareCodePoints } from "../canonicalOrdering.js";
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
import type { ApplicationNode } from "./javascriptApplicationGraphSchemas.js";
import {
featureSeedMatchMode,
@@ -30,7 +30,7 @@ export const findApplicationFeatureSeeds = (
nodes
.flatMap((node) => seedMatch(node, seed))
.sort((left, right) =>
compareCodePoints(
compareUnicodeCodePoints(
`${left.node_id}\0${left.basis}\0${left.field}`,
`${right.node_id}\0${right.basis}\0${right.field}`,
),
@@ -230,7 +230,9 @@ const propertyFields = (
continue;
}
if (current !== null && typeof current === "object") {
for (const key of Object.keys(current).sort(compareCodePoints).reverse())
for (const key of Object.keys(current)
.sort(compareUnicodeCodePoints)
.reverse())
pending.push({
current: current[key] ?? null,
path: `${path}.${key}`,
@@ -1,5 +1,6 @@
import { canonicalDigest } from "../comparisonSemantics.js";
import { compareCodePoints, uniqueSorted } from "../canonicalOrdering.js";
import { digestCanonicalValue } from "../canonicalDigest.js";
import { uniqueSorted } from "../canonicalOrdering.js";
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
import type { Evidence } from "../evidence.js";
import {
createJavaScriptApplicationGraph,
@@ -86,7 +87,7 @@ export const traceApplicationFeature = (
};
return applicationFeatureTraceResultSchema.parse({
...semantic,
trace_id: `jatr_${canonicalDigest(semantic, "Feature trace")}`,
trace_id: `jatr_${digestCanonicalValue(semantic, "Feature trace")}`,
});
};
@@ -126,7 +127,7 @@ const noMatchResult = (
};
return applicationFeatureTraceResultSchema.parse({
...semantic,
trace_id: `jatr_${canonicalDigest(semantic, "Feature trace")}`,
trace_id: `jatr_${digestCanonicalValue(semantic, "Feature trace")}`,
});
};
@@ -170,7 +171,9 @@ const terminalPaths = (
)
.map((node) => pathTo(node, traversal.predecessors, edgeById))
.filter((path): path is NonNullable<typeof path> => path !== null)
.sort((left, right) => compareCodePoints(left.path_id, right.path_id));
.sort((left, right) =>
compareUnicodeCodePoints(left.path_id, right.path_id),
);
};
const pathTo = (
@@ -207,7 +210,7 @@ const pathTo = (
};
return {
...semantic,
path_id: `jatp_${canonicalDigest(semantic, "Feature trace")}`,
path_id: `jatp_${digestCanonicalValue(semantic, "Feature trace")}`,
};
};
@@ -3,7 +3,7 @@ import type {
ApplicationNode,
} from "./javascriptApplicationGraphSchemas.js";
import type { JavaScriptApplicationGraph } from "./javascriptApplicationGraph.js";
import { compareCodePoints } from "../canonicalOrdering.js";
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
interface AdjacencyEntry {
readonly edge: ApplicationEdge;
@@ -53,9 +53,11 @@ export const traverseApplicationFeature = (
nodes: [...visited]
.map((nodeId) => nodeById.get(nodeId))
.filter((node): node is ApplicationNode => node !== undefined)
.sort((left, right) => compareCodePoints(left.node_id, right.node_id)),
.sort((left, right) =>
compareUnicodeCodePoints(left.node_id, right.node_id),
),
edges: [...retainedEdges.values()].sort((left, right) =>
compareCodePoints(left.edge_id, right.edge_id),
compareUnicodeCodePoints(left.edge_id, right.edge_id),
),
predecessors,
};
@@ -80,7 +82,7 @@ const buildAdjacency = (
}
for (const entries of adjacency.values())
entries.sort((left, right) =>
compareCodePoints(
compareUnicodeCodePoints(
`${left.edge.edge_id}\0${left.nextNodeId}`,
`${right.edge.edge_id}\0${right.nextNodeId}`,
),
@@ -1,4 +1,4 @@
import { compareCodePoints } from "../canonicalOrdering.js";
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
import type { Evidence } from "../evidence.js";
import {
type ApplicationEdge,
@@ -38,7 +38,7 @@ export const buildJavaScriptNativeHandoffs = (
providers: uniqueProviders(linked),
evidence_ids: linked
.map(({ evidence_id: id }) => id)
.sort(compareCodePoints),
.sort(compareUnicodeCodePoints),
recommended_tools: [
"open_binary" as const,
"binary_overview" as const,
@@ -50,7 +50,7 @@ export const buildJavaScriptNativeHandoffs = (
];
})
.sort((left, right) =>
compareCodePoints(left.native_node_id, right.native_node_id),
compareUnicodeCodePoints(left.native_node_id, right.native_node_id),
);
};
@@ -95,7 +95,9 @@ const nativeExportNodes = (
return [...new Set(ids)]
.map((id) => nodeById.get(id))
.filter((node): node is ApplicationNode => node?.kind === "native-export")
.sort((left, right) => compareCodePoints(left.node_id, right.node_id));
.sort((left, right) =>
compareUnicodeCodePoints(left.node_id, right.node_id),
);
};
const requestedExports = (nodes: readonly ApplicationNode[]): string[] => {
@@ -106,7 +108,7 @@ const requestedExports = (nodes: readonly ApplicationNode[]): string[] => {
...stringArray(observation.properties.members),
]),
);
return [...new Set(values)].sort(compareCodePoints);
return [...new Set(values)].sort(compareUnicodeCodePoints);
};
const stringArray = (value: unknown): string[] =>
@@ -125,7 +127,7 @@ const uniqueProviders = (
]),
).values(),
].sort((left, right) =>
compareCodePoints(
compareUnicodeCodePoints(
`${left.id}\0${left.name}\0${left.version ?? ""}`,
`${right.id}\0${right.name}\0${right.version ?? ""}`,
),
@@ -1,6 +1,6 @@
import { isAbsolute } from "node:path";
import { canonicalDigest } from "../comparisonSemantics.js";
import { digestCanonicalValue } from "../canonicalDigest.js";
import { isPathWithinRoot } from "../localPath.js";
import { z } from "zod";
@@ -44,7 +44,7 @@ export const parseActiveElectronCapture = (
kind: "electron-active",
evidence,
inspection: normalizeInspection(result),
captureSha256: canonicalDigest(result, "Runtime reconciliation"),
captureSha256: digestCanonicalValue(result, "Runtime reconciliation"),
scriptsCompleteWithinScope: false,
};
};
@@ -83,7 +83,7 @@ const normalizeInspection = (
readonly completeness: BrowserCompleteness;
} => ({
target: {
target_id: `electron-active:${canonicalDigest(result.application.application_path, "Runtime reconciliation").slice(0, 32)}`,
target_id: `electron-active:${digestCanonicalValue(result.application.application_path, "Runtime reconciliation").slice(0, 32)}`,
type: "electron-application",
title: result.application.application_path,
attached: true,
@@ -11,7 +11,8 @@ import type {
ParsedRuntimeCapture,
ParsedStaticLayer,
} from "./javascriptRuntimeReconciliationParsing.js";
import { compareCodePoints, uniqueSorted } from "../canonicalOrdering.js";
import { uniqueSorted } from "../canonicalOrdering.js";
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
import type { RuntimeProjection } from "./javascriptRuntimeReconciliationRuntime.js";
interface ReconciledGraphInput {
@@ -93,12 +94,14 @@ const boundGraphContent = (
const allRoots = preferredRootIds(input);
const rootNodeIds = allRoots;
const sortedNodes = [...nodes].sort((left, right) =>
compareCodePoints(left.node_id, right.node_id),
compareUnicodeCodePoints(left.node_id, right.node_id),
);
const retainedNodes = sortedNodes;
const retainedIds = new Set(retainedNodes.map(({ node_id: id }) => id));
const retainedEdges = [...edges]
.sort((left, right) => compareCodePoints(left.edge_id, right.edge_id))
.sort((left, right) =>
compareUnicodeCodePoints(left.edge_id, right.edge_id),
)
.filter(
({ source_node_id: source, target_node_id: target }) =>
retainedIds.has(source) && retainedIds.has(target),
@@ -231,7 +234,7 @@ const mergeNodes = (nodes: readonly ApplicationNode[]): ApplicationNode[] => {
.map((observation) => [observation.observation_id, observation]),
).values(),
].sort((left, right) =>
compareCodePoints(left.observation_id, right.observation_id),
compareUnicodeCodePoints(left.observation_id, right.observation_id),
);
return createJavaScriptApplicationNode({
kind: first.kind,
@@ -1,9 +1,9 @@
import { createJavaScriptApplicationEdge } from "./javascriptApplicationGraph.js";
import type { ApplicationEdge } from "./javascriptApplicationGraphSchemas.js";
import type { ApplicationGraphEvidence } from "./javascriptApplicationEvidenceSchemas.js";
import { canonicalDigest } from "../comparisonSemantics.js";
import { digestCanonicalValue } from "../canonicalDigest.js";
import type { ParsedStaticLayer } from "./javascriptRuntimeReconciliationParsing.js";
import { compareCodePoints } from "../canonicalOrdering.js";
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
import type { JavaScriptRuntimeReconciliationItem } from "./javascriptRuntimeReconciliationSchemas.js";
import type { RuntimeReconciliationEntity } from "./javascriptRuntimeReconciliationRuntime.js";
import {
@@ -29,7 +29,7 @@ export const reconcileRuntimeEntities = (input: {
reconcileEntity(entity, index, input.layers),
);
const retained = evaluated.sort((left, right) =>
compareCodePoints(
compareUnicodeCodePoints(
left.item.reconciliation_id,
right.item.reconciliation_id,
),
@@ -177,7 +177,7 @@ const candidatesWithLocation = (
if (!located.has(key)) located.set(key, { candidate, mapping });
}
return [...located.values()].sort((left, right) =>
compareCodePoints(
compareUnicodeCodePoints(
candidateKey(left.candidate),
candidateKey(right.candidate),
),
@@ -331,7 +331,7 @@ const createItem = (
candidate_static_nodes: candidateReferences.items,
} satisfies Omit<JavaScriptRuntimeReconciliationItem, "reconciliation_id">;
return {
reconciliation_id: `jrr_item_${canonicalDigest(semantic, "Runtime reconciliation")}`,
reconciliation_id: `jrr_item_${digestCanonicalValue(semantic, "Runtime reconciliation")}`,
...semantic,
};
};
@@ -22,8 +22,8 @@ import {
type JavaScriptRuntimeObservation,
type JavaScriptRuntimeTargetLocation,
} from "./javascriptRuntimeObservation.js";
import { canonicalDigest } from "../comparisonSemantics.js";
import { compareCodePoints } from "../canonicalOrdering.js";
import { digestCanonicalValue } from "../canonicalDigest.js";
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
import { parseEvidence, type Evidence } from "../evidence.js";
import {
javascriptApplicationAnalysisResultSchema,
@@ -140,7 +140,9 @@ export const parseStaticLayers = (
): ParsedStaticLayer[] =>
layers
.map((layer) => parseStaticLayer(layer))
.sort((left, right) => compareCodePoints(left.layerId, right.layerId));
.sort((left, right) =>
compareUnicodeCodePoints(left.layerId, right.layerId),
);
/** Parse only supported passive web/Electron inspection Evidence. */
export const parseRuntimeCaptures = (
@@ -149,7 +151,10 @@ export const parseRuntimeCaptures = (
observations
.map((observation) => parseRuntimeCapture(observation))
.sort((left, right) =>
compareCodePoints(left.evidence.evidence_id, right.evidence.evidence_id),
compareUnicodeCodePoints(
left.evidence.evidence_id,
right.evidence.evidence_id,
),
);
const parseStaticLayer = (layer: StaticLayerInput): ParsedStaticLayer => {
@@ -176,7 +181,7 @@ const parseStaticLayer = (layer: StaticLayerInput): ParsedStaticLayer => {
"JavaScript application Evidence subject disagrees with its result",
);
return {
layerId: `jrl_${canonicalDigest(
layerId: `jrl_${digestCanonicalValue(
{
role: layer.role,
evidence_id: evidence.evidence_id,
@@ -219,7 +224,7 @@ const parseRuntimeCapture = (input: Evidence): ParsedRuntimeCapture => {
kind: "browser",
evidence,
inspection,
captureSha256: canonicalDigest(inspection, "Runtime reconciliation"),
captureSha256: digestCanonicalValue(inspection, "Runtime reconciliation"),
scriptsCompleteWithinScope: scriptsComplete(inspection.completeness),
};
}
@@ -247,7 +252,7 @@ const parseRuntimeCapture = (input: Evidence): ParsedRuntimeCapture => {
kind: "electron",
evidence,
inspection,
captureSha256: canonicalDigest(inspection, "Runtime reconciliation"),
captureSha256: digestCanonicalValue(inspection, "Runtime reconciliation"),
scriptsCompleteWithinScope: scriptsComplete(inspection.completeness),
};
}
@@ -270,7 +275,7 @@ const parseRuntimeCapture = (input: Evidence): ParsedRuntimeCapture => {
kind: "v8-inspector",
evidence,
inspection,
captureSha256: canonicalDigest(result, "Runtime reconciliation"),
captureSha256: digestCanonicalValue(result, "Runtime reconciliation"),
scriptsCompleteWithinScope: false,
};
}
@@ -1,5 +1,5 @@
import { type JavaScriptApplicationGraph } from "./javascriptApplicationGraph.js";
import { canonicalDigest } from "../comparisonSemantics.js";
import { digestCanonicalValue } from "../canonicalDigest.js";
import { uniqueSorted } from "../canonicalOrdering.js";
import type { StaticLoadStateProjection } from "./javascriptRuntimeLoadState.js";
import type { RuntimeMatchingProjection } from "./javascriptRuntimeReconciliationMatching.js";
@@ -49,7 +49,7 @@ export const createJavaScriptRuntimeReconciliationResult = (
limitations: reconciliationLimitations(completion),
};
return javascriptRuntimeReconciliationResultSchema.parse({
reconciliation_id: `jrr_${canonicalDigest(semantic, "Runtime reconciliation")}`,
reconciliation_id: `jrr_${digestCanonicalValue(semantic, "Runtime reconciliation")}`,
...semantic,
});
};
@@ -1,6 +1,6 @@
import { z } from "zod";
import { compareCodePoints } from "../canonicalOrdering.js";
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
import { evidenceSchema } from "../evidence.js";
import { javascriptRuntimeUnresolvedLocationSchema } from "./javascriptRuntimeObservation.js";
import { javascriptApplicationGraphSchema } from "./javascriptApplicationGraph.js";
@@ -85,7 +85,7 @@ function normalizeRuntimeMappings(
mappings.map((mapping) => [runtimeMappingKey(mapping), mapping]),
).values(),
].sort((left, right) =>
compareCodePoints(runtimeMappingKey(left), runtimeMappingKey(right)),
compareUnicodeCodePoints(runtimeMappingKey(left), runtimeMappingKey(right)),
);
}
@@ -2,7 +2,8 @@ import { posix, relative, win32 } from "node:path";
import type { ApplicationNode } from "./javascriptApplicationGraphSchemas.js";
import type { JavaScriptApplicationGraph } from "./javascriptApplicationGraph.js";
import { compareCodePoints, uniqueSorted } from "../canonicalOrdering.js";
import { uniqueSorted } from "../canonicalOrdering.js";
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
import type { ParsedStaticLayer } from "./javascriptRuntimeReconciliationParsing.js";
import type { RuntimeReconciliationEntity } from "./javascriptRuntimeReconciliationRuntime.js";
@@ -39,8 +40,8 @@ export const collectStaticRuntimeCandidates = (
.flatMap((layer) => candidatesForLayer(layer))
.sort(
(left, right) =>
compareCodePoints(left.layer.layerId, right.layer.layerId) ||
compareCodePoints(left.node.node_id, right.node.node_id),
compareUnicodeCodePoints(left.layer.layerId, right.layer.layerId) ||
compareUnicodeCodePoints(left.node.node_id, right.node.node_id),
);
/** Translate one already-authorized runtime location into artifact paths. */
@@ -284,7 +285,7 @@ const activeArtifactPrefixes = (
)
)
prefixes.add(mapping.artifact_prefix);
return [...prefixes].sort(compareCodePoints);
return [...prefixes].sort(compareUnicodeCodePoints);
};
const pathWithinPrefix = (path: string, prefix: string): boolean =>
@@ -315,6 +316,6 @@ const uniqueMappedPaths = (
).values(),
].sort(
(left, right) =>
compareCodePoints(left.path, right.path) ||
compareCodePoints(left.basis, right.basis),
compareUnicodeCodePoints(left.path, right.path) ||
compareUnicodeCodePoints(left.basis, right.basis),
);
@@ -1,8 +1,8 @@
import { describe, expect, it } from "vitest";
import { analyzeJavaScriptSemantics } from "./javascriptSemanticAnalysis.js";
import { semanticBinding } from "./javascriptSemanticIr.js";
import {
bindingById,
onlyCallable,
topLevelBinding,
} from "./javascriptSemanticAnalysis.fixture.js";
@@ -39,7 +39,7 @@ describe("JavaScript semantic analysis: calls 1", () => {
.filter(({ callSiteId }) => callSiteId === call.callSiteId)
.map(({ argumentIndex, parameterBindingId }) => ({
argumentIndex,
parameter: semanticBinding(ir, parameterBindingId)?.name,
parameter: bindingById(ir, parameterBindingId)?.name,
})),
).toEqual([
{ argumentIndex: 0, parameter: "value" },
@@ -309,7 +309,7 @@ describe("JavaScript semantic rest parameter flow", () => {
expect(
ir.argumentFlows.map(({ argumentIndex, parameterBindingId }) => [
argumentIndex,
semanticBinding(ir, parameterBindingId)?.name,
bindingById(ir, parameterBindingId)?.name,
]),
).toEqual(expected);
},
@@ -17,6 +17,12 @@ export const bindingsNamed = (
): JavaScriptSemanticBinding[] =>
ir.bindings.filter(({ name: candidate }) => candidate === name);
export const bindingById = (
ir: JavaScriptSemanticIr,
bindingId: string,
): JavaScriptSemanticBinding | undefined =>
ir.bindings.find(({ bindingId: candidate }) => candidate === bindingId);
export const onlyBinding = (
ir: JavaScriptSemanticIr,
name: string,
@@ -1,12 +1,9 @@
import { describe, expect, it } from "vitest";
import { analyzeJavaScriptSemantics } from "./javascriptSemanticAnalysis.js";
import {
semanticBinding,
semanticReferenceAt,
} from "./javascriptSemanticIr.js";
import {
programScope,
bindingById,
bindingsNamed,
onlyBinding,
topLevelBinding,
@@ -88,12 +85,12 @@ describe("JavaScript semantic analysis: structure 1", () => {
);
if (shadowReference === undefined || shadowReference.bindingId === null)
throw new Error("Missing shadow reference");
expect(semanticBinding(ir, shadowReference.bindingId)).toEqual(shadow);
expect(bindingById(ir, shadowReference.bindingId)).toEqual(shadow);
expect(
semanticReferenceAt(
ir,
shadowReference.location.start.line,
shadowReference.location.start.column,
ir.references.find(
({ location }) =>
location.start.line === shadowReference.location.start.line &&
location.start.column === shadowReference.location.start.column,
),
).toEqual(shadowReference);
});
@@ -4,7 +4,7 @@ import type {
JavaScriptSemanticArgumentFlow,
JavaScriptSemanticCallable,
} from "./javascriptSemanticIr.js";
import { compareCodePoints } from "../canonicalOrdering.js";
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
import { semanticCallableIdForNode } from "./javascriptSemanticProjection.js";
import {
resolveSemanticBindingState,
@@ -182,7 +182,9 @@ export const parameterBindings = (
parameterAcceptsArgument(callable, location, index, state),
),
)
.sort((left, right) => compareCodePoints(left.bindingId, right.bindingId));
.sort((left, right) =>
compareUnicodeCodePoints(left.bindingId, right.bindingId),
);
};
const parameterAcceptsArgument = (
@@ -216,4 +218,4 @@ const rangeContains = (
outer.end.column >= inner.end.column));
const uniqueCallableIds = (values: readonly string[]): string[] =>
[...new Set(values)].sort(compareCodePoints);
[...new Set(values)].sort(compareUnicodeCodePoints);
@@ -14,7 +14,7 @@ import {
type JavaScriptSemanticAnalysisState,
type JavaScriptSemanticBindingState,
} from "./javascriptSemanticState.js";
import { compareCodePoints } from "../canonicalOrdering.js";
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
import { traverseJavaScriptAst } from "./javascriptSemanticTraversal.js";
import { range } from "./javascriptStaticAnalysisHelpers.js";
import {
@@ -219,7 +219,7 @@ const childInteraction = (
processBindingId: binding?.bindingId ?? null,
linkedProcessIds: linked
.map((candidate) => spawnId(candidate))
.sort(compareCodePoints),
.sort(compareUnicodeCodePoints),
eventName: kind === "listener" ? eventName : null,
signalName:
kind === "signal"
@@ -1,4 +1,10 @@
import type { JavaScriptSemanticResourceLimit } from "./javascriptSemanticValueTypes.js";
import type { ApplicationCoverage } from "./javascriptApplicationEvidenceSchemas.js";
import {
SEMANTIC_EXPRESSION_DEPTH_LIMIT,
SEMANTIC_PRIMITIVE_CANDIDATE_LIMIT,
SEMANTIC_PRIMITIVE_JSON_BYTES_LIMIT,
} from "./javascriptSemanticResourceLimits.js";
/** Exact retention state for one callable's direct returns. */
export type JavaScriptSemanticReturnCoverage = {
@@ -40,6 +46,33 @@ export const semanticCoverageResourceLimits = (
coverage: JavaScriptSemanticCoverage,
): readonly JavaScriptSemanticResourceLimit[] => coverage.resourceLimits ?? [];
const semanticResourceBounds: Record<
JavaScriptSemanticResourceLimit,
Pick<ApplicationCoverage["limits"][number], "value" | "unit">
> = {
"primitive-candidates": {
value: SEMANTIC_PRIMITIVE_CANDIDATE_LIMIT,
unit: "items",
},
"primitive-bytes": {
value: SEMANTIC_PRIMITIVE_JSON_BYTES_LIMIT,
unit: "bytes",
},
"expression-depth": {
value: SEMANTIC_EXPRESSION_DEPTH_LIMIT,
unit: "depth",
},
};
/** Map semantic evaluator bounds to their caller-visible application coverage. */
export const semanticResourceLimitCoverage = (
resourceLimits: readonly JavaScriptSemanticResourceLimit[],
): ApplicationCoverage["limits"] =>
resourceLimits.map((resourceLimit) => ({
name: `javascript_semantic_${resourceLimit.replaceAll("-", "_")}`,
...semanticResourceBounds[resourceLimit],
}));
/** Classify one callable's direct-return coverage. */
export const semanticReturnCoverage = (
retainedCount: number,
@@ -8,7 +8,7 @@ import type {
JavaScriptSemanticConfigurationOperation,
JavaScriptSemanticRequestOperation,
} from "./javascriptSemanticIr.js";
import { compareCodePoints } from "../canonicalOrdering.js";
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
import {
semanticStaticPropertyKey,
semanticStaticPropertyName,
@@ -301,7 +301,7 @@ const collectResponseConsumers = (
),
linkedRequestIds: linked
.map(({ operation }) => operation.requestId)
.sort(compareCodePoints),
.sort(compareUnicodeCodePoints),
endpoint: null,
fields: [],
resolution:
@@ -2,7 +2,7 @@ import { createHash } from "node:crypto";
import * as t from "@babel/types";
import { compareCodePoints } from "../canonicalOrdering.js";
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
import type { JavaScriptSemanticCallAnalysis } from "./javascriptSemanticCalls.js";
import type {
JavaScriptSemanticCallable,
@@ -100,7 +100,7 @@ export const collectJavaScriptSemanticFingerprints = (
),
),
literalSetSha256: digest(
[...new Set(syntax.literals)].sort(compareCodePoints),
[...new Set(syntax.literals)].sort(compareUnicodeCodePoints),
),
effects: [
...(isAsyncCallable(node) ? (["async"] as const) : []),
@@ -1,7 +1,9 @@
import { z } from "zod";
import { canonicalDigest, canonicalJson } from "../comparisonSemantics.js";
import { compareCodePoints, uniqueSorted } from "../canonicalOrdering.js";
import { canonicalJson } from "../comparisonSemantics.js";
import { digestCanonicalValue } from "../canonicalDigest.js";
import { uniqueSorted } from "../canonicalOrdering.js";
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
import { canonicalJsonDigestSteps } from "../canonicalJsonDigestSteps.js";
import { freezeOwnedJsonSnapshotSteps } from "../immutableJson.js";
import type { ApplicationGraphEvidence } from "./javascriptApplicationEvidenceSchemas.js";
@@ -30,7 +32,7 @@ const normalizeEvidence = (
coverage: {
...evidence.coverage,
limits: [...evidence.coverage.limits].sort((left, right) =>
compareCodePoints(
compareUnicodeCodePoints(
canonicalJson(left, "JavaScript semantic graph"),
canonicalJson(right, "JavaScript semantic graph"),
),
@@ -44,7 +46,7 @@ const normalizeEvidence = (
export const javaScriptSemanticNodeId = (
node: Pick<JavaScriptSemanticGraphNode, "kind" | "identity">,
): string =>
`jsrg_node_${canonicalDigest({ kind: node.kind, identity: node.identity }, "JavaScript semantic graph")}`;
`jsrg_node_${digestCanonicalValue({ kind: node.kind, identity: node.identity }, "JavaScript semantic graph")}`;
/** Normalize one semantic entity and derive its artifact-version identifier. */
export const createJavaScriptSemanticGraphNode = (
@@ -81,7 +83,7 @@ export const createJavaScriptSemanticGraphRelation = (
};
return javaScriptSemanticRelationSchema.parse({
...semantic,
relation_id: `jsrg_relation_${canonicalDigest(semantic, "JavaScript semantic graph")}`,
relation_id: `jsrg_relation_${digestCanonicalValue(semantic, "JavaScript semantic graph")}`,
});
};
@@ -96,7 +98,7 @@ export const createJavaScriptSemanticGraphUnknown = (input: unknown) => {
};
return javaScriptSemanticUnknownSchema.parse({
...semantic,
unknown_id: `jsrg_unknown_${canonicalDigest(semantic, "JavaScript semantic graph")}`,
unknown_id: `jsrg_unknown_${digestCanonicalValue(semantic, "JavaScript semantic graph")}`,
});
};
@@ -112,14 +114,14 @@ export const createJavaScriptSemanticFingerprint = (input: unknown) => {
limitations: uniqueSorted(parsed.limitations),
evidence: normalizeEvidence(parsed.evidence),
};
const fingerprintSha256 = canonicalDigest(
const fingerprintSha256 = digestCanonicalValue(
semantic.components,
"JavaScript semantic graph",
);
return javaScriptSemanticFingerprintSchema.parse({
...semantic,
fingerprint_sha256: fingerprintSha256,
fingerprint_id: `jsrg_fingerprint_${canonicalDigest(
fingerprint_id: `jsrg_fingerprint_${digestCanonicalValue(
{
function_node_id: semantic.function_node_id,
algorithm: semantic.algorithm,
@@ -149,7 +151,9 @@ const sortedUniqueIssue = (
context: GraphIssueReporter,
): void => {
for (let index = 1; index < values.length; index += 1) {
if (compareCodePoints(values[index - 1] ?? "", values[index] ?? "") < 0)
if (
compareUnicodeCodePoints(values[index - 1] ?? "", values[index] ?? "") < 0
)
continue;
context.addIssue({
code: "custom",
@@ -326,7 +330,8 @@ function* checkRelationsSteps(
const { relation_id: identifier, ...semantic } = relation;
if (
identifier !==
"jsrg_relation_" + canonicalDigest(semantic, "JavaScript semantic graph")
"jsrg_relation_" +
digestCanonicalValue(semantic, "JavaScript semantic graph")
)
context.addIssue({
code: "custom",
@@ -370,7 +375,8 @@ function* checkUnknownsSteps(
const { unknown_id: identifier, ...semantic } = unknown;
if (
identifier !==
"jsrg_unknown_" + canonicalDigest(semantic, "JavaScript semantic graph")
"jsrg_unknown_" +
digestCanonicalValue(semantic, "JavaScript semantic graph")
)
context.addIssue({
code: "custom",
@@ -439,7 +445,7 @@ function* checkFingerprintsSteps(
});
if (
fingerprint.fingerprint_sha256 !==
canonicalDigest(fingerprint.components, "JavaScript semantic graph")
digestCanonicalValue(fingerprint.components, "JavaScript semantic graph")
)
context.addIssue({
code: "custom",
@@ -448,7 +454,7 @@ function* checkFingerprintsSteps(
});
const expectedIdentifier =
"jsrg_fingerprint_" +
canonicalDigest(
digestCanonicalValue(
{
function_node_id: fingerprint.function_node_id,
algorithm: fingerprint.algorithm,
@@ -514,7 +520,7 @@ const checkGraph = (graph: GraphRecord, context: GraphIssueReporter): void => {
const { graph_id: identifier, ...semantic } = graph;
if (
identifier !==
"jsrg_" + canonicalDigest(semantic, "JavaScript semantic graph")
"jsrg_" + digestCanonicalValue(semantic, "JavaScript semantic graph")
)
context.addIssue({
code: "custom",
@@ -538,21 +544,21 @@ const normalizeGraphInput = (
...parsed,
root_node_ids: uniqueSorted(parsed.root_node_ids),
nodes: [...parsed.nodes].sort((left, right) =>
compareCodePoints(left.node_id, right.node_id),
compareUnicodeCodePoints(left.node_id, right.node_id),
),
relations: [...parsed.relations].sort((left, right) =>
compareCodePoints(left.relation_id, right.relation_id),
compareUnicodeCodePoints(left.relation_id, right.relation_id),
),
fingerprints: [...parsed.fingerprints].sort((left, right) =>
compareCodePoints(left.fingerprint_id, right.fingerprint_id),
compareUnicodeCodePoints(left.fingerprint_id, right.fingerprint_id),
),
unknowns: [...parsed.unknowns].sort((left, right) =>
compareCodePoints(left.unknown_id, right.unknown_id),
compareUnicodeCodePoints(left.unknown_id, right.unknown_id),
),
coverage: {
...parsed.coverage,
limits: [...parsed.coverage.limits].sort((left, right) =>
compareCodePoints(
compareUnicodeCodePoints(
canonicalJson(left, "JavaScript semantic graph"),
canonicalJson(right, "JavaScript semantic graph"),
),
@@ -562,7 +568,9 @@ const normalizeGraphInput = (
...family,
unknown_ids: uniqueSorted(family.unknown_ids),
}))
.sort((left, right) => compareCodePoints(left.family, right.family)),
.sort((left, right) =>
compareUnicodeCodePoints(left.family, right.family),
),
},
limitations: uniqueSorted(parsed.limitations),
});
@@ -579,7 +587,7 @@ export const createJavaScriptSemanticGraph = (
// public schema still verifies arbitrary records and their commitments.
const record: GraphRecord = {
...semantic,
graph_id: `jsrg_${canonicalDigest(semantic, "JavaScript semantic graph")}`,
graph_id: `jsrg_${digestCanonicalValue(semantic, "JavaScript semantic graph")}`,
};
const issues: SemanticGraphIssue[] = [];
checkGraphContent(record, {
@@ -435,24 +435,6 @@ export interface JavaScriptSemanticIr {
readonly limitations: readonly string[];
}
/** Find the innermost resolved reference at one source coordinate. */
export const semanticReferenceAt = (
ir: JavaScriptSemanticIr,
line: number,
column: number,
): JavaScriptSemanticReference | undefined =>
ir.references.find(
({ location }) =>
location.start.line === line && location.start.column === column,
);
/** Read one binding by its deterministic semantic identifier. */
export const semanticBinding = (
ir: JavaScriptSemanticIr,
bindingId: string,
): JavaScriptSemanticBinding | undefined =>
ir.bindings.find(({ bindingId: candidate }) => candidate === bindingId);
/** Fail-closed result when Babel cannot produce an inert syntax tree. */
export const failedJavaScriptSemanticIr = (): JavaScriptSemanticIr => ({
schema: "JavaScriptSemanticIR",
@@ -2,7 +2,7 @@ import type {
JavaScriptSemanticPrimitive,
JavaScriptSemanticValue,
} from "./javascriptSemanticValueTypes.js";
import { compareCodePoints } from "../canonicalOrdering.js";
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
import { semanticPrimitiveKey } from "./javascriptSemanticProvenance.js";
import {
SEMANTIC_PRIMITIVE_CANDIDATE_LIMIT,
@@ -35,7 +35,7 @@ export const semanticPrimitiveSet = (
return semanticResourceLimitUnknown("primitive-bytes");
const unique = [...uniqueValues]
.map((value) => ({ key: semanticPrimitiveKey(value), value }))
.sort((left, right) => compareCodePoints(left.key, right.key))
.sort((left, right) => compareUnicodeCodePoints(left.key, right.key))
.map(({ value }) => value);
const only = unique[0];
return unique.length === 1 && only !== undefined
@@ -14,7 +14,7 @@ import {
type JavaScriptSemanticScopeState,
} from "./javascriptSemanticState.js";
import { semanticRequireOrigin } from "./javascriptSemanticRequireOrigin.js";
import { compareCodePoints } from "../canonicalOrdering.js";
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
import { traverseJavaScriptAst } from "./javascriptSemanticTraversal.js";
import {
evaluateSemanticBinding,
@@ -160,7 +160,7 @@ export const immutableSemanticScopes = (
bindingsComplete: scope.bindingsComplete,
bindingIds: [...scope.bindings.values()]
.map(({ bindingId }) => bindingId)
.sort(compareCodePoints),
.sort(compareUnicodeCodePoints),
}));
/** Evaluate and freeze bindings into deterministic IR order. */
@@ -178,7 +178,9 @@ export const immutableSemanticBindings = (
value: evaluateSemanticBinding(binding, state),
provenance: evaluateSemanticProvenance(binding, state),
}))
.sort((left, right) => compareCodePoints(left.bindingId, right.bindingId));
.sort((left, right) =>
compareUnicodeCodePoints(left.bindingId, right.bindingId),
);
const callableKind = (
node: t.Node,
@@ -11,7 +11,7 @@ import {
semanticResolutionBlocked,
type JavaScriptSemanticAnalysisState,
} from "./javascriptSemanticState.js";
import { compareCodePoints } from "../canonicalOrdering.js";
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
import { traverseJavaScriptAst } from "./javascriptSemanticTraversal.js";
import { range } from "./javascriptStaticAnalysisHelpers.js";
import {
@@ -87,7 +87,7 @@ export const collectJavaScriptSemanticPromises = (
ownerCallableId: candidate.ownerCallableId,
...ownership,
sourcePromiseIds: [...new Set(sources.promiseIds)].sort(
compareCodePoints,
compareUnicodeCodePoints,
),
sourceResolution: sources.status,
};
@@ -3,7 +3,7 @@ import type {
JavaScriptModuleOrigin,
} from "./javascriptSemanticIr.js";
import type { JavaScriptSemanticPrimitive } from "./javascriptSemanticValueTypes.js";
import { compareCodePoints } from "../canonicalOrdering.js";
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
/** Construct exact local provenance without inventing module origins. */
export const semanticLocalProvenance = (): JavaScriptBindingProvenance => ({
@@ -44,7 +44,7 @@ export const uniqueSemanticOrigins = (
origins.map((origin) => [semanticOriginKey(origin), origin]),
).values(),
].sort((left, right) =>
compareCodePoints(semanticOriginKey(left), semanticOriginKey(right)),
compareUnicodeCodePoints(semanticOriginKey(left), semanticOriginKey(right)),
);
const semanticOriginKey = (origin: JavaScriptModuleOrigin): string =>
@@ -3,7 +3,7 @@ import type {
JavaScriptSemanticGraphNode,
JavaScriptSemanticGraphRelation,
} from "./javascriptSemanticGraphSchemas.js";
import { compareCodePoints } from "../canonicalOrdering.js";
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
import type { JavaScriptSemanticGraphUnknown } from "./javascriptSemanticGraphSchemas.js";
import { isJavaScriptSemanticOwnershipRelation as ownershipRelation } from "./javascriptSemanticQueryRelations.js";
import {
@@ -147,7 +147,7 @@ const buildAdjacency = (
}
for (const entries of adjacency.values())
entries.sort((left, right) =>
compareCodePoints(
compareUnicodeCodePoints(
`${left.relation.relation_id}\0${left.nextNodeId}`,
`${right.relation.relation_id}\0${right.nextNodeId}`,
),
@@ -4,7 +4,8 @@ import canonicalize from "canonicalize";
import type { JavaScriptSemanticGraph } from "./javascriptSemanticGraph.js";
import type { JavaScriptSemanticGraphNode } from "./javascriptSemanticGraphSchemas.js";
import { compareCodePoints, uniqueSorted } from "../canonicalOrdering.js";
import { uniqueSorted } from "../canonicalOrdering.js";
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
import type { JavaScriptSemanticQueryInput } from "./javascriptSemanticQuerySchemas.js";
/** Canonical JSON used by semantic query commitments and ordering. */
@@ -66,7 +67,7 @@ export const resolveJavaScriptSemanticQuerySeeds = (
return graph.nodes
.filter((node) => seedMatches(node, input, fingerprintFunctions))
.map(({ node_id }) => node_id)
.sort(compareCodePoints);
.sort(compareUnicodeCodePoints);
};
const seedMatches = (
@@ -15,7 +15,7 @@ import type {
ProjectedPropertyCoverage,
ProjectedReturnField,
} from "./javascriptExportShapeComparisonSchemas.js";
import { compareCodePoints } from "../canonicalOrdering.js";
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
import { semanticCallableIdForNode } from "./javascriptSemanticProjection.js";
import {
resolveSemanticBindingState,
@@ -204,9 +204,9 @@ export const flattenSemanticReturnValue = (
const fields: ProjectedReturnField[] = [];
const propertyCoverage: ProjectedPropertyCoverage[] = [];
flattenValue(value, "", fields, propertyCoverage);
fields.sort((left, right) => compareCodePoints(left.path, right.path));
fields.sort((left, right) => compareUnicodeCodePoints(left.path, right.path));
propertyCoverage.sort((left, right) =>
compareCodePoints(left.path, right.path),
compareUnicodeCodePoints(left.path, right.path),
);
return { fields, propertyCoverage };
};
@@ -3,12 +3,9 @@ import type {
JavaScriptSemanticValue,
} from "./javascriptSemanticValueTypes.js";
/** A container's own slot, using the value lattice's canonical presence model. */
export type JavaScriptSemanticSlot = JavaScriptSemanticProperty;
/** Static container inventory; partial siblings do not weaken retained slots. */
export interface JavaScriptSemanticContainer {
readonly slots: readonly JavaScriptSemanticSlot[];
readonly slots: readonly JavaScriptSemanticProperty[];
readonly coverage:
| { readonly status: "complete"; readonly omitted: 0 }
| { readonly status: "partial"; readonly omitted: number | null };
@@ -48,9 +45,9 @@ export function semanticContainer(
export const semanticSlotAtPath = (
value: JavaScriptSemanticValue,
path: readonly string[],
): JavaScriptSemanticSlot => {
): JavaScriptSemanticProperty => {
const leafName = path.at(-1) ?? "";
let current: JavaScriptSemanticSlot = {
let current: JavaScriptSemanticProperty = {
name: "",
presence: "present",
value,
@@ -16,7 +16,7 @@ import {
type JavaScriptSemanticBindingState,
} from "./javascriptSemanticState.js";
import { semanticRequireOrigin } from "./javascriptSemanticRequireOrigin.js";
import { compareCodePoints } from "../canonicalOrdering.js";
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
import {
readExactJavaScriptLiteral,
semanticStaticPropertyKey,
@@ -300,7 +300,7 @@ const evaluateObject = (
});
}
const properties = [...propertiesByName.values()].sort((left, right) =>
compareCodePoints(left.name, right.name),
compareUnicodeCodePoints(left.name, right.name),
);
return unknownProperties
? {
@@ -6,7 +6,7 @@ import {
collectJavaScriptExports,
fingerprintJavaScriptAst,
} from "./javascriptAstFingerprint.js";
import { compareCodePoints } from "../canonicalOrdering.js";
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
import { addFindingOnce } from "./javascriptStaticAnalysisFindings.js";
import {
argumentValue,
@@ -66,7 +66,7 @@ export const inspectBundlerRegistration = (
unknown_async_chunk_keys:
runtimeValue.unknownAsyncChunkKeys + recovered.unknownAsyncChunkKeys,
modules: recovered.modules.sort((left, right) =>
compareCodePoints(left.module_key, right.module_key),
compareUnicodeCodePoints(left.module_key, right.module_key),
),
location: range(call),
};
@@ -107,7 +107,7 @@ export const inspectEsbuildWrapper = (
async_chunk_keys: recovered.asyncChunkKeys,
unknown_async_chunk_keys: recovered.unknownAsyncChunkKeys,
modules: recovered.modules.sort((left, right) =>
compareCodePoints(left.module_key, right.module_key),
compareUnicodeCodePoints(left.module_key, right.module_key),
),
location: range(call),
};
@@ -225,7 +225,7 @@ const runtimeMetadata = (node: t.Node | null | undefined): RuntimeMetadata => {
asyncChunkKeys: [],
unknownAsyncChunkKeys: 0,
};
const entries = collectBundlerEntryModuleKeys(node, requireName);
const entries = collectBundlerCallArgumentValues(node, requireName);
const asyncChunks = collectBundlerAsyncChunkKeys(node, requireName);
return {
requireName,
@@ -236,11 +236,6 @@ const runtimeMetadata = (node: t.Node | null | undefined): RuntimeMetadata => {
};
};
const collectBundlerEntryModuleKeys = (
factory: BundlerFunction,
requireName: string,
): StaticValues => collectBundlerCallArgumentValues(factory, requireName);
const collectBundlerAsyncChunkKeys = (
factory: BundlerFunction,
requireName: string | null,
@@ -277,7 +272,7 @@ const isBundlerFunction = (
t.isObjectMethod(node);
const uniqueValues = (values: readonly string[], unknown = 0): StaticValues => {
const unique = [...new Set(values)].sort(compareCodePoints);
const unique = [...new Set(values)].sort(compareUnicodeCodePoints);
return {
values: unique,
unknown,
@@ -15,7 +15,7 @@ import {
readExactJavaScriptLiteral,
semanticStaticPropertyName,
} from "./javascriptAstValues.js";
import { compareCodePoints } from "../canonicalOrdering.js";
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
/** Explicit result for source text that Babel cannot parse. */
export const failedJavaScriptStaticAnalysis = (): JavaScriptStaticAnalysis => ({
@@ -156,17 +156,13 @@ export const staticArrayValues = (
const value = argumentValue(element);
return value === undefined ? [] : [value];
});
const values = [...new Set(staticValues)].sort(compareCodePoints);
const values = [...new Set(staticValues)].sort(compareUnicodeCodePoints);
return {
values,
unknown: array.elements.length - staticValues.length,
};
};
/** Resolve a path composed only from inert literal syntax. */
export const staticPath = (node: t.Node): string | undefined =>
staticPathAt(node);
/** Classify whether inert path syntax is a module specifier or file expression. */
export const staticPathResolutionContext = (
node: t.Node,
@@ -175,13 +171,14 @@ export const staticPathResolutionContext = (
? "filesystem-expression"
: "module-specifier";
const staticPathAt = (node: t.Node): string | undefined => {
/** Resolve a path composed only from inert literal syntax. */
export const staticPath = (node: t.Node): string | undefined => {
if (t.isStringLiteral(node)) return node.value;
if (t.isTemplateLiteral(node) && node.expressions.length === 0)
return stringValue(node);
if (t.isBinaryExpression(node, { operator: "+" })) {
const left = staticPathAt(node.left);
const right = staticPathAt(node.right);
const left = staticPath(node.left);
const right = staticPath(node.right);
return left === undefined || right === undefined
? undefined
: `${left}${right}`;
@@ -215,7 +212,7 @@ const staticCallPath = (
const parts: string[] = [];
for (const argument of node.arguments) {
if (isDirectoryIdentity(argumentNode(argument))) continue;
const value = t.isNode(argument) ? staticPathAt(argument) : undefined;
const value = t.isNode(argument) ? staticPath(argument) : undefined;
if (value === undefined) return undefined;
parts.push(value);
}
@@ -228,7 +225,7 @@ const staticFileUrlPath = (
if (argument === undefined) return undefined;
return isSourceFileUrl(argument)
? sourceRelativeFileUrlPath(argument)
: staticPathAt(argument);
: staticPath(argument);
};
/** Project a known source-relative URL through the actual file URL decoder. */
@@ -284,7 +281,7 @@ const staticResolvedPath = (
parts.push(null);
continue;
}
const value = t.isNode(argument) ? staticPathAt(argument) : undefined;
const value = t.isNode(argument) ? staticPath(argument) : undefined;
// Validate every argument, including those before the last anchor.
if (value === undefined) return undefined;
parts.push(value);
@@ -684,7 +681,7 @@ export const sortedUnique = <Value>(
key: (value: Value) => string,
): Value[] =>
[...new Map(values.map((value) => [key(value), value])).values()].sort(
(left, right) => compareCodePoints(key(left), key(right)),
(left, right) => compareUnicodeCodePoints(key(left), key(right)),
);
/** Hash exact UTF-8 source text. */
@@ -6,7 +6,7 @@ import {
createJavaScriptApplicationNode,
} from "./javascriptApplicationGraph.js";
import { createHistoricalSourceGraph } from "../referenceSourceGraph.js";
import { compareCodePoints } from "../canonicalOrdering.js";
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
import { compareSourceToBundle } from "./sourceToBundleComparison.js";
import {
SOURCE_TO_BUNDLE_SIGNAL_WEIGHTS,
@@ -365,7 +365,7 @@ const historicalGraph = (
content_state: "hashed" as const,
limitations: [],
})),
].sort((left, right) => compareCodePoints(left.path, right.path)),
].sort((left, right) => compareUnicodeCodePoints(left.path, right.path)),
relationships: [],
parse_failures: [],
exclusions: [],
@@ -1,5 +1,7 @@
import { compareCodePoints, uniqueSorted } from "../canonicalOrdering.js";
import { absenceClaimable, canonicalDigest } from "../comparisonSemantics.js";
import { uniqueSorted } from "../canonicalOrdering.js";
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
import { absenceClaimable } from "../comparisonSemantics.js";
import { digestCanonicalValue } from "../canonicalDigest.js";
import type { JavaScriptApplicationGraph } from "./javascriptApplicationGraph.js";
import type { HistoricalSourceGraph } from "../referenceSourceGraph.js";
@@ -84,7 +86,7 @@ export const compareSourceToBundle = (
};
return sourceToBundleComparisonResultSchema.parse({
...semanticResult,
comparison_id: `stbc_${canonicalDigest(semanticResult, "Source-to-bundle comparison")}`,
comparison_id: `stbc_${digestCanonicalValue(semanticResult, "Source-to-bundle comparison")}`,
});
};
@@ -316,7 +318,7 @@ const mappingId = (
input: SourceToBundleComparisonInput,
item: Omit<SourceToBundleComparisonItem, "mapping_id">,
): string =>
`stbc_item_${canonicalDigest(
`stbc_item_${digestCanonicalValue(
{
reference_root_sha256: input.reference.root_sha256,
application_graph_id: input.application.graph.graph_id,
@@ -382,4 +384,4 @@ const compareCandidates = (
right: SourceToBundleCandidate,
): number =>
right.score - left.score ||
compareCodePoints(left.current_node_id, right.current_node_id);
compareUnicodeCodePoints(left.current_node_id, right.current_node_id);
@@ -1,6 +1,6 @@
import { posix } from "node:path";
import { compareCodePoints } from "../canonicalOrdering.js";
import { compareUnicodeCodePoints } from "../unicodeCodePointOrder.js";
import type { ApplicationNode } from "./javascriptApplicationGraphSchemas.js";
import type { HistoricalSourceGraph } from "../referenceSourceGraph.js";
import {
@@ -70,7 +70,9 @@ export const sourceBearingNodes = (
): ApplicationNode[] =>
nodes
.filter(({ kind }) => RELEVANT_NODE_KINDS.has(kind))
.sort((left, right) => compareCodePoints(left.node_id, right.node_id));
.sort((left, right) =>
compareUnicodeCodePoints(left.node_id, right.node_id),
);
/** Keep historical source files in stable path order. */
export const historicalSourceFiles = (
@@ -81,7 +83,7 @@ export const historicalSourceFiles = (
(entry): entry is SourceFile =>
entry.kind === "file" && entry.classifications.includes("source"),
)
.sort((left, right) => compareCodePoints(left.path, right.path));
.sort((left, right) => compareUnicodeCodePoints(left.path, right.path));
/** Build deterministic candidate indices without assigning fuzzy matches. */
export const buildSourceToBundleCandidateIndex = (
@@ -130,7 +132,7 @@ export const candidateIdsForSource = (
const output: string[] = [];
for (const group of groups) {
if (group === undefined) continue;
for (const nodeId of [...group].sort(compareCodePoints)) {
for (const nodeId of [...group].sort(compareUnicodeCodePoints)) {
if (seen.has(nodeId)) continue;
seen.add(nodeId);
output.push(nodeId);
@@ -256,7 +258,7 @@ const signal = (
kind,
weight: signalWeight(kind),
source_value: sourceValue,
current_values: [...new Set(currentValues)].sort(compareCodePoints),
current_values: [...new Set(currentValues)].sort(compareUnicodeCodePoints),
});
const candidateConfidence = (
@@ -325,7 +327,7 @@ const uniquePaths = (paths: readonly CurrentPath[]): CurrentPath[] =>
]),
).values(),
].sort((left, right) =>
compareCodePoints(
compareUnicodeCodePoints(
`${left.kind}\0${left.value}`,
`${right.kind}\0${right.value}`,
),
+2 -2
View File
@@ -1,6 +1,6 @@
import { z } from "zod";
import { compareCodePoints } from "./canonicalOrdering.js";
import { compareUnicodeCodePoints } from "./unicodeCodePointOrder.js";
const jsonValueTypeSchema = z.enum([
"object",
@@ -93,7 +93,7 @@ export const inferJsonShape = (text: string): JsonShape | null => {
.sort(
(left, right) =>
left.path.localeCompare(right.path) ||
compareCodePoints(left.path, right.path),
compareUnicodeCodePoints(left.path, right.path),
),
});
};
-4
View File
@@ -7,10 +7,6 @@ export const localPathStringSchema = z
.min(1)
.regex(/^[^\u0000]*$/u, "Local filesystem paths cannot contain NUL");
/** True when the value is an absolute filesystem path on the host platform. */
export const isAbsoluteLocalPath = (value: string): boolean =>
isAbsolute(value);
/** Test lexical containment on the host platform; callers must resolve symlinks first. */
export const isPathWithinRoot = (root: string, path: string): boolean => {
const value = relative(root, path);

Some files were not shown because too many files have changed in this diff Show More