refactor(ownership): consolidate snapshot and comparison facts

Retain authenticated immutable snapshot payloads while keeping mutable exports independent. Derive artifact metadata comparisons once and reuse the owning native trace limits.
This commit is contained in:
morluto
2026-10-27 07:13:32 +08:00
parent 3052a067a3
commit dbe2c97af3
5 changed files with 174 additions and 79 deletions
+2 -4
View File
@@ -3,6 +3,7 @@ import { nativeDispatchMetadataInputSchema } from "../domain/native/objcSwiftMet
import { nativeProcedureInputSchema } from "../domain/native/nativeProcedureInput.js";
import { decompileDirectoryInputSchema } from "./decompileDirectory.js";
import { nativeValueTraceInputSchema } from "../domain/native/nativeValueTrace.js";
import { nativeInvestigationTraceLimitsSchema } from "../domain/native/nativeInvestigationGraph.js";
const traceLiteralInputSchema = z.strictObject({
query: z.string().min(1),
case_sensitive: z.boolean().default(false),
@@ -64,16 +65,13 @@ export const enhancedInputSchemas = {
.optional(),
direction: callRelationshipDirectionSchema,
}),
trace_native_ui_action: z.strictObject({
trace_native_ui_action: nativeInvestigationTraceLimitsSchema.extend({
action: z
.string()
.min(1)
.describe(
"A unique compiled UI action selector, interface object ID, native symbol or exact function address",
),
max_depth: z.int().min(0).max(32).default(8),
max_nodes: z.int().min(1).max(2_000).default(250),
max_edges: z.int().min(1).max(5_000).default(500),
}),
} as const;
+91
View File
@@ -5,6 +5,7 @@ import {
analysisSnapshotSchema,
analysisQueryId,
parseAnalysisSnapshot,
immutableAnalysisSnapshot,
snapshotBinding,
snapshotEvidenceForQuery,
snapshotTarget,
@@ -639,3 +640,93 @@ describe("analysis snapshot Evidence binding", () => {
}
});
});
describe("analysis snapshot payload ownership", () => {
it.each(["provider", "workflow"] as const)(
"shares authenticated %s payloads only in immutable snapshots",
(kind) => {
const { snapshot } = snapshotWithHistoricalEvidence();
const entry = snapshot.entries[0];
if (entry === undefined) throw new Error("Missing fixture query");
entry.execution.raw_result = { source: "raw" };
const evidence = createEvidence(
ANALYSIS_SNAPSHOT_TARGET,
ANALYSIS_SNAPSHOT_PROVIDER,
{
operation: entry.operation,
parameters: entry.parameters,
result: entry.execution.result,
rawResult: entry.execution.raw_result,
analysisProfile: ANALYSIS_SNAPSHOT_PROFILE,
confidence: kind === "provider" ? "observed" : "derived",
},
);
const input = {
...snapshot,
entries: kind === "provider" ? [entry] : [],
workflow_entries:
kind === "workflow"
? [
{
...entry,
execution: {
...entry.execution,
analysis_profile: ANALYSIS_SNAPSHOT_PROFILE,
},
},
]
: [],
evidence_bundle: createEvidenceBundle([evidence]),
};
const original = structuredClone(input);
const immutable = immutableAnalysisSnapshot(input);
const immutableEntry = (
kind === "provider" ? immutable.entries : immutable.workflow_entries
)[0];
const immutableEvidence = immutable.evidence_bundle.records[0];
if (immutableEntry === undefined || immutableEvidence === undefined)
throw new Error("Missing immutable payload");
expect(immutableEntry.execution.result).toBe(
immutableEvidence.normalized_result,
);
expect(immutableEntry.execution.raw_result).toBe(
immutableEvidence.raw_result,
);
expect(Object.isFrozen(immutableEntry.execution.result)).toBe(true);
expect(Object.isFrozen(immutableEntry.execution.raw_result)).toBe(true);
expect(Object.isFrozen(immutable)).toBe(true);
expect(immutableAnalysisSnapshot(immutable)).toBe(immutable);
expect(parseAnalysisSnapshot(immutable)).toBe(immutable);
expect(input).toEqual(original);
expect(Object.isFrozen(entry.execution.result)).toBe(false);
expect(Object.isFrozen(entry.execution.raw_result)).toBe(false);
const mutable = parseAnalysisSnapshot(input);
const mutableEntry = (
kind === "provider" ? mutable.entries : mutable.workflow_entries
)[0];
const mutableEvidence = mutable.evidence_bundle.records[0];
if (mutableEntry === undefined || mutableEvidence === undefined)
throw new Error("Missing mutable payload");
expect(mutableEntry.execution.result).not.toBe(
mutableEvidence.normalized_result,
);
expect(mutableEntry.execution.raw_result).not.toBe(
mutableEvidence.raw_result,
);
mutableEntry.execution.result = { changed: true };
mutableEntry.execution.raw_result = { changed: true };
expect(mutableEvidence).toEqual(evidence);
expect(input).toEqual(original);
const tampered = structuredClone(input);
const tamperedEntry = (
kind === "provider" ? tampered.entries : tampered.workflow_entries
)[0];
if (tamperedEntry === undefined) throw new Error("Missing tamper query");
tamperedEntry.execution.result = { changed: true };
expect(() => immutableAnalysisSnapshot(tampered)).toThrow(
/differs from its Evidence/u,
);
expect(Object.isFrozen(tamperedEntry.execution.result)).toBe(false);
},
);
});
+13 -5
View File
@@ -607,13 +607,21 @@ export const createAnalysisSnapshotEntry = (input: {
const validatedSnapshots = new WeakMap<object, AnalysisSnapshot>();
/** Parse a provider/profile-exact snapshot and verify its entries and evidence. */
export const parseAnalysisSnapshot = (input: unknown): AnalysisSnapshot => {
export const parseAnalysisSnapshot = (input: unknown): AnalysisSnapshot =>
authenticateSnapshot(input).parsed;
const authenticateSnapshot = (
input: unknown,
): {
readonly parsed: AnalysisSnapshot;
readonly restored: AnalysisSnapshot;
} => {
if (typeof input === "object" && input !== null) {
const owned = validatedSnapshots.get(input);
if (owned !== undefined) return owned;
if (owned !== undefined) return { parsed: owned, restored: owned };
}
const parsed = analysisSnapshotSchema.parse(input);
restoreSnapshot(
const restored = restoreSnapshot(
parsed,
parseEvidenceBundle(parsed.evidence_bundle),
parsed.entries.map((entry) => ({
@@ -635,12 +643,12 @@ export const parseAnalysisSnapshot = (input: unknown): AnalysisSnapshot => {
),
})),
);
return parsed;
return { parsed, restored };
};
/** Own an authenticated immutable cache while preserving mutable caller exports. */
export const immutableAnalysisSnapshot = (input: unknown): AnalysisSnapshot =>
sealSnapshot(parseAnalysisSnapshot(input));
sealSnapshot(authenticateSnapshot(input).restored);
const sealSnapshot = (snapshot: AnalysisSnapshot): AnalysisSnapshot => {
for (const entry of [...snapshot.entries, ...snapshot.workflow_entries]) {
+58 -60
View File
@@ -289,19 +289,13 @@ const compareOccurrences = (
}
const leftOccurrence = leftMatches[0];
const rightOccurrence = rightMatches[0];
if (
leftOccurrence !== undefined &&
rightOccurrence !== undefined &&
asarPackingUnknown({ leftOccurrence, rightOccurrence })
)
hasUnknownAsarPacking = true;
if (
leftOccurrence !== undefined &&
rightOccurrence !== undefined &&
nodeSeaMetadataUnknown({ leftOccurrence, rightOccurrence })
)
hasUnknownNodeSeaMetadata = true;
const metadata = occurrenceMetadata({ leftOccurrence, rightOccurrence });
if (leftOccurrence !== undefined && rightOccurrence !== undefined) {
if (metadata.asarPacking === "unknown") hasUnknownAsarPacking = true;
if (metadata.nodeSea === "unknown") hasUnknownNodeSeaMetadata = true;
}
const change = classifyPath({
metadata,
path,
leftOccurrence,
rightOccurrence,
@@ -337,6 +331,7 @@ const occurrencesByPath = (
};
interface ClassifyPathInput {
readonly metadata: OccurrenceMetadata;
readonly path: string;
readonly leftOccurrence: ArtifactOccurrence | undefined;
readonly rightOccurrence: ArtifactOccurrence | undefined;
@@ -419,7 +414,8 @@ const classifyPath = (input: ClassifyPathInput): ArtifactChange | null => {
if (declaration.handled) return declaration.change;
const availability = classifyAvailability(input, base);
if (availability !== null)
return asarPackingChanged(input) || nodeSeaMetadataChanged(input)
return input.metadata.asarPacking === "changed" ||
input.metadata.nodeSea === "changed"
? {
...base,
classification: "changed",
@@ -427,11 +423,7 @@ const classifyPath = (input: ClassifyPathInput): ArtifactChange | null => {
}
: availability;
const dimensions = changedDimensions(input);
if (
pyInstallerMetadataUnknown(input) ||
asarPackingUnknown(input) ||
nodeSeaMetadataUnknown(input)
) {
if (Object.values(input.metadata).includes("unknown")) {
if (dimensions.length === 0)
return {
...base,
@@ -450,35 +442,52 @@ type OccurrencePair = Pick<
"leftOccurrence" | "rightOccurrence"
>;
const asarPackingUnknown = (input: OccurrencePair): boolean =>
(input.leftOccurrence?.unpacked === undefined) !==
(input.rightOccurrence?.unpacked === undefined);
type MetadataComparison = "unchanged" | "changed" | "unknown";
const asarPackingChanged = (input: OccurrencePair): boolean =>
input.leftOccurrence?.unpacked !== undefined &&
input.rightOccurrence?.unpacked !== undefined &&
input.leftOccurrence.unpacked !== input.rightOccurrence.unpacked;
interface OccurrenceMetadata {
readonly asarPacking: MetadataComparison;
readonly nodeSea: MetadataComparison;
readonly pyInstaller: MetadataComparison;
}
const nodeSeaMetadataUnknown = (input: OccurrencePair): boolean =>
(input.leftOccurrence?.format_metadata?.kind === "node-sea-entry") !==
(input.rightOccurrence?.format_metadata?.kind === "node-sea-entry");
const nodeSeaMetadataChanged = (input: OccurrencePair): boolean => {
const left = input.leftOccurrence?.format_metadata;
const right = input.rightOccurrence?.format_metadata;
return (
left?.kind === "node-sea-entry" &&
right?.kind === "node-sea-entry" &&
canonicalJson(nodeSeaEntryDeclaration(left)) !==
canonicalJson(nodeSeaEntryDeclaration(right))
);
const compareOptionalMetadata = <T>(
left: T | undefined,
right: T | undefined,
declaration: (value: T) => unknown,
): MetadataComparison => {
if (left === undefined || right === undefined)
return left === right ? "unchanged" : "unknown";
return canonicalJson(declaration(left)) === canonicalJson(declaration(right))
? "unchanged"
: "changed";
};
const pyInstallerMetadataUnknown = (input: ClassifyPathInput): boolean =>
(isPyInstallerEntry(input.leftOccurrence) &&
!isPyInstallerEntry(input.rightOccurrence)) ||
(isPyInstallerEntry(input.rightOccurrence) &&
!isPyInstallerEntry(input.leftOccurrence));
/** Interpret each pair's format declarations once, preserving historical omission. */
const occurrenceMetadata = (input: OccurrencePair): OccurrenceMetadata => {
const left = input.leftOccurrence;
const right = input.rightOccurrence;
return {
asarPacking: compareOptionalMetadata(
left?.unpacked,
right?.unpacked,
(value) => value,
),
nodeSea: compareOptionalMetadata(
left?.format_metadata?.kind === "node-sea-entry"
? left.format_metadata
: undefined,
right?.format_metadata?.kind === "node-sea-entry"
? right.format_metadata
: undefined,
nodeSeaEntryDeclaration,
),
pyInstaller: compareOptionalMetadata(
isPyInstallerEntry(left) ? left.format_metadata : undefined,
isPyInstallerEntry(right) ? right.format_metadata : undefined,
pyInstallerEntryDeclaration,
),
};
};
const isPyInstallerEntry = (
occurrence: ArtifactOccurrence | undefined,
@@ -518,13 +527,7 @@ const classifyPyInstallerDeclaration = (
dimensions: ["availability"],
},
};
const changed =
canonicalJson(
pyInstallerEntryDeclaration(input.leftOccurrence.format_metadata),
) !==
canonicalJson(
pyInstallerEntryDeclaration(input.rightOccurrence.format_metadata),
);
const changed = input.metadata.pyInstaller === "changed";
return {
handled: true,
change: changed
@@ -534,6 +537,7 @@ const classifyPyInstallerDeclaration = (
};
const changedDimensions = (input: {
readonly metadata: OccurrenceMetadata;
readonly leftNode: ArtifactNode | undefined;
readonly rightNode: ArtifactNode | undefined;
readonly leftOccurrence: ArtifactOccurrence | undefined;
@@ -579,14 +583,15 @@ const changedDimensions = (input: {
const metadataChanged = (
input: {
readonly metadata: OccurrenceMetadata;
readonly leftOccurrence: ArtifactOccurrence | undefined;
readonly rightOccurrence: ArtifactOccurrence | undefined;
},
left: ArtifactNode,
right: ArtifactNode,
): boolean =>
asarPackingChanged(input) ||
nodeSeaMetadataChanged(input) ||
input.metadata.asarPacking === "changed" ||
input.metadata.nodeSea === "changed" ||
left.media_type !== right.media_type ||
left.architecture !== right.architecture ||
left.content_state !== right.content_state ||
@@ -595,14 +600,7 @@ const metadataChanged = (
JSON.stringify(left.limitations) !== JSON.stringify(right.limitations) ||
JSON.stringify(input.leftOccurrence?.limitations) !==
JSON.stringify(input.rightOccurrence?.limitations) ||
(isPyInstallerEntry(input.leftOccurrence) &&
isPyInstallerEntry(input.rightOccurrence) &&
canonicalJson(
pyInstallerEntryDeclaration(input.leftOccurrence.format_metadata),
) !==
canonicalJson(
pyInstallerEntryDeclaration(input.rightOccurrence.format_metadata),
));
input.metadata.pyInstaller === "changed";
const nodeFor = (
occurrence: ArtifactOccurrence | undefined,
+10 -10
View File
@@ -246,11 +246,7 @@ const validateInspectionSubject = (
throw new ArtifactInventoryValidationError(
"Artifact inspection subject does not match its inventory manifest",
);
if (
subject === null ||
subject.digest.sha256 !== manifest.root_sha256 ||
subject.format !== manifest.root_format
)
if (!subjectMatchesInventoryRoot(subject, manifest))
throw new ArtifactInventoryValidationError(
"Artifact inspection Evidence subject does not match its root manifest",
);
@@ -295,16 +291,20 @@ const parseInventoryEvidencePage = (
}
};
const subjectMatchesInventoryRoot = (
subject: Evidence["subject"],
manifest: ArtifactInventoryResult["manifest"],
): boolean =>
subject !== null &&
subject.digest.sha256 === manifest.root_sha256 &&
subject.format === manifest.root_format;
/** Both inventory carriers require the same authenticated root identity. */
export const validateArtifactInventorySubject = (
subject: Evidence["subject"],
manifest: ArtifactInventoryResult["manifest"],
): void => {
if (
subject === null ||
subject.digest.sha256 !== manifest.root_sha256 ||
subject.format !== manifest.root_format
)
if (!subjectMatchesInventoryRoot(subject, manifest))
throw new ArtifactInventoryValidationError(
"Artifact inventory Evidence subject does not match its root digest",
);