rea-agents 2.1.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +84 -18
- package/dist/application/AndroidApplicationService.js +17 -0
- package/dist/application/AppleApplicationService.js +17 -0
- package/dist/application/ApplicationWorkflowEvidenceResolver.js +162 -0
- package/dist/application/BinarySession.js +9 -2
- package/dist/application/BinarySessionStatus.js +39 -1
- package/dist/application/CapabilityInventory.js +15 -0
- package/dist/application/ClientRegistrationStatus.js +11 -4
- package/dist/application/CommandShimReplay.js +6 -3
- package/dist/application/CompositeProvider.js +5 -2
- package/dist/application/DirectAnalysis.js +1 -20
- package/dist/application/DirectAnalysisStatus.js +83 -0
- package/dist/application/Doctor.js +34 -25
- package/dist/application/DoctorDiagnostics.js +1 -4
- package/dist/application/DoctorProjection.js +43 -0
- package/dist/application/DoctorScope.js +102 -0
- package/dist/application/EvidenceReferenceResolver.js +26 -0
- package/dist/application/InventoryProjectionEvidence.js +41 -0
- package/dist/application/InvestigationProviders.js +12 -0
- package/dist/application/JavaScriptApplicationEvidence.js +1 -1
- package/dist/application/JavaScriptApplicationEvidenceGraph.js +10 -1
- package/dist/application/JavaScriptApplicationService.js +3 -2
- package/dist/application/JavaScriptApplicationWorkflowEvidence.js +26 -0
- package/dist/application/JavaScriptApplicationWorkflowService.js +49 -1
- package/dist/application/JavaScriptArtifactReconstruction.js +7 -0
- package/dist/application/JavaScriptModuleRelationships.js +25 -1
- package/dist/application/JavaScriptReturnShapeProjection.js +111 -0
- package/dist/application/JavaScriptSemanticGraphBuilder.js +294 -0
- package/dist/application/JavaScriptSemanticGraphConstruction.js +144 -0
- package/dist/application/JavaScriptSemanticGraphEvidence.js +86 -0
- package/dist/application/JavaScriptSemanticGraphFlowProjection.js +72 -0
- package/dist/application/JavaScriptSemanticGraphProjection.js +33 -0
- package/dist/application/JavaScriptSemanticTraceService.js +45 -0
- package/dist/application/LoopbackReplay.js +277 -133
- package/dist/application/LoopbackReplayRecorder.js +101 -0
- package/dist/application/PermissionAuthority.js +7 -3
- package/dist/application/ProcessCaptureJournal.js +95 -0
- package/dist/application/ProcessCaptureLifecycle.js +24 -13
- package/dist/application/ProcessCheckpoints.js +30 -4
- package/dist/application/ProcessCli.js +26 -4
- package/dist/application/ProcessHarness.js +20 -50
- package/dist/application/ProcessNormalization.js +3 -2
- package/dist/application/ProcessPairedExperiment.js +72 -0
- package/dist/application/ProcessReactiveCoordinator.js +218 -0
- package/dist/application/ProcessReactiveEffects.js +133 -0
- package/dist/application/ProcessReactiveObservations.js +20 -0
- package/dist/application/ProcessSampling.js +2 -0
- package/dist/application/SessionProviderRouter.js +7 -6
- package/dist/application/Setup.js +14 -13
- package/dist/application/SetupClientConfiguration.js +10 -6
- package/dist/application/SetupPlan.js +4 -3
- package/dist/application/SetupRegistrationEnvironment.js +12 -0
- package/dist/application/SetupSkill.js +44 -40
- package/dist/application/TerminalRenderer.js +3 -1
- package/dist/application/Uninstall.js +7 -1
- package/dist/application/Upgrade.js +16 -1
- package/dist/browser/CdpEndpoint.js +37 -4
- package/dist/catalogIdentity.js +16 -7
- package/dist/cli/coreAnalysisCommands.js +34 -2
- package/dist/cli/javascriptApplicationAnalysis.js +19 -0
- package/dist/cli/setupCommands.js +84 -30
- package/dist/cli/utilityCommands.js +10 -2
- package/dist/cliApplicationCommands.js +79 -16
- package/dist/cliCommandNames.js +3 -0
- package/dist/cliElectronCommands.js +23 -33
- package/dist/cliObservationOptions.js +1 -1
- package/dist/cliOutput.js +56 -0
- package/dist/cliProcessCommands.js +32 -1
- package/dist/cliSetup.js +55 -29
- package/dist/contracts/applicationToolContracts.js +54 -7
- package/dist/contracts/applicationWorkflowInputContracts.js +119 -0
- package/dist/contracts/electronToolContracts.js +73 -4
- package/dist/contracts/javascriptApplicationWorkflowExamples.js +32 -9
- package/dist/contracts/managedToolContracts.js +7 -2
- package/dist/contracts/processCaptureExample.js +1 -0
- package/dist/contracts/replayMachineExample.js +37 -0
- package/dist/contracts/sessionStatusContract.js +26 -0
- package/dist/contracts/toolContractExamples.js +2 -0
- package/dist/contracts/toolContractTypes.js +12 -1
- package/dist/contracts/toolContracts.js +8 -11
- package/dist/contracts/toolEffects.js +3 -0
- package/dist/contracts/toolOutputSchemaGroups.js +53 -9
- package/dist/contracts/toolOutputSchemaPrimitives.js +73 -32
- package/dist/doctorRuntime.js +41 -0
- package/dist/domain/analysisErrorPresentation.js +5 -3
- package/dist/domain/analysisErrorProjection.js +7 -1
- package/dist/domain/androidApplication.js +206 -0
- package/dist/domain/appleApplication.js +221 -0
- package/dist/domain/boundedCartesianProjection.js +15 -0
- package/dist/domain/changedBehavior.js +5 -4
- package/dist/domain/completionLedgerGeneration.js +190 -0
- package/dist/domain/errors.js +4 -2
- package/dist/domain/evidenceCompletionLedger.js +113 -0
- package/dist/domain/hopperStartupFailure.js +9 -0
- package/dist/domain/javascriptApplicationAnalysis.js +44 -2
- package/dist/domain/javascriptExportShapeComparison.js +145 -0
- package/dist/domain/javascriptExportShapeComparisonIdentity.js +11 -0
- package/dist/domain/javascriptExportShapeComparisonSchemas.js +277 -0
- package/dist/domain/javascriptExportShapeSelection.js +132 -0
- package/dist/domain/javascriptExportShapeVariants.js +272 -0
- package/dist/domain/javascriptRuntimeReconciliationParsing.js +2 -2
- package/dist/domain/javascriptSemanticAnalysis.js +17 -36
- package/dist/domain/javascriptSemanticCallResolution.js +129 -0
- package/dist/domain/javascriptSemanticCalls.js +269 -0
- package/dist/domain/javascriptSemanticGraph.js +356 -0
- package/dist/domain/javascriptSemanticGraphSchemas.js +329 -0
- package/dist/domain/javascriptSemanticGraphSerialization.js +23 -0
- package/dist/domain/javascriptSemanticIr.js +14 -1
- package/dist/domain/javascriptSemanticPrimitives.js +36 -0
- package/dist/domain/javascriptSemanticProjection.js +36 -4
- package/dist/domain/javascriptSemanticProvenance.js +29 -0
- package/dist/domain/javascriptSemanticQuery.js +294 -0
- package/dist/domain/javascriptSemanticQueryAssessment.js +74 -0
- package/dist/domain/javascriptSemanticQueryIdentity.js +86 -0
- package/dist/domain/javascriptSemanticQueryRelations.js +15 -0
- package/dist/domain/javascriptSemanticQuerySchemas.js +188 -0
- package/dist/domain/javascriptSemanticReturns.js +187 -0
- package/dist/domain/javascriptSemanticState.js +32 -0
- package/dist/domain/javascriptSemanticTraceSchemas.js +14 -0
- package/dist/domain/javascriptSemanticValues.js +54 -70
- package/dist/domain/processCapture.js +36 -0
- package/dist/domain/processCaptureValidation.js +101 -12
- package/dist/domain/processComparison.js +96 -24
- package/dist/domain/processObservation.js +153 -0
- package/dist/domain/processPairedExperiment.js +128 -0
- package/dist/domain/processReactiveCheckpointDataflow.js +32 -0
- package/dist/domain/processReactiveMatching.js +210 -0
- package/dist/domain/processReactiveRuntime.js +109 -0
- package/dist/domain/processReactiveScenario.js +361 -0
- package/dist/domain/processReactiveScenarioPreflight.js +120 -0
- package/dist/domain/processReactiveTransition.js +159 -0
- package/dist/domain/processScenario.js +12 -0
- package/dist/domain/processTraceComparison.js +43 -0
- package/dist/domain/processTraceDimensionProjection.js +50 -0
- package/dist/domain/processTraceEvaluation.js +312 -0
- package/dist/domain/processTraceSpecification.js +373 -0
- package/dist/domain/reconstructionVerification.js +5 -4
- package/dist/domain/replayMachine.js +332 -0
- package/dist/domain/replayMachineRun.js +216 -0
- package/dist/domain/replayMachineRuntime.js +294 -0
- package/dist/domain/runtimeIdentification.js +210 -0
- package/dist/domain/staticRuntimeCorrelation.js +4 -2
- package/dist/evaluation/CodexAgentEval.js +156 -0
- package/dist/generatedPackageMetadata.js +2 -2
- package/dist/ghidra/GhidraClient.js +13 -2
- package/dist/ghidra/GhidraDiagnostics.js +1 -0
- package/dist/ghidra/GhidraLauncher.js +7 -3
- package/dist/ghidra/GhidraProvider.js +8 -1
- package/dist/hopper/BridgeLauncher.js +56 -14
- package/dist/hopper/HopperClient.js +23 -10
- package/dist/hopper/HopperDiagnostics.js +14 -0
- package/dist/hopper/HopperProcessDiagnostic.js +14 -0
- package/dist/hopper/HopperProvider.js +8 -1
- package/dist/hopper/LinuxPrivateDisplayDiagnostic.js +72 -0
- package/dist/hopper/LinuxPrivateDisplayProbe.js +217 -0
- package/dist/identity.js +2 -1
- package/dist/main/transport.js +2 -0
- package/dist/mcpDoctor.js +310 -0
- package/dist/process/ProcessOwnership.js +200 -53
- package/dist/process/ProviderRunLineage.js +21 -0
- package/dist/server/createServer.js +8 -2
- package/dist/server/javascriptApplicationResult.js +106 -0
- package/dist/server/registerApplicationTools/characterization.js +3 -3
- package/dist/server/registerApplicationTools/compareExportShapes.js +37 -0
- package/dist/server/registerApplicationTools/compareVersions.js +12 -6
- package/dist/server/registerApplicationTools/controlledReplay.js +2 -2
- package/dist/server/registerApplicationTools/coverage.js +3 -3
- package/dist/server/registerApplicationTools/helpers.js +37 -25
- package/dist/server/registerApplicationTools/traceFeature.js +9 -5
- package/dist/server/registerApplicationTools/traceSemantics.js +30 -0
- package/dist/server/registerApplicationTools.js +4 -0
- package/dist/server/registerElectronTools.js +19 -5
- package/dist/server/registerEvidenceResources.js +7 -1
- package/dist/server/registerJavaScriptApplicationGraphResource.js +69 -0
- package/dist/server/registerManagedTools.js +18 -1
- package/dist/server/registerProcessComparisonTool.js +17 -7
- package/dist/server/registerReplayMachineTool.js +20 -0
- package/dist/server/registerSessionStatusTool.js +95 -25
- package/dist/server/registerSessionTools.js +12 -7
- package/dist/server/sessionAvailabilityPolicy.js +2 -1
- package/dist/server/toolResult.js +10 -4
- package/dist/serverIdentity.js +19 -0
- package/package.json +62 -29
- package/scripts/hopper-demo-x11.py +459 -61
- package/scripts/prepack.mjs +55 -0
- package/scripts/prepare.mjs +28 -0
- package/scripts/rea.mjs +26 -5
- package/skills/reverse-engineer-anything/SKILL.md +69 -228
- package/skills/reverse-engineer-anything/references/controlled-replay.md +12 -0
- package/skills/reverse-engineer-anything/references/evidence-workflows.md +33 -0
- package/skills/reverse-engineer-anything/references/javascript-applications.md +35 -0
- package/skills/reverse-engineer-anything/references/native-and-artifacts.md +36 -0
- package/skills/reverse-engineer-anything/references/runtime-observation.md +24 -0
|
@@ -63,24 +63,25 @@ export const changedBehaviorResultSchema = z.object({
|
|
|
63
63
|
});
|
|
64
64
|
const EXPECTED_PROVIDERS = {
|
|
65
65
|
compare_process_captures: {
|
|
66
|
-
|
|
66
|
+
predicates: ["rea.process-comparison/v3", "rea.process-comparison/v4"],
|
|
67
67
|
id: "rea-process",
|
|
68
68
|
name: "REA deterministic process harness",
|
|
69
69
|
version: "3",
|
|
70
70
|
},
|
|
71
71
|
compare_artifacts: {
|
|
72
|
-
|
|
72
|
+
predicates: ["rea.artifact-comparison/v1"],
|
|
73
73
|
id: "rea-artifact-comparison",
|
|
74
74
|
name: "REA artifact comparison",
|
|
75
75
|
version: "1",
|
|
76
76
|
},
|
|
77
77
|
compare_functions: {
|
|
78
|
-
|
|
78
|
+
predicates: ["rea.function-comparison/v1"],
|
|
79
79
|
id: "rea-function-comparison",
|
|
80
80
|
name: "REA function comparison",
|
|
81
81
|
version: "1",
|
|
82
82
|
},
|
|
83
83
|
};
|
|
84
|
+
const acceptsPredicate = (expected, actual) => expected.some((predicate) => predicate === actual);
|
|
84
85
|
/** Find observed runtime changes and separately report static change candidates. */
|
|
85
86
|
export const findChangedBehavior = (comparisonsInput, offset, limit) => {
|
|
86
87
|
const parsedInput = changedBehaviorInputSchema.parse({
|
|
@@ -141,7 +142,7 @@ const parseComparisonEvidence = (input) => {
|
|
|
141
142
|
])
|
|
142
143
|
.parse(evidence.operation);
|
|
143
144
|
const expected = EXPECTED_PROVIDERS[operation];
|
|
144
|
-
if (evidence.predicate_type
|
|
145
|
+
if (!acceptsPredicate(expected.predicates, evidence.predicate_type) ||
|
|
145
146
|
evidence.provider.id !== expected.id ||
|
|
146
147
|
evidence.provider.name !== expected.name ||
|
|
147
148
|
evidence.provider.version !== expected.version ||
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import canonicalize from "canonicalize";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { createEvidenceCompletionLedger, evidenceCompletionRecordSchema, } from "./evidenceCompletionLedger.js";
|
|
5
|
+
const identifierSchema = z.string().regex(/^[A-Za-z][A-Za-z0-9._-]{0,99}$/u);
|
|
6
|
+
const digestSchema = z.string().regex(/^[a-f0-9]{64}$/u);
|
|
7
|
+
const identitySchema = z.strictObject({
|
|
8
|
+
id: identifierSchema,
|
|
9
|
+
version: z.string().min(1).max(100),
|
|
10
|
+
});
|
|
11
|
+
const environmentSchema = z.strictObject({
|
|
12
|
+
platform: identifierSchema,
|
|
13
|
+
architecture: identifierSchema,
|
|
14
|
+
runtime: identifierSchema,
|
|
15
|
+
runtime_version: z.string().min(1).max(100),
|
|
16
|
+
});
|
|
17
|
+
const scenarioSchema = z.strictObject({
|
|
18
|
+
id: identifierSchema,
|
|
19
|
+
version: z.number().int().positive(),
|
|
20
|
+
});
|
|
21
|
+
const skillDigestSchema = z.strictObject({
|
|
22
|
+
skill_id: identifierSchema,
|
|
23
|
+
sha256: digestSchema,
|
|
24
|
+
});
|
|
25
|
+
const verifierRunSchema = z
|
|
26
|
+
.strictObject({
|
|
27
|
+
schema_version: z.literal(1),
|
|
28
|
+
run_id: z.string().uuid(),
|
|
29
|
+
verifier_pid: z.number().int().positive(),
|
|
30
|
+
parent_pid: z.number().int().nonnegative(),
|
|
31
|
+
process_lineage: z.discriminatedUnion("status", [
|
|
32
|
+
z.strictObject({
|
|
33
|
+
status: z.literal("verified"),
|
|
34
|
+
schema_version: z.literal(1),
|
|
35
|
+
observed_at: z.iso.datetime(),
|
|
36
|
+
launcher_pid: z.number().int().positive(),
|
|
37
|
+
launcher_parent_pid: z.number().int().nonnegative(),
|
|
38
|
+
process_group_id: z.number().int().positive(),
|
|
39
|
+
descendants: z.array(z.strictObject({
|
|
40
|
+
pid: z.number().int().positive(),
|
|
41
|
+
parent_pid: z.number().int().nonnegative(),
|
|
42
|
+
process_group_id: z.number().int().positive(),
|
|
43
|
+
})),
|
|
44
|
+
}),
|
|
45
|
+
z.strictObject({
|
|
46
|
+
status: z.literal("unavailable"),
|
|
47
|
+
observed_at: z.iso.datetime(),
|
|
48
|
+
launcher_pid: z.number().int().positive(),
|
|
49
|
+
launcher_parent_pid: z.number().int().nonnegative(),
|
|
50
|
+
process_group_id: z.number().int().positive().nullable(),
|
|
51
|
+
reason: z.string().min(1).max(500),
|
|
52
|
+
}),
|
|
53
|
+
]),
|
|
54
|
+
})
|
|
55
|
+
.superRefine((run, context) => {
|
|
56
|
+
if (run.process_lineage.launcher_pid !== run.verifier_pid)
|
|
57
|
+
context.addIssue({
|
|
58
|
+
code: "custom",
|
|
59
|
+
path: ["process_lineage", "launcher_pid"],
|
|
60
|
+
message: "Lineage launcher PID must identify the verifier process",
|
|
61
|
+
});
|
|
62
|
+
if (run.process_lineage.launcher_parent_pid !== run.parent_pid)
|
|
63
|
+
context.addIssue({
|
|
64
|
+
code: "custom",
|
|
65
|
+
path: ["process_lineage", "launcher_parent_pid"],
|
|
66
|
+
message: "Lineage parent PID must identify the verifier parent",
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
const reportClaimSchema = z.strictObject({
|
|
70
|
+
claim_id: identifierSchema,
|
|
71
|
+
scenario: scenarioSchema,
|
|
72
|
+
artifact_sha256s: z.array(digestSchema).max(100),
|
|
73
|
+
provider: identitySchema,
|
|
74
|
+
result_schema_version: z.number().int().positive(),
|
|
75
|
+
status: evidenceCompletionRecordSchema.shape.status,
|
|
76
|
+
evidence_ids: evidenceCompletionRecordSchema.shape.evidence_ids,
|
|
77
|
+
});
|
|
78
|
+
const completionVerifierReportObjectSchema = z.strictObject({
|
|
79
|
+
schema_version: z.literal(1),
|
|
80
|
+
verifier: identitySchema,
|
|
81
|
+
verifier_run: verifierRunSchema,
|
|
82
|
+
environment: environmentSchema,
|
|
83
|
+
claims: z.array(reportClaimSchema).min(1).max(10_000),
|
|
84
|
+
});
|
|
85
|
+
/** Strict output contract implemented by completion-aware verifiers. */
|
|
86
|
+
export const completionVerifierReportSchema = completionVerifierReportObjectSchema.superRefine((report, context) => {
|
|
87
|
+
for (const [index, claim] of report.claims.entries())
|
|
88
|
+
if (claim.status === "pass" && claim.artifact_sha256s.length === 0)
|
|
89
|
+
context.addIssue({
|
|
90
|
+
code: "custom",
|
|
91
|
+
path: ["claims", index, "artifact_sha256s"],
|
|
92
|
+
message: "Passing claims must commit at least one artifact digest",
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
const manifestClaimSchema = reportClaimSchema.omit({
|
|
96
|
+
status: true,
|
|
97
|
+
evidence_ids: true,
|
|
98
|
+
});
|
|
99
|
+
const completionManifestObjectSchema = z.strictObject({
|
|
100
|
+
schema_version: z.literal(1),
|
|
101
|
+
manifest_id: z.string().regex(/^ecm_[a-f0-9]{64}$/u),
|
|
102
|
+
verifier: identitySchema,
|
|
103
|
+
environment: environmentSchema,
|
|
104
|
+
skill_digests: z.array(skillDigestSchema).min(1).max(100),
|
|
105
|
+
claims: z.array(manifestClaimSchema).min(1).max(10_000),
|
|
106
|
+
});
|
|
107
|
+
const compareIdentifiers = (left, right) => left < right ? -1 : left > right ? 1 : 0;
|
|
108
|
+
const isSortedUnique = (values) => values.every((value, index) => index === 0 || compareIdentifiers(values[index - 1] ?? "", value) < 0);
|
|
109
|
+
const canonicalJson = (value) => {
|
|
110
|
+
const serialized = canonicalize(value);
|
|
111
|
+
if (serialized === undefined)
|
|
112
|
+
throw new TypeError("RFC 8785 canonicalization rejected completion metadata");
|
|
113
|
+
return serialized;
|
|
114
|
+
};
|
|
115
|
+
const computeManifestId = (manifest) => `ecm_${createHash("sha256").update(canonicalJson(manifest)).digest("hex")}`;
|
|
116
|
+
const manifestIssues = (manifest, context) => {
|
|
117
|
+
if (!isSortedUnique(manifest.claims.map(({ claim_id }) => claim_id)))
|
|
118
|
+
context.addIssue({
|
|
119
|
+
code: "custom",
|
|
120
|
+
path: ["claims"],
|
|
121
|
+
message: "Manifest claims must be sorted by unique claim ID",
|
|
122
|
+
});
|
|
123
|
+
if (!isSortedUnique(manifest.skill_digests.map(({ skill_id }) => skill_id)))
|
|
124
|
+
context.addIssue({
|
|
125
|
+
code: "custom",
|
|
126
|
+
path: ["skill_digests"],
|
|
127
|
+
message: "Skill digests must be sorted by unique skill ID",
|
|
128
|
+
});
|
|
129
|
+
for (const [index, claim] of manifest.claims.entries())
|
|
130
|
+
if (!isSortedUnique(claim.artifact_sha256s))
|
|
131
|
+
context.addIssue({
|
|
132
|
+
code: "custom",
|
|
133
|
+
path: ["claims", index, "artifact_sha256s"],
|
|
134
|
+
message: "Artifact digests must be sorted and unique",
|
|
135
|
+
});
|
|
136
|
+
const { manifest_id: manifestId, ...projection } = manifest;
|
|
137
|
+
if (computeManifestId(projection) !== manifestId)
|
|
138
|
+
context.addIssue({
|
|
139
|
+
code: "custom",
|
|
140
|
+
path: ["manifest_id"],
|
|
141
|
+
message: "Completion manifest identifier does not match its content",
|
|
142
|
+
});
|
|
143
|
+
};
|
|
144
|
+
/** Canonical immutable commitments for one verifier run. */
|
|
145
|
+
export const completionManifestSchema = completionManifestObjectSchema.superRefine(manifestIssues);
|
|
146
|
+
const canonicalClaims = (claims) => claims
|
|
147
|
+
.map((claim) => ({
|
|
148
|
+
...claim,
|
|
149
|
+
artifact_sha256s: [...claim.artifact_sha256s].sort(compareIdentifiers),
|
|
150
|
+
evidence_ids: [...claim.evidence_ids].sort(compareIdentifiers),
|
|
151
|
+
}))
|
|
152
|
+
.sort((left, right) => compareIdentifiers(left.claim_id, right.claim_id));
|
|
153
|
+
/** Generate canonical manifest and ledger artifacts from live verifier output. */
|
|
154
|
+
export const createCompletionLedgerArtifacts = (input, skillDigests) => {
|
|
155
|
+
const report = completionVerifierReportSchema.parse(input);
|
|
156
|
+
const claims = canonicalClaims(report.claims);
|
|
157
|
+
if (!isSortedUnique(claims.map(({ claim_id }) => claim_id)))
|
|
158
|
+
throw new TypeError("Completion verifier claim IDs must be unique");
|
|
159
|
+
for (const claim of claims) {
|
|
160
|
+
if (!isSortedUnique(claim.artifact_sha256s))
|
|
161
|
+
throw new TypeError("Artifact digests must be unique per claim");
|
|
162
|
+
if (!isSortedUnique(claim.evidence_ids))
|
|
163
|
+
throw new TypeError("Evidence IDs must be unique per claim");
|
|
164
|
+
}
|
|
165
|
+
const skills = skillDigests
|
|
166
|
+
.map((skill) => skillDigestSchema.parse(skill))
|
|
167
|
+
.sort((left, right) => compareIdentifiers(left.skill_id, right.skill_id));
|
|
168
|
+
if (!isSortedUnique(skills.map(({ skill_id }) => skill_id)))
|
|
169
|
+
throw new TypeError("Completion skill IDs must be unique");
|
|
170
|
+
const projection = {
|
|
171
|
+
schema_version: 1,
|
|
172
|
+
verifier: report.verifier,
|
|
173
|
+
environment: report.environment,
|
|
174
|
+
skill_digests: skills,
|
|
175
|
+
claims: claims.map(({ status: _status, evidence_ids: _evidenceIds, ...claim }) => claim),
|
|
176
|
+
};
|
|
177
|
+
return {
|
|
178
|
+
manifest: completionManifestSchema.parse({
|
|
179
|
+
...projection,
|
|
180
|
+
manifest_id: computeManifestId(projection),
|
|
181
|
+
}),
|
|
182
|
+
ledger: createEvidenceCompletionLedger(claims.map(({ claim_id, status, evidence_ids }) => ({
|
|
183
|
+
claim_id,
|
|
184
|
+
status,
|
|
185
|
+
evidence_ids,
|
|
186
|
+
}))),
|
|
187
|
+
};
|
|
188
|
+
};
|
|
189
|
+
/** Parse generated completion commitments and reject manual tampering. */
|
|
190
|
+
export const parseCompletionManifest = (input) => completionManifestSchema.parse(input);
|
package/dist/domain/errors.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { hopperStartupFailure, } from "./hopperStartupFailure.js";
|
|
2
|
-
export { hopperStartupFailure };
|
|
2
|
+
export { hopperStartupFailure, };
|
|
3
3
|
export { projectAnalysisError } from "./analysisErrorProjection.js";
|
|
4
4
|
/** Stable tags exposed by safe analysis-error projections. */
|
|
5
5
|
const ANALYSIS_ERROR_TAGS = [
|
|
@@ -292,12 +292,14 @@ export class HopperRemoteError extends HopperError {
|
|
|
292
292
|
/** The owned Hopper bridge stopped before its client was closed. */
|
|
293
293
|
export class HopperProcessError extends HopperError {
|
|
294
294
|
exitCode;
|
|
295
|
+
diagnostic;
|
|
295
296
|
_tag = "HopperProcessError";
|
|
296
297
|
failureCode;
|
|
297
298
|
userMessage;
|
|
298
|
-
constructor(exitCode) {
|
|
299
|
+
constructor(exitCode, diagnostic) {
|
|
299
300
|
super(`Hopper bridge stopped unexpectedly with code ${String(exitCode)}`);
|
|
300
301
|
this.exitCode = exitCode;
|
|
302
|
+
this.diagnostic = diagnostic;
|
|
301
303
|
const failure = hopperStartupFailure(exitCode);
|
|
302
304
|
this.failureCode = failure?.code;
|
|
303
305
|
this.userMessage = failure?.message;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import canonicalize from "canonicalize";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
const evidenceIdSchema = z.string().regex(/^ev_[a-f0-9]{64}$/u);
|
|
5
|
+
const claimIdSchema = z.string().regex(/^[A-Za-z][A-Za-z0-9._-]{0,99}$/u);
|
|
6
|
+
/** Terminal outcome recorded for one verifier claim. */
|
|
7
|
+
export const evidenceCompletionStatusSchema = z.enum([
|
|
8
|
+
"pass",
|
|
9
|
+
"fail",
|
|
10
|
+
"unsupported",
|
|
11
|
+
"truncated",
|
|
12
|
+
"unknown",
|
|
13
|
+
]);
|
|
14
|
+
/** Strict evidence-linked result for one verifier claim. */
|
|
15
|
+
export const evidenceCompletionRecordSchema = z.strictObject({
|
|
16
|
+
claim_id: claimIdSchema,
|
|
17
|
+
status: evidenceCompletionStatusSchema,
|
|
18
|
+
evidence_ids: z.array(evidenceIdSchema).min(1).max(100),
|
|
19
|
+
});
|
|
20
|
+
const completionSummarySchema = z.strictObject({
|
|
21
|
+
total: z.number().int().min(1).max(10_000),
|
|
22
|
+
pass: z.number().int().nonnegative(),
|
|
23
|
+
fail: z.number().int().nonnegative(),
|
|
24
|
+
unsupported: z.number().int().nonnegative(),
|
|
25
|
+
truncated: z.number().int().nonnegative(),
|
|
26
|
+
unknown: z.number().int().nonnegative(),
|
|
27
|
+
complete: z.boolean(),
|
|
28
|
+
});
|
|
29
|
+
const completionLedgerObjectSchema = z.strictObject({
|
|
30
|
+
schema_version: z.literal(1),
|
|
31
|
+
ledger_id: z.string().regex(/^ecl_[a-f0-9]{64}$/u),
|
|
32
|
+
records: z.array(evidenceCompletionRecordSchema).min(1).max(10_000),
|
|
33
|
+
summary: completionSummarySchema,
|
|
34
|
+
});
|
|
35
|
+
const compareIdentifiers = (left, right) => left < right ? -1 : left > right ? 1 : 0;
|
|
36
|
+
const isSortedUnique = (values) => values.every((value, index) => index === 0 || compareIdentifiers(values[index - 1] ?? "", value) < 0);
|
|
37
|
+
const summarize = (records) => {
|
|
38
|
+
const counts = {
|
|
39
|
+
pass: 0,
|
|
40
|
+
fail: 0,
|
|
41
|
+
unsupported: 0,
|
|
42
|
+
truncated: 0,
|
|
43
|
+
unknown: 0,
|
|
44
|
+
};
|
|
45
|
+
for (const record of records)
|
|
46
|
+
counts[record.status] += 1;
|
|
47
|
+
return {
|
|
48
|
+
total: records.length,
|
|
49
|
+
...counts,
|
|
50
|
+
complete: counts.pass === records.length,
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
const canonicalJson = (value) => {
|
|
54
|
+
const serialized = canonicalize(value);
|
|
55
|
+
if (serialized === undefined)
|
|
56
|
+
throw new TypeError("RFC 8785 canonicalization rejected the completion ledger");
|
|
57
|
+
return serialized;
|
|
58
|
+
};
|
|
59
|
+
const computeLedgerId = (records) => `ecl_${createHash("sha256")
|
|
60
|
+
.update(canonicalJson({ schema_version: 1, records }))
|
|
61
|
+
.digest("hex")}`;
|
|
62
|
+
const ledgerIssues = (ledger, context) => {
|
|
63
|
+
if (!isSortedUnique(ledger.records.map((record) => record.claim_id)))
|
|
64
|
+
context.addIssue({
|
|
65
|
+
code: "custom",
|
|
66
|
+
path: ["records"],
|
|
67
|
+
message: "Completion records must be sorted by unique claim ID",
|
|
68
|
+
});
|
|
69
|
+
for (const [index, record] of ledger.records.entries())
|
|
70
|
+
if (!isSortedUnique(record.evidence_ids))
|
|
71
|
+
context.addIssue({
|
|
72
|
+
code: "custom",
|
|
73
|
+
path: ["records", index, "evidence_ids"],
|
|
74
|
+
message: "Evidence IDs must be sorted and unique",
|
|
75
|
+
});
|
|
76
|
+
if (JSON.stringify(summarize(ledger.records)) !== JSON.stringify(ledger.summary))
|
|
77
|
+
context.addIssue({
|
|
78
|
+
code: "custom",
|
|
79
|
+
path: ["summary"],
|
|
80
|
+
message: "Completion summary does not match its records",
|
|
81
|
+
});
|
|
82
|
+
if (computeLedgerId(ledger.records) !== ledger.ledger_id)
|
|
83
|
+
context.addIssue({
|
|
84
|
+
code: "custom",
|
|
85
|
+
path: ["ledger_id"],
|
|
86
|
+
message: "Completion ledger identifier does not match its records",
|
|
87
|
+
});
|
|
88
|
+
};
|
|
89
|
+
/** Strict path-free Evidence v2 completion ledger. */
|
|
90
|
+
export const evidenceCompletionLedgerSchema = completionLedgerObjectSchema.superRefine(ledgerIssues);
|
|
91
|
+
/** Build a canonical path-independent ledger from verifier claim outcomes. */
|
|
92
|
+
export const createEvidenceCompletionLedger = (input) => {
|
|
93
|
+
const records = input
|
|
94
|
+
.map((record) => evidenceCompletionRecordSchema.parse(record))
|
|
95
|
+
.map((record) => ({
|
|
96
|
+
...record,
|
|
97
|
+
evidence_ids: [...record.evidence_ids].sort(compareIdentifiers),
|
|
98
|
+
}))
|
|
99
|
+
.sort((left, right) => compareIdentifiers(left.claim_id, right.claim_id));
|
|
100
|
+
if (!isSortedUnique(records.map((record) => record.claim_id)))
|
|
101
|
+
throw new TypeError("Completion claim IDs must be unique");
|
|
102
|
+
for (const record of records)
|
|
103
|
+
if (!isSortedUnique(record.evidence_ids))
|
|
104
|
+
throw new TypeError("Completion Evidence IDs must be unique per claim");
|
|
105
|
+
return evidenceCompletionLedgerSchema.parse({
|
|
106
|
+
schema_version: 1,
|
|
107
|
+
ledger_id: computeLedgerId(records),
|
|
108
|
+
records,
|
|
109
|
+
summary: summarize(records),
|
|
110
|
+
});
|
|
111
|
+
};
|
|
112
|
+
/** Parse a completion ledger and reject non-canonical or tampered content. */
|
|
113
|
+
export const parseEvidenceCompletionLedger = (input) => evidenceCompletionLedgerSchema.parse(input);
|
|
@@ -49,7 +49,16 @@ const failures = [
|
|
|
49
49
|
"runtime_dependency_unavailable",
|
|
50
50
|
"A Linux demo-session dependency is unavailable. Rerun rea setup --yes --install-hopper, then rea doctor.",
|
|
51
51
|
],
|
|
52
|
+
[
|
|
53
|
+
80,
|
|
54
|
+
"x11_socket_directory_unusable",
|
|
55
|
+
"REA cannot use the host X11 socket directory for a private display. Run rea doctor --provider hopper for exact mount and namespace diagnostics.",
|
|
56
|
+
],
|
|
52
57
|
];
|
|
53
58
|
const HOPPER_STARTUP_FAILURES = new Map(failures.map(([exitCode, code, message]) => [exitCode, { code, message }]));
|
|
54
59
|
/** Return the stable public failure associated with a Linux demo adapter exit. */
|
|
55
60
|
export const hopperStartupFailure = (exitCode) => exitCode === null ? undefined : HOPPER_STARTUP_FAILURES.get(exitCode);
|
|
61
|
+
/** Return the adapter exit associated with one stable startup failure code. */
|
|
62
|
+
export const hopperStartupExitCode = (code) => failures.find(([, candidate]) => candidate === code)?.[0];
|
|
63
|
+
/** Narrow an untrusted diagnostic field to the public startup code union. */
|
|
64
|
+
export const isHopperStartupFailureCode = (value) => failures.some(([, candidate]) => candidate === value);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isAbsolute } from "node:path";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { javascriptApplicationGraphSchema } from "./javascriptApplicationGraph.js";
|
|
4
|
+
import { javaScriptSemanticGraphSchema } from "./javascriptSemanticGraph.js";
|
|
4
5
|
const MAX_GRAPH_NODES = 100_000;
|
|
5
6
|
const MAX_GRAPH_EDGES = 200_000;
|
|
6
7
|
const countSchema = z.number().int().min(0).max(Number.MAX_SAFE_INTEGER);
|
|
@@ -140,8 +141,8 @@ const reconstructionStatisticsSchema = z.strictObject({
|
|
|
140
141
|
parse_failures: countSchema,
|
|
141
142
|
truncated_scopes: countSchema,
|
|
142
143
|
});
|
|
143
|
-
/**
|
|
144
|
-
export const
|
|
144
|
+
/** Legacy structural JavaScript application analysis result. */
|
|
145
|
+
export const javaScriptApplicationAnalysisResultV1Schema = z.strictObject({
|
|
145
146
|
schema_version: z.literal(1),
|
|
146
147
|
input_path: z.string().min(1).max(16_384),
|
|
147
148
|
format: z.enum(["asar", "directory"]),
|
|
@@ -153,3 +154,44 @@ export const javascriptApplicationAnalysisResultSchema = z.strictObject({
|
|
|
153
154
|
statistics: reconstructionStatisticsSchema,
|
|
154
155
|
limitations: z.array(z.string().min(1).max(4_096)).max(1_000),
|
|
155
156
|
});
|
|
157
|
+
/** JavaScript application analysis with an authenticated semantic companion. */
|
|
158
|
+
export const javaScriptApplicationAnalysisResultV2Schema = javaScriptApplicationAnalysisResultV1Schema
|
|
159
|
+
.extend({
|
|
160
|
+
schema_version: z.literal(2),
|
|
161
|
+
semantic_graph: javaScriptSemanticGraphSchema,
|
|
162
|
+
})
|
|
163
|
+
.superRefine((result, context) => {
|
|
164
|
+
if (result.semantic_graph.application_graph_id !== result.graph.graph_id)
|
|
165
|
+
context.addIssue({
|
|
166
|
+
code: "custom",
|
|
167
|
+
path: ["semantic_graph", "application_graph_id"],
|
|
168
|
+
message: "Semantic graph must commit the containing application graph",
|
|
169
|
+
});
|
|
170
|
+
if (result.semantic_graph.root_artifact_sha256 !==
|
|
171
|
+
result.root_artifact_sha256)
|
|
172
|
+
context.addIssue({
|
|
173
|
+
code: "custom",
|
|
174
|
+
path: ["semantic_graph", "root_artifact_sha256"],
|
|
175
|
+
message: "Semantic graph must commit the containing root artifact",
|
|
176
|
+
});
|
|
177
|
+
const applicationNodeIds = new Set(result.graph.nodes.map(({ node_id }) => node_id));
|
|
178
|
+
for (const [nodeIndex, node] of result.semantic_graph.nodes.entries())
|
|
179
|
+
for (const [identifierIndex, identifier,] of node.application_node_ids.entries())
|
|
180
|
+
if (!applicationNodeIds.has(identifier))
|
|
181
|
+
context.addIssue({
|
|
182
|
+
code: "custom",
|
|
183
|
+
path: [
|
|
184
|
+
"semantic_graph",
|
|
185
|
+
"nodes",
|
|
186
|
+
nodeIndex,
|
|
187
|
+
"application_node_ids",
|
|
188
|
+
identifierIndex,
|
|
189
|
+
],
|
|
190
|
+
message: "Semantic node references an absent application node",
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
/** Strict versioned high-level result returned inside Evidence v2. */
|
|
194
|
+
export const javascriptApplicationAnalysisResultSchema = z.discriminatedUnion("schema_version", [
|
|
195
|
+
javaScriptApplicationAnalysisResultV1Schema,
|
|
196
|
+
javaScriptApplicationAnalysisResultV2Schema,
|
|
197
|
+
]);
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { compareCodePoints } from "./javascriptApplicationGraph.js";
|
|
2
|
+
import { digestExportShapeValue } from "./javascriptExportShapeComparisonIdentity.js";
|
|
3
|
+
import { javaScriptExportShapeComparisonResultSchema, } from "./javascriptExportShapeComparisonSchemas.js";
|
|
4
|
+
import { retainJavaScriptExportShapes, selectJavaScriptExport, } from "./javascriptExportShapeSelection.js";
|
|
5
|
+
import { buildJavaScriptExportShapeChanges, compareJavaScriptExportShapeChanges, hasPartialJavaScriptExportPropertyCoverage, pairJavaScriptExportShapeVariants, } from "./javascriptExportShapeVariants.js";
|
|
6
|
+
/** Compare exact selected export return shapes without executing JavaScript. */
|
|
7
|
+
export const compareJavaScriptExportShapes = (input) => {
|
|
8
|
+
const left = selectJavaScriptExport(input.left, input.limits.max_candidate_exports);
|
|
9
|
+
const right = selectJavaScriptExport(input.right, input.limits.max_candidate_exports);
|
|
10
|
+
const leftRetained = retainJavaScriptExportShapes(left, input.limits.max_return_variants);
|
|
11
|
+
const rightRetained = retainJavaScriptExportShapes(right, input.limits.max_return_variants);
|
|
12
|
+
const pairing = pairJavaScriptExportShapeVariants(leftRetained.shapes, rightRetained.shapes);
|
|
13
|
+
const evidenceLinks = evidencePair(input.left.evidenceId, input.right.evidenceId);
|
|
14
|
+
const allChanges = buildJavaScriptExportShapeChanges({
|
|
15
|
+
leftSelection: left,
|
|
16
|
+
rightSelection: right,
|
|
17
|
+
pairing,
|
|
18
|
+
leftShapes: leftRetained.shapes,
|
|
19
|
+
rightShapes: rightRetained.shapes,
|
|
20
|
+
evidenceLinks,
|
|
21
|
+
}).sort(compareJavaScriptExportShapeChanges);
|
|
22
|
+
const changes = allChanges.slice(0, input.limits.max_changes);
|
|
23
|
+
const omittedChanges = allChanges.length - changes.length;
|
|
24
|
+
const coverage = comparisonCoverage({
|
|
25
|
+
input,
|
|
26
|
+
left,
|
|
27
|
+
right,
|
|
28
|
+
leftRetained,
|
|
29
|
+
rightRetained,
|
|
30
|
+
pairing,
|
|
31
|
+
allChanges,
|
|
32
|
+
omittedChanges,
|
|
33
|
+
});
|
|
34
|
+
const limitations = comparisonLimitations(left, right, coverage.status);
|
|
35
|
+
const semantic = {
|
|
36
|
+
schema_version: 1,
|
|
37
|
+
left: left.selection,
|
|
38
|
+
right: right.selection,
|
|
39
|
+
summary: summarize(allChanges),
|
|
40
|
+
changes,
|
|
41
|
+
coverage,
|
|
42
|
+
evidence_links: evidenceLinks,
|
|
43
|
+
limitations,
|
|
44
|
+
runtime_validation: {
|
|
45
|
+
recommended_tool: "run_controlled_replay",
|
|
46
|
+
automatically_started: false,
|
|
47
|
+
required_for: "runtime-semantics",
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
return javaScriptExportShapeComparisonResultSchema.parse({
|
|
51
|
+
...semantic,
|
|
52
|
+
comparison_id: `jesc_${digestExportShapeValue(semantic)}`,
|
|
53
|
+
});
|
|
54
|
+
};
|
|
55
|
+
const comparisonCoverage = (context) => {
|
|
56
|
+
const leftSource = context.left.projection?.return_shape_coverage;
|
|
57
|
+
const rightSource = context.right.projection?.return_shape_coverage;
|
|
58
|
+
const omittedCandidates = context.left.selection.omitted_candidates +
|
|
59
|
+
context.right.selection.omitted_candidates;
|
|
60
|
+
const truncated = comparisonTruncated(context, omittedCandidates);
|
|
61
|
+
const partial = comparisonPartial(context);
|
|
62
|
+
return {
|
|
63
|
+
status: truncated
|
|
64
|
+
? "truncated"
|
|
65
|
+
: partial
|
|
66
|
+
? "partial"
|
|
67
|
+
: "complete-within-inputs",
|
|
68
|
+
left_graph_status: context.input.left.graph.coverage.status,
|
|
69
|
+
right_graph_status: context.input.right.graph.coverage.status,
|
|
70
|
+
paired_variants: context.pairing.pairs.length,
|
|
71
|
+
unpaired_left_variants: context.pairing.unpairedLeft.length,
|
|
72
|
+
unpaired_right_variants: context.pairing.unpairedRight.length,
|
|
73
|
+
omitted_left_variants: context.leftRetained.omitted,
|
|
74
|
+
omitted_right_variants: context.rightRetained.omitted,
|
|
75
|
+
left_source_omitted_variants: leftSource?.omitted_return_sites ?? null,
|
|
76
|
+
right_source_omitted_variants: rightSource?.omitted_return_sites ?? null,
|
|
77
|
+
left_omitted_fields: leftSource?.omitted_fields ?? 0,
|
|
78
|
+
right_omitted_fields: rightSource?.omitted_fields ?? 0,
|
|
79
|
+
left_omitted_property_coverage: leftSource?.omitted_property_coverage ?? 0,
|
|
80
|
+
right_omitted_property_coverage: rightSource?.omitted_property_coverage ?? 0,
|
|
81
|
+
omitted_candidates: omittedCandidates,
|
|
82
|
+
omitted_changes: context.omittedChanges,
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
const comparisonTruncated = (context, omittedCandidates) => context.leftRetained.omitted > 0 ||
|
|
86
|
+
context.rightRetained.omitted > 0 ||
|
|
87
|
+
omittedCandidates > 0 ||
|
|
88
|
+
context.omittedChanges > 0 ||
|
|
89
|
+
sourceProjectionTruncated(context.left) ||
|
|
90
|
+
sourceProjectionTruncated(context.right);
|
|
91
|
+
const sourceProjectionTruncated = (selection) => {
|
|
92
|
+
const coverage = selection.projection?.return_shape_coverage;
|
|
93
|
+
if (coverage === undefined)
|
|
94
|
+
return false;
|
|
95
|
+
return (coverage.status === "truncated" ||
|
|
96
|
+
coverage.omitted_fields > 0 ||
|
|
97
|
+
coverage.omitted_property_coverage > 0 ||
|
|
98
|
+
coverage.projection_complete === false);
|
|
99
|
+
};
|
|
100
|
+
const comparisonPartial = (context) => selectionPartial(context.left) ||
|
|
101
|
+
selectionPartial(context.right) ||
|
|
102
|
+
context.input.left.graph.coverage.status !== "complete" ||
|
|
103
|
+
context.input.right.graph.coverage.status !== "complete" ||
|
|
104
|
+
hasPartialJavaScriptExportPropertyCoverage(context.leftRetained.shapes) ||
|
|
105
|
+
hasPartialJavaScriptExportPropertyCoverage(context.rightRetained.shapes) ||
|
|
106
|
+
context.pairing.unpairedLeft.length > 0 ||
|
|
107
|
+
context.pairing.unpairedRight.length > 0 ||
|
|
108
|
+
context.allChanges.some(({ status }) => status === "unknown");
|
|
109
|
+
const selectionPartial = (selection) => {
|
|
110
|
+
const coverage = selection.projection?.return_shape_coverage;
|
|
111
|
+
return (selection.selection.status !== "selected" ||
|
|
112
|
+
!selection.observationComplete ||
|
|
113
|
+
coverage?.status !== "complete" ||
|
|
114
|
+
coverage.projection_complete !== true);
|
|
115
|
+
};
|
|
116
|
+
const comparisonLimitations = (left, right, status) => uniqueSorted([
|
|
117
|
+
"Return shapes are inferred from inert syntax and do not prove runtime behavior.",
|
|
118
|
+
"Return variants are paired only by unique exact literal discriminants, never by source order.",
|
|
119
|
+
"Dynamic values, incomplete object coverage, and ambiguous variants remain unknown.",
|
|
120
|
+
"Controlled replay is recommended separately for runtime semantics and was not started.",
|
|
121
|
+
...left.limitations,
|
|
122
|
+
...right.limitations,
|
|
123
|
+
...(status === "truncated"
|
|
124
|
+
? [
|
|
125
|
+
"Comparison output or source return-shape recovery reached an explicit limit.",
|
|
126
|
+
]
|
|
127
|
+
: []),
|
|
128
|
+
...(status === "partial"
|
|
129
|
+
? [
|
|
130
|
+
"At least one selector, graph, return shape, or property boundary is incomplete.",
|
|
131
|
+
]
|
|
132
|
+
: []),
|
|
133
|
+
]);
|
|
134
|
+
const summarize = (changes) => ({
|
|
135
|
+
added: countStatus(changes, "added"),
|
|
136
|
+
removed: countStatus(changes, "removed"),
|
|
137
|
+
changed: countStatus(changes, "changed"),
|
|
138
|
+
unknown: countStatus(changes, "unknown"),
|
|
139
|
+
});
|
|
140
|
+
const countStatus = (changes, status) => changes.filter((change) => change.status === status).length;
|
|
141
|
+
const evidencePair = (left, right) => [
|
|
142
|
+
left,
|
|
143
|
+
right,
|
|
144
|
+
];
|
|
145
|
+
const uniqueSorted = (values) => [...new Set(values)].sort(compareCodePoints);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import canonicalize from "canonicalize";
|
|
3
|
+
/** Canonically encode one export-shape comparison value. */
|
|
4
|
+
export const canonicalExportShapeValue = (value) => {
|
|
5
|
+
const encoded = canonicalize(value);
|
|
6
|
+
if (encoded === undefined)
|
|
7
|
+
throw new TypeError("JavaScript export shape comparison could not canonicalize data");
|
|
8
|
+
return encoded;
|
|
9
|
+
};
|
|
10
|
+
/** Derive one deterministic SHA-256 comparison identifier component. */
|
|
11
|
+
export const digestExportShapeValue = (value) => createHash("sha256").update(canonicalExportShapeValue(value)).digest("hex");
|