agentxchain 2.64.0 → 2.65.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentxchain",
3
- "version": "2.64.0",
3
+ "version": "2.65.0",
4
4
  "description": "CLI for AgentXchain — governed multi-agent software delivery",
5
5
  "type": "module",
6
6
  "bin": {
@@ -166,6 +166,9 @@ export async function acceptTurnCommand(opts = {}) {
166
166
  if (accepted?.cost?.usd != null) {
167
167
  console.log(` ${chalk.dim('Cost:')} $${formatUsd(accepted.cost.usd)}`);
168
168
  }
169
+ if (accepted?.verification_replay) {
170
+ console.log(` ${chalk.dim('Replay:')} ${accepted.verification_replay.overall} (${accepted.verification_replay.matched_commands}/${accepted.verification_replay.replayed_commands})`);
171
+ }
169
172
  if (result.budget_warning) {
170
173
  console.log(` ${chalk.yellow('Budget warning:')} ${result.budget_warning}`);
171
174
  }
@@ -1023,6 +1023,9 @@ function printAcceptSummary(result) {
1023
1023
  if (accepted?.cost?.usd != null) {
1024
1024
  console.log(` ${chalk.dim('Cost:')} $${(accepted.cost.usd || 0).toFixed(2)}`);
1025
1025
  }
1026
+ if (accepted?.verification_replay) {
1027
+ console.log(` ${chalk.dim('Replay:')} ${accepted.verification_replay.overall} (${accepted.verification_replay.matched_commands}/${accepted.verification_replay.replayed_commands})`);
1028
+ }
1026
1029
  console.log('');
1027
1030
 
1028
1031
  if (result.state?.status === 'completed') {
@@ -1,5 +1,4 @@
1
1
  import chalk from 'chalk';
2
- import { spawnSync } from 'node:child_process';
3
2
  import { loadProjectContext, loadProjectState } from '../lib/config.js';
4
3
  import { getActiveTurns } from '../lib/governed-state.js';
5
4
  import { normalizeVerification } from '../lib/repo-observer.js';
@@ -8,6 +7,10 @@ import { getTurnStagingResultPath } from '../lib/turn-paths.js';
8
7
  import { resolve } from 'node:path';
9
8
  import { loadExportArtifact, verifyExportArtifact } from '../lib/export-verifier.js';
10
9
  import { verifyProtocolConformance } from '../lib/protocol-conformance.js';
10
+ import {
11
+ DEFAULT_VERIFICATION_REPLAY_TIMEOUT_MS,
12
+ replayVerificationMachineEvidence,
13
+ } from '../lib/verification-replay.js';
11
14
 
12
15
  export async function verifyProtocolCommand(opts, command) {
13
16
  const requestedTier = Number.parseInt(String(opts.tier || '1'), 10);
@@ -113,7 +116,7 @@ export async function verifyTurnCommand(turnId, opts = {}) {
113
116
  process.exit(2);
114
117
  }
115
118
 
116
- const timeoutMs = Number.parseInt(String(opts.timeout || '30000'), 10);
119
+ const timeoutMs = Number.parseInt(String(opts.timeout || String(DEFAULT_VERIFICATION_REPLAY_TIMEOUT_MS)), 10);
117
120
  if (!Number.isInteger(timeoutMs) || timeoutMs <= 0) {
118
121
  console.log(chalk.red('verify turn requires a positive integer --timeout in milliseconds.'));
119
122
  process.exit(2);
@@ -142,10 +145,6 @@ export async function verifyTurnCommand(turnId, opts = {}) {
142
145
  const runtimeType = config.runtimes?.[selectedTurn.runtime_id]?.type || 'manual';
143
146
  const declaredStatus = turnResult.verification?.status || 'skipped';
144
147
  const normalizedStatus = normalizeVerification(turnResult.verification, runtimeType).status;
145
- const machineEvidence = Array.isArray(turnResult.verification?.machine_evidence)
146
- ? turnResult.verification.machine_evidence
147
- : [];
148
-
149
148
  const payload = {
150
149
  turn_id: selectedTurnId,
151
150
  role: selectedTurn.assigned_role,
@@ -159,23 +158,13 @@ export async function verifyTurnCommand(turnId, opts = {}) {
159
158
  warnings: validation.warnings || [],
160
159
  },
161
160
  timeout_ms: timeoutMs,
162
- overall: 'not_reproducible',
163
- replayed_commands: 0,
164
- matched_commands: 0,
165
- commands: [],
161
+ ...replayVerificationMachineEvidence({
162
+ root,
163
+ verification: turnResult.verification,
164
+ timeoutMs,
165
+ }),
166
166
  };
167
167
 
168
- if (machineEvidence.length === 0) {
169
- payload.reason = 'No verification.machine_evidence commands were declared. commands/evidence_summary are not executable proof.';
170
- emitTurnVerification(payload, opts.json);
171
- process.exit(1);
172
- }
173
-
174
- payload.commands = machineEvidence.map((entry, index) => replayEvidenceCommand(root, entry, index, timeoutMs));
175
- payload.replayed_commands = payload.commands.length;
176
- payload.matched_commands = payload.commands.filter((entry) => entry.matched).length;
177
- payload.overall = payload.commands.every((entry) => entry.matched) ? 'match' : 'mismatch';
178
-
179
168
  emitTurnVerification(payload, opts.json);
180
169
  process.exit(payload.overall === 'match' ? 0 : 1);
181
170
  }
@@ -310,31 +299,6 @@ function emitTurnValidationFailure(validation, jsonMode) {
310
299
  console.log('');
311
300
  }
312
301
 
313
- function replayEvidenceCommand(root, entry, index, timeoutMs) {
314
- const result = spawnSync(entry.command, {
315
- cwd: root,
316
- encoding: 'utf8',
317
- shell: true,
318
- timeout: timeoutMs,
319
- maxBuffer: 1024 * 1024,
320
- });
321
-
322
- const timedOut = result.error?.code === 'ETIMEDOUT';
323
- const actualExitCode = Number.isInteger(result.status) ? result.status : null;
324
- const errorMessage = result.error?.message || null;
325
-
326
- return {
327
- index,
328
- command: entry.command,
329
- declared_exit_code: entry.exit_code,
330
- actual_exit_code: actualExitCode,
331
- matched: actualExitCode === entry.exit_code,
332
- timed_out: timedOut,
333
- signal: result.signal || null,
334
- error: errorMessage,
335
- };
336
- }
337
-
338
302
  function emitTurnVerification(payload, jsonMode) {
339
303
  if (jsonMode) {
340
304
  console.log(JSON.stringify(payload, null, 2));
@@ -44,6 +44,10 @@ import { emitRunEvent } from './run-events.js';
44
44
  import { writeSessionCheckpoint } from './session-checkpoint.js';
45
45
  import { recordRunHistory } from './run-history.js';
46
46
  import { buildDefaultRunProvenance } from './run-provenance.js';
47
+ import {
48
+ replayVerificationMachineEvidence,
49
+ summarizeVerificationReplay,
50
+ } from './verification-replay.js';
47
51
 
48
52
  // ── Constants ────────────────────────────────────────────────────────────────
49
53
 
@@ -2367,6 +2371,9 @@ function _acceptGovernedTurnLocked(root, config, opts) {
2367
2371
  const normalizedVerification = normalizeVerification(turnResult.verification, runtimeType);
2368
2372
  const artifactType = turnResult.artifact?.type || 'review';
2369
2373
  const derivedRef = deriveAcceptedRef(observation, artifactType, state.accepted_integration_ref);
2374
+ const verificationReplay = (config.policies || []).some((policy) => policy?.rule === 'require_reproducible_verification')
2375
+ ? replayVerificationMachineEvidence({ root, verification: turnResult.verification })
2376
+ : null;
2370
2377
 
2371
2378
  // Policy evaluation — declarative governance rules (spec: POLICY_ENGINE_SPEC.md)
2372
2379
  const policyResult = evaluatePolicies(config.policies || [], {
@@ -2375,6 +2382,7 @@ function _acceptGovernedTurnLocked(root, config, opts) {
2375
2382
  turnStatus: turnResult.status,
2376
2383
  turnCostUsd: readTurnCostUsd(turnResult),
2377
2384
  history: historyEntries,
2385
+ verificationReplay,
2378
2386
  });
2379
2387
 
2380
2388
  if (policyResult.blocks.length > 0) {
@@ -2572,6 +2580,7 @@ function _acceptGovernedTurnLocked(root, config, opts) {
2572
2580
  artifacts_created: turnResult.artifacts_created || [],
2573
2581
  verification: turnResult.verification || {},
2574
2582
  normalized_verification: normalizedVerification,
2583
+ ...(verificationReplay ? { verification_replay: summarizeVerificationReplay(verificationReplay) } : {}),
2575
2584
  artifact: turnResult.artifact || {},
2576
2585
  observed_artifact: observedArtifact,
2577
2586
  proposed_next_role: turnResult.proposed_next_role,
@@ -3176,6 +3185,7 @@ function _acceptGovernedTurnLocked(root, config, opts) {
3176
3185
  hookResults,
3177
3186
  ...(budgetWarning ? { budget_warning: budgetWarning } : {}),
3178
3187
  ...(policyResult.warnings.length > 0 ? { policy_warnings: policyResult.warnings } : {}),
3188
+ ...(verificationReplay ? { verification_replay: summarizeVerificationReplay(verificationReplay) } : {}),
3179
3189
  };
3180
3190
  }
3181
3191
 
@@ -78,6 +78,32 @@ const RULE_EVALUATORS = {
78
78
  }
79
79
  return { triggered: false, message: '' };
80
80
  },
81
+
82
+ require_reproducible_verification: (_params, ctx) => {
83
+ const replay = ctx.verificationReplay;
84
+ if (!replay) {
85
+ return {
86
+ triggered: true,
87
+ message: 'verification replay context is missing at acceptance time',
88
+ };
89
+ }
90
+
91
+ if (replay.overall === 'match') {
92
+ return { triggered: false, message: '' };
93
+ }
94
+
95
+ if (replay.overall === 'not_reproducible') {
96
+ return {
97
+ triggered: true,
98
+ message: replay.reason || 'verification.machine_evidence did not provide executable proof',
99
+ };
100
+ }
101
+
102
+ return {
103
+ triggered: true,
104
+ message: `verification replay mismatch: ${replay.matched_commands || 0}/${replay.replayed_commands || 0} commands matched declared exit codes`,
105
+ };
106
+ },
81
107
  };
82
108
 
83
109
  export const VALID_POLICY_RULES = Object.keys(RULE_EVALUATORS);
@@ -185,6 +211,12 @@ function validatePolicyParams(rule, params, prefix) {
185
211
  }
186
212
  }
187
213
  break;
214
+
215
+ case 'require_reproducible_verification':
216
+ if (params != null && typeof params !== 'object') {
217
+ errors.push(`${prefix}: params must be an object when provided`);
218
+ }
219
+ break;
188
220
  }
189
221
 
190
222
  return errors;
@@ -0,0 +1,68 @@
1
+ import { spawnSync } from 'node:child_process';
2
+
3
+ export const DEFAULT_VERIFICATION_REPLAY_TIMEOUT_MS = 30_000;
4
+
5
+ export function replayVerificationMachineEvidence({ root, verification, timeoutMs = DEFAULT_VERIFICATION_REPLAY_TIMEOUT_MS }) {
6
+ const machineEvidence = Array.isArray(verification?.machine_evidence)
7
+ ? verification.machine_evidence
8
+ : [];
9
+
10
+ const payload = {
11
+ timeout_ms: timeoutMs,
12
+ overall: 'not_reproducible',
13
+ replayed_commands: 0,
14
+ matched_commands: 0,
15
+ commands: [],
16
+ };
17
+
18
+ if (machineEvidence.length === 0) {
19
+ payload.reason = 'No verification.machine_evidence commands were declared. commands/evidence_summary are not executable proof.';
20
+ return payload;
21
+ }
22
+
23
+ payload.commands = machineEvidence.map((entry, index) => replayEvidenceCommand(root, entry, index, timeoutMs));
24
+ payload.replayed_commands = payload.commands.length;
25
+ payload.matched_commands = payload.commands.filter((entry) => entry.matched).length;
26
+ payload.overall = payload.commands.every((entry) => entry.matched) ? 'match' : 'mismatch';
27
+
28
+ return payload;
29
+ }
30
+
31
+ export function replayEvidenceCommand(root, entry, index, timeoutMs = DEFAULT_VERIFICATION_REPLAY_TIMEOUT_MS) {
32
+ const result = spawnSync(entry.command, {
33
+ cwd: root,
34
+ encoding: 'utf8',
35
+ shell: true,
36
+ timeout: timeoutMs,
37
+ maxBuffer: 1024 * 1024,
38
+ });
39
+
40
+ const timedOut = result.error?.code === 'ETIMEDOUT';
41
+ const actualExitCode = Number.isInteger(result.status) ? result.status : null;
42
+ const errorMessage = result.error?.message || null;
43
+
44
+ return {
45
+ index,
46
+ command: entry.command,
47
+ declared_exit_code: entry.exit_code,
48
+ actual_exit_code: actualExitCode,
49
+ matched: actualExitCode === entry.exit_code,
50
+ timed_out: timedOut,
51
+ signal: result.signal || null,
52
+ error: errorMessage,
53
+ };
54
+ }
55
+
56
+ export function summarizeVerificationReplay(payload) {
57
+ if (!payload) {
58
+ return null;
59
+ }
60
+
61
+ return {
62
+ overall: payload.overall,
63
+ replayed_commands: payload.replayed_commands || 0,
64
+ matched_commands: payload.matched_commands || 0,
65
+ timeout_ms: payload.timeout_ms || DEFAULT_VERIFICATION_REPLAY_TIMEOUT_MS,
66
+ ...(payload.reason ? { reason: payload.reason } : {}),
67
+ };
68
+ }