rea-agents 1.3.0 → 1.4.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 (72) hide show
  1. package/README.md +21 -0
  2. package/bridge/hopper_bridge.py +12 -3
  3. package/dist/application/ArtifactGraphConstruction.js +1 -1
  4. package/dist/application/ArtifactInventory.js +98 -26
  5. package/dist/application/AuthorizedArtifactInventory.js +2 -2
  6. package/dist/application/BinarySession.js +83 -0
  7. package/dist/application/CapabilityInventory.js +127 -0
  8. package/dist/application/ClientRegistrationStatus.js +69 -0
  9. package/dist/application/CrossVersionInventory.js +19 -8
  10. package/dist/application/CrossVersionInvestigation.js +68 -5
  11. package/dist/application/DirectAnalysis.js +97 -9
  12. package/dist/application/Doctor.js +74 -2
  13. package/dist/application/PermissionAuthority.js +183 -0
  14. package/dist/application/PermissionConfiguration.js +26 -0
  15. package/dist/application/ProcessCaptureError.js +13 -0
  16. package/dist/application/ProcessCaptureLifecycle.js +3 -1
  17. package/dist/application/ProcessCli.js +23 -10
  18. package/dist/application/ProcessHarness.js +4 -1
  19. package/dist/application/ProgressReporter.js +30 -0
  20. package/dist/application/ProjectPermissionStore.js +110 -0
  21. package/dist/application/Setup.js +2 -41
  22. package/dist/application/SupportedClients.js +41 -0
  23. package/dist/application/Uninstall.js +1 -1
  24. package/dist/application/Upgrade.js +2 -0
  25. package/dist/application/runtime.js +1 -1
  26. package/dist/artifacts/ArtifactProvider.js +13 -3
  27. package/dist/catalogIdentity.js +121 -0
  28. package/dist/cli.js +46 -3
  29. package/dist/cliEvidenceCommands.js +42 -1
  30. package/dist/cliInvestigationCommands.js +57 -6
  31. package/dist/cliLogging.js +20 -3
  32. package/dist/cliPolicyCommands.js +115 -0
  33. package/dist/cliProcessCommands.js +4 -14
  34. package/dist/config.js +63 -3
  35. package/dist/contracts/artifactToolContracts.js +14 -1
  36. package/dist/contracts/errorSchemas.js +98 -0
  37. package/dist/contracts/toolContracts.js +10 -1
  38. package/dist/contracts/toolOutputSchemas.js +62 -30
  39. package/dist/domain/artifactComparison.js +38 -11
  40. package/dist/domain/artifactGraph.js +25 -1
  41. package/dist/domain/artifactInventoryEvidence.js +18 -0
  42. package/dist/domain/changedBehavior.js +5 -3
  43. package/dist/domain/errors.js +216 -3
  44. package/dist/domain/investigationWorkspace.js +131 -4
  45. package/dist/domain/permissionPolicy.js +157 -0
  46. package/dist/domain/processComparison.js +1 -0
  47. package/dist/domain/reconstructionVerification.js +1 -1
  48. package/dist/domain/reconstructionVerificationSchemas.js +1 -0
  49. package/dist/domain/staticRuntimeCorrelation.js +5 -1
  50. package/dist/generatedPackageMetadata.js +9 -0
  51. package/dist/hopper/BridgeLauncher.js +7 -3
  52. package/dist/hopper/HopperClient.js +32 -5
  53. package/dist/identity.js +10 -3
  54. package/dist/logger.js +2 -2
  55. package/dist/main.js +95 -7
  56. package/dist/server/createServer.js +52 -5
  57. package/dist/server/mcpProgress.js +23 -0
  58. package/dist/server/registerArtifactComparisonTool.js +6 -2
  59. package/dist/server/registerBundleComparisonTool.js +6 -2
  60. package/dist/server/registerEnhancedTools.js +18 -1
  61. package/dist/server/registerEvidenceResources.js +302 -0
  62. package/dist/server/registerEvidenceTools.js +58 -1
  63. package/dist/server/registerFunctionComparisonTool.js +6 -2
  64. package/dist/server/registerInvestigationTools.js +83 -10
  65. package/dist/server/registerOfficialTools.js +19 -1
  66. package/dist/server/registerProcessComparisonTool.js +7 -3
  67. package/dist/server/registerSessionStatusTool.js +44 -3
  68. package/dist/server/registerSessionTools.js +129 -7
  69. package/dist/server/runDerivedOperation.js +35 -0
  70. package/dist/server/toolResult.js +27 -3
  71. package/dist/serverIdentity.js +70 -0
  72. package/package.json +6 -4
@@ -0,0 +1,98 @@
1
+ import { z } from "zod";
2
+ const categorySchema = z.enum([
3
+ "invalid_input",
4
+ "permission_required",
5
+ "unsupported_provider",
6
+ "integrity_mismatch",
7
+ "truncated",
8
+ "cancelled",
9
+ "timeout",
10
+ "unavailable",
11
+ "execution_failure",
12
+ ]);
13
+ const remediationSchema = z
14
+ .object({
15
+ action: z.string().min(1),
16
+ restart_required: z.boolean(),
17
+ elicitation_supported: z.boolean().optional(),
18
+ })
19
+ .strict();
20
+ const common = {
21
+ category: categorySchema,
22
+ message: z.string().min(1),
23
+ retryable: z.boolean(),
24
+ remediation: remediationSchema,
25
+ };
26
+ const genericDetails = z.record(z.string(), z.json()).optional();
27
+ const generic = (code) => z
28
+ .object({ code: z.literal(code), ...common, details: genericDetails })
29
+ .strict();
30
+ const scopeSchema = z
31
+ .object({
32
+ capability: z.string(),
33
+ roots: z.array(z.string()),
34
+ executables: z.array(z.string()),
35
+ environment_names: z.array(z.string()),
36
+ network: z.enum(["none", "loopback", "external"]),
37
+ mount: z.boolean(),
38
+ })
39
+ .strict();
40
+ const missingScopeSchema = z
41
+ .object({
42
+ roots: z.array(z.string()).optional(),
43
+ executables: z.array(z.string()).optional(),
44
+ environment_names: z.array(z.string()).optional(),
45
+ network: z.enum(["none", "loopback", "external"]).optional(),
46
+ mount: z.literal(true).optional(),
47
+ })
48
+ .strict();
49
+ /** Stable discriminated schema shared by every CLI and MCP error surface. */
50
+ export const analysisErrorProjectionSchema = z.discriminatedUnion("code", [
51
+ generic("invalid_request"),
52
+ generic("unreadable_output"),
53
+ generic("capability_unavailable"),
54
+ generic("provider_unavailable"),
55
+ generic("provider_timeout"),
56
+ generic("cancelled"),
57
+ z
58
+ .object({
59
+ code: z.literal("artifact_integrity_mismatch"),
60
+ ...common,
61
+ details: z
62
+ .object({
63
+ logical_path: z.string(),
64
+ declared_sha256: z.string().nullable(),
65
+ calculated_sha256: z.string().nullable(),
66
+ unpacked: z.boolean(),
67
+ })
68
+ .strict(),
69
+ })
70
+ .strict(),
71
+ generic("artifact_operation_failed"),
72
+ generic("evidence_integrity_mismatch"),
73
+ generic("truncated"),
74
+ z
75
+ .object({
76
+ code: z.literal("permission_required"),
77
+ ...common,
78
+ details: z
79
+ .object({
80
+ capability: z.string(),
81
+ requested: scopeSchema,
82
+ missing: missingScopeSchema,
83
+ ceiling: scopeSchema.nullable(),
84
+ })
85
+ .strict()
86
+ .optional(),
87
+ })
88
+ .strict(),
89
+ generic("process_capture_failed"),
90
+ generic("cleanup_incomplete"),
91
+ generic("revision_conflict"),
92
+ generic("outside_approved_root"),
93
+ generic("configuration_invalid"),
94
+ generic("target_unavailable"),
95
+ generic("execution_failure"),
96
+ ]);
97
+ /** JSON Schema document used by generated API documentation and clients. */
98
+ export const analysisErrorJsonSchema = z.toJSONSchema(analysisErrorProjectionSchema);
@@ -158,11 +158,20 @@ export const ENHANCED_TOOL_CONTRACTS = [
158
158
  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),
159
159
  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),
160
160
  ];
161
+ /** Caller observations used to compare a live server with expected identity. */
162
+ export const binarySessionInputSchema = z.object({
163
+ expected_package_version: z.string().min(1).optional(),
164
+ expected_catalog_digest: z
165
+ .string()
166
+ .regex(/^[a-f0-9]{64}$/u)
167
+ .optional(),
168
+ expected_server_path: z.string().min(1).optional(),
169
+ });
161
170
  /** Target lifecycle tools available only on the long-lived MCP adapter. */
162
171
  export const SESSION_TOOL_CONTRACTS = [
163
172
  session("open_binary", "Open a local executable, application bundle, archive, JavaScript, source map, plist, or Hopper database after validation. An optional provider-neutral snapshot is imported atomically and must match the binary digest, format, and architecture exactly; provider resources may still start before an MCP cache replay.", openBinaryInputSchema),
164
173
  session("close_binary", "Optionally write a provider-neutral analysis snapshot atomically, then close the active target and every provider resource started for it. Snapshot files require an operator-approved root and explicit overwrite; a failed save leaves the session open so cached analysis is not lost.", closeBinaryInputSchema),
165
- 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({})),
174
+ 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.", binarySessionInputSchema),
166
175
  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({
167
176
  path: z.string().min(1).optional(),
168
177
  overwrite: z.boolean().default(false),
@@ -13,6 +13,7 @@ import { changedBehaviorResultSchema } from "../domain/changedBehavior.js";
13
13
  import { callPathResultSchema } from "../domain/callPath.js";
14
14
  import { staticRuntimeCorrelationResultSchema } from "../domain/staticRuntimeCorrelation.js";
15
15
  import { reconstructionVerificationResultSchema } from "../domain/reconstructionVerification.js";
16
+ import { analysisErrorProjectionSchema } from "./errorSchemas.js";
16
17
  const resultOf = (schema) => evidenceSchema
17
18
  .omit({ normalized_result: true })
18
19
  .extend({ normalized_result: schema });
@@ -78,6 +79,67 @@ const sessionProvider = z.object({
78
79
  provider: providerIdentity,
79
80
  providers: z.array(providerIdentity),
80
81
  capabilities: z.array(providerCapability),
82
+ tool_availability: z.array(z.object({
83
+ name: z.string(),
84
+ surface: z.string(),
85
+ available: z.boolean(),
86
+ reason: z.enum([
87
+ "available",
88
+ "target_required",
89
+ "provider_missing",
90
+ "provider_unavailable",
91
+ "target_unsupported",
92
+ "unsupported_host",
93
+ "policy_disabled",
94
+ ]),
95
+ remediation: z.string().nullable(),
96
+ annotations: z.object({
97
+ read_only: z.boolean(),
98
+ destructive: z.boolean(),
99
+ idempotent: z.boolean(),
100
+ open_world: z.boolean(),
101
+ }),
102
+ })),
103
+ client_features: z.object({
104
+ elicitation_form: z.boolean(),
105
+ elicitation_url: z.boolean(),
106
+ roots: z.boolean(),
107
+ sampling: z.boolean(),
108
+ }),
109
+ server_identity: z.object({
110
+ package: z.object({
111
+ name: z.string(),
112
+ version: z.string(),
113
+ root_path: z.string(),
114
+ build_commit: z.string().nullable(),
115
+ }),
116
+ server: z.object({
117
+ name: z.string(),
118
+ version: z.string(),
119
+ started_at: z.string(),
120
+ command_path: z.string(),
121
+ }),
122
+ sdk: z.object({
123
+ server: z.string(),
124
+ client_test: z.string(),
125
+ core: z.string(),
126
+ }),
127
+ negotiated_protocol_version: z.string().nullable(),
128
+ client: z.object({ name: z.string(), version: z.string() }).nullable(),
129
+ skill: z.object({ name: z.string(), expected_version: z.string() }),
130
+ catalog: z.record(z.string(), z.json()),
131
+ protocol_features: z.object({
132
+ progress: z.boolean(),
133
+ cancellation: z.boolean(),
134
+ evidence_resources: z.boolean(),
135
+ elicitation: z.boolean(),
136
+ }),
137
+ alignment: z.object({
138
+ state: z.enum(["aligned", "mcp_server_restart_required", "unknown"]),
139
+ reasons: z.array(z.string()),
140
+ remediation: z.string().nullable(),
141
+ }),
142
+ }),
81
143
  });
82
144
  const nullableText = z.string().nullable();
83
145
  const addressList = z.array(z.string());
@@ -149,36 +211,6 @@ const symbolDiscoveryOutput = (property) => resultOf(z.object({
149
211
  count: z.number().int().min(0),
150
212
  [property]: z.array(addressedEntry),
151
213
  }));
152
- const analysisErrorProjectionSchema = z
153
- .object({
154
- category: z.enum([
155
- "invalid_input",
156
- "permission_required",
157
- "unsupported_provider",
158
- "integrity_mismatch",
159
- "truncated",
160
- "cancelled",
161
- "timeout",
162
- "unavailable",
163
- "execution_failure",
164
- ]),
165
- message: z.string(),
166
- code: z
167
- .enum([
168
- "private_display_unavailable",
169
- "x11_authorization_failed",
170
- "unsupported_hopper_build",
171
- "invalid_launch_command",
172
- "process_ownership_mismatch",
173
- "hopper_exited_during_startup",
174
- "unsupported_demo_dialog",
175
- "unexpected_display_geometry",
176
- "x11_input_failed",
177
- "runtime_dependency_unavailable",
178
- ])
179
- .optional(),
180
- })
181
- .strict();
182
214
  const graphNode = z.discriminatedUnion("status", [
183
215
  z.object({
184
216
  address: z.string(),
@@ -11,8 +11,15 @@ const comparisonStatusSchema = z.enum([
11
11
  "changed",
12
12
  "truncated",
13
13
  "unknown",
14
+ "contradiction",
15
+ ]);
16
+ const changeKindSchema = z.enum([
17
+ "added",
18
+ "removed",
19
+ "changed",
20
+ "unknown",
21
+ "contradiction",
14
22
  ]);
15
- const changeKindSchema = z.enum(["added", "removed", "changed", "unknown"]);
16
23
  const comparisonDimensionSchema = z.enum([
17
24
  "content",
18
25
  "kind",
@@ -22,6 +29,7 @@ const comparisonDimensionSchema = z.enum([
22
29
  "relations",
23
30
  "metadata",
24
31
  "availability",
32
+ "integrity",
25
33
  ]);
26
34
  /** Strict Evidence-backed input for deterministic artifact comparison. */
27
35
  export const artifactComparisonInputSchema = z.object({
@@ -68,6 +76,7 @@ export const artifactComparisonResultSchema = z.object({
68
76
  removed: z.number().int().min(0),
69
77
  changed: z.number().int().min(0),
70
78
  unknown: z.number().int().min(0),
79
+ contradiction: z.number().int().min(0).default(0),
71
80
  }),
72
81
  changes: z.object({
73
82
  items: z.array(artifactChangeSchema).max(500),
@@ -84,8 +93,8 @@ export const compareArtifacts = (leftEvidence, rightEvidence, offset, limit) =>
84
93
  const right = parseArtifactInventoryEvidence(rightEvidence);
85
94
  const leftComplete = left.inventory.complete;
86
95
  const rightComplete = right.inventory.complete;
87
- const leftCovered = leftComplete && left.inventory.limitations.length === 0;
88
- const rightCovered = rightComplete && right.inventory.limitations.length === 0;
96
+ const leftCovered = leftComplete && !hasCoverageLimitation(left.inventory.limitations);
97
+ const rightCovered = rightComplete && !hasCoverageLimitation(right.inventory.limitations);
89
98
  const limitations = [
90
99
  ...left.inventory.limitations.map((item) => `Left: ${item}`),
91
100
  ...right.inventory.limitations.map((item) => `Right: ${item}`),
@@ -101,13 +110,15 @@ export const compareArtifacts = (leftEvidence, rightEvidence, offset, limit) =>
101
110
  return artifactComparisonResultSchema.parse({
102
111
  status: !leftComplete || !rightComplete
103
112
  ? "truncated"
104
- : !leftCovered || !rightCovered
105
- ? "unknown"
106
- : changes.some(({ classification }) => classification === "unknown")
113
+ : changes.some(({ classification }) => classification === "contradiction")
114
+ ? "contradiction"
115
+ : !leftCovered || !rightCovered
107
116
  ? "unknown"
108
- : changes.length === 0
109
- ? "unchanged"
110
- : "changed",
117
+ : changes.some(({ classification }) => classification === "unknown")
118
+ ? "unknown"
119
+ : changes.length === 0
120
+ ? "unchanged"
121
+ : "changed",
111
122
  left_manifest_id: left.inventory.manifest.manifest_id,
112
123
  right_manifest_id: right.inventory.manifest.manifest_id,
113
124
  summary,
@@ -172,6 +183,13 @@ const classifyPath = (input) => {
172
183
  classification: input.complete ? "removed" : "unknown",
173
184
  dimensions: [input.complete ? "content" : "availability"],
174
185
  };
186
+ if (input.leftOccurrence.hash_status === "mismatched" ||
187
+ input.rightOccurrence.hash_status === "mismatched")
188
+ return {
189
+ ...base,
190
+ classification: "contradiction",
191
+ dimensions: ["integrity"],
192
+ };
175
193
  if (input.leftOccurrence.hash_status !== "verified" ||
176
194
  input.rightOccurrence.hash_status !== "verified" ||
177
195
  input.leftNode === undefined ||
@@ -253,13 +271,20 @@ const relationsByPath = (inventory) => {
253
271
  return output;
254
272
  };
255
273
  const summarize = (left, right, changes, covered) => {
256
- const counts = { added: 0, removed: 0, changed: 0, unknown: 0 };
274
+ const counts = {
275
+ added: 0,
276
+ removed: 0,
277
+ changed: 0,
278
+ unknown: 0,
279
+ contradiction: 0,
280
+ };
257
281
  for (const change of changes)
258
282
  counts[change.classification] += 1;
259
283
  return {
260
284
  unchanged: covered
261
285
  ? Math.max(0, Math.min(left.occurrences.length, right.occurrences.length) -
262
286
  counts.changed -
287
+ counts.contradiction -
263
288
  counts.unknown)
264
289
  : 0,
265
290
  ...counts,
@@ -268,6 +293,8 @@ const summarize = (left, right, changes, covered) => {
268
293
  : Math.max(counts.unknown, Math.max(left.manifest.occurrence_count, right.manifest.occurrence_count) -
269
294
  counts.added -
270
295
  counts.removed -
271
- counts.changed),
296
+ counts.changed -
297
+ counts.contradiction),
272
298
  };
273
299
  };
300
+ const hasCoverageLimitation = (limitations) => limitations.some((limitation) => !/integrity contradiction\(s\) were recorded/u.test(limitation));
@@ -5,6 +5,7 @@ const occurrenceIdSchema = z.string().regex(/^occ_[a-f0-9]{64}$/u);
5
5
  const edgeIdSchema = z.string().regex(/^edge_[a-f0-9]{64}$/u);
6
6
  const manifestIdSchema = z.string().regex(/^agm_[a-f0-9]{64}$/u);
7
7
  const extractionIdSchema = z.string().regex(/^aex_[a-f0-9]{64}$/u);
8
+ const contradictionIdSchema = z.string().regex(/^ic_[a-f0-9]{64}$/u);
8
9
  const boundedRelativePathSchema = z
9
10
  .string()
10
11
  .min(1)
@@ -106,10 +107,29 @@ const artifactOccurrenceSchema = z.object({
106
107
  .nullable(),
107
108
  executable: z.boolean(),
108
109
  encrypted: z.boolean(),
109
- hash_status: z.enum(["verified", "not-hashed-limit", "unavailable"]),
110
+ hash_status: z.enum([
111
+ "verified",
112
+ "mismatched",
113
+ "not-hashed-limit",
114
+ "unavailable",
115
+ ]),
110
116
  source_location: artifactByteRangeSchema.nullable(),
111
117
  limitations: z.array(z.string()),
112
118
  });
119
+ /** Observed bytes that contradict a container's declared integrity identity. */
120
+ const integrityContradictionSchema = z.object({
121
+ contradiction_id: contradictionIdSchema,
122
+ occurrence_id: occurrenceIdSchema,
123
+ parent_artifact_id: artifactIdSchema,
124
+ logical_path: boundedRelativePathSchema,
125
+ declared_sha256: sha256Schema,
126
+ observed_sha256: sha256Schema,
127
+ entry_kind: z.enum(["file", "slice"]),
128
+ unpacked: z.boolean(),
129
+ trust: z.literal("observed-untrusted"),
130
+ provenance: z.literal("container-integrity-metadata-versus-observed-bytes"),
131
+ limitations: z.array(z.string()).min(1),
132
+ });
113
133
  /** Parent-child derivation relation between two content-addressed artifacts. */
114
134
  const artifactEdgeSchema = z.object({
115
135
  edge_id: edgeIdSchema,
@@ -157,6 +177,10 @@ export const artifactInventoryResultSchema = z.object({
157
177
  edges: artifactPageSchema(artifactEdgeSchema),
158
178
  limits: artifactTraversalLimitsSchema,
159
179
  provenance: z.array(artifactCommandSchema).max(256),
180
+ integrity_contradictions: z
181
+ .array(integrityContradictionSchema)
182
+ .max(100)
183
+ .default([]),
160
184
  limitations: z.array(z.string()),
161
185
  });
162
186
  /** One safely materialized artifact beneath caller-approved output root. */
@@ -39,6 +39,9 @@ const assembleInventorySet = (pages) => {
39
39
  for (const page of pages) {
40
40
  if (canonicalJson(page.inventory.manifest) !== manifestJson)
41
41
  throw new TypeError("Artifact inventory pages do not share one manifest");
42
+ if (canonicalJson(page.inventory.integrity_contradictions) !==
43
+ canonicalJson(first.inventory.integrity_contradictions))
44
+ throw new TypeError("Artifact inventory pages do not share integrity contradictions");
42
45
  for (const node of page.inventory.nodes.items)
43
46
  mergeExact(nodes, node.artifact_id, node, "node");
44
47
  for (const occurrence of page.inventory.occurrences.items) {
@@ -66,6 +69,7 @@ const orderedInventory = (first, pages, values) => {
66
69
  nodes,
67
70
  occurrences,
68
71
  edges,
72
+ integrityContradictions: first.integrity_contradictions,
69
73
  limitations: [
70
74
  ...new Set(pages.flatMap(({ inventory }) => inventory.limitations)),
71
75
  ].sort((left, right) => left.localeCompare(right)),
@@ -84,10 +88,24 @@ const validateCompleteInventory = (inventory) => {
84
88
  validateOccurrence(occurrence, inventory, nodeIds, occurrenceIds);
85
89
  for (const edge of inventory.edges)
86
90
  validateEdge(edge, nodeIds, occurrenceIds);
91
+ for (const contradiction of inventory.integrityContradictions) {
92
+ if (!nodeIds.has(contradiction.parent_artifact_id) ||
93
+ !occurrenceIds.has(contradiction.occurrence_id))
94
+ throw new TypeError("Integrity contradiction references a missing graph member");
95
+ const expectedId = `ic_${digestCanonical({
96
+ root_artifact_id: inventory.manifest.root_artifact_id,
97
+ logical_path: contradiction.logical_path,
98
+ declared_sha256: contradiction.declared_sha256,
99
+ observed_sha256: contradiction.observed_sha256,
100
+ })}`;
101
+ if (contradiction.contradiction_id !== expectedId)
102
+ throw new TypeError("Integrity contradiction ID does not match its identity");
103
+ }
87
104
  const graphSha256 = digestCanonical({
88
105
  nodes: inventory.nodes,
89
106
  occurrences: inventory.occurrences,
90
107
  edges: inventory.edges,
108
+ integrity_contradictions: inventory.integrityContradictions,
91
109
  });
92
110
  if (graphSha256 !== inventory.manifest.graph_sha256)
93
111
  throw new TypeError("Artifact graph commitment does not match its members");
@@ -247,9 +247,11 @@ const artifactFindings = (evidence) => {
247
247
  dimension: `artifact:${change.logical_path}`,
248
248
  scope: "static_candidate",
249
249
  status: change.classification,
250
- classification: change.classification === "unknown"
251
- ? "unresolved_branch"
252
- : "derived_relationship",
250
+ classification: change.classification === "contradiction"
251
+ ? "contradiction"
252
+ : change.classification === "unknown"
253
+ ? "unresolved_branch"
254
+ : "derived_relationship",
253
255
  limitations: result.limitations,
254
256
  links: change.evidence_links,
255
257
  }));