rea-agents 0.3.0 → 0.5.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 (125) hide show
  1. package/README.md +139 -21
  2. package/bridge/hopper_bridge.py +156 -14
  3. package/dist/application/AnalysisProvider.js +12 -1
  4. package/dist/application/ArtifactExtraction.js +166 -0
  5. package/dist/application/ArtifactGraphConstruction.js +257 -0
  6. package/dist/application/ArtifactInventory.js +253 -0
  7. package/dist/application/BinarySession.js +174 -14
  8. package/dist/application/CompositeProvider.js +73 -0
  9. package/dist/application/DirectAnalysis.js +44 -3
  10. package/dist/application/Doctor.js +35 -6
  11. package/dist/application/EnhancedTools.js +10 -7
  12. package/dist/application/EvidenceBundleCommands.js +51 -0
  13. package/dist/application/EvidenceBundleFiles.js +127 -0
  14. package/dist/application/EvidenceLedger.js +225 -18
  15. package/dist/application/FilesystemSnapshot.js +124 -0
  16. package/dist/application/LinuxHopper.js +186 -0
  17. package/dist/application/LoopbackReplay.js +195 -37
  18. package/dist/application/ProcessHarness.js +200 -191
  19. package/dist/application/ProcessNormalization.js +44 -0
  20. package/dist/application/ProcessOwnership.js +105 -0
  21. package/dist/application/ProcessSampling.js +284 -0
  22. package/dist/application/RealHopperAssertions.js +116 -0
  23. package/dist/application/ReferenceSourceImport.js +182 -0
  24. package/dist/application/ReferenceSourceImportEntries.js +122 -0
  25. package/dist/application/ReferenceSourceImportPolicy.js +73 -0
  26. package/dist/application/ReferenceSourceImportTypes.js +18 -0
  27. package/dist/application/ReferenceSourceVcsAdapter.js +34 -0
  28. package/dist/application/Setup.js +192 -41
  29. package/dist/application/Uninstall.js +130 -0
  30. package/dist/application/runtime.js +8 -1
  31. package/dist/artifacts/ArtifactPaths.js +51 -0
  32. package/dist/artifacts/ArtifactProvider.js +130 -0
  33. package/dist/artifacts/ArtifactReader.js +9 -0
  34. package/dist/artifacts/AsarArtifactReader.js +62 -0
  35. package/dist/artifacts/DirectoryArtifactReader.js +107 -0
  36. package/dist/artifacts/MachOSliceArtifactReader.js +66 -0
  37. package/dist/artifacts/SafeOutputTree.js +199 -0
  38. package/dist/artifacts/StreamBytes.js +10 -0
  39. package/dist/artifacts/ZipArtifactReader.js +109 -0
  40. package/dist/cli.js +229 -21
  41. package/dist/cliEvidenceCommands.js +68 -0
  42. package/dist/cliLogging.js +21 -0
  43. package/dist/config.js +35 -1
  44. package/dist/contracts/artifactComparisonExample.js +95 -0
  45. package/dist/contracts/artifactToolContracts.js +84 -0
  46. package/dist/contracts/enhancedInputs.js +4 -0
  47. package/dist/contracts/functionComparisonExample.js +57 -0
  48. package/dist/contracts/investigationExamples.js +118 -0
  49. package/dist/contracts/nativeToolContracts.js +52 -0
  50. package/dist/contracts/processCaptureExample.js +25 -0
  51. package/dist/contracts/toolContractExamples.js +69 -0
  52. package/dist/contracts/toolContracts.js +99 -36
  53. package/dist/contracts/toolOutputSchemas.js +156 -82
  54. package/dist/contracts/unknownContractExamples.js +33 -0
  55. package/dist/domain/artifactComparison.js +273 -0
  56. package/dist/domain/artifactGraph.js +194 -0
  57. package/dist/domain/artifactInventoryEvidence.js +150 -0
  58. package/dist/domain/binaryTarget.js +55 -0
  59. package/dist/domain/bundleComparison.js +266 -0
  60. package/dist/domain/callPath.js +346 -0
  61. package/dist/domain/changedBehavior.js +294 -0
  62. package/dist/domain/errors.js +199 -5
  63. package/dist/domain/evidence.js +41 -10
  64. package/dist/domain/evidenceBundle.js +187 -6
  65. package/dist/domain/functionComparison.js +201 -0
  66. package/dist/domain/functionComparisonNormalization.js +112 -0
  67. package/dist/domain/functionComparisonResults.js +54 -0
  68. package/dist/domain/functionComparisonSchemas.js +82 -0
  69. package/dist/domain/functionDossierEvidence.js +171 -0
  70. package/dist/domain/hopperValues.js +35 -8
  71. package/dist/domain/nativeInspection.js +142 -0
  72. package/dist/domain/processCapture.js +152 -54
  73. package/dist/domain/processComparison.js +106 -0
  74. package/dist/domain/reconstructionUnknowns.js +90 -0
  75. package/dist/domain/reconstructionVerification.js +285 -0
  76. package/dist/domain/reconstructionVerificationSchemas.js +126 -0
  77. package/dist/domain/referenceSourceClassification.js +496 -0
  78. package/dist/domain/referenceSourceGraph.js +376 -0
  79. package/dist/domain/referenceSourceImportParsing.js +235 -0
  80. package/dist/domain/referenceSourcePolicy.js +1 -0
  81. package/dist/domain/residualUnknown.js +239 -0
  82. package/dist/domain/staticRuntimeCorrelation.js +375 -0
  83. package/dist/hopper/BridgeLauncher.js +39 -3
  84. package/dist/hopper/HopperClient.js +14 -5
  85. package/dist/hopper/HopperProvider.js +57 -22
  86. package/dist/hopper/protocol.js +13 -2
  87. package/dist/identity.js +1 -0
  88. package/dist/main.js +5 -1
  89. package/dist/native/CommandRunner.js +156 -0
  90. package/dist/native/NativeMacOSProvider.js +306 -0
  91. package/dist/native/NativeMachoInspection.js +135 -0
  92. package/dist/native/parsers/codesign.js +55 -0
  93. package/dist/native/parsers/demangle.js +26 -0
  94. package/dist/native/parsers/dyldInfo.js +25 -0
  95. package/dist/native/parsers/lipo.js +67 -0
  96. package/dist/native/parsers/otool.js +193 -0
  97. package/dist/native/parsers/plist.js +23 -0
  98. package/dist/reference/ReferenceSourceReader.js +73 -0
  99. package/dist/reference/ReferenceSourceReaderEntries.js +206 -0
  100. package/dist/reference/ReferenceSourceReaderErrors.js +19 -0
  101. package/dist/reference/ReferenceSourceReaderFile.js +119 -0
  102. package/dist/reference/ReferenceSourceReaderPaths.js +23 -0
  103. package/dist/reference/ReferenceSourceReaderTypes.js +2 -0
  104. package/dist/reference/ReferenceSourceReaderValidate.js +71 -0
  105. package/dist/server/createServer.js +31 -5
  106. package/dist/server/recordDerivedEvidence.js +10 -0
  107. package/dist/server/registerArtifactComparisonTool.js +62 -0
  108. package/dist/server/registerArtifactTools.js +6 -0
  109. package/dist/server/registerBundleComparisonTool.js +47 -0
  110. package/dist/server/registerEnhancedTools.js +64 -12
  111. package/dist/server/registerEvidenceTools.js +36 -0
  112. package/dist/server/registerFunctionComparisonTool.js +68 -0
  113. package/dist/server/registerInvestigationTools.js +224 -0
  114. package/dist/server/registerNativeTools.js +6 -0
  115. package/dist/server/registerOfficialTools.js +47 -14
  116. package/dist/server/registerProcessComparisonTool.js +106 -0
  117. package/dist/server/registerSessionTools.js +179 -70
  118. package/dist/server/sessionEvidence.js +28 -0
  119. package/dist/server/sessionToolPolicies.js +64 -0
  120. package/dist/server/toolRegistrationOptions.js +7 -0
  121. package/dist/server/toolResult.js +8 -5
  122. package/install.sh +198 -0
  123. package/package.json +18 -1
  124. package/scripts/rea.mjs +5 -1
  125. package/skills/rea-analysis/SKILL.md +77 -2
@@ -1,13 +1,28 @@
1
1
  import { z } from "zod";
2
- import { evidenceBundleSchema } from "../domain/evidenceBundle.js";
2
+ import { jsonValueSchema } from "../domain/jsonValue.js";
3
3
  import { processCaptureSchema, processScenarioSchema, } from "../domain/processCapture.js";
4
+ import { recordUnknownInputSchema, updateUnknownInputSchema, } from "../domain/residualUnknown.js";
5
+ import { artifactComparisonInputSchema } from "../domain/artifactComparison.js";
6
+ import { functionComparisonInputSchema } from "../domain/functionComparison.js";
7
+ import { bundleComparisonInputSchema } from "../domain/bundleComparison.js";
8
+ import { changedBehaviorInputSchema } from "../domain/changedBehavior.js";
9
+ import { callPathInputSchema } from "../domain/callPath.js";
10
+ import { staticRuntimeCorrelationInputSchema } from "../domain/staticRuntimeCorrelation.js";
11
+ import { reconstructionVerificationInputSchema } from "../domain/reconstructionVerification.js";
4
12
  import { enhancedInputSchemas } from "./enhancedInputs.js";
5
- import { enhancedOutputSchemas, officialOutputSchemas, sessionOutputSchemas, } from "./toolOutputSchemas.js";
13
+ import { enhancedOutputSchemas, officialOutputSchemas, requireOutputSchema, sessionOutputSchemas, } from "./toolOutputSchemas.js";
14
+ import { TOOL_EXAMPLE_OVERRIDES } from "./toolContractExamples.js";
15
+ import { NATIVE_TOOL_CONTRACTS } from "./nativeToolContracts.js";
16
+ import { ARTIFACT_TOOL_CONTRACTS } from "./artifactToolContracts.js";
6
17
  const document = z.string().optional().describe("The document name");
7
18
  const address = z.string().describe("A Hopper address");
8
19
  const optionalAddress = address.optional();
9
20
  const procedure = z.string().describe("The procedure name or address");
10
- const pattern = z.string().describe("The regex pattern to search for");
21
+ const searchPattern = z
22
+ .string()
23
+ .min(1)
24
+ .max(256)
25
+ .describe("The literal text or bounded regex pattern to search for");
11
26
  const caseSensitive = z
12
27
  .boolean()
13
28
  .default(false)
@@ -16,29 +31,55 @@ const pagination = {
16
31
  offset: z.number().int().min(0).default(0),
17
32
  limit: z.number().int().min(1).max(500).default(100),
18
33
  };
34
+ const searchInput = {
35
+ pattern: searchPattern,
36
+ mode: z.enum(["literal", "regex"]).default("literal"),
37
+ case_sensitive: caseSensitive,
38
+ offset: z.number().int().min(0).default(0),
39
+ limit: z.number().int().min(1).max(100).default(100),
40
+ document,
41
+ };
42
+ const exampleInputSchema = z.record(z.string(), jsonValueSchema);
43
+ const examplesFor = (name, inputSchema) => {
44
+ const parsed = inputSchema.parse(TOOL_EXAMPLE_OVERRIDES[name] ?? {});
45
+ return [
46
+ {
47
+ title: `Example ${name.replaceAll("_", " ")} request`,
48
+ input: exampleInputSchema.parse(parsed),
49
+ },
50
+ ];
51
+ };
19
52
  const annotations = (name, kind) => ({
20
- readOnlyHint: kind === "enhanced" ||
21
- name === "export_evidence_bundle" ||
22
- (!name.startsWith("set_") &&
23
- name !== "unset_bookmark" &&
24
- name !== "goto_address" &&
25
- kind !== "session"),
26
- destructiveHint: name === "unset_bookmark" ||
53
+ readOnlyHint: (kind === "enhanced" && name !== "trace_feature") ||
54
+ name === "binary_session" ||
55
+ name === "list_unknowns" ||
56
+ name === "verify_unknown_resolution",
57
+ destructiveHint: name === "export_evidence_bundle" ||
58
+ name === "unset_bookmark" ||
27
59
  name === "set_address_name" ||
28
60
  name === "set_addresses_names" ||
29
61
  name === "set_comment" ||
30
62
  name === "set_inline_comment",
31
- idempotentHint: true,
32
- openWorldHint: false,
33
- });
34
- const official = (name, description, inputSchema) => ({
35
- name,
36
- description,
37
- kind: "official-proxy",
38
- inputSchema,
39
- outputSchema: requireOutputSchema(officialOutputSchemas, name),
40
- annotations: annotations(name, "official-proxy"),
63
+ idempotentHint: name !== "record_unknown" && name !== "update_unknown",
64
+ openWorldHint: name === "capture_process_scenario",
41
65
  });
66
+ const official = (name, description, inputSchema) => {
67
+ const trackedInputSchema = inputSchema.extend({
68
+ unknown_registry_approved: z
69
+ .literal(true)
70
+ .optional()
71
+ .describe("Explicit approval to record typed capability unavailability as a residual unknown"),
72
+ });
73
+ return {
74
+ name,
75
+ description,
76
+ kind: "official-proxy",
77
+ inputSchema: trackedInputSchema,
78
+ outputSchema: requireOutputSchema(officialOutputSchemas, name),
79
+ annotations: annotations(name, "official-proxy"),
80
+ examples: examplesFor(name, trackedInputSchema),
81
+ };
82
+ };
42
83
  const enhanced = (name, description, inputSchema) => ({
43
84
  name,
44
85
  description,
@@ -46,6 +87,7 @@ const enhanced = (name, description, inputSchema) => ({
46
87
  inputSchema,
47
88
  outputSchema: requireOutputSchema(enhancedOutputSchemas, name),
48
89
  annotations: annotations(name, "enhanced"),
90
+ examples: examplesFor(name, inputSchema),
49
91
  });
50
92
  const session = (name, description, inputSchema) => ({
51
93
  name,
@@ -54,13 +96,8 @@ const session = (name, description, inputSchema) => ({
54
96
  inputSchema,
55
97
  outputSchema: requireOutputSchema(sessionOutputSchemas, name),
56
98
  annotations: annotations(name, "session"),
99
+ examples: examplesFor(name, inputSchema),
57
100
  });
58
- const requireOutputSchema = (schemas, name) => {
59
- const schema = schemas[name];
60
- if (schema === undefined)
61
- throw new Error(`Missing output schema for ${name}`);
62
- return schema;
63
- };
64
101
  /** Bridge operations exposed without additional application composition. */
65
102
  export const OFFICIAL_TOOL_CONTRACTS = [
66
103
  official("address_name", "Resolve the analyzed name at a code or data address, defaulting to Hopper's current cursor. Use before following a symbol into xrefs; null means Hopper has no name at that address.", z.object({ document, address: optionalAddress })),
@@ -93,8 +130,8 @@ export const OFFICIAL_TOOL_CONTRACTS = [
93
130
  })),
94
131
  official("procedure_pseudo_code", "Decompile one analyzed procedure by symbol name or hexadecimal address. Returns Hopper pseudocode, not original source, and may return null; request procedure_assembly when instruction precision matters.", z.object({ procedure, document })),
95
132
  official("resolve_containing_procedure", "Resolve an arbitrary address, including an xref source or interior instruction, to its Hopper-analyzed containing procedure. A negative result includes an explicit reason and is not guessed from nearby symbols.", z.object({ address, document })),
96
- official("search_procedures", "Regex-search all analyzed procedure names with optional case sensitivity. The current bridge returns an unpaginated address/name map, so constrain patterns and use list_procedures for controlled exhaustive traversal.", z.object({ pattern, case_sensitive: caseSensitive, document })),
97
- official("search_strings", "Regex-search all analyzed strings with optional case sensitivity. The current bridge is unpaginated and evaluates Python regex, so use narrow patterns and follow matches with xrefs.", z.object({ pattern, case_sensitive: caseSensitive, document })),
133
+ official("search_procedures", "Search analyzed procedure names using literal matching by default or a structurally bounded regex. Returns a deterministic, offset-paginated page; continue at next_offset while has_more is true.", z.object(searchInput)),
134
+ official("search_strings", "Search analyzed strings using literal matching by default or a structurally bounded regex. Returns a deterministic, offset-paginated page with explicit value truncation; follow matches with xrefs.", z.object(searchInput)),
98
135
  official("set_address_name", "Assign an analyst name to one hexadecimal address and report Hopper's boolean result. This mutates analysis metadata; read it back with address_name before relying on it.", z.object({ address, name: z.string(), document })),
99
136
  official("set_addresses_names", "Assign analyst names to an address/name map and return per-address success booleans. This mutates analysis metadata; use for bounded batches and verify failures individually.", z.object({ names: z.record(z.string(), z.string()), document })),
100
137
  official("set_bookmark", "Create or replace a bookmark at a hexadecimal address. This mutates navigation metadata; verify with list_bookmarks and do not treat bookmarks as binary evidence.", z.object({ address, name: z.string().optional(), document })),
@@ -115,30 +152,56 @@ export const ENHANCED_TOOL_CONTRACTS = [
115
152
  enhanced("find_xrefs_to_name", "Resolve a name through Hopper and return analyzed references to its address. Use when starting from a selector or symbol; resolution failure is returned explicitly and xrefs remain untyped.", enhancedInputSchemas.find_xrefs_to_name),
116
153
  enhanced("binary_overview", "Use immediately after opening a target to summarize document, exhaustive procedure/string counts, and a bounded segment sample. detail controls segment fields and limit controls only the returned segment sample.", enhancedInputSchemas.binary_overview),
117
154
  enhanced("analyze_function", "Preferred bounded analysis for one procedure symbol or address. Returns identity, pseudocode, optional assembly, comments, calls, incoming references, and blocks; unsupported outgoing references and CFG edges carry explicit unavailable metadata.", enhancedInputSchemas.analyze_function),
118
- enhanced("trace_feature", "Trace a bounded literal feature query through matching strings and procedures, xrefs, and truthful containing-procedure resolution. Returns the operation budget, truncation, and residual unknowns; it does not infer reference kinds.", enhancedInputSchemas.trace_feature),
155
+ enhanced("trace_feature", "Trace a bounded literal feature query through matching strings and procedures, xrefs, and truthful containing-procedure resolution. Returns the operation budget, truncation, and residual unknowns; unknown_registry_approved: true records them durably without inferring reference kinds.", enhancedInputSchemas.trace_feature),
119
156
  ];
120
157
  /** Target lifecycle tools available only on the long-lived MCP adapter. */
121
158
  export const SESSION_TOOL_CONTRACTS = [
122
- session("open_binary", "Open a local executable, application bundle, or Hopper database, replacing the active target only after validation. This launches Hopper and may show UI; call binary_overview after success.", z.object({ path: z.string().min(1) })),
123
- session("close_binary", "Close the active Hopper-backed target and release its provider process. The operation is idempotent; call binary_session to verify the session is closed.", z.object({})),
124
- session("binary_session", "Report whether a target is open and, when open, its canonical path, format, and kind. Use before analysis calls or target switches; this performs no analysis.", z.object({})),
125
- session("export_evidence_bundle", "Return the session's deterministic Evidence v2 bundle without clearing it. Records are sorted by evidence ID, and array order carries no investigative meaning.", z.object({})),
126
- session("import_evidence_bundle", "Validate and atomically merge a local Evidence v2 bundle supplied as data. Semantic IDs are recomputed; tampering, unsupported versions, conflicts, and ledger overflow reject the entire import.", z.object({ bundle: evidenceBundleSchema })),
127
- session("capture_process_scenario", "Run one bounded process under a PTY using operator-approved executable and working roots. Requires approved: true on every call. Captures normalized terminal frames, sampled descendants, filesystem snapshots, and loopback HTTP/WebSocket replay. This launches a process and is disabled unless operator policy enables it; it is not a security sandbox.", processScenarioSchema),
159
+ session("open_binary", "Open a local executable, application bundle, archive, JavaScript, source map, plist, or Hopper database after validation. Providers start lazily: inventory_artifact does not launch Hopper; deep native operations may show Hopper UI.", z.object({ path: z.string().min(1) })),
160
+ session("close_binary", "Close the active target and every provider resource started for it. The operation is idempotent; call binary_session to verify the session is closed.", z.object({})),
161
+ session("binary_session", "Report provider identity, deterministic capability descriptors, and whether a target is open; open targets include canonical path, format, and kind. Use availability, effects, limits, and limitations before selecting analysis operations.", z.object({})),
162
+ session("export_evidence_bundle", "Return the session's deterministic Evidence v2 bundle, or atomically write it beneath an operator-approved root. Existing files require overwrite: true; records and manifests use canonical byte-stable ordering.", z.object({
163
+ path: z.string().min(1).optional(),
164
+ overwrite: z.boolean().default(false),
165
+ })),
166
+ session("import_evidence_bundle", "Read a bounded local JSON bundle beneath an operator-approved root, validate every Evidence v2 ID and canonical manifest, then atomically merge it. Imported content is data only and is never executed.", z.object({ path: z.string().min(1) })),
167
+ session("capture_process_scenario", "Run one bounded process under a PTY using operator-approved executable and working roots. Requires approved: true; unknown_registry_approved: true separately records capture residuals. Captures normalized terminal frames, descendants, filesystem snapshots, and loopback replay. Disabled unless operator policy enables it; not a security sandbox.", processScenarioSchema),
128
168
  session("compare_process_captures", "Compare two bounded process captures across terminal, exit, sampled process, filesystem, HTTP, and WebSocket evidence. Missing or truncated observations are never treated as equivalent.", z.object({
129
169
  left_evidence_id: z.string().regex(/^ev_[a-f0-9]{64}$/u),
130
170
  left: processCaptureSchema,
131
171
  right_evidence_id: z.string().regex(/^ev_[a-f0-9]{64}$/u),
132
172
  right: processCaptureSchema,
173
+ unknown_registry_approved: z
174
+ .literal(true)
175
+ .optional()
176
+ .describe("Explicit approval to record capture disagreement durably"),
177
+ })),
178
+ session("compare_artifacts", "Compare two bounded sets of inventory_artifact Evidence pages by logical occurrence path, content identity, metadata, and graph relations. Pages must share and satisfy their graph commitment; every delta cites both sets, and gaps yield truncated or unknown, never equivalence.", artifactComparisonInputSchema),
179
+ session("compare_functions", "Compare two explicit bounded sets of analyze_function Evidence pages across identity, exact provider text, calls, references, strings, and address-normalized CFG topology. Missing or provider-incompatible facets remain truncated or unknown; every conclusion cites both Evidence sets.", functionComparisonInputSchema),
180
+ session("compare_bundles", "Compare two canonical Evidence v2 bundles by exact record membership, explicit one-to-one observation pairs, and complete residual-unknown revision histories. Missing bundle members describe omission only, never behavioral equivalence; output is digest-anchored and deterministically paginated.", bundleComparisonInputSchema),
181
+ session("find_changed_behavior", "Aggregate validated process, artifact, and function comparison Evidence into a deterministic change report. Runtime observations remain distinct from static behavior candidates; missing or incomplete comparisons produce unresolved findings, never causal claims.", changedBehaviorInputSchema),
182
+ session("build_call_path", "Build bounded shortest-first direct-callee paths from explicit analyze_function Evidence groups using exact canonical addresses. Missing dossiers, incomplete callee pages, provider mixing, and depth frontiers remain unknown; every node and edge cites source Evidence.", callPathInputSchema),
183
+ session("correlate_static_and_runtime", "Evaluate explicit caller-declared hypotheses between exact static comparison findings and runtime comparison dimensions. Similar names or paths are never auto-matched, consistent cochange never proves causality, and unknown or truncated inputs remain unresolved.", staticRuntimeCorrelationInputSchema),
184
+ session("verify_reconstruction", "Verify a finite typed behavioral and structural specification against a canonical Evidence bundle. Pass means every declared claim has complete comparable authority—not global source equivalence; changed claims fail and missing, limited, or unresolved evidence stays unknown.", reconstructionVerificationInputSchema),
185
+ session("list_unknowns", "List current residual-unknown heads in deterministic ID order, with optional exact status, severity, and domain filters. This is read-only; unresolved, contradicted, and non-truth dispositions remain distinct.", z.object({
186
+ status: z
187
+ .enum(["open", "investigating", "blocked", "contradicted", "resolved"])
188
+ .optional(),
189
+ severity: z.enum(["low", "medium", "high", "critical"]).optional(),
190
+ domain: z.string().trim().min(1).max(100).optional(),
133
191
  })),
192
+ session("record_unknown", "Create one deterministic residual unknown and immutable mutation evidence. Requires approved: true, validates all evidence and relationship references, and rejects duplicate stable identity.", recordUnknownInputSchema),
193
+ session("update_unknown", "Append one immutable full-state revision and mutation evidence. Requires approved: true and exact expected_revision; stale concurrent writers fail instead of overwriting newer analysis.", updateUnknownInputSchema),
194
+ session("verify_unknown_resolution", "Revalidate the current residual-unknown head against live bundled evidence, exact authority/confidence/environment requirements, and revision integrity. Withdrawn and out-of-scope dispositions are not truth claims.", z.object({ unknown_id: z.string().regex(/^unk_[a-f0-9]{64}$/u) })),
134
195
  ];
135
196
  /**
136
197
  * Complete ordered public inventory used by registration and verification.
137
- * Keep this collection at 50 tools unless a deliberate contract change updates
198
+ * Keep this collection at 68 tools unless a deliberate contract change updates
138
199
  * snapshots, package verification, and real-Hopper verification together.
139
200
  */
140
201
  export const TOOL_CONTRACTS = [
141
202
  ...OFFICIAL_TOOL_CONTRACTS,
142
203
  ...ENHANCED_TOOL_CONTRACTS,
204
+ ...NATIVE_TOOL_CONTRACTS,
205
+ ...ARTIFACT_TOOL_CONTRACTS,
143
206
  ...SESSION_TOOL_CONTRACTS,
144
207
  ];
@@ -1,21 +1,92 @@
1
1
  import { z } from "zod";
2
2
  import { evidenceSchema } from "../domain/evidence.js";
3
- import { processCaptureSchema } from "../domain/processCapture.js";
3
+ import { processCaptureComparisonSchema, processCaptureSchema, } from "../domain/processCapture.js";
4
4
  import { evidenceBundleSchema } from "../domain/evidenceBundle.js";
5
- const resultOf = (schema) => evidenceSchema.omit({ result: true }).extend({ result: schema });
5
+ import { residualUnknownSchema } from "../domain/residualUnknown.js";
6
+ import { functionDossierSchema } from "../domain/hopperValues.js";
7
+ import { demangleSwiftSchema, inspectMachoSchema, inspectPlistSchema, inspectSignatureSchema, listArchitecturesSchema, } from "../domain/nativeInspection.js";
8
+ import { artifactExtractionResultSchema, artifactInventoryResultSchema, } from "../domain/artifactGraph.js";
9
+ import { artifactComparisonResultSchema } from "../domain/artifactComparison.js";
10
+ import { functionComparisonResultSchema } from "../domain/functionComparison.js";
11
+ import { bundleComparisonResultSchema } from "../domain/bundleComparison.js";
12
+ import { changedBehaviorResultSchema } from "../domain/changedBehavior.js";
13
+ import { callPathResultSchema } from "../domain/callPath.js";
14
+ import { staticRuntimeCorrelationResultSchema } from "../domain/staticRuntimeCorrelation.js";
15
+ import { reconstructionVerificationResultSchema } from "../domain/reconstructionVerification.js";
16
+ const resultOf = (schema) => evidenceSchema
17
+ .omit({ normalized_result: true })
18
+ .extend({ normalized_result: schema });
6
19
  const lifecycleResultOf = (schema) => z.object({ result: schema });
7
- const comparisonStatus = z.enum([
8
- "unchanged",
9
- "added",
10
- "removed",
11
- "changed",
12
- "truncated",
13
- "unknown",
20
+ /** Resolve a required named output schema or reject contract drift. */
21
+ export const requireOutputSchema = (schemas, name) => {
22
+ const schema = schemas[name];
23
+ if (schema === undefined)
24
+ throw new Error(`Missing output schema for ${name}`);
25
+ return schema;
26
+ };
27
+ const targetFormatSchema = z.enum([
28
+ "hopper",
29
+ "mach-o",
30
+ "elf",
31
+ "pe",
32
+ "zip",
33
+ "ipa",
34
+ "apk",
35
+ "asar",
36
+ "dmg",
37
+ "pkg",
38
+ "plist",
39
+ "javascript",
40
+ "source-map",
41
+ ]);
42
+ const targetKindSchema = z.enum([
43
+ "executable",
44
+ "database",
45
+ "archive",
46
+ "artifact",
14
47
  ]);
48
+ const providerCapability = z.object({
49
+ operation: z.string(),
50
+ available: z.boolean(),
51
+ reason: z.string().nullable(),
52
+ input_contract_version: z.number().int().min(1),
53
+ output_contract_version: z.number().int().min(1),
54
+ pagination: z.enum(["none", "offset", "cursor"]),
55
+ exhaustive: z.boolean(),
56
+ effects: z.object({
57
+ mutates_artifact: z.boolean(),
58
+ launches_process: z.boolean(),
59
+ may_show_ui: z.boolean(),
60
+ may_access_network: z.boolean(),
61
+ may_write_filesystem: z.boolean(),
62
+ changes_permissions: z.boolean(),
63
+ requires_root: z.boolean(),
64
+ }),
65
+ limits: z.object({
66
+ max_results: z.number().int().min(0).nullable(),
67
+ max_payload_bytes: z.number().int().min(0).nullable(),
68
+ timeout_ms: z.number().int().min(0).nullable(),
69
+ }),
70
+ limitations: z.array(z.string()),
71
+ });
72
+ const providerIdentity = z.object({
73
+ id: z.string(),
74
+ name: z.string(),
75
+ version: z.string().nullable(),
76
+ });
77
+ const sessionProvider = z.object({
78
+ provider: providerIdentity,
79
+ providers: z.array(providerIdentity),
80
+ capabilities: z.array(providerCapability),
81
+ });
15
82
  const nullableText = z.string().nullable();
16
83
  const addressList = z.array(z.string());
17
84
  const addressedEntry = z.object({ address: z.string(), name: z.string() });
18
85
  const procedureIdentity = z.object({ address: z.string(), name: z.string() });
86
+ const localVariable = z.object({
87
+ description: z.string(),
88
+ provenance: z.literal("hopper-public-python-api"),
89
+ });
19
90
  const containingProcedureResolution = z.discriminatedUnion("found", [
20
91
  z.object({
21
92
  query_address: z.string(),
@@ -48,22 +119,31 @@ const pageOutput = z.object({
48
119
  next_offset: z.number().int().min(0).nullable(),
49
120
  has_more: z.boolean(),
50
121
  });
51
- const segmentOutput = resultOf(z.array(z.object({
122
+ const searchPageOutput = pageOutput.extend({
123
+ items: z.array(z.object({
124
+ address: z.string(),
125
+ value: z.string(),
126
+ value_truncated: z.boolean(),
127
+ })),
128
+ });
129
+ const memoryRegionOutput = z.object({
52
130
  name: z.string(),
53
131
  start: z.string(),
54
132
  end: z.string(),
133
+ readable: z.boolean().nullable(),
55
134
  writable: z.boolean().nullable(),
56
135
  executable: z.boolean().nullable(),
57
136
  permissions: unavailable,
58
- sections: z.array(z.object({ name: z.string(), start: z.string(), end: z.string() })),
59
- })));
137
+ provenance: z.literal("hopper-public-python-api"),
138
+ });
139
+ const segmentOutput = resultOf(z.array(memoryRegionOutput.extend({ sections: z.array(memoryRegionOutput) })));
60
140
  const procedureInfoOutput = resultOf(z.object({
61
141
  name: z.string(),
62
142
  entrypoint: z.string(),
63
143
  basicblock_count: z.number().int().min(0),
64
144
  length: z.number().min(0),
65
145
  signature: nullableText,
66
- locals: z.array(z.json()),
146
+ locals: z.array(localVariable),
67
147
  }));
68
148
  const symbolDiscoveryOutput = (property) => resultOf(z.object({
69
149
  count: z.number().int().min(0),
@@ -73,54 +153,7 @@ const graphNode = z.union([
73
153
  z.object({ address: z.string(), calls: z.array(z.string()) }),
74
154
  z.object({ address: z.string(), error: z.string() }),
75
155
  ]);
76
- const referenceEdge = z.object({
77
- source_address: z.string(),
78
- target_address: z.string(),
79
- source_procedure: procedureIdentity.nullable(),
80
- target_procedure: procedureIdentity.nullable(),
81
- kind: unavailable,
82
- });
83
- const referencedValue = z.object({
84
- address: z.string(),
85
- value: z.string(),
86
- source_address: z.string(),
87
- });
88
- const functionDossierOutput = resultOf(z.object({
89
- procedure: z.object({
90
- address: z.string(),
91
- name: z.string(),
92
- signature: nullableText,
93
- locals: z.array(z.json()),
94
- }),
95
- pseudocode: z.object({
96
- text: z.string(),
97
- total_chars: z.number().int().min(0),
98
- returned_chars: z.number().int().min(0),
99
- truncated: z.boolean(),
100
- next_offset: z.number().int().min(0).nullable(),
101
- }),
102
- assembly: bounded(z.string()),
103
- comments: bounded(z.object({
104
- address: z.string(),
105
- kind: z.enum(["comment", "inline"]),
106
- text: z.string(),
107
- })),
108
- callers: bounded(procedureIdentity),
109
- callees: bounded(procedureIdentity),
110
- incoming_references: bounded(referenceEdge),
111
- outgoing_references: bounded(referenceEdge),
112
- referenced_strings: bounded(referencedValue),
113
- referenced_names: bounded(referencedValue),
114
- basic_blocks: bounded(z.object({
115
- start: z.string(),
116
- end: z.string(),
117
- successors: z.array(z.string()),
118
- })),
119
- instruction_scan: z.object({
120
- scanned: z.number().int().min(0),
121
- truncated: z.boolean(),
122
- }),
123
- }));
156
+ const functionDossierOutput = resultOf(functionDossierSchema);
124
157
  /** Exact structured-content schemas for the direct Hopper operations. */
125
158
  export const officialOutputSchemas = {
126
159
  address_name: resultOf(nullableText),
@@ -158,8 +191,8 @@ export const officialOutputSchemas = {
158
191
  })),
159
192
  procedure_pseudo_code: resultOf(nullableText),
160
193
  resolve_containing_procedure: resultOf(containingProcedureResolution),
161
- search_procedures: resultOf(z.record(z.string(), z.string())),
162
- search_strings: resultOf(z.record(z.string(), z.string())),
194
+ search_procedures: resultOf(searchPageOutput),
195
+ search_strings: resultOf(searchPageOutput),
163
196
  set_address_name: resultOf(z.boolean()),
164
197
  set_addresses_names: resultOf(z.record(z.string(), z.boolean())),
165
198
  set_bookmark: resultOf(z.boolean()),
@@ -223,45 +256,86 @@ export const enhancedOutputSchemas = {
223
256
  residual_unknowns: z.array(z.string()),
224
257
  })),
225
258
  };
259
+ /** Exact Evidence v2 schemas for provider-neutral native inspection. */
260
+ export const nativeOutputSchemas = {
261
+ inspect_macho: resultOf(inspectMachoSchema),
262
+ inspect_signature: resultOf(inspectSignatureSchema),
263
+ inspect_plist: resultOf(inspectPlistSchema),
264
+ list_architectures: resultOf(listArchitecturesSchema),
265
+ demangle_swift: resultOf(demangleSwiftSchema),
266
+ };
267
+ /** Exact Evidence v2 schemas for provider-neutral artifact graph operations. */
268
+ export const artifactOutputSchemas = {
269
+ inventory_artifact: resultOf(artifactInventoryResultSchema),
270
+ extract_artifact: resultOf(artifactExtractionResultSchema),
271
+ };
226
272
  /** Exact structured-content schemas for target lifecycle operations. */
227
273
  export const sessionOutputSchemas = {
228
274
  open_binary: lifecycleResultOf(z.object({
229
275
  path: z.string(),
230
- format: z.enum(["hopper", "mach-o", "elf", "pe"]),
231
- kind: z.enum(["executable", "database"]),
276
+ format: targetFormatSchema,
277
+ kind: targetKindSchema,
232
278
  loaderArgs: z.array(z.string()),
233
279
  sha256: z.string().regex(/^[a-f0-9]{64}$/u),
234
280
  architecture: z.enum(["x86", "x86_64", "arm", "arm64"]).nullable(),
235
281
  })),
236
282
  close_binary: lifecycleResultOf(z.null()),
237
283
  binary_session: lifecycleResultOf(z.union([
238
- z.object({ open: z.literal(false) }),
239
- z.object({
284
+ sessionProvider.extend({ open: z.literal(false) }),
285
+ sessionProvider.extend({
240
286
  open: z.literal(true),
241
287
  path: z.string(),
242
- format: z.enum(["hopper", "mach-o", "elf", "pe"]),
243
- kind: z.enum(["executable", "database"]),
288
+ format: targetFormatSchema,
289
+ kind: targetKindSchema,
244
290
  sha256: z.string().regex(/^[a-f0-9]{64}$/u),
245
291
  architecture: z.enum(["x86", "x86_64", "arm", "arm64"]).nullable(),
246
292
  }),
247
293
  ])),
248
- export_evidence_bundle: lifecycleResultOf(evidenceBundleSchema),
294
+ export_evidence_bundle: lifecycleResultOf(z.union([
295
+ evidenceBundleSchema,
296
+ z.object({
297
+ path: z.string(),
298
+ bytes: z.number().int().min(0),
299
+ records: z.number().int().min(0),
300
+ }),
301
+ ])),
249
302
  import_evidence_bundle: lifecycleResultOf(z.object({
250
303
  imported: z.number().int().min(0),
251
304
  total: z.number().int().min(0),
252
305
  })),
253
306
  capture_process_scenario: lifecycleResultOf(evidenceSchema
254
- .omit({ result: true })
255
- .extend({ result: processCaptureSchema })),
256
- compare_process_captures: lifecycleResultOf(evidenceSchema.omit({ result: true }).extend({
257
- result: z.object({
258
- status: comparisonStatus,
259
- terminal: comparisonStatus,
260
- exit: comparisonStatus,
261
- filesystem: comparisonStatus,
262
- protocol: comparisonStatus,
263
- process: comparisonStatus,
264
- limitations: z.array(z.string()),
265
- }),
307
+ .omit({ normalized_result: true })
308
+ .extend({ normalized_result: processCaptureSchema })),
309
+ compare_process_captures: lifecycleResultOf(evidenceSchema.omit({ normalized_result: true }).extend({
310
+ normalized_result: processCaptureComparisonSchema,
311
+ })),
312
+ compare_artifacts: lifecycleResultOf(evidenceSchema.omit({ normalized_result: true }).extend({
313
+ normalized_result: artifactComparisonResultSchema,
314
+ })),
315
+ compare_functions: lifecycleResultOf(evidenceSchema.omit({ normalized_result: true }).extend({
316
+ normalized_result: functionComparisonResultSchema,
317
+ })),
318
+ compare_bundles: lifecycleResultOf(evidenceSchema.omit({ normalized_result: true }).extend({
319
+ normalized_result: bundleComparisonResultSchema,
320
+ })),
321
+ find_changed_behavior: lifecycleResultOf(evidenceSchema.omit({ normalized_result: true }).extend({
322
+ normalized_result: changedBehaviorResultSchema,
323
+ })),
324
+ build_call_path: lifecycleResultOf(evidenceSchema.omit({ normalized_result: true }).extend({
325
+ normalized_result: callPathResultSchema,
326
+ })),
327
+ correlate_static_and_runtime: lifecycleResultOf(evidenceSchema.omit({ normalized_result: true }).extend({
328
+ normalized_result: staticRuntimeCorrelationResultSchema,
329
+ })),
330
+ verify_reconstruction: lifecycleResultOf(evidenceSchema.omit({ normalized_result: true }).extend({
331
+ normalized_result: reconstructionVerificationResultSchema,
332
+ })),
333
+ list_unknowns: lifecycleResultOf(z.array(residualUnknownSchema)),
334
+ record_unknown: lifecycleResultOf(residualUnknownSchema),
335
+ update_unknown: lifecycleResultOf(residualUnknownSchema),
336
+ verify_unknown_resolution: lifecycleResultOf(z.object({
337
+ valid: z.boolean(),
338
+ truthVerified: z.boolean(),
339
+ unknown: residualUnknownSchema,
266
340
  })),
267
341
  };
@@ -0,0 +1,33 @@
1
+ /** Exact example inputs for residual-unknown lifecycle contracts. */
2
+ export const UNKNOWN_CONTRACT_EXAMPLES = {
3
+ list_unknowns: {},
4
+ record_unknown: {
5
+ approved: true,
6
+ question: "Does this branch require an unavailable external service?",
7
+ severity: "medium",
8
+ domain: "protocol",
9
+ required_authority: "controlled-replay",
10
+ required_confidence: "observed",
11
+ required_environment: null,
12
+ recommended_probes: [
13
+ { operation: "capture_process_scenario", rationale: "Replay branch." },
14
+ ],
15
+ relationships: [],
16
+ },
17
+ update_unknown: {
18
+ approved: true,
19
+ unknown_id: `unk_${"0".repeat(64)}`,
20
+ expected_revision: 1,
21
+ status: "investigating",
22
+ severity: "medium",
23
+ supporting_evidence_ids: [],
24
+ contradicting_evidence_ids: [],
25
+ required_authority: "controlled-replay",
26
+ required_confidence: "observed",
27
+ required_environment: null,
28
+ recommended_probes: [],
29
+ relationships: [],
30
+ resolution: null,
31
+ },
32
+ verify_unknown_resolution: { unknown_id: `unk_${"0".repeat(64)}` },
33
+ };