rea-agents 1.7.0 → 2.0.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.
Files changed (156) hide show
  1. package/README.md +38 -31
  2. package/dist/application/CapabilityInventory.js +18 -0
  3. package/dist/application/CrossVersionInvestigation.js +10 -6
  4. package/dist/application/Doctor.js +56 -6
  5. package/dist/application/ElectronBoundaryAnalysis.js +96 -0
  6. package/dist/application/ElectronBoundaryGraph.js +9 -0
  7. package/dist/application/ElectronBoundaryGraphContext.js +62 -0
  8. package/dist/application/ElectronBoundaryGraphIpc.js +242 -0
  9. package/dist/application/ElectronBoundaryGraphNative.js +86 -0
  10. package/dist/application/ElectronBoundaryGraphWindows.js +199 -0
  11. package/dist/application/EnhancedTools.js +38 -13
  12. package/dist/application/InvestigationProviders.js +30 -0
  13. package/dist/application/JavaScriptApplicationEvidence.js +26 -0
  14. package/dist/application/JavaScriptApplicationEvidenceGraph.js +89 -0
  15. package/dist/application/JavaScriptApplicationService.js +74 -0
  16. package/dist/application/JavaScriptApplicationWorkflowEvidence.js +29 -0
  17. package/dist/application/JavaScriptApplicationWorkflowService.js +90 -0
  18. package/dist/application/JavaScriptArtifactAnalysis.js +7 -1
  19. package/dist/application/JavaScriptArtifactGraphBuilder.js +59 -16
  20. package/dist/application/JavaScriptArtifactGraphEvidence.js +24 -14
  21. package/dist/application/JavaScriptArtifactReconstruction.js +3 -0
  22. package/dist/application/JavaScriptArtifactReconstructionInput.js +2 -83
  23. package/dist/application/JavaScriptReplayPlanning.js +201 -0
  24. package/dist/application/JavaScriptReplayService.js +266 -0
  25. package/dist/application/JavaScriptRuntimeReconciliationEvidence.js +25 -0
  26. package/dist/application/JavaScriptRuntimeReconciliationService.js +27 -0
  27. package/dist/application/ManagedApplicationGraphService.js +55 -0
  28. package/dist/application/ManagedMemberComparisonService.js +122 -0
  29. package/dist/application/ManagedNativeVerificationService.js +39 -0
  30. package/dist/application/ManagedReconstructionService.js +58 -0
  31. package/dist/application/ManagedRuntimeCorrelationService.js +103 -0
  32. package/dist/application/PermissionAuthority.js +1 -1
  33. package/dist/application/ProcessSampling.js +15 -11
  34. package/dist/application/ProjectPermissionStore.js +1 -0
  35. package/dist/application/runtime.js +2 -0
  36. package/dist/browser/CdpCaptureEvents.js +49 -0
  37. package/dist/browser/CdpElectronInspection.js +106 -162
  38. package/dist/browser/CdpElectronProvider.js +5 -1
  39. package/dist/browser/CdpElectronScriptEvents.js +69 -0
  40. package/dist/browser/CdpElectronScripts.js +94 -0
  41. package/dist/browser/CdpElectronWorkers.js +56 -0
  42. package/dist/browser/CdpPageCapture.js +8 -4
  43. package/dist/catalogIdentity.js +4 -1
  44. package/dist/cli.js +388 -0
  45. package/dist/cliApplicationCommands.js +62 -0
  46. package/dist/cliCommandNames.js +13 -0
  47. package/dist/cliElectronCommands.js +103 -1
  48. package/dist/cliJsonInput.js +54 -0
  49. package/dist/config.js +83 -0
  50. package/dist/contracts/applicationToolContracts.js +91 -0
  51. package/dist/contracts/artifactToolContracts.js +5 -9
  52. package/dist/contracts/browserToolContracts.js +14 -62
  53. package/dist/contracts/electronToolContracts.js +45 -19
  54. package/dist/contracts/errorSchemas.js +1 -0
  55. package/dist/contracts/investigationExamples.js +0 -6
  56. package/dist/contracts/javascriptApplicationWorkflowExamples.js +46 -0
  57. package/dist/contracts/javascriptRuntimeReconciliationExample.js +208 -0
  58. package/dist/contracts/managedToolContracts.js +153 -0
  59. package/dist/contracts/managedWorkflowExamples.js +346 -0
  60. package/dist/contracts/managedWorkflowToolContracts.js +203 -0
  61. package/dist/contracts/nativeToolContracts.js +2 -6
  62. package/dist/contracts/promptContracts.js +18 -2
  63. package/dist/contracts/toolContractExamples.js +10 -25
  64. package/dist/contracts/toolContracts.js +47 -45
  65. package/dist/contracts/toolEffects.js +160 -0
  66. package/dist/contracts/toolOutputSchemas.js +46 -31
  67. package/dist/doctorRuntime.js +42 -1
  68. package/dist/domain/artifactComparison.js +3 -4
  69. package/dist/domain/browserObservation.js +3 -0
  70. package/dist/domain/bundleComparison.js +4 -4
  71. package/dist/domain/electronObservation.js +13 -0
  72. package/dist/domain/electronStaticAnalysis.js +9 -0
  73. package/dist/domain/electronStaticAnalysisBrowser.js +194 -0
  74. package/dist/domain/electronStaticAnalysisIpc.js +171 -0
  75. package/dist/domain/electronStaticAnalysisNative.js +164 -0
  76. package/dist/domain/electronStaticAnalysisTypes.js +1 -0
  77. package/dist/domain/electronStaticAnalysisValues.js +103 -0
  78. package/dist/domain/errors.js +67 -1
  79. package/dist/domain/functionComparisonSchemas.js +3 -8
  80. package/dist/domain/inputIssueProjection.js +52 -0
  81. package/dist/domain/javascriptApplicationAnalysis.js +155 -0
  82. package/dist/domain/javascriptApplicationChangeGraph.js +231 -0
  83. package/dist/domain/javascriptApplicationEvidenceSchemas.js +18 -0
  84. package/dist/domain/javascriptApplicationGraph.js +2 -1
  85. package/dist/domain/javascriptApplicationGraphSchemas.js +7 -0
  86. package/dist/domain/javascriptApplicationVersionComparison.js +116 -0
  87. package/dist/domain/javascriptApplicationVersionComparisonSchemas.js +139 -0
  88. package/dist/domain/javascriptApplicationVersionItems.js +221 -0
  89. package/dist/domain/javascriptApplicationVersionKeys.js +170 -0
  90. package/dist/domain/javascriptFeatureSeed.js +140 -0
  91. package/dist/domain/javascriptFeatureTrace.js +232 -0
  92. package/dist/domain/javascriptFeatureTraceSchemas.js +136 -0
  93. package/dist/domain/javascriptFeatureTraversal.js +88 -0
  94. package/dist/domain/javascriptNativeHandoff.js +85 -0
  95. package/dist/domain/javascriptReplay.js +361 -0
  96. package/dist/domain/javascriptRuntimeLoadState.js +106 -0
  97. package/dist/domain/javascriptRuntimeReconciliation.js +42 -0
  98. package/dist/domain/javascriptRuntimeReconciliationGraph.js +169 -0
  99. package/dist/domain/javascriptRuntimeReconciliationMatching.js +247 -0
  100. package/dist/domain/javascriptRuntimeReconciliationParsing.js +160 -0
  101. package/dist/domain/javascriptRuntimeReconciliationResult.js +119 -0
  102. package/dist/domain/javascriptRuntimeReconciliationRuntime.js +316 -0
  103. package/dist/domain/javascriptRuntimeReconciliationSchemas.js +245 -0
  104. package/dist/domain/javascriptRuntimeStaticCandidates.js +191 -0
  105. package/dist/domain/javascriptStaticAnalysis.js +31 -43
  106. package/dist/domain/javascriptStaticAnalysisFindings.js +39 -0
  107. package/dist/domain/javascriptStaticAnalysisHelpers.js +10 -0
  108. package/dist/domain/javascriptStaticAnalysisState.js +6 -0
  109. package/dist/domain/managedApplicationGraph.js +556 -0
  110. package/dist/domain/managedArtifact.js +444 -0
  111. package/dist/domain/managedMemberComparison.js +565 -0
  112. package/dist/domain/managedNativeVerification.js +408 -0
  113. package/dist/domain/managedReconstruction.js +183 -0
  114. package/dist/domain/managedRuntimeCorrelation.js +175 -0
  115. package/dist/domain/reconstructionVerificationSchemas.js +1 -3
  116. package/dist/dotnet/ManagedArtifactInspector.js +318 -0
  117. package/dist/dotnet/ManagedMemberInspector.js +887 -0
  118. package/dist/dotnet/ManagedMetadataHeaps.js +131 -0
  119. package/dist/dotnet/ManagedMetadataInventory.js +368 -0
  120. package/dist/dotnet/ManagedMetadataLayout.js +322 -0
  121. package/dist/dotnet/ManagedNativeBoundaryInspector.js +391 -0
  122. package/dist/dotnet/ManagedPeReader.js +157 -0
  123. package/dist/dotnet/ManagedReaderFailure.js +10 -0
  124. package/dist/dotnet/ManagedStaticProvider.js +203 -0
  125. package/dist/generatedPackageMetadata.js +2 -2
  126. package/dist/ghidra/GhidraProvider.js +6 -0
  127. package/dist/main.js +14 -0
  128. package/dist/replay/JavaScriptReplayWorker.js +286 -0
  129. package/dist/replay/LinuxJavaScriptReplayRunner.js +434 -0
  130. package/dist/replay/LinuxRuntimeClosure.js +46 -0
  131. package/dist/replay/LinuxSeccompPolicy.js +40 -0
  132. package/dist/replay/ReplayWorkerProtocol.js +55 -0
  133. package/dist/replay/SystemJavaScriptReplayHost.js +182 -0
  134. package/dist/server/createServer.js +52 -0
  135. package/dist/server/registerApplicationTools.js +115 -0
  136. package/dist/server/registerArtifactComparisonTool.js +16 -9
  137. package/dist/server/registerBrowserTools.js +33 -8
  138. package/dist/server/registerBundleComparisonTool.js +17 -20
  139. package/dist/server/registerElectronTools.js +45 -3
  140. package/dist/server/registerEnhancedTools.js +58 -13
  141. package/dist/server/registerEvidenceTools.js +5 -1
  142. package/dist/server/registerFunctionComparisonTool.js +14 -7
  143. package/dist/server/registerInvestigationTools.js +21 -17
  144. package/dist/server/registerManagedTools.js +6 -0
  145. package/dist/server/registerManagedWorkflowTools.js +277 -0
  146. package/dist/server/registerOfficialTools.js +8 -8
  147. package/dist/server/registerProcessComparisonTool.js +25 -47
  148. package/dist/server/registerSessionStatusTool.js +7 -7
  149. package/dist/server/registerSessionTools.js +51 -86
  150. package/dist/server/sessionEvidence.js +14 -19
  151. package/dist/server/toolInputValidation.js +77 -0
  152. package/dist/server/toolRegistrationOptions.js +79 -2
  153. package/dist/server/toolResult.js +25 -17
  154. package/package.json +12 -4
  155. package/scripts/rea.mjs +1 -1
  156. package/skills/rea-analysis/SKILL.md +59 -3
@@ -0,0 +1,565 @@
1
+ import { createHash } from "node:crypto";
2
+ import canonicalize from "canonicalize";
3
+ import { z } from "zod";
4
+ import { evidenceSchema, parseEvidence } from "./evidence.js";
5
+ import { managedMemberInspectionSchema, } from "./managedArtifact.js";
6
+ const digestSchema = z.string().regex(/^[a-f0-9]{64}$/u);
7
+ const evidenceIdSchema = z.string().regex(/^ev_[a-f0-9]{64}$/u);
8
+ const tokenSchema = z.string().regex(/^0x[0-9a-f]{8}$/u);
9
+ const boundedTextSchema = z.string().min(1).max(4_096);
10
+ const comparisonLimitsSchema = z.strictObject({
11
+ max_method_matches: z.number().int().min(1).max(50_000).default(10_000),
12
+ max_field_matches: z.number().int().min(0).max(50_000).default(5_000),
13
+ max_candidates: z.number().int().min(1).max(500).default(50),
14
+ });
15
+ /** Two authenticated managed member observations and deterministic bounds. */
16
+ export const compareManagedMembersInputSchema = z
17
+ .strictObject({
18
+ left: evidenceSchema,
19
+ right: evidenceSchema,
20
+ limits: comparisonLimitsSchema.default({
21
+ max_method_matches: 10_000,
22
+ max_field_matches: 5_000,
23
+ max_candidates: 50,
24
+ }),
25
+ unknown_registry_approved: z.literal(true).optional(),
26
+ })
27
+ .superRefine((input, context) => {
28
+ if (input.left.evidence_id === input.right.evidence_id)
29
+ context.addIssue({
30
+ code: "custom",
31
+ path: ["right"],
32
+ message: "Managed member Evidence must be distinct",
33
+ });
34
+ });
35
+ const matchBasisSchema = z.enum([
36
+ "exact-il-signature",
37
+ "structural-method-shape",
38
+ "field-signature",
39
+ "none",
40
+ ]);
41
+ const matchStatusSchema = z.enum(["matched", "unmatched", "ambiguous"]);
42
+ const itemStatusSchema = z.enum([
43
+ "unchanged",
44
+ "changed",
45
+ "added",
46
+ "removed",
47
+ "unknown",
48
+ ]);
49
+ const memberIdentitySchema = z.strictObject({
50
+ token: tokenSchema,
51
+ declaring_type: z.string().nullable(),
52
+ name: z.string(),
53
+ signature_sha256: digestSchema,
54
+ normalized_il_sha256: digestSchema.nullable(),
55
+ });
56
+ const methodComparisonItemSchema = z.strictObject({
57
+ item_id: z.string().regex(/^mmc_method_[a-f0-9]{64}$/u),
58
+ status: itemStatusSchema,
59
+ left: memberIdentitySchema.nullable(),
60
+ right: memberIdentitySchema.nullable(),
61
+ match: z.strictObject({
62
+ status: matchStatusSchema,
63
+ basis: matchBasisSchema,
64
+ confidence: z.enum(["exact", "high", "unknown"]),
65
+ candidate_left_tokens: z.array(tokenSchema).max(500),
66
+ candidate_right_tokens: z.array(tokenSchema).max(500),
67
+ }),
68
+ dimensions: z
69
+ .array(z.enum([
70
+ "signature",
71
+ "cil",
72
+ "opcode-shape",
73
+ "call-shape",
74
+ "field-shape",
75
+ "exception-shape",
76
+ "availability",
77
+ ]))
78
+ .max(7),
79
+ evidence_links: z.array(evidenceIdSchema).length(2),
80
+ limitations: z.array(boundedTextSchema).max(100),
81
+ });
82
+ const fieldComparisonItemSchema = z.strictObject({
83
+ item_id: z.string().regex(/^mmc_field_[a-f0-9]{64}$/u),
84
+ status: itemStatusSchema,
85
+ left: memberIdentitySchema.omit({ normalized_il_sha256: true }).nullable(),
86
+ right: memberIdentitySchema.omit({ normalized_il_sha256: true }).nullable(),
87
+ match: z.strictObject({
88
+ status: matchStatusSchema,
89
+ basis: matchBasisSchema,
90
+ confidence: z.enum(["exact", "high", "unknown"]),
91
+ candidate_left_tokens: z.array(tokenSchema).max(500),
92
+ candidate_right_tokens: z.array(tokenSchema).max(500),
93
+ }),
94
+ evidence_links: z.array(evidenceIdSchema).length(2),
95
+ limitations: z.array(boundedTextSchema).max(100),
96
+ });
97
+ /** Obfuscation-resistant, execution-free managed member comparison. */
98
+ export const managedMemberComparisonResultSchema = z.strictObject({
99
+ schema_version: z.literal(1),
100
+ comparison_id: z.string().regex(/^mmc_[a-f0-9]{64}$/u),
101
+ algorithm: z.strictObject({
102
+ name: z.literal("rea-managed-member-comparison"),
103
+ version: z.literal(1),
104
+ token_identity: z.literal("build-local"),
105
+ name_matching: z.literal("not-used"),
106
+ }),
107
+ left: z.strictObject({
108
+ evidence_id: evidenceIdSchema,
109
+ artifact_sha256: digestSchema,
110
+ mvid: z.string().uuid().nullable(),
111
+ module_name: z.string().nullable(),
112
+ metadata_status: z.enum(["absent", "complete", "partial", "malformed"]),
113
+ methods_total: z.number().int().min(0),
114
+ fields_total: z.number().int().min(0),
115
+ }),
116
+ right: z.strictObject({
117
+ evidence_id: evidenceIdSchema,
118
+ artifact_sha256: digestSchema,
119
+ mvid: z.string().uuid().nullable(),
120
+ module_name: z.string().nullable(),
121
+ metadata_status: z.enum(["absent", "complete", "partial", "malformed"]),
122
+ methods_total: z.number().int().min(0),
123
+ fields_total: z.number().int().min(0),
124
+ }),
125
+ summary: z.strictObject({
126
+ unchanged: z.number().int().min(0),
127
+ changed: z.number().int().min(0),
128
+ added: z.number().int().min(0),
129
+ removed: z.number().int().min(0),
130
+ unknown: z.number().int().min(0),
131
+ }),
132
+ matching: z.strictObject({
133
+ exact_il_signature: z.number().int().min(0),
134
+ structural_method_shape: z.number().int().min(0),
135
+ field_signature: z.number().int().min(0),
136
+ ambiguous: z.number().int().min(0),
137
+ unmatched: z.number().int().min(0),
138
+ }),
139
+ methods: z.array(methodComparisonItemSchema).max(50_000),
140
+ fields: z.array(fieldComparisonItemSchema).max(50_000),
141
+ coverage: z.strictObject({
142
+ status: z.enum(["complete-within-inputs", "partial", "truncated"]),
143
+ left_status: z.enum(["complete", "partial", "unavailable"]),
144
+ right_status: z.enum(["complete", "partial", "unavailable"]),
145
+ omitted_methods: z.number().int().min(0),
146
+ omitted_fields: z.number().int().min(0),
147
+ omitted_candidates: z.number().int().min(0),
148
+ }),
149
+ evidence_links: z.array(evidenceIdSchema).length(2),
150
+ limitations: z.array(boundedTextSchema).max(1_000),
151
+ });
152
+ const sha256 = (value) => {
153
+ const serialized = canonicalize(value);
154
+ if (serialized === undefined)
155
+ throw new TypeError("Managed comparison canonicalization failed");
156
+ return createHash("sha256").update(serialized).digest("hex");
157
+ };
158
+ /** Compare two parsed managed member observations without name-based matching. */
159
+ export const compareManagedMembers = (left, right, limits) => {
160
+ const methodMatches = matchMethods(left.result.methods.items.map(keyMethod), right.result.methods.items.map(keyMethod), limits.max_candidates);
161
+ const fieldMatches = matchFields(left.result.fields.items.map(keyField), right.result.fields.items.map(keyField), limits.max_candidates);
162
+ const methodItems = buildMethodItems(methodMatches, left.evidenceId, right.evidenceId, limits);
163
+ const fieldItems = buildFieldItems(fieldMatches, left.evidenceId, right.evidenceId, limits);
164
+ const allItems = [...methodItems.items, ...fieldItems.items];
165
+ const summary = {
166
+ unchanged: allItems.filter(({ status }) => status === "unchanged").length,
167
+ changed: allItems.filter(({ status }) => status === "changed").length,
168
+ added: allItems.filter(({ status }) => status === "added").length,
169
+ removed: allItems.filter(({ status }) => status === "removed").length,
170
+ unknown: allItems.filter(({ status }) => status === "unknown").length,
171
+ };
172
+ const matching = {
173
+ exact_il_signature: methodItems.items.filter(({ match }) => match.basis === "exact-il-signature").length,
174
+ structural_method_shape: methodItems.items.filter(({ match }) => match.basis === "structural-method-shape").length,
175
+ field_signature: fieldItems.items.filter(({ match }) => match.basis === "field-signature").length,
176
+ ambiguous: methodItems.items.filter(({ match }) => match.status === "ambiguous")
177
+ .length +
178
+ fieldItems.items.filter(({ match }) => match.status === "ambiguous")
179
+ .length,
180
+ unmatched: allItems.filter(({ match }) => match.status === "unmatched")
181
+ .length,
182
+ };
183
+ const limitations = comparisonLimitations(left.result, right.result, methodItems.omitted + fieldItems.omitted, methodMatches.omittedCandidates + fieldMatches.omittedCandidates);
184
+ const result = {
185
+ schema_version: 1,
186
+ comparison_id: `mmc_${sha256({
187
+ left: left.evidenceId,
188
+ right: right.evidenceId,
189
+ methods: [...methodItems.items],
190
+ fields: [...fieldItems.items],
191
+ limits,
192
+ })}`,
193
+ algorithm: {
194
+ name: "rea-managed-member-comparison",
195
+ version: 1,
196
+ token_identity: "build-local",
197
+ name_matching: "not-used",
198
+ },
199
+ left: sideManifest(left),
200
+ right: sideManifest(right),
201
+ summary,
202
+ matching,
203
+ methods: methodItems.items,
204
+ fields: fieldItems.items,
205
+ coverage: {
206
+ status: methodItems.omitted + fieldItems.omitted > 0 ||
207
+ !left.result.methods.complete ||
208
+ !right.result.methods.complete ||
209
+ !left.result.fields.complete ||
210
+ !right.result.fields.complete
211
+ ? "truncated"
212
+ : left.result.coverage.state === "complete" &&
213
+ right.result.coverage.state === "complete"
214
+ ? "complete-within-inputs"
215
+ : "partial",
216
+ left_status: left.result.coverage.state,
217
+ right_status: right.result.coverage.state,
218
+ omitted_methods: methodItems.omitted,
219
+ omitted_fields: fieldItems.omitted,
220
+ omitted_candidates: methodMatches.omittedCandidates + fieldMatches.omittedCandidates,
221
+ },
222
+ evidence_links: [left.evidenceId, right.evidenceId],
223
+ limitations,
224
+ };
225
+ return managedMemberComparisonResultSchema.parse(result);
226
+ };
227
+ const sideManifest = (side) => ({
228
+ evidence_id: side.evidenceId,
229
+ artifact_sha256: side.result.artifact.sha256,
230
+ mvid: side.result.module?.mvid ?? null,
231
+ module_name: side.result.module?.name ?? null,
232
+ metadata_status: side.result.metadata.status,
233
+ methods_total: side.result.methods.total,
234
+ fields_total: side.result.fields.total,
235
+ });
236
+ const keyMethod = (item) => ({
237
+ item,
238
+ exactKey: item.signature.parse_status === "decoded" &&
239
+ item.body.normalized_il_sha256 !== null
240
+ ? stableKey([
241
+ "method-exact",
242
+ item.signature.raw_sha256,
243
+ item.body.normalized_il_sha256,
244
+ ])
245
+ : null,
246
+ structuralKey: stableKey([
247
+ "method-structural",
248
+ item.signature.kind,
249
+ item.signature.calling_convention,
250
+ item.signature.generic_parameter_count,
251
+ item.signature.parameter_count,
252
+ item.signature.return_type,
253
+ item.signature.parameter_types,
254
+ item.body.status,
255
+ item.body.header_format,
256
+ item.body.il_size,
257
+ item.body.max_stack,
258
+ item.body.init_locals,
259
+ item.body.opcode_counts,
260
+ item.body.anchors.map(({ opcode, operand_kind }) => [opcode, operand_kind]),
261
+ item.body.exception_regions.map((region) => [
262
+ region.flags,
263
+ region.try_length,
264
+ region.handler_length,
265
+ region.class_token === null ? null : "type-token",
266
+ region.filter_offset === null ? null : "filter",
267
+ ]),
268
+ ]),
269
+ });
270
+ const keyField = (item) => ({
271
+ item,
272
+ exactKey: item.signature.parse_status === "decoded"
273
+ ? stableKey(["field-exact", item.signature.raw_sha256])
274
+ : null,
275
+ structuralKey: stableKey([
276
+ "field-structural",
277
+ item.signature.kind,
278
+ item.signature.field_type,
279
+ item.flags,
280
+ ]),
281
+ });
282
+ const stableKey = (value) => sha256(value);
283
+ const matchMethods = (left, right, maxCandidates) => matchByKeys(left, right, maxCandidates, "exact-il-signature", [
284
+ "structural-method-shape",
285
+ ]);
286
+ const matchFields = (left, right, maxCandidates) => matchByKeys(left, right, maxCandidates, "field-signature", []);
287
+ const matchByKeys = (left, right, maxCandidates, exactBasis, fallbackBases) => {
288
+ const usedLeft = new Set();
289
+ const usedRight = new Set();
290
+ const pairs = [];
291
+ const ambiguous = [];
292
+ let omittedCandidates = 0;
293
+ const rounds = [
294
+ { basis: exactBasis, key: ({ exactKey }) => exactKey },
295
+ ...fallbackBases.map((basis) => ({
296
+ basis,
297
+ key: ({ structuralKey }) => structuralKey,
298
+ })),
299
+ ];
300
+ for (const round of rounds) {
301
+ const leftGroups = groupBy(left.filter((item) => !usedLeft.has(item)), round.key);
302
+ const rightGroups = groupBy(right.filter((item) => !usedRight.has(item)), round.key);
303
+ for (const [key, leftItems] of leftGroups) {
304
+ const rightItems = rightGroups.get(key);
305
+ if (rightItems === undefined)
306
+ continue;
307
+ if (leftItems.length === 1 && rightItems.length === 1) {
308
+ const [leftItem] = leftItems;
309
+ const [rightItem] = rightItems;
310
+ if (leftItem === undefined || rightItem === undefined)
311
+ continue;
312
+ usedLeft.add(leftItem);
313
+ usedRight.add(rightItem);
314
+ pairs.push({
315
+ left: leftItem,
316
+ right: rightItem,
317
+ basis: round.basis,
318
+ confidence: round.basis === "exact-il-signature" ||
319
+ round.basis === "field-signature"
320
+ ? "exact"
321
+ : "high",
322
+ });
323
+ }
324
+ else {
325
+ for (const item of leftItems)
326
+ usedLeft.add(item);
327
+ for (const item of rightItems)
328
+ usedRight.add(item);
329
+ omittedCandidates += Math.max(0, leftItems.length + rightItems.length - maxCandidates * 2);
330
+ ambiguous.push({
331
+ left: leftItems.slice(0, maxCandidates),
332
+ right: rightItems.slice(0, maxCandidates),
333
+ basis: round.basis,
334
+ });
335
+ }
336
+ }
337
+ }
338
+ return {
339
+ pairs,
340
+ ambiguous,
341
+ leftOnly: left.filter((item) => !usedLeft.has(item)),
342
+ rightOnly: right.filter((item) => !usedRight.has(item)),
343
+ omittedCandidates,
344
+ };
345
+ };
346
+ const groupBy = (items, keyOf) => {
347
+ const grouped = new Map();
348
+ for (const item of items) {
349
+ const key = keyOf(item);
350
+ if (key === null)
351
+ continue;
352
+ const bucket = grouped.get(key) ?? [];
353
+ bucket.push(item);
354
+ grouped.set(key, bucket);
355
+ }
356
+ return grouped;
357
+ };
358
+ const buildMethodItems = (matches, leftEvidenceId, rightEvidenceId, limits) => {
359
+ const items = [];
360
+ for (const pair of matches.pairs) {
361
+ const dimensions = changedMethodDimensions(pair.left.item, pair.right.item);
362
+ items.push({
363
+ item_id: `mmc_method_${sha256({
364
+ left: pair.left.item.token,
365
+ right: pair.right.item.token,
366
+ basis: pair.basis,
367
+ })}`,
368
+ status: dimensions.length === 0 ? "unchanged" : "changed",
369
+ left: methodIdentity(pair.left.item),
370
+ right: methodIdentity(pair.right.item),
371
+ match: {
372
+ status: "matched",
373
+ basis: pair.basis,
374
+ confidence: pair.confidence,
375
+ candidate_left_tokens: [],
376
+ candidate_right_tokens: [],
377
+ },
378
+ dimensions,
379
+ evidence_links: [leftEvidenceId, rightEvidenceId],
380
+ limitations: [],
381
+ });
382
+ }
383
+ for (const ambiguous of matches.ambiguous) {
384
+ items.push({
385
+ item_id: `mmc_method_${sha256({
386
+ left: ambiguous.left.map(({ item }) => item.token),
387
+ right: ambiguous.right.map(({ item }) => item.token),
388
+ basis: ambiguous.basis,
389
+ })}`,
390
+ status: "unknown",
391
+ left: null,
392
+ right: null,
393
+ match: {
394
+ status: "ambiguous",
395
+ basis: ambiguous.basis,
396
+ confidence: "unknown",
397
+ candidate_left_tokens: ambiguous.left.map(({ item }) => item.token),
398
+ candidate_right_tokens: ambiguous.right.map(({ item }) => item.token),
399
+ },
400
+ dimensions: ["availability"],
401
+ evidence_links: [leftEvidenceId, rightEvidenceId],
402
+ limitations: [
403
+ "Multiple managed methods share the same non-name identity key; REA did not guess a token remap.",
404
+ ],
405
+ });
406
+ }
407
+ for (const item of matches.leftOnly)
408
+ items.push(methodOnlyItem(item.item, leftEvidenceId, rightEvidenceId, true));
409
+ for (const item of matches.rightOnly)
410
+ items.push(methodOnlyItem(item.item, leftEvidenceId, rightEvidenceId, false));
411
+ return limitItems(items, limits.max_method_matches);
412
+ };
413
+ const buildFieldItems = (matches, leftEvidenceId, rightEvidenceId, limits) => {
414
+ const items = [];
415
+ for (const pair of matches.pairs) {
416
+ const changed = pair.left.item.signature.raw_sha256 !==
417
+ pair.right.item.signature.raw_sha256 ||
418
+ pair.left.item.flags !== pair.right.item.flags;
419
+ items.push({
420
+ item_id: `mmc_field_${sha256({
421
+ left: pair.left.item.token,
422
+ right: pair.right.item.token,
423
+ })}`,
424
+ status: changed ? "changed" : "unchanged",
425
+ left: fieldIdentity(pair.left.item),
426
+ right: fieldIdentity(pair.right.item),
427
+ match: {
428
+ status: "matched",
429
+ basis: pair.basis,
430
+ confidence: pair.confidence,
431
+ candidate_left_tokens: [],
432
+ candidate_right_tokens: [],
433
+ },
434
+ evidence_links: [leftEvidenceId, rightEvidenceId],
435
+ limitations: [],
436
+ });
437
+ }
438
+ for (const ambiguous of matches.ambiguous)
439
+ items.push({
440
+ item_id: `mmc_field_${sha256({
441
+ left: ambiguous.left.map(({ item }) => item.token),
442
+ right: ambiguous.right.map(({ item }) => item.token),
443
+ })}`,
444
+ status: "unknown",
445
+ left: null,
446
+ right: null,
447
+ match: {
448
+ status: "ambiguous",
449
+ basis: ambiguous.basis,
450
+ confidence: "unknown",
451
+ candidate_left_tokens: ambiguous.left.map(({ item }) => item.token),
452
+ candidate_right_tokens: ambiguous.right.map(({ item }) => item.token),
453
+ },
454
+ evidence_links: [leftEvidenceId, rightEvidenceId],
455
+ limitations: [
456
+ "Multiple managed fields share the same signature; REA did not guess a token remap from names.",
457
+ ],
458
+ });
459
+ for (const item of matches.leftOnly)
460
+ items.push(fieldOnlyItem(item.item, leftEvidenceId, rightEvidenceId, true));
461
+ for (const item of matches.rightOnly)
462
+ items.push(fieldOnlyItem(item.item, leftEvidenceId, rightEvidenceId, false));
463
+ return limitItems(items, limits.max_field_matches);
464
+ };
465
+ const limitItems = (items, limit) => ({
466
+ items: items.slice(0, limit),
467
+ omitted: Math.max(0, items.length - limit),
468
+ });
469
+ const methodOnlyItem = (item, leftEvidenceId, rightEvidenceId, left) => ({
470
+ item_id: `mmc_method_${sha256({ token: item.token, side: left ? "left" : "right" })}`,
471
+ status: left ? "removed" : "added",
472
+ left: left ? methodIdentity(item) : null,
473
+ right: left ? null : methodIdentity(item),
474
+ match: {
475
+ status: "unmatched",
476
+ basis: "none",
477
+ confidence: "unknown",
478
+ candidate_left_tokens: [],
479
+ candidate_right_tokens: [],
480
+ },
481
+ dimensions: ["availability"],
482
+ evidence_links: [leftEvidenceId, rightEvidenceId],
483
+ limitations: [],
484
+ });
485
+ const fieldOnlyItem = (item, leftEvidenceId, rightEvidenceId, left) => ({
486
+ item_id: `mmc_field_${sha256({ token: item.token, side: left ? "left" : "right" })}`,
487
+ status: left ? "removed" : "added",
488
+ left: left ? fieldIdentity(item) : null,
489
+ right: left ? null : fieldIdentity(item),
490
+ match: {
491
+ status: "unmatched",
492
+ basis: "none",
493
+ confidence: "unknown",
494
+ candidate_left_tokens: [],
495
+ candidate_right_tokens: [],
496
+ },
497
+ evidence_links: [leftEvidenceId, rightEvidenceId],
498
+ limitations: [],
499
+ });
500
+ const methodIdentity = (item) => ({
501
+ token: item.token,
502
+ declaring_type: item.declaring_type,
503
+ name: item.name,
504
+ signature_sha256: item.signature.raw_sha256,
505
+ normalized_il_sha256: item.body.normalized_il_sha256,
506
+ });
507
+ const fieldIdentity = (item) => ({
508
+ token: item.token,
509
+ declaring_type: item.declaring_type,
510
+ name: item.name,
511
+ signature_sha256: item.signature.raw_sha256,
512
+ });
513
+ const changedMethodDimensions = (left, right) => {
514
+ const dimensions = [];
515
+ if (left.signature.raw_sha256 !== right.signature.raw_sha256)
516
+ dimensions.push("signature");
517
+ if (left.body.normalized_il_sha256 !== right.body.normalized_il_sha256)
518
+ dimensions.push("cil");
519
+ if (canonicalize(left.body.opcode_counts) !==
520
+ canonicalize(right.body.opcode_counts))
521
+ dimensions.push("opcode-shape");
522
+ if (callShape(left.body.anchors) !== callShape(right.body.anchors))
523
+ dimensions.push("call-shape");
524
+ if (fieldShape(left.body.anchors) !== fieldShape(right.body.anchors))
525
+ dimensions.push("field-shape");
526
+ if (canonicalize(left.body.exception_regions) !==
527
+ canonicalize(right.body.exception_regions))
528
+ dimensions.push("exception-shape");
529
+ if (left.body.status !== right.body.status)
530
+ dimensions.push("availability");
531
+ return dimensions;
532
+ };
533
+ const callShape = (anchors) => JSON.stringify(anchors
534
+ .filter(({ operand_kind }) => operand_kind === "method")
535
+ .map(({ opcode, operand_kind }) => [opcode, operand_kind]));
536
+ const fieldShape = (anchors) => JSON.stringify(anchors
537
+ .filter(({ operand_kind }) => operand_kind === "field")
538
+ .map(({ opcode, operand_kind }) => [opcode, operand_kind]));
539
+ const comparisonLimitations = (left, right, omittedItems, omittedCandidates) => {
540
+ const limitations = [
541
+ "Metadata tokens are build-local coordinates; matched pairs are remaps, not persistent identities.",
542
+ "Names are reported as observations but are not used as a matching basis.",
543
+ ];
544
+ if (!left.methods.complete || !right.methods.complete)
545
+ limitations.push("At least one method page is incomplete.");
546
+ if (!left.fields.complete || !right.fields.complete)
547
+ limitations.push("At least one field page is incomplete.");
548
+ if (left.coverage.state !== "complete" || right.coverage.state !== "complete")
549
+ limitations.push("At least one managed member observation is partial.");
550
+ if (omittedItems > 0)
551
+ limitations.push(`${String(omittedItems)} comparison items were omitted by output limits.`);
552
+ if (omittedCandidates > 0)
553
+ limitations.push(`${String(omittedCandidates)} ambiguous candidates were omitted by candidate limits.`);
554
+ return limitations;
555
+ };
556
+ /** Extract and authenticate a managed member inspection Evidence record. */
557
+ export const parseManagedMemberEvidence = (evidence) => {
558
+ const parsed = parseEvidence(evidence);
559
+ if (parsed.operation !== "inspect_managed_members")
560
+ throw new TypeError("Evidence operation is not inspect_managed_members");
561
+ return {
562
+ evidenceId: parsed.evidence_id,
563
+ result: managedMemberInspectionSchema.parse(parsed.normalized_result),
564
+ };
565
+ };