mirror of
https://github.com/morluto/rea
synced 2026-10-26 06:16:17 -04:00
refactor(domain): reuse canonical ordering and digest owners
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
import { createHash } from "node:crypto";
|
||||
|
||||
import canonicalize from "canonicalize";
|
||||
import { uniqueSorted, uniqueSortedBy } from "../domain/canonicalOrdering.js";
|
||||
import { digestCanonicalValue } from "../domain/canonicalDigest.js";
|
||||
|
||||
import type { Evidence } from "../domain/evidence.js";
|
||||
import {
|
||||
@@ -407,12 +406,12 @@ const addCandidate = (
|
||||
}
|
||||
candidates.set(candidate.obligation_id, {
|
||||
...current,
|
||||
authority_references: uniqueBy(
|
||||
authority_references: uniqueSortedBy(
|
||||
[...current.authority_references, ...candidate.authority_references],
|
||||
(reference) =>
|
||||
`${reference.evidence_id}:${reference.state}:${reference.location}`,
|
||||
),
|
||||
observed_cases: uniqueBy(
|
||||
observed_cases: uniqueSortedBy(
|
||||
[...current.observed_cases, ...candidate.observed_cases],
|
||||
(item) => `${item.kind}:${item.evidence_id}:${item.location}`,
|
||||
),
|
||||
@@ -421,14 +420,14 @@ const addCandidate = (
|
||||
...candidate.authority_references,
|
||||
]),
|
||||
// Each observation keeps its own unresolved authority and next steps.
|
||||
unavailable_authority: uniqueBy(
|
||||
[...current.unavailable_authority, ...candidate.unavailable_authority],
|
||||
(item) => item,
|
||||
),
|
||||
required_next_evidence: uniqueBy(
|
||||
[...current.required_next_evidence, ...candidate.required_next_evidence],
|
||||
(item) => item,
|
||||
),
|
||||
unavailable_authority: uniqueSorted([
|
||||
...current.unavailable_authority,
|
||||
...candidate.unavailable_authority,
|
||||
]),
|
||||
required_next_evidence: uniqueSorted([
|
||||
...current.required_next_evidence,
|
||||
...candidate.required_next_evidence,
|
||||
]),
|
||||
// A generated ID digests its derivation key, so two generated candidates
|
||||
// with one ID are the same claim seen by several records. Only another
|
||||
// reviewed definition can introduce a competing definition.
|
||||
@@ -448,20 +447,5 @@ const sourceState = (
|
||||
return "unknown";
|
||||
};
|
||||
|
||||
const uniqueBy = <Value>(
|
||||
values: readonly Value[],
|
||||
key: (value: Value) => string,
|
||||
): Value[] =>
|
||||
[...new Map(values.map((value) => [key(value), value])).values()].sort(
|
||||
(left, right) => compareUnicodeCodePoints(key(left), key(right)),
|
||||
);
|
||||
|
||||
const obligationId = (key: string): string =>
|
||||
`obl_${digest({ schema: "ReconstructionObligation", key })}`;
|
||||
|
||||
const digest = (value: unknown): string => {
|
||||
const encoded = canonicalize(value);
|
||||
if (encoded === undefined)
|
||||
throw new TypeError("Reconstruction obligation key is not canonical JSON");
|
||||
return createHash("sha256").update(encoded).digest("hex");
|
||||
};
|
||||
`obl_${digestCanonicalValue({ schema: "ReconstructionObligation", key }, "Reconstruction obligation key")}`;
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
unavailableComponentSchema,
|
||||
type ProjectedInventoryEntry,
|
||||
} from "../mobileApplicationGraphProjection.js";
|
||||
import { uniqueSortedBy } from "../canonicalOrdering.js";
|
||||
import { compareUnicodeCodePoints as compare } from "../unicodeCodePointOrder.js";
|
||||
|
||||
const evidenceIdSchema = prefixedDigestSchema("ev");
|
||||
@@ -145,10 +146,10 @@ export const projectAppleApplication = (
|
||||
const runtimeFamilies = identifyRuntimeFamilies(all);
|
||||
const bridgeProjection = identifyBridgeCandidates(
|
||||
classified.javascript,
|
||||
deduplicateComponents([
|
||||
...classified.executables,
|
||||
...classified.native_libraries,
|
||||
]),
|
||||
uniqueSortedBy(
|
||||
[...classified.executables, ...classified.native_libraries],
|
||||
(component) => component.path,
|
||||
),
|
||||
roots,
|
||||
);
|
||||
const uninventoriedImage =
|
||||
@@ -304,11 +305,6 @@ const classifyComponents = (
|
||||
};
|
||||
};
|
||||
|
||||
const deduplicateComponents = (values: readonly Component[]): Component[] =>
|
||||
[...new Map(values.map((value) => [value.path, value])).values()].sort(
|
||||
(left, right) => compare(left.path, right.path),
|
||||
);
|
||||
|
||||
const identifyRuntimeFamilies = (all: readonly Component[]) => {
|
||||
const families = new Set<
|
||||
z.infer<
|
||||
|
||||
@@ -5,6 +5,18 @@ export const uniqueSorted = <Value extends string>(
|
||||
values: readonly Value[],
|
||||
): Value[] => [...new Set(values)].sort(compareUnicodeCodePoints);
|
||||
|
||||
/** Retain the last value for each exact key, deriving keys once per input. */
|
||||
export const uniqueSortedBy = <Value>(
|
||||
values: readonly Value[],
|
||||
key: (value: Value) => string,
|
||||
): Value[] => {
|
||||
const byKey = new Map<string, Value>();
|
||||
for (const value of values) byKey.set(key(value), value);
|
||||
return [...byKey]
|
||||
.sort(([left], [right]) => compareUnicodeCodePoints(left, right))
|
||||
.map(([, value]) => value);
|
||||
};
|
||||
|
||||
/** Lexical ordering for identifiers and digests already validated as ASCII. */
|
||||
export const compareAsciiStrings = (left: string, right: string): number =>
|
||||
left < right ? -1 : left > right ? 1 : 0;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { uniqueSortedBy } from "../canonicalOrdering.js";
|
||||
import * as t from "@babel/types";
|
||||
import { RETURNED_REFERENCE_WIDENING_LIMITATION } from "./javascriptSemanticCoverage.js";
|
||||
import { collectElectronWrappedCallPaths } from "./electronWrappedCallPaths.js";
|
||||
@@ -20,7 +21,6 @@ import {
|
||||
detectVendors,
|
||||
failedJavaScriptStaticAnalysis,
|
||||
registrationKey,
|
||||
sortedUnique,
|
||||
stringValue,
|
||||
} from "./javascriptStaticAnalysisHelpers.js";
|
||||
import {
|
||||
@@ -218,7 +218,7 @@ const finalizeStaticAnalysis = (
|
||||
accumulator.storage,
|
||||
accumulator.moduleRangeIndex,
|
||||
),
|
||||
bundler_registrations: sortedUnique(
|
||||
bundler_registrations: uniqueSortedBy(
|
||||
accumulator.registrations,
|
||||
registrationKey,
|
||||
),
|
||||
|
||||
@@ -829,15 +829,6 @@ export const registrationKey = (
|
||||
]),
|
||||
]);
|
||||
|
||||
/** Sort values by a deterministic unique semantic key. */
|
||||
export const sortedUnique = <Value>(
|
||||
values: readonly Value[],
|
||||
key: (value: Value) => string,
|
||||
): Value[] =>
|
||||
[...new Map(values.map((value) => [key(value), value])).values()].sort(
|
||||
(left, right) => compareUnicodeCodePoints(key(left), key(right)),
|
||||
);
|
||||
|
||||
/** Hash exact UTF-8 source text. */
|
||||
export const sha256Text = (value: string): string =>
|
||||
createHash("sha256").update(value).digest("hex");
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { uniqueSorted } from "./canonicalOrdering.js";
|
||||
import { z } from "zod";
|
||||
import { digestCanonicalValue } from "./canonicalDigest.js";
|
||||
import {
|
||||
@@ -51,7 +52,7 @@ const comparisonConclusionScopeSchema = z
|
||||
})
|
||||
.strict()
|
||||
.superRefine(({ dimensions }, context) => {
|
||||
if (!sameOrderedValues(dimensions, sortedUnique(dimensions)))
|
||||
if (!sameOrderedValues(dimensions, uniqueSorted(dimensions)))
|
||||
context.addIssue({
|
||||
code: "custom",
|
||||
message: "Comparison dimensions must be sorted and unique",
|
||||
@@ -339,7 +340,7 @@ export const updateResidualUnknown = (
|
||||
: { comparison_conclusion_scope: current.comparison_conclusion_scope }),
|
||||
...projectRevisionDetails(input),
|
||||
resolution: input.resolution,
|
||||
mutation_evidence_ids: sortedUnique([
|
||||
mutation_evidence_ids: uniqueSorted([
|
||||
...current.mutation_evidence_ids,
|
||||
mutationEvidenceId,
|
||||
]),
|
||||
@@ -367,8 +368,8 @@ type RevisionDetailsInput = Pick<
|
||||
|
||||
const projectRevisionDetails = (input: RevisionDetailsInput) => ({
|
||||
severity: input.severity,
|
||||
supporting_evidence_ids: sortedUnique(input.supporting_evidence_ids),
|
||||
contradicting_evidence_ids: sortedUnique(input.contradicting_evidence_ids),
|
||||
supporting_evidence_ids: uniqueSorted(input.supporting_evidence_ids),
|
||||
contradicting_evidence_ids: uniqueSorted(input.contradicting_evidence_ids),
|
||||
required_authority: input.required_authority,
|
||||
required_confidence: input.required_confidence,
|
||||
required_environment: input.required_environment,
|
||||
@@ -385,8 +386,6 @@ const computeRevisionDigest = (
|
||||
return digestCanonicalValue(semantic, "Unknown identity");
|
||||
};
|
||||
|
||||
const sortedUnique = (values: readonly string[]): string[] =>
|
||||
[...new Set(values)].sort(compareUnicodeCodePoints);
|
||||
const sameOrderedValues = (
|
||||
left: readonly string[],
|
||||
right: readonly string[],
|
||||
|
||||
Reference in New Issue
Block a user