agentxchain 2.158.0 → 2.159.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/bin/agentxchain.js +6 -0
- package/package.json +1 -1
- package/src/commands/role.js +69 -1
- package/src/lib/report.js +2 -0
- package/src/lib/role-charter.js +294 -0
package/bin/agentxchain.js
CHANGED
|
@@ -971,6 +971,12 @@ roleCmd
|
|
|
971
971
|
.option('-j, --json', 'Output as JSON')
|
|
972
972
|
.action((roleId, opts) => roleCommand('show', roleId, opts));
|
|
973
973
|
|
|
974
|
+
roleCmd
|
|
975
|
+
.command('validate [role_id]')
|
|
976
|
+
.description('Validate role charter well-formedness against the four VISION:123–128 invariants (exit 1 if any role incomplete)')
|
|
977
|
+
.option('-j, --json', 'Output as JSON')
|
|
978
|
+
.action((roleId, opts) => roleCommand('validate', roleId, opts));
|
|
979
|
+
|
|
974
980
|
const turnCmd = program
|
|
975
981
|
.command('turn')
|
|
976
982
|
.description('Inspect active governed turn dispatch bundles');
|
package/package.json
CHANGED
package/src/commands/role.js
CHANGED
|
@@ -4,6 +4,10 @@ import {
|
|
|
4
4
|
getRoleRuntimeCapabilityContract,
|
|
5
5
|
summarizeRuntimeCapabilityContract,
|
|
6
6
|
} from '../lib/runtime-capabilities.js';
|
|
7
|
+
import {
|
|
8
|
+
evaluateRoleCharter,
|
|
9
|
+
evaluateAllRoleCharters,
|
|
10
|
+
} from '../lib/role-charter.js';
|
|
7
11
|
|
|
8
12
|
export function roleCommand(subcommand, roleId, opts) {
|
|
9
13
|
const context = loadProjectContext();
|
|
@@ -12,7 +16,7 @@ export function roleCommand(subcommand, roleId, opts) {
|
|
|
12
16
|
process.exit(1);
|
|
13
17
|
}
|
|
14
18
|
|
|
15
|
-
const { config, version } = context;
|
|
19
|
+
const { config, rawConfig, version } = context;
|
|
16
20
|
|
|
17
21
|
if (version !== 4) {
|
|
18
22
|
console.log(chalk.red(' Not a governed AgentXchain project (requires v4 config).'));
|
|
@@ -27,6 +31,10 @@ export function roleCommand(subcommand, roleId, opts) {
|
|
|
27
31
|
return showRole(roleId, roles, runtimes, roleIds, opts);
|
|
28
32
|
}
|
|
29
33
|
|
|
34
|
+
if (subcommand === 'validate') {
|
|
35
|
+
return validateRoles(roleId, config, rawConfig, roleIds, opts);
|
|
36
|
+
}
|
|
37
|
+
|
|
30
38
|
// Default: list
|
|
31
39
|
return listRoles(roles, runtimes, roleIds, opts);
|
|
32
40
|
}
|
|
@@ -155,3 +163,63 @@ function showRole(roleId, roles, runtimes, roleIds, opts) {
|
|
|
155
163
|
}
|
|
156
164
|
console.log('');
|
|
157
165
|
}
|
|
166
|
+
|
|
167
|
+
function renderCharterReport(report) {
|
|
168
|
+
const mark = report.overall === 'well_formed' ? chalk.green('✓') : chalk.red('✗');
|
|
169
|
+
if (report.overall === 'well_formed') {
|
|
170
|
+
console.log(` ${mark} ${chalk.cyan(report.role_id)} well-formed (4/4 invariants)`);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
console.log(` ${mark} ${chalk.cyan(report.role_id)} incomplete — missing: ${report.missing.join(', ')}`);
|
|
174
|
+
for (const inv of report.invariants) {
|
|
175
|
+
if (!inv.satisfied && inv.fix_hint) {
|
|
176
|
+
console.log(chalk.dim(` → ${inv.fix_hint}`));
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function validateRoles(roleId, config, rawConfig, roleIds, opts) {
|
|
182
|
+
// Single-role validation
|
|
183
|
+
if (roleId) {
|
|
184
|
+
if (!config.roles?.[roleId]) {
|
|
185
|
+
console.log(chalk.red(` Unknown role: ${roleId}`));
|
|
186
|
+
console.log(chalk.dim(` Available: ${roleIds.join(', ')}`));
|
|
187
|
+
process.exit(1);
|
|
188
|
+
}
|
|
189
|
+
const report = evaluateRoleCharter(config, rawConfig, roleId);
|
|
190
|
+
if (opts.json) {
|
|
191
|
+
console.log(JSON.stringify(report, null, 2));
|
|
192
|
+
} else {
|
|
193
|
+
console.log('');
|
|
194
|
+
renderCharterReport(report);
|
|
195
|
+
console.log('');
|
|
196
|
+
}
|
|
197
|
+
process.exit(report.overall === 'well_formed' ? 0 : 1);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// All-roles validation
|
|
201
|
+
const all = evaluateAllRoleCharters(config, rawConfig);
|
|
202
|
+
if (opts.json) {
|
|
203
|
+
console.log(JSON.stringify(all, null, 2));
|
|
204
|
+
process.exit(all.incomplete === 0 ? 0 : 1);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (all.total === 0) {
|
|
208
|
+
console.log(' No roles defined.');
|
|
209
|
+
process.exit(0);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (all.incomplete === 0) {
|
|
213
|
+
console.log(chalk.bold(`\n Role charter validation (${all.total} roles): all well-formed (4/4 invariants each).\n`));
|
|
214
|
+
process.exit(0);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
console.log(chalk.bold(`\n Role charter validation (${all.total} roles):\n`));
|
|
218
|
+
for (const report of all.roles) {
|
|
219
|
+
renderCharterReport(report);
|
|
220
|
+
}
|
|
221
|
+
console.log('');
|
|
222
|
+
console.log(` ${all.incomplete} of ${all.total} roles incomplete.`);
|
|
223
|
+
console.log('');
|
|
224
|
+
process.exit(1);
|
|
225
|
+
}
|
package/src/lib/report.js
CHANGED
|
@@ -15,6 +15,7 @@ import { extractGateActionDigest } from './gate-actions.js';
|
|
|
15
15
|
import { buildRecoveryClassificationReport } from './recovery-classification.js';
|
|
16
16
|
import { buildShipStatusSummary } from './ship-status.js';
|
|
17
17
|
import { buildHumanAttentionSummary } from './human-attention.js';
|
|
18
|
+
import { buildRoleCharterSummary } from './role-charter.js';
|
|
18
19
|
|
|
19
20
|
export const GOVERNANCE_REPORT_VERSION = '0.1';
|
|
20
21
|
|
|
@@ -1081,6 +1082,7 @@ function buildRunSubject(artifact) {
|
|
|
1081
1082
|
repo_decisions: artifact.summary?.repo_decisions || null,
|
|
1082
1083
|
ship_status: buildShipStatusSummary(artifact),
|
|
1083
1084
|
human_attention: buildHumanAttentionSummary(artifact),
|
|
1085
|
+
role_charters: buildRoleCharterSummary(artifact),
|
|
1084
1086
|
},
|
|
1085
1087
|
artifacts: {
|
|
1086
1088
|
history_entries: artifact.summary?.history_entries || 0,
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Role Charter Well-Formedness — VISION.md:100–130 "Roles Are Open-Ended, Not Fixed".
|
|
3
|
+
*
|
|
4
|
+
* The vision fixes a precise, testable four-part invariant for *any* chartered
|
|
5
|
+
* role (VISION.md:123–128):
|
|
6
|
+
*
|
|
7
|
+
* 1. every role has a mandate
|
|
8
|
+
* 2. every role has authority boundaries
|
|
9
|
+
* 3. every role produces governed artifacts
|
|
10
|
+
* 4. every role participates in a structured workflow
|
|
11
|
+
*
|
|
12
|
+
* This module scores a role definition against those four invariants. It is
|
|
13
|
+
* **read-only** and **composes** existing primitives — it does not reimplement
|
|
14
|
+
* runtime-capability derivation (`runtime-capabilities.js`), gate-artifact
|
|
15
|
+
* resolution (`gate-evaluator.js`), or the manual-runtime / file-production
|
|
16
|
+
* logic that admission control (`admission-control.js`) already relies on.
|
|
17
|
+
*
|
|
18
|
+
* `agentxchain role` permits arbitrary roles; this module *governs* them.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { getEffectiveGateArtifacts } from './gate-evaluator.js';
|
|
22
|
+
import {
|
|
23
|
+
getRoleRuntimeCapabilityContract,
|
|
24
|
+
canRoleParticipateInRequiredFileProduction,
|
|
25
|
+
canRoleSatisfyWorkflowArtifactOwnership,
|
|
26
|
+
} from './runtime-capabilities.js';
|
|
27
|
+
|
|
28
|
+
const VALID_WRITE_AUTHORITIES = ['authoritative', 'proposed', 'review_only'];
|
|
29
|
+
|
|
30
|
+
// effective_write_path values that mean the authority binding is NOT coherent
|
|
31
|
+
// with the runtime — i.e. the role can never actually exercise its declared
|
|
32
|
+
// authority (e.g. review_only on a local_cli runtime that only writes directly).
|
|
33
|
+
const INCOHERENT_WRITE_PATHS = new Set([
|
|
34
|
+
'none',
|
|
35
|
+
'unknown',
|
|
36
|
+
'invalid_review_only_binding',
|
|
37
|
+
'invalid_authoritative_binding',
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
export const ROLE_CHARTER_INVARIANT_IDS = [
|
|
41
|
+
'mandate',
|
|
42
|
+
'authority_boundary',
|
|
43
|
+
'produces_artifacts',
|
|
44
|
+
'workflow_participation',
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Resolve the config surfaces this module needs, tolerating either a normalized
|
|
49
|
+
* config (roles carry `runtime_id`) or a raw agentxchain.json (roles carry
|
|
50
|
+
* `runtime`). Mirrors admission-control.js's `config || rawConfig` fallback so
|
|
51
|
+
* the same evaluator works from the CLI (normalized) and the governance report
|
|
52
|
+
* (raw export artifact config).
|
|
53
|
+
*/
|
|
54
|
+
function resolveConfigSources(config, rawConfig) {
|
|
55
|
+
return {
|
|
56
|
+
roles: config?.roles || rawConfig?.roles || {},
|
|
57
|
+
runtimes: config?.runtimes || rawConfig?.runtimes || {},
|
|
58
|
+
routing: config?.routing || rawConfig?.routing || {},
|
|
59
|
+
gates: config?.gates || rawConfig?.gates || {},
|
|
60
|
+
// getEffectiveGateArtifacts only reads `config.workflow_kit`, so hand it a
|
|
61
|
+
// shim carrying whichever workflow_kit is available.
|
|
62
|
+
workflowKitConfig: { workflow_kit: config?.workflow_kit || rawConfig?.workflow_kit || null },
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function getRoleRuntime(role, runtimes) {
|
|
67
|
+
const key = role?.runtime_id || role?.runtime;
|
|
68
|
+
return key ? runtimes?.[key] : undefined;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Phases whose routing references this role as entry_role or allowed_next_roles. */
|
|
72
|
+
function getRoutedPhases(routing, roleId) {
|
|
73
|
+
const phases = [];
|
|
74
|
+
for (const [phase, route] of Object.entries(routing || {})) {
|
|
75
|
+
const inEntry = route?.entry_role === roleId;
|
|
76
|
+
const inNext = Array.isArray(route?.allowed_next_roles) && route.allowed_next_roles.includes(roleId);
|
|
77
|
+
if (inEntry || inNext) phases.push(phase);
|
|
78
|
+
}
|
|
79
|
+
return phases;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ── Invariant 1: Mandate (VISION.md:124) ────────────────────────────────────
|
|
83
|
+
|
|
84
|
+
function evaluateMandate(role, roleId) {
|
|
85
|
+
const mandate = typeof role?.mandate === 'string' ? role.mandate.trim() : '';
|
|
86
|
+
const satisfied = mandate.length > 0;
|
|
87
|
+
return {
|
|
88
|
+
id: 'mandate',
|
|
89
|
+
name: 'Has a mandate',
|
|
90
|
+
satisfied,
|
|
91
|
+
detail: satisfied
|
|
92
|
+
? `Role "${roleId}" declares a non-empty mandate.`
|
|
93
|
+
: `Role "${roleId}" has no mandate text.`,
|
|
94
|
+
fix_hint: satisfied
|
|
95
|
+
? null
|
|
96
|
+
: `Set a non-empty "mandate" for role "${roleId}" in agentxchain.json`,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ── Invariant 2: Authority boundary (VISION.md:125) ─────────────────────────
|
|
101
|
+
|
|
102
|
+
function evaluateAuthorityBoundary(role, roleId, runtimes) {
|
|
103
|
+
const authority = role?.write_authority;
|
|
104
|
+
const runtimeKey = role?.runtime_id || role?.runtime || null;
|
|
105
|
+
const runtime = getRoleRuntime(role, runtimes);
|
|
106
|
+
|
|
107
|
+
if (!VALID_WRITE_AUTHORITIES.includes(authority)) {
|
|
108
|
+
return {
|
|
109
|
+
id: 'authority_boundary',
|
|
110
|
+
name: 'Has coherent authority boundaries',
|
|
111
|
+
satisfied: false,
|
|
112
|
+
detail: `Role "${roleId}" write_authority "${authority ?? '(unset)'}" is not one of ${VALID_WRITE_AUTHORITIES.join('/')}.`,
|
|
113
|
+
fix_hint: `Set role "${roleId}" write_authority to one of ${VALID_WRITE_AUTHORITIES.join('/')}`,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (!runtime) {
|
|
118
|
+
return {
|
|
119
|
+
id: 'authority_boundary',
|
|
120
|
+
name: 'Has coherent authority boundaries',
|
|
121
|
+
satisfied: false,
|
|
122
|
+
detail: `Role "${roleId}" is bound to runtime "${runtimeKey ?? '(none)'}" which is not defined in runtimes.`,
|
|
123
|
+
fix_hint: `Bind role "${roleId}" to a defined runtime so its authority "${authority}" can resolve`,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const contract = getRoleRuntimeCapabilityContract(roleId, role, runtime);
|
|
128
|
+
const coherent = !INCOHERENT_WRITE_PATHS.has(contract.effective_write_path);
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
id: 'authority_boundary',
|
|
132
|
+
name: 'Has coherent authority boundaries',
|
|
133
|
+
satisfied: coherent,
|
|
134
|
+
detail: coherent
|
|
135
|
+
? `Role "${roleId}" write_authority "${authority}" on ${runtime.type} runtime resolves to write path "${contract.effective_write_path}".`
|
|
136
|
+
: `Role "${roleId}" write_authority "${authority}" on ${runtime.type} runtime resolves to "${contract.effective_write_path}".`,
|
|
137
|
+
fix_hint: coherent
|
|
138
|
+
? null
|
|
139
|
+
: `Role "${roleId}" write_authority "${authority}" on ${runtime.type} runtime resolves to no usable write path; bind a runtime that supports it or change authority`,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ── Invariant 3: Produces governed artifacts (VISION.md:126) ────────────────
|
|
144
|
+
|
|
145
|
+
function evaluateProducesArtifacts(role, roleId, sources) {
|
|
146
|
+
const { runtimes, routing, gates, workflowKitConfig } = sources;
|
|
147
|
+
const runtime = getRoleRuntime(role, runtimes);
|
|
148
|
+
const routedPhases = getRoutedPhases(routing, roleId);
|
|
149
|
+
|
|
150
|
+
// (a) The role can reach required-file production in at least one routed
|
|
151
|
+
// phase. canRoleParticipateInRequiredFileProduction already returns true for
|
|
152
|
+
// manual-runtime roles (humans satisfy required files outside the governed
|
|
153
|
+
// turn mechanism) — that is the manual-runtime carve-out admission control
|
|
154
|
+
// also relies on, so no special-casing is needed here.
|
|
155
|
+
let reachesFileProduction = false;
|
|
156
|
+
if (canRoleParticipateInRequiredFileProduction(role, runtime)) {
|
|
157
|
+
for (const phase of routedPhases) {
|
|
158
|
+
const gateId = routing[phase]?.exit_gate;
|
|
159
|
+
const gateDef = gateId ? gates?.[gateId] : null;
|
|
160
|
+
if (!gateDef) continue;
|
|
161
|
+
const artifacts = getEffectiveGateArtifacts(workflowKitConfig, gateDef, phase);
|
|
162
|
+
if (artifacts.some((a) => a.required)) {
|
|
163
|
+
reachesFileProduction = true;
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// (b) The role owns at least one workflow-kit artifact it can satisfy.
|
|
170
|
+
let ownsSatisfiableArtifact = false;
|
|
171
|
+
if (canRoleSatisfyWorkflowArtifactOwnership(role, runtime)) {
|
|
172
|
+
for (const phase of Object.keys(routing || {})) {
|
|
173
|
+
const gateId = routing[phase]?.exit_gate;
|
|
174
|
+
const gateDef = (gateId ? gates?.[gateId] : null) || {};
|
|
175
|
+
const artifacts = getEffectiveGateArtifacts(workflowKitConfig, gateDef, phase);
|
|
176
|
+
if (artifacts.some((a) => a.owned_by === roleId)) {
|
|
177
|
+
ownsSatisfiableArtifact = true;
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const satisfied = reachesFileProduction || ownsSatisfiableArtifact;
|
|
184
|
+
let detail;
|
|
185
|
+
if (reachesFileProduction && ownsSatisfiableArtifact) {
|
|
186
|
+
detail = `Role "${roleId}" reaches required-file production in a routed phase and owns a governed artifact.`;
|
|
187
|
+
} else if (reachesFileProduction) {
|
|
188
|
+
detail = `Role "${roleId}" reaches required-file production in a routed phase.`;
|
|
189
|
+
} else if (ownsSatisfiableArtifact) {
|
|
190
|
+
detail = `Role "${roleId}" owns a governed workflow-kit artifact it can satisfy.`;
|
|
191
|
+
} else {
|
|
192
|
+
detail = `Role "${roleId}" reaches no required-file-producing routed phase and owns no satisfiable artifact.`;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
id: 'produces_artifacts',
|
|
197
|
+
name: 'Produces governed artifacts',
|
|
198
|
+
satisfied,
|
|
199
|
+
detail,
|
|
200
|
+
fix_hint: satisfied
|
|
201
|
+
? null
|
|
202
|
+
: `Role "${roleId}" produces no governed artifact: route it to a file-producing phase or give it owned_by on a workflow artifact`,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// ── Invariant 4: Participates in a structured workflow (VISION.md:127) ───────
|
|
207
|
+
|
|
208
|
+
function evaluateWorkflowParticipation(roleId, routing) {
|
|
209
|
+
const phases = getRoutedPhases(routing, roleId);
|
|
210
|
+
const satisfied = phases.length > 0;
|
|
211
|
+
return {
|
|
212
|
+
id: 'workflow_participation',
|
|
213
|
+
name: 'Participates in a structured workflow',
|
|
214
|
+
satisfied,
|
|
215
|
+
detail: satisfied
|
|
216
|
+
? `Role "${roleId}" appears in routing for phase(s): ${phases.join(', ')}.`
|
|
217
|
+
: `Role "${roleId}" is not referenced in any phase routing.`,
|
|
218
|
+
fix_hint: satisfied
|
|
219
|
+
? null
|
|
220
|
+
: `Role "${roleId}" is not in any phase routing; add it to entry_role or allowed_next_roles of a phase`,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Score a single role against the four VISION:123–128 charter invariants.
|
|
226
|
+
* Read-only. Each invariant is evaluated independently — one failing invariant
|
|
227
|
+
* never suppresses evaluation of the others.
|
|
228
|
+
*
|
|
229
|
+
* @returns {{ role_id, overall, invariants, missing, evidence_summary }}
|
|
230
|
+
*/
|
|
231
|
+
export function evaluateRoleCharter(config, rawConfig, roleId) {
|
|
232
|
+
const sources = resolveConfigSources(config, rawConfig);
|
|
233
|
+
const role = sources.roles?.[roleId] || null;
|
|
234
|
+
|
|
235
|
+
const invariants = [
|
|
236
|
+
evaluateMandate(role, roleId),
|
|
237
|
+
evaluateAuthorityBoundary(role, roleId, sources.runtimes),
|
|
238
|
+
evaluateProducesArtifacts(role, roleId, sources),
|
|
239
|
+
evaluateWorkflowParticipation(roleId, sources.routing),
|
|
240
|
+
];
|
|
241
|
+
|
|
242
|
+
const missing = invariants.filter((inv) => !inv.satisfied).map((inv) => inv.id);
|
|
243
|
+
const overall = missing.length === 0 ? 'well_formed' : 'incomplete';
|
|
244
|
+
|
|
245
|
+
return {
|
|
246
|
+
role_id: roleId,
|
|
247
|
+
overall,
|
|
248
|
+
invariants,
|
|
249
|
+
missing,
|
|
250
|
+
evidence_summary:
|
|
251
|
+
overall === 'well_formed'
|
|
252
|
+
? `Role "${roleId}" is well-formed (4/4 charter invariants satisfied).`
|
|
253
|
+
: `Role "${roleId}" is incomplete — missing: ${missing.join(', ')}.`,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Score every defined role against the four charter invariants. Roles are
|
|
259
|
+
* reported in stable order (sorted by id) so output and tests are deterministic.
|
|
260
|
+
*
|
|
261
|
+
* @returns {{ total, well_formed, incomplete, incomplete_role_ids, roles }}
|
|
262
|
+
*/
|
|
263
|
+
export function evaluateAllRoleCharters(config, rawConfig) {
|
|
264
|
+
const sources = resolveConfigSources(config, rawConfig);
|
|
265
|
+
const roleIds = Object.keys(sources.roles || {}).sort();
|
|
266
|
+
const roles = roleIds.map((id) => evaluateRoleCharter(config, rawConfig, id));
|
|
267
|
+
const incomplete = roles.filter((r) => r.overall === 'incomplete');
|
|
268
|
+
|
|
269
|
+
return {
|
|
270
|
+
total: roles.length,
|
|
271
|
+
well_formed: roles.length - incomplete.length,
|
|
272
|
+
incomplete: incomplete.length,
|
|
273
|
+
incomplete_role_ids: incomplete.map((r) => r.role_id),
|
|
274
|
+
roles,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Compact summary for the governance report (`report.role_charters`), mirroring
|
|
280
|
+
* buildHumanAttentionSummary / buildShipStatusSummary. The export artifact's
|
|
281
|
+
* `config` is the raw agentxchain.json, which evaluateAllRoleCharters tolerates.
|
|
282
|
+
*/
|
|
283
|
+
export function buildRoleCharterSummary(artifact) {
|
|
284
|
+
if (!artifact) return null;
|
|
285
|
+
const config = artifact.config || null;
|
|
286
|
+
if (!config) return null;
|
|
287
|
+
const report = evaluateAllRoleCharters(config, config);
|
|
288
|
+
return {
|
|
289
|
+
total: report.total,
|
|
290
|
+
well_formed: report.well_formed,
|
|
291
|
+
incomplete: report.incomplete,
|
|
292
|
+
incomplete_role_ids: report.incomplete_role_ids,
|
|
293
|
+
};
|
|
294
|
+
}
|