rea-agents 2.4.0 → 3.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 (231) hide show
  1. package/README.md +91 -19
  2. package/bridge/ghidra/ReaGhidraBridge.java +524 -4
  3. package/bridge/hopper_bridge.py +313 -28
  4. package/dist/application/AnalysisClientCleanup.js +14 -0
  5. package/dist/application/AnalysisContextQueries.js +130 -0
  6. package/dist/application/AnalysisSnapshotCache.js +4 -2
  7. package/dist/application/ApplicationWorkflowEvidenceResolver.js +31 -13
  8. package/dist/application/ArtifactInventory/scanCanonical.js +1 -1
  9. package/dist/application/BinarySession.js +20 -8
  10. package/dist/application/BinarySessionComposition.js +21 -0
  11. package/dist/application/BinarySessionRecords.js +79 -9
  12. package/dist/application/BinarySessionStatus.js +31 -1
  13. package/dist/application/BrowserEvidence.js +14 -0
  14. package/dist/application/BrowserScenarioCapturePort.js +1 -0
  15. package/dist/application/BrowserScenarioCaptureService.js +53 -0
  16. package/dist/application/BrowserScenarioEvidence.js +40 -0
  17. package/dist/application/CallPathTracing.js +160 -0
  18. package/dist/application/CapabilityInventory.js +84 -14
  19. package/dist/application/ClientRegistrationStatus.js +4 -1
  20. package/dist/application/CompositeProvider.js +10 -1
  21. package/dist/application/DirectAnalysis.js +13 -4
  22. package/dist/application/EnhancedLiteralTracing.js +118 -0
  23. package/dist/application/EnhancedTools.js +71 -127
  24. package/dist/application/EvidenceLedger.js +45 -16
  25. package/dist/application/JavaScriptApplicationWorkflowEvidence.js +13 -0
  26. package/dist/application/JavaScriptApplicationWorkflowService.js +27 -1
  27. package/dist/application/JavaScriptArtifactAnalysis.js +13 -4
  28. package/dist/application/JavaScriptRuntimeObservationEvidence.js +34 -0
  29. package/dist/application/JavaScriptRuntimeObservationPort.js +1 -0
  30. package/dist/application/JavaScriptRuntimeObservationService.js +46 -0
  31. package/dist/application/JavaScriptSemanticGraphAsyncProjection.js +147 -0
  32. package/dist/application/JavaScriptSemanticGraphBuilder.js +34 -6
  33. package/dist/application/JavaScriptSemanticGraphChildProcessProjection.js +156 -0
  34. package/dist/application/JavaScriptSemanticGraphConstruction.js +2 -2
  35. package/dist/application/JavaScriptSemanticGraphDataProjection.js +203 -0
  36. package/dist/application/JavaScriptSemanticGraphFingerprintProjection.js +25 -0
  37. package/dist/application/JavaScriptSemanticGraphFlowProjection.js +129 -8
  38. package/dist/application/JavaScriptSemanticGraphObjectProjection.js +81 -0
  39. package/dist/application/JavaScriptSemanticGraphProjection.js +14 -7
  40. package/dist/application/JavaScriptSemanticGraphResourceProjection.js +59 -0
  41. package/dist/application/JavaScriptSemanticGraphValueProjection.js +77 -0
  42. package/dist/application/LazyAnalysisProvider.js +77 -0
  43. package/dist/application/LoopbackReplay.js +1 -1
  44. package/dist/application/NativeApiInspection.js +80 -0
  45. package/dist/application/ProcessReactiveEffects.js +6 -0
  46. package/dist/application/ProcessReactiveHarness.js +16 -1
  47. package/dist/application/ProjectPermissionStore.js +1 -0
  48. package/dist/application/ReconstructionObligationAuthority.js +72 -0
  49. package/dist/application/ReconstructionObligationCandidates.js +265 -0
  50. package/dist/application/ReconstructionObligationLedgerDiagnostics.js +47 -0
  51. package/dist/application/ReconstructionObligationLedgerEvaluation.js +263 -0
  52. package/dist/application/ReconstructionObligationLedgerProjection.js +86 -0
  53. package/dist/application/ReconstructionObligationLedgerService.js +72 -0
  54. package/dist/application/ReconstructionObligationOriginalCases.js +33 -0
  55. package/dist/application/ReconstructionObligationPolicies.js +132 -0
  56. package/dist/application/ReconstructionReadinessService.js +48 -0
  57. package/dist/application/SessionProviderRouter.js +6 -6
  58. package/dist/application/SetupClientConfiguration.js +4 -2
  59. package/dist/application/runtime.js +26 -9
  60. package/dist/artifacts/ArtifactProvider.js +87 -22
  61. package/dist/browser/BrowserScenarioSecrets.js +53 -0
  62. package/dist/browser/BrowserScenarioSessionPort.js +1 -0
  63. package/dist/browser/CdpBrowserProvider.js +2 -2
  64. package/dist/browser/CdpCaptureDocuments.js +57 -0
  65. package/dist/browser/CdpCaptureStorage.js +71 -29
  66. package/dist/browser/CdpCaptureStorageFingerprints.js +328 -0
  67. package/dist/browser/CdpEndpoint.js +8 -6
  68. package/dist/browser/CdpPageCapture.js +2 -0
  69. package/dist/browser/JavaScriptRuntimeScope.js +38 -0
  70. package/dist/browser/PlaywrightBrowserScenarioProvider.js +259 -0
  71. package/dist/browser/PlaywrightExecutionBoundary.js +42 -0
  72. package/dist/browser/PlaywrightScenarioActions.js +62 -0
  73. package/dist/browser/PlaywrightScenarioArtifacts.js +238 -0
  74. package/dist/browser/PlaywrightScenarioBrowser.js +144 -0
  75. package/dist/browser/PlaywrightScenarioEvents.js +301 -0
  76. package/dist/browser/PlaywrightScenarioSession.js +300 -0
  77. package/dist/browser/V8InspectorCaptureProjection.js +110 -0
  78. package/dist/browser/V8InspectorEndpoint.js +72 -0
  79. package/dist/browser/V8InspectorProvider.js +267 -0
  80. package/dist/catalogIdentity.js +11 -3
  81. package/dist/cli/artifactCommands.js +44 -3
  82. package/dist/cli/coreAnalysisCommands.js +78 -0
  83. package/dist/cli.js +4 -0
  84. package/dist/cliApplicationCommands.js +30 -2
  85. package/dist/cliBrowserAdvancedCommands.js +17 -5
  86. package/dist/cliBrowserCommands.js +1 -0
  87. package/dist/cliBrowserScenarioCommands.js +43 -0
  88. package/dist/cliCommandNames.js +9 -0
  89. package/dist/cliJavaScriptRuntimeCommands.js +164 -0
  90. package/dist/cliObservationOptions.js +4 -0
  91. package/dist/cliPolicyCommands.js +1 -0
  92. package/dist/config/environment.js +12 -0
  93. package/dist/config/parseConfig.js +88 -17
  94. package/dist/contracts/analysisSearchContract.js +14 -0
  95. package/dist/contracts/applicationToolContracts.js +86 -2
  96. package/dist/contracts/applicationWorkflowInputContracts.js +18 -0
  97. package/dist/contracts/artifactToolContracts.js +29 -2
  98. package/dist/contracts/browserProviderToolContracts.js +7 -0
  99. package/dist/contracts/browserScenarioToolContracts.js +81 -0
  100. package/dist/contracts/browserToolContracts.js +88 -98
  101. package/dist/contracts/enhancedInputs.js +35 -6
  102. package/dist/contracts/functionInstructionContract.js +8 -0
  103. package/dist/contracts/functionWorkflowToolContracts.js +30 -0
  104. package/dist/contracts/hopperMemoryContracts.js +22 -0
  105. package/dist/contracts/javascriptApplicationWorkflowExamples.js +42 -0
  106. package/dist/contracts/javascriptRuntimeObservationToolContracts.js +58 -0
  107. package/dist/contracts/promptContracts.js +14 -15
  108. package/dist/contracts/reconstructionReadinessExample.js +436 -0
  109. package/dist/contracts/toolContractExamples.js +8 -0
  110. package/dist/contracts/toolContractTypes.js +1 -0
  111. package/dist/contracts/toolContracts.js +34 -25
  112. package/dist/contracts/toolEffects.js +25 -0
  113. package/dist/contracts/toolOutputSchemaGroups.js +120 -30
  114. package/dist/contracts/toolOutputSchemaPrimitives.js +37 -0
  115. package/dist/domain/analysisErrorPresentation.js +8 -2
  116. package/dist/domain/analysisErrorProjection.js +9 -0
  117. package/dist/domain/artifactInspection.js +347 -0
  118. package/dist/domain/browserCaptureComparison.js +82 -0
  119. package/dist/domain/browserObservation.js +8 -1
  120. package/dist/domain/browserObservationErrors.js +1 -0
  121. package/dist/domain/browserObservationSchemas.js +26 -0
  122. package/dist/domain/browserScenario.js +165 -0
  123. package/dist/domain/browserScenarioCapture.js +57 -0
  124. package/dist/domain/browserScenarioCaptureValues.js +204 -0
  125. package/dist/domain/browserScenarioDiff.js +324 -0
  126. package/dist/domain/browserScenarioDiffValues.js +157 -0
  127. package/dist/domain/browserScenarioNormalization.js +45 -0
  128. package/dist/domain/browserScenarioValues.js +372 -0
  129. package/dist/domain/bytecodeProvider.js +179 -0
  130. package/dist/domain/conformancePackage.js +169 -0
  131. package/dist/domain/conformanceReplay.js +104 -0
  132. package/dist/domain/conformanceTrustGate.js +193 -0
  133. package/dist/domain/customProtocolCapture.js +137 -0
  134. package/dist/domain/eventProcessTree.js +214 -0
  135. package/dist/domain/evidenceBundle.js +47 -0
  136. package/dist/domain/hopperStartupFailure.js +1 -1
  137. package/dist/domain/hopperValues.js +14 -1
  138. package/dist/domain/inputIssueProjection.js +7 -1
  139. package/dist/domain/javascriptRuntimeObservation.js +170 -0
  140. package/dist/domain/javascriptRuntimeReconciliationParsing.js +120 -2
  141. package/dist/domain/javascriptRuntimeReconciliationResult.js +3 -3
  142. package/dist/domain/javascriptRuntimeReconciliationRuntime.js +2 -2
  143. package/dist/domain/javascriptRuntimeReconciliationSchemas.js +1 -1
  144. package/dist/domain/javascriptSemanticAnalysis.js +13 -16
  145. package/dist/domain/javascriptSemanticAsyncEffects.js +253 -0
  146. package/dist/domain/javascriptSemanticChildProcesses.js +237 -0
  147. package/dist/domain/javascriptSemanticDataEffectHelpers.js +125 -0
  148. package/dist/domain/javascriptSemanticDataEffects.js +284 -0
  149. package/dist/domain/javascriptSemanticDerivedAnalysis.js +41 -0
  150. package/dist/domain/javascriptSemanticFingerprints.js +174 -0
  151. package/dist/domain/javascriptSemanticIr.js +21 -1
  152. package/dist/domain/javascriptSemanticObjects.js +115 -0
  153. package/dist/domain/javascriptSemanticPromises.js +290 -0
  154. package/dist/domain/javascriptSemanticResources.js +136 -0
  155. package/dist/domain/javascriptSourceParser.js +15 -0
  156. package/dist/domain/javascriptStaticAnalysis.js +12 -15
  157. package/dist/domain/javascriptStaticAnalysisBundler.js +38 -0
  158. package/dist/domain/javascriptStaticAnalysisCalls.js +23 -1
  159. package/dist/domain/javascriptStaticAnalysisHelpers.js +8 -0
  160. package/dist/domain/mobileApplicationInvestigation.js +159 -0
  161. package/dist/domain/nativeApiBoundary.js +130 -0
  162. package/dist/domain/objcSwiftMetadata.js +150 -0
  163. package/dist/domain/packageAnalysis.js +123 -0
  164. package/dist/domain/peInspection.js +196 -0
  165. package/dist/domain/processCapture.js +8 -2
  166. package/dist/domain/processReactiveScenario.js +1 -0
  167. package/dist/domain/processReactiveTransition.js +9 -4
  168. package/dist/domain/protocolCapture.js +243 -0
  169. package/dist/domain/providerCleanupError.js +11 -0
  170. package/dist/domain/reconstructionObligationLedgerSchemas.js +278 -0
  171. package/dist/domain/reconstructionReadiness.js +121 -0
  172. package/dist/domain/reconstructionReadinessFindingHelpers.js +17 -0
  173. package/dist/domain/reconstructionReadinessIntegrityFindings.js +118 -0
  174. package/dist/domain/reconstructionReadinessJourneyFindings.js +193 -0
  175. package/dist/domain/reconstructionReadinessSchemas.js +240 -0
  176. package/dist/domain/sourceToBundleComparison.js +335 -0
  177. package/dist/domain/sourceToBundleComparisonSchemas.js +133 -0
  178. package/dist/domain/sourceToBundleSignals.js +202 -0
  179. package/dist/domain/webCaptureDiff.js +141 -27
  180. package/dist/domain/webScreenshot.js +1 -1
  181. package/dist/dotnet/ManagedStaticProvider.js +34 -5
  182. package/dist/generatedMcpToolCatalog.js +37 -0
  183. package/dist/generatedPackageMetadata.js +4 -4
  184. package/dist/ghidra/GhidraDefaults.js +1 -1
  185. package/dist/ghidra/GhidraFunctionValues.js +40 -2
  186. package/dist/ghidra/GhidraProviderCapabilities.js +47 -38
  187. package/dist/hopper/HopperCleanup.js +158 -0
  188. package/dist/hopper/HopperClient.js +93 -99
  189. package/dist/hopper/HopperProvider.js +61 -30
  190. package/dist/hopper/HopperRequestQueue.js +215 -0
  191. package/dist/hopper/HopperResponseStream.js +46 -0
  192. package/dist/hopper/protocol.js +63 -20
  193. package/dist/main/transport.js +24 -27
  194. package/dist/main.js +14 -6
  195. package/dist/mcpDoctor.js +27 -4
  196. package/dist/mcpStartupPolicy.js +14 -0
  197. package/dist/process/ProcessCommandIdentity.js +19 -0
  198. package/dist/process/ProcessOwnership.js +11 -8
  199. package/dist/server/DynamicToolAvailability.js +70 -0
  200. package/dist/server/LazyToolCatalog.js +134 -0
  201. package/dist/server/createServer.js +92 -154
  202. package/dist/server/hydrateServerTools.js +191 -0
  203. package/dist/server/lifecycleProgress.js +10 -0
  204. package/dist/server/permissionFailure.js +12 -0
  205. package/dist/server/registerApplicationTools/compareSourceToBundle.js +35 -0
  206. package/dist/server/registerApplicationTools/helpers.js +11 -7
  207. package/dist/server/registerApplicationTools/obligationLedger.js +27 -0
  208. package/dist/server/registerApplicationTools/readiness.js +55 -0
  209. package/dist/server/registerApplicationTools.js +6 -0
  210. package/dist/server/registerArtifactTools.js +9 -1
  211. package/dist/server/registerBrowserScenarioTool.js +25 -0
  212. package/dist/server/registerBrowserTools.js +2 -2
  213. package/dist/server/registerCloseLifecycleTool.js +57 -0
  214. package/dist/server/registerEnhancedTools.js +28 -7
  215. package/dist/server/registerEvidenceResources.js +54 -101
  216. package/dist/server/registerEvidenceTools.js +9 -2
  217. package/dist/server/registerInvestigationTools.js +1 -2
  218. package/dist/server/registerJavaScriptApplicationGraphResource.js +0 -4
  219. package/dist/server/registerJavaScriptRuntimeObservationTools.js +37 -0
  220. package/dist/server/registerReconstructionObligationLedgerResource.js +32 -0
  221. package/dist/server/registerReconstructionReadinessResource.js +32 -0
  222. package/dist/server/registerSessionRecordTools.js +82 -12
  223. package/dist/server/registerSessionStatusTool.js +11 -8
  224. package/dist/server/registerSessionTools.js +32 -58
  225. package/dist/server/sessionAvailabilityPolicy.js +10 -8
  226. package/dist/server/toolRegistrationOptions.js +21 -6
  227. package/dist/server/toolResult.js +12 -0
  228. package/package.json +28 -11
  229. package/scripts/package-runner-bootstrap.mjs +72 -0
  230. package/scripts/rea.mjs +11 -1
  231. package/skills/reverse-engineer-anything/SKILL.md +10 -3
@@ -0,0 +1,179 @@
1
+ import { z } from "zod";
2
+ /** Supported managed-runtime bytecode families. */
3
+ export const bytecodeFamilySchema = z.enum(["jvm", "python"]);
4
+ /** Classification of bytecode provenance. */
5
+ export const bytecodeProvenanceSchema = z.enum([
6
+ "application",
7
+ "generated",
8
+ "vendored",
9
+ "bundled",
10
+ "standard_library",
11
+ ]);
12
+ /** A bytecode symbol discovered through static analysis. */
13
+ export const bytecodeSymbolSchema = z.strictObject({
14
+ /** Fully qualified name. */
15
+ name: z.string().min(1),
16
+ /** Raw bytecode location or offset. */
17
+ raw_location: z.string().min(1),
18
+ /** Normalized symbol type. */
19
+ kind: z.enum([
20
+ "class",
21
+ "interface",
22
+ "method",
23
+ "field",
24
+ "function",
25
+ "module",
26
+ "variable",
27
+ ]),
28
+ /** Bytecode version if known. */
29
+ bytecode_version: z.number().int().nullable(),
30
+ /** Provenance classification. */
31
+ provenance: bytecodeProvenanceSchema,
32
+ /** Whether the symbol is accessible (public). */
33
+ is_public: z.boolean().default(false),
34
+ });
35
+ /** A discovered bytecode artifact (class file, wheel, etc). */
36
+ export const bytecodeArtifactSchema = z.strictObject({
37
+ /** Artifact path relative to the analysis root. */
38
+ path: z.string().min(1),
39
+ /** Bytecode family. */
40
+ family: bytecodeFamilySchema,
41
+ /** Artifact format. */
42
+ format: z.enum(["class", "jar", "war", "ear", "pyc", "pyo", "whl", "zipapp"]),
43
+ /** Size in bytes. */
44
+ size: z.number().int().nonnegative(),
45
+ /** SHA-256 digest. */
46
+ digest: z.string().regex(/^[a-f0-9]{64}$/u),
47
+ /** Discovered symbols. */
48
+ symbols: z.array(bytecodeSymbolSchema).default([]),
49
+ /** Whether this is a standard library artifact. */
50
+ is_standard_library: z.boolean().default(false),
51
+ /** Whether this is a generated artifact. */
52
+ is_generated: z.boolean().default(false),
53
+ /** Whether this is a vendored artifact. */
54
+ is_vendored: z.boolean().default(false),
55
+ });
56
+ /** Static bytecode analysis result. */
57
+ export const bytecodeAnalysisSchema = z.strictObject({
58
+ /** Bytecode family. */
59
+ family: bytecodeFamilySchema,
60
+ /** All discovered artifacts. */
61
+ artifacts: z.array(bytecodeArtifactSchema).min(0).max(10_000),
62
+ /** Total symbols across all artifacts. */
63
+ total_symbols: z.number().int().nonnegative(),
64
+ /** Symbols classified as application code. */
65
+ application_symbols: z.number().int().nonnegative(),
66
+ /** Symbols classified as generated code. */
67
+ generated_symbols: z.number().int().nonnegative(),
68
+ /** Symbols classified as vendored code. */
69
+ vendored_symbols: z.number().int().nonnegative(),
70
+ /** Symbols classified as standard library. */
71
+ standard_library_symbols: z.number().int().nonnegative(),
72
+ });
73
+ /** Detect bytecode family from file extension. */
74
+ export function detectBytecodeFamily(path) {
75
+ const lower = path.toLowerCase();
76
+ if (lower.endsWith(".class") ||
77
+ lower.endsWith(".jar") ||
78
+ lower.endsWith(".war") ||
79
+ lower.endsWith(".ear")) {
80
+ return "jvm";
81
+ }
82
+ if (lower.endsWith(".pyc") ||
83
+ lower.endsWith(".pyo") ||
84
+ lower.endsWith(".whl") ||
85
+ lower.endsWith(".pyz") ||
86
+ lower.endsWith(".zipapp")) {
87
+ return "python";
88
+ }
89
+ return null;
90
+ }
91
+ /** Detect artifact format from file path. */
92
+ export function detectArtifactFormat(path) {
93
+ const lower = path.toLowerCase();
94
+ if (lower.endsWith(".class"))
95
+ return "class";
96
+ if (lower.endsWith(".jar"))
97
+ return "jar";
98
+ if (lower.endsWith(".war"))
99
+ return "war";
100
+ if (lower.endsWith(".ear"))
101
+ return "ear";
102
+ if (lower.endsWith(".pyc"))
103
+ return "pyc";
104
+ if (lower.endsWith(".pyo"))
105
+ return "pyo";
106
+ if (lower.endsWith(".whl"))
107
+ return "whl";
108
+ if (lower.endsWith(".pyz") || lower.endsWith(".zipapp"))
109
+ return "zipapp";
110
+ return null;
111
+ }
112
+ /** Classify bytecode symbol provenance. */
113
+ export function classifyProvenance(path, symbolName) {
114
+ const lower = path.toLowerCase();
115
+ // Standard library paths
116
+ if (lower.includes("/stdlib/") ||
117
+ lower.includes("/site-packages/") ||
118
+ lower.includes("java/") ||
119
+ lower.includes("javax/") ||
120
+ lower.includes("sun/") ||
121
+ symbolName.startsWith("java.") ||
122
+ symbolName.startsWith("javax.") ||
123
+ symbolName.startsWith("sun.") ||
124
+ symbolName.startsWith("__")) {
125
+ return "standard_library";
126
+ }
127
+ // Generated code
128
+ if (lower.includes("/generated/") ||
129
+ lower.includes("_generated") ||
130
+ lower.includes("/build/") ||
131
+ lower.includes("/target/") ||
132
+ lower.includes("/dist-packages/") ||
133
+ lower.includes("/__pycache__/") ||
134
+ symbolName.includes("Generated") ||
135
+ symbolName.includes("_generated")) {
136
+ return "generated";
137
+ }
138
+ // Vendored code
139
+ if (lower.includes("/vendor/") ||
140
+ lower.includes("/vendored/") ||
141
+ lower.includes("/third_party/") ||
142
+ lower.includes("/3rdparty/") ||
143
+ lower.includes("/node_modules/") ||
144
+ lower.includes("/vendor/")) {
145
+ return "vendored";
146
+ }
147
+ // Bundled code
148
+ if (lower.includes("/bundle/") ||
149
+ lower.includes("/bundled/") ||
150
+ lower.includes(".jar!") ||
151
+ lower.includes(".war!")) {
152
+ return "bundled";
153
+ }
154
+ return "application";
155
+ }
156
+ /** Analyze a set of bytecode artifacts and produce a summary. */
157
+ export function analyzeBytecodeArtifacts(artifacts, family) {
158
+ const applicationSymbols = artifacts
159
+ .flatMap((a) => a.symbols)
160
+ .filter((s) => s.provenance === "application").length;
161
+ const generatedSymbols = artifacts
162
+ .flatMap((a) => a.symbols)
163
+ .filter((s) => s.provenance === "generated").length;
164
+ const vendoredSymbols = artifacts
165
+ .flatMap((a) => a.symbols)
166
+ .filter((s) => s.provenance === "vendored").length;
167
+ const standardLibrarySymbols = artifacts
168
+ .flatMap((a) => a.symbols)
169
+ .filter((s) => s.provenance === "standard_library").length;
170
+ return {
171
+ family,
172
+ artifacts: artifacts.slice(0, 10_000),
173
+ total_symbols: artifacts.reduce((sum, a) => sum + a.symbols.length, 0),
174
+ application_symbols: applicationSymbols,
175
+ generated_symbols: generatedSymbols,
176
+ vendored_symbols: vendoredSymbols,
177
+ standard_library_symbols: standardLibrarySymbols,
178
+ };
179
+ }
@@ -0,0 +1,169 @@
1
+ import { createHash } from "node:crypto";
2
+ import canonicalize from "canonicalize";
3
+ import { z } from "zod";
4
+ import { evidenceEnvelopeSchema } from "./evidence.js";
5
+ import { evidenceBundleSchema } from "./evidenceBundle.js";
6
+ /** Schema version for the conformance package format. */
7
+ export const CONFORMANCE_PACKAGE_VERSION = 1;
8
+ /** A stable, path-independent identifier for a conformance package. */
9
+ export const conformancePackageIdSchema = z
10
+ .string()
11
+ .regex(/^cp_[a-f0-9]{64}$/u);
12
+ /** Identifier for a single scenario within a package. */
13
+ export const scenarioIdSchema = z
14
+ .string()
15
+ .regex(/^[A-Za-z][A-Za-z0-9._-]{0,99}$/u);
16
+ /** Deterministic manifest for a scenario fixture. */
17
+ export const scenarioManifestSchema = z.strictObject({
18
+ scenario_id: scenarioIdSchema,
19
+ name: z.string().min(1),
20
+ description: z.string().min(1),
21
+ /** Source-owned fixture path relative to the repository root. */
22
+ fixture_path: z.string().min(1),
23
+ /** Expected exit code or null if unspecified. */
24
+ expected_exit_code: z.number().int().nullable(),
25
+ /** Expected output patterns that must appear in the capture. */
26
+ expected_patterns: z.array(z.string()).default([]),
27
+ });
28
+ /** Replay plan describing how a scenario is replayed deterministically. */
29
+ export const replayPlanSchema = z.strictObject({
30
+ scenario_id: scenarioIdSchema,
31
+ /** Ordered steps to execute during replay. */
32
+ steps: z
33
+ .array(z.strictObject({
34
+ step_id: z.string().min(1),
35
+ action: z.string().min(1),
36
+ arguments: z.array(z.string()).default([]),
37
+ timeout_ms: z.number().int().positive(),
38
+ }))
39
+ .min(1)
40
+ .max(100),
41
+ /** Environment variables to set (never inherit host paths). */
42
+ environment: z.record(z.string(), z.string()).default({}),
43
+ });
44
+ /** Shim plan for intercepting observable effects. */
45
+ export const shimPlanSchema = z.strictObject({
46
+ scenario_id: scenarioIdSchema,
47
+ /** Shims to install, each intercepting a named effect. */
48
+ shims: z
49
+ .array(z.strictObject({
50
+ shim_id: z.string().min(1),
51
+ kind: z.enum(["filesystem", "network", "process", "signal"]),
52
+ target: z.string().min(1),
53
+ policy: z.enum(["observe", "allow", "block", "emulate"]),
54
+ }))
55
+ .min(0)
56
+ .max(50),
57
+ });
58
+ /** Expected evidence for a scenario, checked against the captured run. */
59
+ export const expectedEvidenceSchema = z.strictObject({
60
+ scenario_id: scenarioIdSchema,
61
+ /** Expected evidence envelopes. */
62
+ envelopes: z.array(evidenceEnvelopeSchema).min(0).max(100),
63
+ /** Expected evidence bundle. */
64
+ bundle: evidenceBundleSchema.nullable(),
65
+ /** Required dimensions that must be present in the evidence. */
66
+ required_dimensions: z.array(z.string().min(1)).default([]),
67
+ });
68
+ /** Verifier contract describing how to validate conformance. */
69
+ export const verifierContractSchema = z.strictObject({
70
+ scenario_id: scenarioIdSchema,
71
+ /** Dimensions to verify. */
72
+ dimensions: z
73
+ .array(z.strictObject({
74
+ name: z.string().min(1),
75
+ required: z.boolean().default(true),
76
+ comparison: z.enum(["exact", "semantic", "fuzzy"]),
77
+ }))
78
+ .min(1)
79
+ .max(50),
80
+ /** Tolerance for timing differences in milliseconds. */
81
+ timing_tolerance_ms: z.number().int().nonnegative().default(0),
82
+ });
83
+ /** Top-level conformance package manifest. */
84
+ export const conformancePackageSchema = z.strictObject({
85
+ schema_version: z.literal(CONFORMANCE_PACKAGE_VERSION),
86
+ package_id: conformancePackageIdSchema,
87
+ name: z.string().min(1),
88
+ description: z.string().min(1),
89
+ created_at: z.string().datetime(),
90
+ /** Scenario manifests. */
91
+ scenarios: z.array(scenarioManifestSchema).min(1).max(100),
92
+ /** Replay plans keyed by scenario_id. */
93
+ replay_plans: z.array(replayPlanSchema).min(1).max(100),
94
+ /** Shim plans keyed by scenario_id. */
95
+ shim_plans: z.array(shimPlanSchema).default([]),
96
+ /** Expected evidence for each scenario. */
97
+ expected_evidence: z.array(expectedEvidenceSchema).min(1).max(100),
98
+ /** Verifier contracts for each scenario. */
99
+ verifier_contracts: z.array(verifierContractSchema).min(1).max(100),
100
+ });
101
+ /** Validate a conformance package and cross-check internal references. */
102
+ export function validateConformancePackage(input) {
103
+ const parsed = conformancePackageSchema.safeParse(input);
104
+ if (!parsed.success)
105
+ return {
106
+ ok: false,
107
+ error: {
108
+ kind: "invalid_package",
109
+ message: parsed.error.issues[0]?.message ?? "invalid package",
110
+ },
111
+ };
112
+ const pkg = parsed.data;
113
+ // Check for duplicate scenario IDs
114
+ const seen = new Set();
115
+ for (const s of pkg.scenarios) {
116
+ if (seen.has(s.scenario_id))
117
+ return {
118
+ ok: false,
119
+ error: { kind: "duplicate_scenario", scenario_id: s.scenario_id },
120
+ };
121
+ seen.add(s.scenario_id);
122
+ }
123
+ // Check that replay plans reference existing scenarios
124
+ for (const rp of pkg.replay_plans) {
125
+ if (!seen.has(rp.scenario_id))
126
+ return {
127
+ ok: false,
128
+ error: {
129
+ kind: "scenario_not_found",
130
+ scenario_id: rp.scenario_id,
131
+ },
132
+ };
133
+ }
134
+ // Check that expected evidence references existing scenarios
135
+ for (const ee of pkg.expected_evidence) {
136
+ if (!seen.has(ee.scenario_id))
137
+ return {
138
+ ok: false,
139
+ error: {
140
+ kind: "scenario_not_found",
141
+ scenario_id: ee.scenario_id,
142
+ },
143
+ };
144
+ }
145
+ // Check that verifier contracts reference existing scenarios
146
+ for (const vc of pkg.verifier_contracts) {
147
+ if (!seen.has(vc.scenario_id))
148
+ return {
149
+ ok: false,
150
+ error: {
151
+ kind: "scenario_not_found",
152
+ scenario_id: vc.scenario_id,
153
+ },
154
+ };
155
+ }
156
+ return { ok: true, value: pkg };
157
+ }
158
+ /** Compute a deterministic package ID from the canonical JSON. */
159
+ export function computePackageId(pkg) {
160
+ const json = canonicalize(pkg);
161
+ if (!json)
162
+ throw new Error("failed to canonicalize package");
163
+ return `cp_${createHash("sha256").update(json).digest("hex")}`;
164
+ }
165
+ /** Create a conformance package with an auto-computed package_id. */
166
+ export function createConformancePackage(pkg) {
167
+ const package_id = computePackageId(pkg);
168
+ return { package_id, ...pkg };
169
+ }
@@ -0,0 +1,104 @@
1
+ import { z } from "zod";
2
+ import { conformancePackageSchema, } from "./conformancePackage.js";
3
+ import { trustGateResultSchema } from "./conformanceTrustGate.js";
4
+ /** Result of a single scenario replay. */
5
+ export const scenarioReplayResultSchema = z.strictObject({
6
+ scenario_id: z.string().min(1),
7
+ status: z.enum(["pass", "fail", "error", "skipped"]),
8
+ exit_code: z.number().int().nullable(),
9
+ duration_ms: z.number().int().nonnegative(),
10
+ output: z.string().default(""),
11
+ error: z.string().nullable(),
12
+ });
13
+ /** Result of replaying an entire conformance package. */
14
+ export const packageReplayResultSchema = z.strictObject({
15
+ package_id: z.string().min(1),
16
+ total_scenarios: z.number().int().positive(),
17
+ passed: z.number().int().nonnegative(),
18
+ failed: z.number().int().nonnegative(),
19
+ errored: z.number().int().nonnegative(),
20
+ skipped: z.number().int().nonnegative(),
21
+ scenario_results: z.array(scenarioReplayResultSchema),
22
+ trust_gate_results: z.array(trustGateResultSchema),
23
+ drift_detected: z.boolean(),
24
+ first_drift: z
25
+ .strictObject({
26
+ scenario_id: z.string().min(1),
27
+ dimension: z.string().min(1),
28
+ message: z.string(),
29
+ })
30
+ .nullable(),
31
+ });
32
+ /**
33
+ * Default no-op scenario runner that skips all scenarios.
34
+ * Real CI replays inject an actual runner.
35
+ */
36
+ async function defaultRunner() {
37
+ return {
38
+ scenario_id: "unknown",
39
+ status: "skipped",
40
+ exit_code: null,
41
+ duration_ms: 0,
42
+ output: "",
43
+ error: "no runner provided",
44
+ };
45
+ }
46
+ /**
47
+ * Replay a conformance package from a clean checkout.
48
+ * Runs each scenario, evaluates trust gates, and detects drift
49
+ * between the captured run and the committed package.
50
+ */
51
+ export async function replayConformancePackage(packageInput, actualEvidence, runner = defaultRunner, options = {}) {
52
+ const parsed = conformancePackageSchema.safeParse(packageInput);
53
+ if (!parsed.success)
54
+ throw new TypeError(`invalid conformance package: ${parsed.error.issues[0]?.message ?? "unknown"}`);
55
+ const pkg = parsed.data;
56
+ const scenarioResults = [];
57
+ let passed = 0;
58
+ let failed = 0;
59
+ let errored = 0;
60
+ let skipped = 0;
61
+ for (const scenario of pkg.scenarios) {
62
+ const result = await runner(scenario.scenario_id, scenario.fixture_path);
63
+ scenarioResults.push(result);
64
+ switch (result.status) {
65
+ case "pass":
66
+ passed++;
67
+ break;
68
+ case "fail":
69
+ failed++;
70
+ break;
71
+ case "error":
72
+ errored++;
73
+ break;
74
+ case "skipped":
75
+ skipped++;
76
+ break;
77
+ }
78
+ }
79
+ const trustGateResults = evaluatePackageTrustGates(pkg, actualEvidence, options);
80
+ const driftGates = trustGateResults.filter((r) => r.verdict === "fail");
81
+ const drift_detected = driftGates.length > 0;
82
+ const first_drift_gate = driftGates[0];
83
+ const first_drift = first_drift_gate?.first_divergence
84
+ ? {
85
+ scenario_id: first_drift_gate.scenario_id,
86
+ dimension: first_drift_gate.first_divergence.dimension,
87
+ message: first_drift_gate.first_divergence.message,
88
+ }
89
+ : null;
90
+ return {
91
+ package_id: pkg.package_id,
92
+ total_scenarios: pkg.scenarios.length,
93
+ passed,
94
+ failed,
95
+ errored,
96
+ skipped,
97
+ scenario_results: scenarioResults,
98
+ trust_gate_results: trustGateResults,
99
+ drift_detected,
100
+ first_drift,
101
+ };
102
+ }
103
+ /** Import trust gate evaluator for replay. */
104
+ import { evaluatePackageTrustGates } from "./conformanceTrustGate.js";
@@ -0,0 +1,193 @@
1
+ import { z } from "zod";
2
+ /** Result of comparing a single dimension. */
3
+ export const dimensionResultSchema = z.strictObject({
4
+ name: z.string().min(1),
5
+ status: z.enum(["match", "mismatch", "unknown", "truncated"]),
6
+ message: z.string().default(""),
7
+ evidence_ids: z.array(z.string()).default([]),
8
+ });
9
+ /** Result of evaluating a trust gate for one scenario. */
10
+ export const trustGateResultSchema = z.strictObject({
11
+ scenario_id: z.string().min(1),
12
+ verdict: z.enum(["pass", "fail", "unknown"]),
13
+ dimension_results: z.array(dimensionResultSchema),
14
+ first_divergence: z
15
+ .strictObject({
16
+ dimension: z.string().min(1),
17
+ evidence_id: z.string().min(1),
18
+ message: z.string(),
19
+ })
20
+ .nullable(),
21
+ });
22
+ /** Dimension that is volatile due to timing or normalization. */
23
+ const VOLATILE_DIMENSIONS = new Set([
24
+ "timing",
25
+ "timestamp",
26
+ "pid",
27
+ "ppid",
28
+ "duration_ms",
29
+ "created_at",
30
+ "updated_at",
31
+ ]);
32
+ /** Dimension that carries semantic content. */
33
+ const SEMANTIC_DIMENSIONS = new Set([
34
+ "exit_code",
35
+ "stdout",
36
+ "stderr",
37
+ "filesystem",
38
+ "process_tree",
39
+ "shim_events",
40
+ "protocol_events",
41
+ "event_journal",
42
+ ]);
43
+ /** Check if a dimension name is volatile (timing/normalization noise). */
44
+ export function isVolatileDimension(name) {
45
+ return VOLATILE_DIMENSIONS.has(name);
46
+ }
47
+ /** Check if a dimension name carries semantic content. */
48
+ export function isSemanticDimension(name) {
49
+ return SEMANTIC_DIMENSIONS.has(name);
50
+ }
51
+ /**
52
+ * Compare two values for a dimension, classifying semantic diffs
53
+ * from timing/normalization noise.
54
+ *
55
+ * Volatile dimensions (timing, timestamps, PIDs) are always treated
56
+ * as matches because their values are expected to differ across
57
+ * runs and do not carry semantic meaning.
58
+ */
59
+ export function compareDimension(dimensionName, expected, actual, options = {}) {
60
+ // Volatile dimensions: always match regardless of values
61
+ if (isVolatileDimension(dimensionName)) {
62
+ return {
63
+ name: dimensionName,
64
+ status: "match",
65
+ message: "volatile dimension ignored",
66
+ evidence_ids: [],
67
+ };
68
+ }
69
+ if (options.truncated) {
70
+ return {
71
+ name: dimensionName,
72
+ status: "truncated",
73
+ message: "evidence was truncated",
74
+ evidence_ids: [],
75
+ };
76
+ }
77
+ if (expected === undefined && actual === undefined) {
78
+ return {
79
+ name: dimensionName,
80
+ status: "unknown",
81
+ message: "both expected and actual are undefined",
82
+ evidence_ids: [],
83
+ };
84
+ }
85
+ if (expected === undefined || actual === undefined) {
86
+ return {
87
+ name: dimensionName,
88
+ status: "mismatch",
89
+ message: `expected ${expected === undefined ? "present" : "absent"}, actual ${actual === undefined ? "absent" : "present"}`,
90
+ evidence_ids: [],
91
+ };
92
+ }
93
+ // Semantic comparison for structured objects
94
+ if (typeof expected === "object" && typeof actual === "object") {
95
+ const expectedJson = JSON.stringify(sortKeys(expected));
96
+ const actualJson = JSON.stringify(sortKeys(actual));
97
+ if (expectedJson === actualJson) {
98
+ return {
99
+ name: dimensionName,
100
+ status: "match",
101
+ message: "",
102
+ evidence_ids: [],
103
+ };
104
+ }
105
+ return {
106
+ name: dimensionName,
107
+ status: "mismatch",
108
+ message: "semantic content differs",
109
+ evidence_ids: [],
110
+ };
111
+ }
112
+ // Primitive comparison
113
+ if (expected === actual) {
114
+ return {
115
+ name: dimensionName,
116
+ status: "match",
117
+ message: "",
118
+ evidence_ids: [],
119
+ };
120
+ }
121
+ return {
122
+ name: dimensionName,
123
+ status: "mismatch",
124
+ message: `expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`,
125
+ evidence_ids: [],
126
+ };
127
+ }
128
+ /** Recursively sort object keys for deterministic comparison. */
129
+ function sortKeys(value) {
130
+ if (value === null || typeof value !== "object")
131
+ return value;
132
+ if (Array.isArray(value))
133
+ return value.map(sortKeys);
134
+ const sorted = {};
135
+ for (const key of Object.keys(value).sort()) {
136
+ sorted[key] = sortKeys(value[key]);
137
+ }
138
+ return sorted;
139
+ }
140
+ /**
141
+ * Evaluate a trust gate for one scenario by comparing expected
142
+ * evidence against the actual captured run.
143
+ */
144
+ export function evaluateTrustGate(contract, expectedEvidence, actualEvidence, options = {}) {
145
+ const dimensionResults = [];
146
+ let firstDivergence = null;
147
+ for (const dim of contract.dimensions) {
148
+ if (!dim.required)
149
+ continue;
150
+ const expectedValue = expectedEvidence[dim.name];
151
+ const actualValue = actualEvidence[dim.name];
152
+ const result = compareDimension(dim.name, expectedValue, actualValue, options);
153
+ dimensionResults.push(result);
154
+ if (result.status === "mismatch" && firstDivergence === null) {
155
+ firstDivergence = {
156
+ dimension: dim.name,
157
+ evidence_id: result.evidence_ids[0] ?? "unknown",
158
+ message: result.message,
159
+ };
160
+ }
161
+ }
162
+ const hasFail = dimensionResults.some((r) => r.status === "mismatch" || r.status === "truncated");
163
+ const hasUnknown = dimensionResults.some((r) => r.status === "unknown");
164
+ const verdict = hasFail
165
+ ? "fail"
166
+ : hasUnknown
167
+ ? "unknown"
168
+ : "pass";
169
+ return {
170
+ scenario_id: contract.scenario_id,
171
+ verdict,
172
+ dimension_results: dimensionResults,
173
+ first_divergence: firstDivergence,
174
+ };
175
+ }
176
+ /**
177
+ * Evaluate trust gates for all scenarios in a conformance package.
178
+ * Rejects runs that differ in required dimensions. Unknown/truncated
179
+ * evidence is never treated as equivalence.
180
+ */
181
+ export function evaluatePackageTrustGates(pkg, actualEvidence, options = {}) {
182
+ const results = [];
183
+ for (const contract of pkg.verifier_contracts) {
184
+ const expected = pkg.expected_evidence.find((e) => e.scenario_id === contract.scenario_id);
185
+ if (!expected)
186
+ continue;
187
+ const expectedEvidence = expected.envelopes[0] ?? {};
188
+ const actual = actualEvidence[contract.scenario_id] ?? {};
189
+ const result = evaluateTrustGate(contract, expectedEvidence, actual, options);
190
+ results.push(result);
191
+ }
192
+ return results;
193
+ }