agentxchain 2.135.1 → 2.136.1
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/bin/agentxchain.js +19 -1
- package/package.json +4 -2
- package/scripts/release-preflight.sh +2 -0
- package/src/commands/connector.js +113 -0
- package/src/commands/restart.js +3 -0
- package/src/commands/resume.js +15 -0
- package/src/commands/step.js +12 -0
- package/src/commands/workflow-kit.js +186 -0
- package/src/lib/connector-schema-contract.js +57 -0
- package/src/lib/connector-validate.js +41 -0
- package/src/lib/continuous-run.js +60 -5
- package/src/lib/dispatch-bundle.js +14 -0
- package/src/lib/governed-state.js +42 -46
- package/src/lib/intake.js +17 -57
- package/src/lib/intent-startup-migration.js +151 -0
- package/src/lib/runtime-capabilities.js +132 -81
- package/src/lib/schemas/agentxchain-config.schema.json +391 -0
- package/src/lib/schemas/connector-capabilities-output.schema.json +104 -0
package/bin/agentxchain.js
CHANGED
|
@@ -123,10 +123,11 @@ import { historyCommand } from '../src/commands/history.js';
|
|
|
123
123
|
import { decisionsCommand } from '../src/commands/decisions.js';
|
|
124
124
|
import { diffCommand } from '../src/commands/diff.js';
|
|
125
125
|
import { eventsCommand } from '../src/commands/events.js';
|
|
126
|
-
import { connectorCheckCommand, connectorValidateCommand } from '../src/commands/connector.js';
|
|
126
|
+
import { connectorCapabilitiesCommand, connectorCheckCommand, connectorValidateCommand } from '../src/commands/connector.js';
|
|
127
127
|
import { scheduleDaemonCommand, scheduleListCommand, scheduleRunDueCommand, scheduleStatusCommand } from '../src/commands/schedule.js';
|
|
128
128
|
import { chainLatestCommand, chainListCommand, chainShowCommand } from '../src/commands/chain.js';
|
|
129
129
|
import { missionAttachChainCommand, missionBindCoordinatorCommand, missionListCommand, missionPlanApproveCommand, missionPlanAutopilotCommand, missionPlanCommand, missionPlanLaunchCommand, missionPlanListCommand, missionPlanShowCommand, missionShowCommand, missionStartCommand } from '../src/commands/mission.js';
|
|
130
|
+
import { workflowKitDescribeCommand } from '../src/commands/workflow-kit.js';
|
|
130
131
|
|
|
131
132
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
132
133
|
const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8'));
|
|
@@ -299,6 +300,13 @@ connectorCmd
|
|
|
299
300
|
.option('--timeout <ms>', 'Per-probe timeout in milliseconds', '8000')
|
|
300
301
|
.action(connectorCheckCommand);
|
|
301
302
|
|
|
303
|
+
connectorCmd
|
|
304
|
+
.command('capabilities [runtime_id]')
|
|
305
|
+
.description('Show merged capability contract for a runtime or all runtimes (machine-readable handshake)')
|
|
306
|
+
.option('-j, --json', 'Output as JSON')
|
|
307
|
+
.option('--all', 'Show capabilities for all configured runtimes')
|
|
308
|
+
.action(connectorCapabilitiesCommand);
|
|
309
|
+
|
|
302
310
|
connectorCmd
|
|
303
311
|
.command('validate <runtime_id>')
|
|
304
312
|
.description('Dispatch one synthetic governed turn through a runtime and validate the staged turn result')
|
|
@@ -839,6 +847,16 @@ phaseCmd
|
|
|
839
847
|
.option('-j, --json', 'Output as JSON')
|
|
840
848
|
.action((phaseId, opts) => phaseCommand('show', phaseId, opts));
|
|
841
849
|
|
|
850
|
+
const workflowKitCmd = program
|
|
851
|
+
.command('workflow-kit')
|
|
852
|
+
.description('Inspect the workflow kit contract for this project');
|
|
853
|
+
|
|
854
|
+
workflowKitCmd
|
|
855
|
+
.command('describe')
|
|
856
|
+
.description('Show the workflow kit contract — templates, artifacts, semantic validators, gate coverage')
|
|
857
|
+
.option('-j, --json', 'Output as JSON')
|
|
858
|
+
.action(workflowKitDescribeCommand);
|
|
859
|
+
|
|
842
860
|
const gateCmd = program
|
|
843
861
|
.command('gate')
|
|
844
862
|
.description('Inspect governed gate definitions');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentxchain",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.136.1",
|
|
4
4
|
"description": "CLI for AgentXchain — governed multi-agent software delivery",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
".": "./bin/agentxchain.js",
|
|
11
11
|
"./adapter-interface": "./src/lib/adapter-interface.js",
|
|
12
12
|
"./runner-interface": "./src/lib/runner-interface.js",
|
|
13
|
-
"./run-loop": "./src/lib/run-loop.js"
|
|
13
|
+
"./run-loop": "./src/lib/run-loop.js",
|
|
14
|
+
"./schemas/agentxchain-config": "./src/lib/schemas/agentxchain-config.schema.json",
|
|
15
|
+
"./schemas/connector-capabilities-output": "./src/lib/schemas/connector-capabilities-output.schema.json"
|
|
14
16
|
},
|
|
15
17
|
"files": [
|
|
16
18
|
"bin/",
|
|
@@ -154,6 +154,8 @@ if [[ "$PUBLISH_GATE" -eq 1 ]]; then
|
|
|
154
154
|
test/release-identity-hardening.test.js
|
|
155
155
|
test/normalized-config.test.js
|
|
156
156
|
test/conformance.test.js
|
|
157
|
+
test/beta-scenario-emission-guard.test.js
|
|
158
|
+
test/claim-reality-preflight.test.js
|
|
157
159
|
test/beta-tester-scenarios/*.test.js
|
|
158
160
|
)
|
|
159
161
|
GATE_TEST_ARGS=()
|
|
@@ -3,6 +3,7 @@ import chalk from 'chalk';
|
|
|
3
3
|
import { loadProjectContext } from '../lib/config.js';
|
|
4
4
|
import { DEFAULT_VALIDATE_TIMEOUT_MS, validateConfiguredConnector } from '../lib/connector-validate.js';
|
|
5
5
|
import { DEFAULT_TIMEOUT_MS, probeConfiguredConnectors } from '../lib/connector-probe.js';
|
|
6
|
+
import { buildRuntimeCapabilityReport } from '../lib/runtime-capabilities.js';
|
|
6
7
|
|
|
7
8
|
function printJson(result, exitCode) {
|
|
8
9
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -164,6 +165,14 @@ function printValidateText(result, exitCode) {
|
|
|
164
165
|
}
|
|
165
166
|
}
|
|
166
167
|
|
|
168
|
+
if (result.schema_contract) {
|
|
169
|
+
console.log('');
|
|
170
|
+
console.log(` ${chalk.dim('Schema:')} ${result.schema_contract.ok ? chalk.green('ok') : chalk.red('failed')}`);
|
|
171
|
+
if (Array.isArray(result.schema_contract.failures) && result.schema_contract.failures.length > 0) {
|
|
172
|
+
console.log(` ${chalk.dim('Contract:')} ${result.schema_contract.failures.join(' | ')}`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
167
176
|
if (result.dispatch) {
|
|
168
177
|
console.log('');
|
|
169
178
|
console.log(` ${chalk.dim('Dispatch:')} ${result.dispatch.ok ? chalk.green('ok') : chalk.red('failed')}`);
|
|
@@ -192,6 +201,110 @@ function printValidateText(result, exitCode) {
|
|
|
192
201
|
process.exit(exitCode);
|
|
193
202
|
}
|
|
194
203
|
|
|
204
|
+
function printCapabilitiesText(report) {
|
|
205
|
+
console.log('');
|
|
206
|
+
console.log(chalk.bold(` ${report.runtime_id}`) + chalk.dim(` (${report.runtime_type})`));
|
|
207
|
+
console.log(chalk.dim(' ' + '-'.repeat(44)));
|
|
208
|
+
|
|
209
|
+
const c = report.merged_contract;
|
|
210
|
+
console.log(` ${chalk.dim('Transport:')} ${c.transport}`);
|
|
211
|
+
console.log(` ${chalk.dim('Write files:')} ${c.can_write_files}`);
|
|
212
|
+
console.log(` ${chalk.dim('Proposals:')} ${c.proposal_support}`);
|
|
213
|
+
console.log(` ${chalk.dim('Artifact ownership:')} ${c.workflow_artifact_ownership}`);
|
|
214
|
+
console.log(` ${chalk.dim('Local binary:')} ${c.requires_local_binary ? 'yes' : 'no'}`);
|
|
215
|
+
|
|
216
|
+
if (Object.keys(report.declared_capabilities).length > 0) {
|
|
217
|
+
console.log('');
|
|
218
|
+
console.log(` ${chalk.dim('Declared overrides:')}`);
|
|
219
|
+
for (const [k, v] of Object.entries(report.declared_capabilities)) {
|
|
220
|
+
console.log(` ${k}: ${v}`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (report.declaration_warnings.length > 0) {
|
|
225
|
+
console.log('');
|
|
226
|
+
for (const w of report.declaration_warnings) {
|
|
227
|
+
console.log(` ${chalk.yellow('!')} ${w}`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (report.role_bindings.length > 0) {
|
|
232
|
+
console.log('');
|
|
233
|
+
console.log(` ${chalk.dim('Role bindings:')}`);
|
|
234
|
+
for (const rb of report.role_bindings) {
|
|
235
|
+
const badge = rb.effective_write_path.startsWith('invalid') ? chalk.red('INVALID') : chalk.green('OK');
|
|
236
|
+
console.log(` ${badge} ${rb.role_id} (${rb.role_write_authority}) -> ${rb.effective_write_path}`);
|
|
237
|
+
for (const note of rb.notes) {
|
|
238
|
+
console.log(` ${chalk.dim(note)}`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export async function connectorCapabilitiesCommand(runtimeId, options = {}) {
|
|
245
|
+
const context = loadProjectContext();
|
|
246
|
+
if (!context) {
|
|
247
|
+
const payload = { error: 'No governed agentxchain.json found.' };
|
|
248
|
+
if (options.json) { printJson(payload, 2); return; }
|
|
249
|
+
console.error(chalk.red('No governed agentxchain.json found. Run this inside a governed project.'));
|
|
250
|
+
process.exit(2);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const config = context.config;
|
|
254
|
+
const runtimes = config.runtimes || {};
|
|
255
|
+
const roles = config.roles || {};
|
|
256
|
+
|
|
257
|
+
if (options.all) {
|
|
258
|
+
const reports = [];
|
|
259
|
+
for (const [id, runtime] of Object.entries(runtimes)) {
|
|
260
|
+
reports.push(buildRuntimeCapabilityReport(id, runtime, roles));
|
|
261
|
+
}
|
|
262
|
+
const payload = { runtimes: reports };
|
|
263
|
+
if (options.json) { printJson(payload, 0); return; }
|
|
264
|
+
|
|
265
|
+
console.log('');
|
|
266
|
+
console.log(chalk.bold(' AgentXchain Connector Capabilities'));
|
|
267
|
+
console.log(chalk.dim(' ' + '='.repeat(44)));
|
|
268
|
+
if (reports.length === 0) {
|
|
269
|
+
console.log(' No runtimes configured.');
|
|
270
|
+
} else {
|
|
271
|
+
for (const r of reports) { printCapabilitiesText(r); }
|
|
272
|
+
}
|
|
273
|
+
console.log('');
|
|
274
|
+
process.exit(0);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if (!runtimeId) {
|
|
278
|
+
const payload = { error: 'Runtime ID required. Use --all to list all runtimes.', available_runtimes: Object.keys(runtimes) };
|
|
279
|
+
if (options.json) { printJson(payload, 2); return; }
|
|
280
|
+
console.error(chalk.red('Runtime ID required. Use --all to list all runtimes.'));
|
|
281
|
+
if (Object.keys(runtimes).length > 0) {
|
|
282
|
+
console.error(chalk.dim(`Available: ${Object.keys(runtimes).join(', ')}`));
|
|
283
|
+
}
|
|
284
|
+
process.exit(2);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (!runtimes[runtimeId]) {
|
|
288
|
+
const payload = { error: `Runtime "${runtimeId}" not found.`, available_runtimes: Object.keys(runtimes) };
|
|
289
|
+
if (options.json) { printJson(payload, 2); return; }
|
|
290
|
+
console.error(chalk.red(`Runtime "${runtimeId}" not found.`));
|
|
291
|
+
if (Object.keys(runtimes).length > 0) {
|
|
292
|
+
console.error(chalk.dim(`Available: ${Object.keys(runtimes).join(', ')}`));
|
|
293
|
+
}
|
|
294
|
+
process.exit(2);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const report = buildRuntimeCapabilityReport(runtimeId, runtimes[runtimeId], roles);
|
|
298
|
+
if (options.json) { printJson(report, 0); return; }
|
|
299
|
+
|
|
300
|
+
console.log('');
|
|
301
|
+
console.log(chalk.bold(' AgentXchain Connector Capabilities'));
|
|
302
|
+
console.log(chalk.dim(' ' + '='.repeat(44)));
|
|
303
|
+
printCapabilitiesText(report);
|
|
304
|
+
console.log('');
|
|
305
|
+
process.exit(0);
|
|
306
|
+
}
|
|
307
|
+
|
|
195
308
|
export async function connectorValidateCommand(runtimeId, options = {}) {
|
|
196
309
|
const context = loadProjectContext();
|
|
197
310
|
if (!context) {
|
package/src/commands/restart.js
CHANGED
|
@@ -326,6 +326,9 @@ export async function restartCommand(opts) {
|
|
|
326
326
|
console.log(chalk.red(`Failed to reactivate run: ${reactivated.error}`));
|
|
327
327
|
process.exit(1);
|
|
328
328
|
}
|
|
329
|
+
if (reactivated.migration_notice) {
|
|
330
|
+
console.log(chalk.yellow(reactivated.migration_notice));
|
|
331
|
+
}
|
|
329
332
|
}
|
|
330
333
|
|
|
331
334
|
// Determine role from option or routing
|
package/src/commands/resume.js
CHANGED
|
@@ -144,6 +144,9 @@ export async function resumeCommand(opts) {
|
|
|
144
144
|
process.exit(1);
|
|
145
145
|
}
|
|
146
146
|
state = reactivated.state;
|
|
147
|
+
if (reactivated.migration_notice) {
|
|
148
|
+
console.log(chalk.yellow(reactivated.migration_notice));
|
|
149
|
+
}
|
|
147
150
|
|
|
148
151
|
// Write dispatch bundle for the existing turn
|
|
149
152
|
const bundleResult = writeDispatchBundle(root, state, config);
|
|
@@ -204,6 +207,9 @@ export async function resumeCommand(opts) {
|
|
|
204
207
|
process.exit(1);
|
|
205
208
|
}
|
|
206
209
|
state = reactivated.state;
|
|
210
|
+
if (reactivated.migration_notice) {
|
|
211
|
+
console.log(chalk.yellow(reactivated.migration_notice));
|
|
212
|
+
}
|
|
207
213
|
|
|
208
214
|
const bundleResult = writeDispatchBundle(root, state, config, { turnId: retainedTurn.turn_id });
|
|
209
215
|
if (!bundleResult.ok) {
|
|
@@ -233,6 +239,9 @@ export async function resumeCommand(opts) {
|
|
|
233
239
|
}
|
|
234
240
|
state = initResult.state;
|
|
235
241
|
console.log(chalk.green(`Initialized governed run: ${state.run_id}`));
|
|
242
|
+
if (initResult.migration_notice) {
|
|
243
|
+
console.log(chalk.yellow(initResult.migration_notice));
|
|
244
|
+
}
|
|
236
245
|
}
|
|
237
246
|
|
|
238
247
|
// §47: paused + run_id exists → resume same run
|
|
@@ -244,6 +253,9 @@ export async function resumeCommand(opts) {
|
|
|
244
253
|
}
|
|
245
254
|
state = reactivated.state;
|
|
246
255
|
console.log(chalk.green(`Resumed blocked run: ${state.run_id}`));
|
|
256
|
+
if (reactivated.migration_notice) {
|
|
257
|
+
console.log(chalk.yellow(reactivated.migration_notice));
|
|
258
|
+
}
|
|
247
259
|
}
|
|
248
260
|
|
|
249
261
|
// §47: paused + run_id exists → resume same run
|
|
@@ -255,6 +267,9 @@ export async function resumeCommand(opts) {
|
|
|
255
267
|
}
|
|
256
268
|
state = reactivated.state;
|
|
257
269
|
console.log(chalk.green(`Resumed governed run: ${state.run_id}`));
|
|
270
|
+
if (reactivated.migration_notice) {
|
|
271
|
+
console.log(chalk.yellow(reactivated.migration_notice));
|
|
272
|
+
}
|
|
258
273
|
}
|
|
259
274
|
|
|
260
275
|
// Print run-context header before dispatch
|
package/src/commands/step.js
CHANGED
|
@@ -260,6 +260,9 @@ export async function stepCommand(opts) {
|
|
|
260
260
|
process.exit(1);
|
|
261
261
|
}
|
|
262
262
|
state = reactivated.state;
|
|
263
|
+
if (reactivated.migration_notice) {
|
|
264
|
+
console.log(chalk.yellow(reactivated.migration_notice));
|
|
265
|
+
}
|
|
263
266
|
skipAssignment = true;
|
|
264
267
|
|
|
265
268
|
// BUG-1 fix: refresh baseline snapshot to capture files dirtied between assignment and dispatch
|
|
@@ -285,6 +288,9 @@ export async function stepCommand(opts) {
|
|
|
285
288
|
}
|
|
286
289
|
state = initResult.state;
|
|
287
290
|
console.log(chalk.green(`Initialized governed run: ${state.run_id}`));
|
|
291
|
+
if (initResult.migration_notice) {
|
|
292
|
+
console.log(chalk.yellow(initResult.migration_notice));
|
|
293
|
+
}
|
|
288
294
|
}
|
|
289
295
|
|
|
290
296
|
// paused → resume
|
|
@@ -296,6 +302,9 @@ export async function stepCommand(opts) {
|
|
|
296
302
|
}
|
|
297
303
|
state = reactivated.state;
|
|
298
304
|
console.log(chalk.green(`Resumed blocked run: ${state.run_id}`));
|
|
305
|
+
if (reactivated.migration_notice) {
|
|
306
|
+
console.log(chalk.yellow(reactivated.migration_notice));
|
|
307
|
+
}
|
|
299
308
|
}
|
|
300
309
|
|
|
301
310
|
if (!skipAssignment && state.status === 'paused' && state.run_id) {
|
|
@@ -306,6 +315,9 @@ export async function stepCommand(opts) {
|
|
|
306
315
|
}
|
|
307
316
|
state = reactivated.state;
|
|
308
317
|
console.log(chalk.green(`Resumed governed run: ${state.run_id}`));
|
|
318
|
+
if (reactivated.migration_notice) {
|
|
319
|
+
console.log(chalk.yellow(reactivated.migration_notice));
|
|
320
|
+
}
|
|
309
321
|
}
|
|
310
322
|
|
|
311
323
|
// Assign the turn
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { existsSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { loadProjectContext } from '../lib/config.js';
|
|
5
|
+
import {
|
|
6
|
+
VALID_WORKFLOW_KIT_PHASE_TEMPLATE_IDS,
|
|
7
|
+
} from '../lib/workflow-kit-phase-templates.js';
|
|
8
|
+
import { getEffectiveGateArtifacts } from '../lib/gate-evaluator.js';
|
|
9
|
+
|
|
10
|
+
const WORKFLOW_KIT_VERSION = '1.0';
|
|
11
|
+
|
|
12
|
+
const SEMANTIC_VALIDATOR_IDS = [
|
|
13
|
+
'pm_signoff',
|
|
14
|
+
'system_spec',
|
|
15
|
+
'implementation_notes',
|
|
16
|
+
'acceptance_matrix',
|
|
17
|
+
'ship_verdict',
|
|
18
|
+
'release_notes',
|
|
19
|
+
'section_check',
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
const DEFAULT_TEMPLATE_BY_PHASE = {
|
|
23
|
+
planning: 'planning-default',
|
|
24
|
+
implementation: 'implementation-default',
|
|
25
|
+
qa: 'qa-default',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export { WORKFLOW_KIT_VERSION, SEMANTIC_VALIDATOR_IDS };
|
|
29
|
+
|
|
30
|
+
function getGateLinkedPhases(config, gateId, gateDef) {
|
|
31
|
+
const linkedPhases = [];
|
|
32
|
+
|
|
33
|
+
if (typeof gateDef?.phase === 'string' && gateDef.phase.trim()) {
|
|
34
|
+
linkedPhases.push(gateDef.phase.trim());
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
for (const [phaseId, route] of Object.entries(config.routing || {})) {
|
|
38
|
+
if (route?.exit_gate === gateId && !linkedPhases.includes(phaseId)) {
|
|
39
|
+
linkedPhases.push(phaseId);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return linkedPhases;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function buildWorkflowKitContract(root, config) {
|
|
47
|
+
const phaseIds = Object.keys(config.routing || {});
|
|
48
|
+
const hasExplicitKit = config.workflow_kit?._explicit === true;
|
|
49
|
+
|
|
50
|
+
const templatesInUse = new Set();
|
|
51
|
+
const phases = {};
|
|
52
|
+
|
|
53
|
+
for (const phaseId of phaseIds) {
|
|
54
|
+
const phaseKit = config.workflow_kit?.phases?.[phaseId] || null;
|
|
55
|
+
// For default (non-explicit) workflow kits, infer template from phase name
|
|
56
|
+
const template = phaseKit?.template || (!hasExplicitKit ? (DEFAULT_TEMPLATE_BY_PHASE[phaseId] || null) : null);
|
|
57
|
+
if (template) templatesInUse.add(template);
|
|
58
|
+
|
|
59
|
+
const source = !hasExplicitKit
|
|
60
|
+
? 'default'
|
|
61
|
+
: phaseKit
|
|
62
|
+
? 'explicit'
|
|
63
|
+
: 'not_declared';
|
|
64
|
+
|
|
65
|
+
const artifacts = (phaseKit?.artifacts || [])
|
|
66
|
+
.filter((a) => a && typeof a.path === 'string')
|
|
67
|
+
.map((artifact) => ({
|
|
68
|
+
path: artifact.path,
|
|
69
|
+
required: artifact.required !== false,
|
|
70
|
+
semantics: artifact.semantics || null,
|
|
71
|
+
exists: existsSync(join(root, artifact.path)),
|
|
72
|
+
}));
|
|
73
|
+
|
|
74
|
+
phases[phaseId] = { template: template || null, source, artifacts };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const overallSource = !hasExplicitKit
|
|
78
|
+
? 'default'
|
|
79
|
+
: phaseIds.every((id) => phases[id].source === 'explicit')
|
|
80
|
+
? 'explicit'
|
|
81
|
+
: 'mixed';
|
|
82
|
+
|
|
83
|
+
const gateArtifactCoverage = {};
|
|
84
|
+
const gates = config.gates || {};
|
|
85
|
+
for (const [gateId, gateDef] of Object.entries(gates)) {
|
|
86
|
+
const linkedPhases = getGateLinkedPhases(config, gateId, gateDef);
|
|
87
|
+
const artifactPaths = [];
|
|
88
|
+
const seenPaths = new Set();
|
|
89
|
+
|
|
90
|
+
for (const linkedPhase of linkedPhases) {
|
|
91
|
+
for (const artifact of getEffectiveGateArtifacts(config, gateDef, linkedPhase)) {
|
|
92
|
+
if (!seenPaths.has(artifact.path)) {
|
|
93
|
+
seenPaths.add(artifact.path);
|
|
94
|
+
artifactPaths.push(artifact.path);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
gateArtifactCoverage[gateId] = {
|
|
100
|
+
linked_phases: linkedPhases,
|
|
101
|
+
predicates_referencing_artifacts: artifactPaths.length,
|
|
102
|
+
artifacts_covered: artifactPaths,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
workflow_kit_version: WORKFLOW_KIT_VERSION,
|
|
108
|
+
source: overallSource,
|
|
109
|
+
phase_templates: {
|
|
110
|
+
available: [...VALID_WORKFLOW_KIT_PHASE_TEMPLATE_IDS],
|
|
111
|
+
in_use: [...templatesInUse],
|
|
112
|
+
},
|
|
113
|
+
phases,
|
|
114
|
+
semantic_validators: [...SEMANTIC_VALIDATOR_IDS],
|
|
115
|
+
gate_artifact_coverage: gateArtifactCoverage,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export { buildWorkflowKitContract };
|
|
120
|
+
|
|
121
|
+
export function workflowKitDescribeCommand(opts) {
|
|
122
|
+
const context = loadProjectContext();
|
|
123
|
+
if (!context) {
|
|
124
|
+
console.log(chalk.red(' No agentxchain.json found. Run `agentxchain init` first.'));
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const { root, config, version } = context;
|
|
129
|
+
if (version !== 4 || config.protocol_mode !== 'governed') {
|
|
130
|
+
console.log(chalk.red(' Not a governed AgentXchain project (requires v4 config).'));
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const phaseIds = Object.keys(config.routing || {});
|
|
135
|
+
if (phaseIds.length === 0) {
|
|
136
|
+
console.log(chalk.red(' No governed phases are defined in routing.'));
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const contract = buildWorkflowKitContract(root, config);
|
|
141
|
+
|
|
142
|
+
if (opts.json) {
|
|
143
|
+
console.log(JSON.stringify(contract, null, 2));
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
console.log(chalk.bold(`\n Workflow Kit v${contract.workflow_kit_version}\n`));
|
|
148
|
+
console.log(` Source: ${contract.source}`);
|
|
149
|
+
console.log(` Templates: ${contract.phase_templates.available.length} available, ${contract.phase_templates.in_use.length} in use`);
|
|
150
|
+
if (contract.phase_templates.in_use.length > 0) {
|
|
151
|
+
console.log(` ${chalk.dim(contract.phase_templates.in_use.join(', '))}`);
|
|
152
|
+
}
|
|
153
|
+
console.log(` Validators: ${contract.semantic_validators.join(', ')}`);
|
|
154
|
+
console.log('');
|
|
155
|
+
|
|
156
|
+
for (const [phaseId, phase] of Object.entries(contract.phases)) {
|
|
157
|
+
const templateLabel = phase.template ? ` (${phase.template})` : '';
|
|
158
|
+
console.log(chalk.bold(` Phase: ${chalk.cyan(phaseId)}`) + chalk.dim(templateLabel));
|
|
159
|
+
console.log(` Source: ${phase.source}`);
|
|
160
|
+
|
|
161
|
+
if (phase.artifacts.length === 0) {
|
|
162
|
+
console.log(` ${chalk.dim('No artifacts declared')}`);
|
|
163
|
+
} else {
|
|
164
|
+
for (const artifact of phase.artifacts) {
|
|
165
|
+
const icon = artifact.exists
|
|
166
|
+
? chalk.green('✓')
|
|
167
|
+
: artifact.required
|
|
168
|
+
? chalk.red('✗')
|
|
169
|
+
: chalk.yellow('○');
|
|
170
|
+
const sem = artifact.semantics ? chalk.dim(` [${artifact.semantics}]`) : '';
|
|
171
|
+
const req = artifact.required ? '' : chalk.dim(' (optional)');
|
|
172
|
+
console.log(` ${icon} ${artifact.path}${sem}${req}`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
console.log('');
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const gateEntries = Object.entries(contract.gate_artifact_coverage);
|
|
179
|
+
if (gateEntries.length > 0) {
|
|
180
|
+
console.log(chalk.bold(' Gate artifact coverage:'));
|
|
181
|
+
for (const [gateId, cov] of gateEntries) {
|
|
182
|
+
console.log(` ${gateId}: ${cov.predicates_referencing_artifacts} artifact(s) — ${cov.artifacts_covered.join(', ') || chalk.dim('none')}`);
|
|
183
|
+
}
|
|
184
|
+
console.log('');
|
|
185
|
+
}
|
|
186
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { buildRuntimeCapabilityReport } from './runtime-capabilities.js';
|
|
2
|
+
|
|
3
|
+
export const CONFIG_SCHEMA_ARTIFACT = 'agentxchain/schemas/agentxchain-config';
|
|
4
|
+
export const CAPABILITIES_OUTPUT_SCHEMA_ARTIFACT = 'agentxchain/schemas/connector-capabilities-output';
|
|
5
|
+
|
|
6
|
+
export function buildConnectorSchemaContract(rawConfig, normalizedConfig, runtimeId, roleId) {
|
|
7
|
+
const continuity = {
|
|
8
|
+
raw_config_runtime_present: Boolean(rawConfig?.runtimes?.[runtimeId]),
|
|
9
|
+
raw_role_present: Boolean(rawConfig?.roles?.[roleId]),
|
|
10
|
+
raw_role_binding_matches_runtime: false,
|
|
11
|
+
capabilities_report_runtime_matches: false,
|
|
12
|
+
capabilities_report_role_binding_matches: false,
|
|
13
|
+
};
|
|
14
|
+
const failures = [];
|
|
15
|
+
|
|
16
|
+
const rawRole = rawConfig?.roles?.[roleId];
|
|
17
|
+
if (continuity.raw_role_present) {
|
|
18
|
+
continuity.raw_role_binding_matches_runtime = rawRole?.runtime === runtimeId;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const report = buildRuntimeCapabilityReport(
|
|
22
|
+
runtimeId,
|
|
23
|
+
normalizedConfig?.runtimes?.[runtimeId],
|
|
24
|
+
normalizedConfig?.roles || {}
|
|
25
|
+
);
|
|
26
|
+
continuity.capabilities_report_runtime_matches = report.runtime_id === runtimeId;
|
|
27
|
+
continuity.capabilities_report_role_binding_matches = report.role_bindings.some(
|
|
28
|
+
(binding) => binding.role_id === roleId
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
if (!continuity.raw_config_runtime_present) {
|
|
32
|
+
failures.push(`Raw config does not define runtime "${runtimeId}".`);
|
|
33
|
+
}
|
|
34
|
+
if (!continuity.raw_role_present) {
|
|
35
|
+
failures.push(`Raw config does not define role "${roleId}".`);
|
|
36
|
+
} else if (!continuity.raw_role_binding_matches_runtime) {
|
|
37
|
+
failures.push(
|
|
38
|
+
`Raw role binding mismatch: roles.${roleId}.runtime is "${rawRole.runtime}" instead of "${runtimeId}".`
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
if (!continuity.capabilities_report_runtime_matches) {
|
|
42
|
+
failures.push(`Capability report emitted runtime "${report.runtime_id}" instead of "${runtimeId}".`);
|
|
43
|
+
}
|
|
44
|
+
if (!continuity.capabilities_report_role_binding_matches) {
|
|
45
|
+
failures.push(`Capability report omitted the "${roleId}" binding under runtime "${runtimeId}".`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
ok: failures.length === 0,
|
|
50
|
+
config_schema_artifact: CONFIG_SCHEMA_ARTIFACT,
|
|
51
|
+
capabilities_output_schema_artifact: CAPABILITIES_OUTPUT_SCHEMA_ARTIFACT,
|
|
52
|
+
runtime_id: runtimeId,
|
|
53
|
+
role_id: roleId,
|
|
54
|
+
continuity,
|
|
55
|
+
failures,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
@@ -23,6 +23,7 @@ import { dispatchRemoteAgent } from './adapters/remote-agent-adapter.js';
|
|
|
23
23
|
import { getDispatchPromptPath, getTurnStagingResultPath } from './turn-paths.js';
|
|
24
24
|
import { validateStagedTurnResult } from './turn-result-validator.js';
|
|
25
25
|
import { probeRuntimeSpawnContext } from './runtime-spawn-context.js';
|
|
26
|
+
import { buildConnectorSchemaContract } from './connector-schema-contract.js';
|
|
26
27
|
|
|
27
28
|
const VALIDATABLE_RUNTIME_TYPES = new Set(['local_cli', 'api_proxy', 'mcp', 'remote_agent']);
|
|
28
29
|
const DEFAULT_VALIDATE_TIMEOUT_MS = 120_000;
|
|
@@ -106,12 +107,42 @@ export async function validateConfiguredConnector(sourceRoot, options = {}) {
|
|
|
106
107
|
const tempBase = mkdtempSync(join(tmpdir(), 'axc-connector-validate-'));
|
|
107
108
|
const scratchRoot = join(tempBase, 'workspace');
|
|
108
109
|
const warnings = [...roleSelection.warnings];
|
|
110
|
+
const schemaContract = buildConnectorSchemaContract(
|
|
111
|
+
sourceContext.rawConfig,
|
|
112
|
+
sourceContext.config,
|
|
113
|
+
runtimeId,
|
|
114
|
+
roleSelection.roleId
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
// Surface capability declaration warnings for self-declared connectors
|
|
118
|
+
const { getCapabilityDeclarationWarnings } = await import('./runtime-capabilities.js');
|
|
119
|
+
const capWarnings = getCapabilityDeclarationWarnings(runtime);
|
|
120
|
+
warnings.push(...capWarnings);
|
|
121
|
+
|
|
109
122
|
let keepArtifacts = options.keepArtifacts === true;
|
|
110
123
|
let dispatch = null;
|
|
111
124
|
let validation = null;
|
|
112
125
|
let costUsd = null;
|
|
113
126
|
|
|
114
127
|
try {
|
|
128
|
+
if (!schemaContract.ok) {
|
|
129
|
+
return {
|
|
130
|
+
ok: false,
|
|
131
|
+
exitCode: 1,
|
|
132
|
+
overall: 'fail',
|
|
133
|
+
runtime_id: runtimeId,
|
|
134
|
+
runtime_type: runtime.type,
|
|
135
|
+
role_id: roleSelection.roleId,
|
|
136
|
+
timeout_ms: timeoutMs,
|
|
137
|
+
warnings,
|
|
138
|
+
schema_contract: schemaContract,
|
|
139
|
+
dispatch: null,
|
|
140
|
+
validation: null,
|
|
141
|
+
error: 'Schema contract continuity failed before synthetic dispatch.',
|
|
142
|
+
scratch_root: null,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
115
146
|
copyRepoForValidation(sourceRoot, scratchRoot);
|
|
116
147
|
initializeScratchGit(scratchRoot);
|
|
117
148
|
|
|
@@ -126,6 +157,7 @@ export async function validateConfiguredConnector(sourceRoot, options = {}) {
|
|
|
126
157
|
role_id: roleSelection.roleId,
|
|
127
158
|
timeout_ms: timeoutMs,
|
|
128
159
|
warnings,
|
|
160
|
+
schema_contract: schemaContract,
|
|
129
161
|
error: 'Failed to load governed config inside scratch workspace.',
|
|
130
162
|
scratch_root: scratchRoot,
|
|
131
163
|
};
|
|
@@ -143,6 +175,7 @@ export async function validateConfiguredConnector(sourceRoot, options = {}) {
|
|
|
143
175
|
role_id: roleSelection.roleId,
|
|
144
176
|
timeout_ms: timeoutMs,
|
|
145
177
|
warnings,
|
|
178
|
+
schema_contract: schemaContract,
|
|
146
179
|
dispatch: null,
|
|
147
180
|
validation: null,
|
|
148
181
|
error: spawnProbe.detail,
|
|
@@ -168,6 +201,7 @@ export async function validateConfiguredConnector(sourceRoot, options = {}) {
|
|
|
168
201
|
role_id: roleSelection.roleId,
|
|
169
202
|
timeout_ms: timeoutMs,
|
|
170
203
|
warnings,
|
|
204
|
+
schema_contract: schemaContract,
|
|
171
205
|
error: initResult.error,
|
|
172
206
|
scratch_root: scratchRoot,
|
|
173
207
|
};
|
|
@@ -184,6 +218,7 @@ export async function validateConfiguredConnector(sourceRoot, options = {}) {
|
|
|
184
218
|
role_id: roleSelection.roleId,
|
|
185
219
|
timeout_ms: timeoutMs,
|
|
186
220
|
warnings,
|
|
221
|
+
schema_contract: schemaContract,
|
|
187
222
|
error: assignResult.error,
|
|
188
223
|
scratch_root: scratchRoot,
|
|
189
224
|
};
|
|
@@ -201,6 +236,7 @@ export async function validateConfiguredConnector(sourceRoot, options = {}) {
|
|
|
201
236
|
role_id: roleSelection.roleId,
|
|
202
237
|
timeout_ms: timeoutMs,
|
|
203
238
|
warnings,
|
|
239
|
+
schema_contract: schemaContract,
|
|
204
240
|
error: 'Synthetic validation turn was not assigned.',
|
|
205
241
|
scratch_root: scratchRoot,
|
|
206
242
|
};
|
|
@@ -217,6 +253,7 @@ export async function validateConfiguredConnector(sourceRoot, options = {}) {
|
|
|
217
253
|
role_id: roleSelection.roleId,
|
|
218
254
|
timeout_ms: timeoutMs,
|
|
219
255
|
warnings,
|
|
256
|
+
schema_contract: schemaContract,
|
|
220
257
|
error: bundleResult.error,
|
|
221
258
|
scratch_root: scratchRoot,
|
|
222
259
|
};
|
|
@@ -260,6 +297,7 @@ export async function validateConfiguredConnector(sourceRoot, options = {}) {
|
|
|
260
297
|
role_id: roleSelection.roleId,
|
|
261
298
|
timeout_ms: timeoutMs,
|
|
262
299
|
warnings,
|
|
300
|
+
schema_contract: schemaContract,
|
|
263
301
|
dispatch,
|
|
264
302
|
validation: null,
|
|
265
303
|
scratch_root: scratchRoot,
|
|
@@ -282,6 +320,7 @@ export async function validateConfiguredConnector(sourceRoot, options = {}) {
|
|
|
282
320
|
role_id: roleSelection.roleId,
|
|
283
321
|
timeout_ms: timeoutMs,
|
|
284
322
|
warnings,
|
|
323
|
+
schema_contract: schemaContract,
|
|
285
324
|
dispatch,
|
|
286
325
|
validation: {
|
|
287
326
|
ok: false,
|
|
@@ -304,6 +343,7 @@ export async function validateConfiguredConnector(sourceRoot, options = {}) {
|
|
|
304
343
|
role_id: roleSelection.roleId,
|
|
305
344
|
timeout_ms: timeoutMs,
|
|
306
345
|
warnings,
|
|
346
|
+
schema_contract: schemaContract,
|
|
307
347
|
dispatch,
|
|
308
348
|
validation: {
|
|
309
349
|
ok: true,
|
|
@@ -326,6 +366,7 @@ export async function validateConfiguredConnector(sourceRoot, options = {}) {
|
|
|
326
366
|
role_id: roleSelection.roleId,
|
|
327
367
|
timeout_ms: timeoutMs,
|
|
328
368
|
warnings,
|
|
369
|
+
schema_contract: schemaContract,
|
|
329
370
|
dispatch,
|
|
330
371
|
validation,
|
|
331
372
|
error: error.message,
|