agentxchain 2.45.0 → 2.46.2

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/src/lib/report.js CHANGED
@@ -78,6 +78,10 @@ function extractHistoryTimeline(artifact) {
78
78
  phase: e.phase || null,
79
79
  phase_transition: e.phase_transition_request || null,
80
80
  files_changed_count: Array.isArray(e.files_changed) ? e.files_changed.length : 0,
81
+ concurrent_with: Array.isArray(e.concurrent_with) && e.concurrent_with.length > 0 ? e.concurrent_with : undefined,
82
+ sibling_attributed_files: Array.isArray(e.observed_artifact?.attributed_to_concurrent_siblings) && e.observed_artifact.attributed_to_concurrent_siblings.length > 0
83
+ ? e.observed_artifact.attributed_to_concurrent_siblings
84
+ : undefined,
81
85
  decisions: Array.isArray(e.decisions) ? e.decisions.map((d) => d?.id || d).filter(Boolean) : [],
82
86
  objections: Array.isArray(e.objections) ? e.objections.map((o) => o?.id || o).filter(Boolean) : [],
83
87
  cost_usd: typeof e.cost?.total_usd === 'number' ? e.cost.total_usd : null,
@@ -99,6 +103,61 @@ function extractDecisionDigest(artifact) {
99
103
  }));
100
104
  }
101
105
 
106
+ function extractApprovalPolicyDigest(artifact) {
107
+ const data = extractFileData(artifact, '.agentxchain/decision-ledger.jsonl');
108
+ if (!Array.isArray(data) || data.length === 0) return [];
109
+ return data
110
+ .filter((d) => d?.type === 'approval_policy')
111
+ .map((d) => ({
112
+ gate_type: d.gate_type || null,
113
+ action: d.action || null,
114
+ matched_rule: d.matched_rule || null,
115
+ from_phase: d.from_phase || null,
116
+ to_phase: d.to_phase || null,
117
+ reason: d.reason || '',
118
+ gate_id: d.gate_id || null,
119
+ timestamp: d.timestamp || null,
120
+ }));
121
+ }
122
+
123
+ function extractGateFailureDigest(artifact) {
124
+ const data = extractFileData(artifact, '.agentxchain/decision-ledger.jsonl');
125
+ if (!Array.isArray(data) || data.length === 0) return [];
126
+ return data
127
+ .filter((d) => d?.type === 'gate_failure')
128
+ .map((d) => ({
129
+ gate_type: d.gate_type || null,
130
+ gate_id: d.gate_id || null,
131
+ phase: d.phase || null,
132
+ from_phase: d.from_phase || null,
133
+ to_phase: d.to_phase || null,
134
+ requested_by_turn: d.requested_by_turn || null,
135
+ failed_at: d.failed_at || null,
136
+ queued_request: d.queued_request === true,
137
+ reasons: Array.isArray(d.reasons) ? d.reasons : [],
138
+ missing_files: Array.isArray(d.missing_files) ? d.missing_files : [],
139
+ missing_verification: d.missing_verification === true,
140
+ }));
141
+ }
142
+
143
+ function extractTimeoutEventDigest(artifact, relPath = '.agentxchain/decision-ledger.jsonl') {
144
+ const data = extractFileData(artifact, relPath);
145
+ if (!Array.isArray(data) || data.length === 0) return [];
146
+ return data
147
+ .filter((d) => typeof d?.type === 'string' && d.type.startsWith('timeout'))
148
+ .map((d) => ({
149
+ type: d.type,
150
+ scope: d.scope || null,
151
+ phase: d.phase || null,
152
+ turn_id: d.turn_id || null,
153
+ limit_minutes: typeof d.limit_minutes === 'number' ? d.limit_minutes : null,
154
+ elapsed_minutes: typeof d.elapsed_minutes === 'number' ? d.elapsed_minutes : null,
155
+ exceeded_by_minutes: typeof d.exceeded_by_minutes === 'number' ? d.exceeded_by_minutes : null,
156
+ action: d.action || null,
157
+ timestamp: d.timestamp || null,
158
+ }));
159
+ }
160
+
102
161
  function extractHookSummary(artifact) {
103
162
  const data = extractFileData(artifact, '.agentxchain/hook-audit.jsonl');
104
163
  if (!Array.isArray(data) || data.length === 0) return null;
@@ -408,6 +467,23 @@ function extractCoordinatorDecisionDigest(artifact) {
408
467
  }));
409
468
  }
410
469
 
470
+ function extractCoordinatorApprovalPolicyDigest(artifact) {
471
+ const data = extractFileData(artifact, '.agentxchain/multirepo/decision-ledger.jsonl');
472
+ if (!Array.isArray(data) || data.length === 0) return [];
473
+ return data
474
+ .filter((d) => d?.type === 'approval_policy')
475
+ .map((d) => ({
476
+ gate_type: d.gate_type || null,
477
+ action: d.action || null,
478
+ matched_rule: d.matched_rule || null,
479
+ from_phase: d.from_phase || null,
480
+ to_phase: d.to_phase || null,
481
+ reason: d.reason || '',
482
+ gate_id: d.gate_id || null,
483
+ timestamp: d.timestamp || null,
484
+ }));
485
+ }
486
+
411
487
  function extractBarrierSummary(artifact) {
412
488
  const data = extractFileData(artifact, '.agentxchain/multirepo/barriers.json');
413
489
  if (!data || typeof data !== 'object' || Array.isArray(data)) return [];
@@ -538,6 +614,9 @@ function buildRunSubject(artifact) {
538
614
 
539
615
  const turns = extractHistoryTimeline(artifact);
540
616
  const decisions = extractDecisionDigest(artifact);
617
+ const approvalPolicyEvents = extractApprovalPolicyDigest(artifact);
618
+ const gateFailures = extractGateFailureDigest(artifact);
619
+ const timeoutEvents = extractTimeoutEventDigest(artifact);
541
620
  const hookSummary = extractHookSummary(artifact);
542
621
  const timing = computeTiming(artifact, turns);
543
622
  const gateSummary = extractGateSummary(artifact);
@@ -571,6 +650,9 @@ function buildRunSubject(artifact) {
571
650
  duration_seconds: timing.duration_seconds,
572
651
  turns,
573
652
  decisions,
653
+ approval_policy_events: approvalPolicyEvents,
654
+ gate_failures: gateFailures,
655
+ timeout_events: timeoutEvents,
574
656
  hook_summary: hookSummary,
575
657
  gate_summary: gateSummary,
576
658
  intake_links: intakeLinks,
@@ -643,6 +725,9 @@ function buildCoordinatorSubject(artifact) {
643
725
  const childExport = repoEntry.export;
644
726
  base.turns = extractHistoryTimeline(childExport);
645
727
  base.decisions = extractDecisionDigest(childExport);
728
+ base.approval_policy_events = extractApprovalPolicyDigest(childExport);
729
+ base.gate_failures = extractGateFailureDigest(childExport);
730
+ base.timeout_events = extractTimeoutEventDigest(childExport);
646
731
  base.hook_summary = extractHookSummary(childExport);
647
732
  base.gate_summary = extractGateSummary(childExport);
648
733
  base.recovery_summary = extractRecoverySummary(childExport);
@@ -656,6 +741,8 @@ function buildCoordinatorSubject(artifact) {
656
741
  const barrierSummary = extractBarrierSummary(artifact);
657
742
  const barrierLedgerTimeline = extractBarrierLedgerTimeline(artifact);
658
743
  const decisionDigest = extractCoordinatorDecisionDigest(artifact);
744
+ const coordinatorApprovalPolicyEvents = extractCoordinatorApprovalPolicyDigest(artifact);
745
+ const coordinatorTimeoutEvents = extractTimeoutEventDigest(artifact, '.agentxchain/multirepo/decision-ledger.jsonl');
659
746
  const timing = computeCoordinatorTiming(artifact, coordinatorTimeline);
660
747
  const blockedReason = normalizeCoordinatorBlockedReason(coordinatorState.blocked_reason);
661
748
  const pendingGate = normalizePendingGate(coordinatorState.pending_gate);
@@ -698,6 +785,8 @@ function buildCoordinatorSubject(artifact) {
698
785
  barrier_summary: barrierSummary,
699
786
  barrier_ledger_timeline: barrierLedgerTimeline,
700
787
  decision_digest: decisionDigest,
788
+ approval_policy_events: coordinatorApprovalPolicyEvents,
789
+ timeout_events: coordinatorTimeoutEvents,
701
790
  recovery_report: extractRecoveryReportSummary(artifact),
702
791
  repos,
703
792
  artifacts: {
@@ -828,7 +917,8 @@ export function formatGovernanceReportText(report) {
828
917
  const t = run.turns[i];
829
918
  const cost = t.cost_usd != null ? formatUsd(t.cost_usd) : 'n/a';
830
919
  const phase = t.phase_transition ? `${t.phase || '?'} -> ${t.phase_transition}` : (t.phase || '?');
831
- lines.push(` ${i + 1}. [${t.role}] ${t.summary || '(no summary)'} | phase: ${phase} | files: ${t.files_changed_count} | cost: ${cost} | ${t.accepted_at || 'n/a'}`);
920
+ const siblingNote = Array.isArray(t.sibling_attributed_files) ? ` (${t.sibling_attributed_files.length} sibling-attributed)` : '';
921
+ lines.push(` ${i + 1}. [${t.role}] ${t.summary || '(no summary)'} | phase: ${phase} | files: ${t.files_changed_count}${siblingNote} | cost: ${cost} | ${t.accepted_at || 'n/a'}`);
832
922
  }
833
923
  }
834
924
 
@@ -846,6 +936,46 @@ export function formatGovernanceReportText(report) {
846
936
  }
847
937
  }
848
938
 
939
+ if (run.gate_failures && run.gate_failures.length > 0) {
940
+ lines.push('', 'Gate Failures:');
941
+ for (const failure of run.gate_failures) {
942
+ const request = failure.gate_type === 'run_completion'
943
+ ? 'run completion'
944
+ : `${failure.from_phase || failure.phase || '?'} -> ${failure.to_phase || '?'}`;
945
+ const source = failure.queued_request ? 'queued drain' : 'direct';
946
+ lines.push(` - ${failure.gate_id || 'unknown'} | ${failure.gate_type || 'unknown'} | request: ${request} | source: ${source} | at: ${failure.failed_at || 'n/a'}`);
947
+ for (const reason of failure.reasons || []) {
948
+ lines.push(` reason: ${reason}`);
949
+ }
950
+ }
951
+ }
952
+
953
+ if (run.approval_policy_events && run.approval_policy_events.length > 0) {
954
+ lines.push('', 'Approval Policy:');
955
+ for (const evt of run.approval_policy_events) {
956
+ const transition = evt.gate_type === 'run_completion'
957
+ ? 'run completion'
958
+ : `${evt.from_phase || '?'} -> ${evt.to_phase || '?'}`;
959
+ const rule = evt.matched_rule ? ` | rule: ${typeof evt.matched_rule === 'object' ? JSON.stringify(evt.matched_rule) : evt.matched_rule}` : '';
960
+ lines.push(` - ${evt.action || 'unknown'} | ${evt.gate_type || 'unknown'} | ${transition}${rule} | at: ${evt.timestamp || 'n/a'}`);
961
+ if (evt.reason) lines.push(` reason: ${evt.reason}`);
962
+ }
963
+ }
964
+
965
+ if (run.timeout_events && run.timeout_events.length > 0) {
966
+ lines.push('', 'Timeout Events:');
967
+ for (const evt of run.timeout_events) {
968
+ const label = evt.type === 'timeout_warning' ? 'warning'
969
+ : evt.type === 'timeout_skip' ? 'skip'
970
+ : evt.type === 'timeout_skip_failed' ? 'skip failed'
971
+ : 'escalation';
972
+ const elapsed = evt.elapsed_minutes != null ? `${evt.elapsed_minutes}m` : '?';
973
+ const limit = evt.limit_minutes != null ? `${evt.limit_minutes}m` : '?';
974
+ const exceeded = evt.exceeded_by_minutes != null ? `+${evt.exceeded_by_minutes}m` : '';
975
+ lines.push(` - ${label} | ${evt.scope || '?'} scope | ${elapsed}/${limit}${exceeded ? ` (${exceeded})` : ''} | action: ${evt.action || 'n/a'} | phase: ${evt.phase || 'n/a'} | at: ${evt.timestamp || 'n/a'}`);
976
+ }
977
+ }
978
+
849
979
  if (run.intake_links && run.intake_links.length > 0) {
850
980
  lines.push('', 'Intake Linkage:');
851
981
  for (const intake of run.intake_links) {
@@ -896,7 +1026,19 @@ export function formatGovernanceReportText(report) {
896
1026
  return lines.join('\n');
897
1027
  }
898
1028
 
899
- const { coordinator, run, artifacts, repos, coordinator_timeline, barrier_summary, barrier_ledger_timeline, decision_digest, recovery_report } = report.subject;
1029
+ const {
1030
+ coordinator,
1031
+ run,
1032
+ artifacts,
1033
+ repos,
1034
+ coordinator_timeline,
1035
+ barrier_summary,
1036
+ barrier_ledger_timeline,
1037
+ decision_digest,
1038
+ approval_policy_events,
1039
+ timeout_events,
1040
+ recovery_report,
1041
+ } = report.subject;
900
1042
  const lines = [
901
1043
  'AgentXchain Governance Report',
902
1044
  `Input: ${report.input}`,
@@ -979,6 +1121,32 @@ export function formatGovernanceReportText(report) {
979
1121
  }
980
1122
  }
981
1123
 
1124
+ if (approval_policy_events && approval_policy_events.length > 0) {
1125
+ lines.push('', 'Approval Policy:');
1126
+ for (const evt of approval_policy_events) {
1127
+ const transition = evt.gate_type === 'run_completion'
1128
+ ? 'run completion'
1129
+ : `${evt.from_phase || '?'} -> ${evt.to_phase || '?'}`;
1130
+ const rule = evt.matched_rule ? ` | rule: ${typeof evt.matched_rule === 'object' ? JSON.stringify(evt.matched_rule) : evt.matched_rule}` : '';
1131
+ lines.push(` - ${evt.action || 'unknown'} | ${evt.gate_type || 'unknown'} | ${transition}${rule} | at: ${evt.timestamp || 'n/a'}`);
1132
+ if (evt.reason) lines.push(` reason: ${evt.reason}`);
1133
+ }
1134
+ }
1135
+
1136
+ if (timeout_events && timeout_events.length > 0) {
1137
+ lines.push('', 'Timeout Events:');
1138
+ for (const evt of timeout_events) {
1139
+ const label = evt.type === 'timeout_warning' ? 'warning'
1140
+ : evt.type === 'timeout_skip' ? 'skip'
1141
+ : evt.type === 'timeout_skip_failed' ? 'skip failed'
1142
+ : 'escalation';
1143
+ const elapsed = evt.elapsed_minutes != null ? `${evt.elapsed_minutes}m` : '?';
1144
+ const limit = evt.limit_minutes != null ? `${evt.limit_minutes}m` : '?';
1145
+ const exceeded = evt.exceeded_by_minutes != null ? `+${evt.exceeded_by_minutes}m` : '';
1146
+ lines.push(` - ${label} | ${evt.scope || '?'} scope | ${elapsed}/${limit}${exceeded ? ` (${exceeded})` : ''} | action: ${evt.action || 'n/a'} | phase: ${evt.phase || 'n/a'} | at: ${evt.timestamp || 'n/a'}`);
1147
+ }
1148
+ }
1149
+
982
1150
  if (recovery_report) {
983
1151
  lines.push('', 'Recovery Report:');
984
1152
  lines.push(` Trigger: ${recovery_report.trigger || 'n/a'}`);
@@ -1003,7 +1171,8 @@ export function formatGovernanceReportText(report) {
1003
1171
  const t = repo.turns[i];
1004
1172
  const cost = t.cost_usd != null ? formatUsd(t.cost_usd) : 'n/a';
1005
1173
  const phase = t.phase_transition ? `${t.phase || '?'} -> ${t.phase_transition}` : (t.phase || '?');
1006
- repoLines.push(` ${i + 1}. [${t.role}] ${t.summary || '(no summary)'} | phase: ${phase} | files: ${t.files_changed_count} | cost: ${cost} | ${t.accepted_at || 'n/a'}`);
1174
+ const siblingNote = Array.isArray(t.sibling_attributed_files) ? ` (${t.sibling_attributed_files.length} sibling-attributed)` : '';
1175
+ repoLines.push(` ${i + 1}. [${t.role}] ${t.summary || '(no summary)'} | phase: ${phase} | files: ${t.files_changed_count}${siblingNote} | cost: ${cost} | ${t.accepted_at || 'n/a'}`);
1007
1176
  }
1008
1177
  }
1009
1178
  if (repo.decisions && repo.decisions.length > 0) {
@@ -1018,6 +1187,39 @@ export function formatGovernanceReportText(report) {
1018
1187
  repoLines.push(` - ${gate.gate_id}: ${gate.status}`);
1019
1188
  }
1020
1189
  }
1190
+ if (repo.gate_failures && repo.gate_failures.length > 0) {
1191
+ repoLines.push(' Gate Failures:');
1192
+ for (const failure of repo.gate_failures) {
1193
+ const request = failure.gate_type === 'run_completion'
1194
+ ? 'run completion'
1195
+ : `${failure.from_phase || failure.phase || '?'} -> ${failure.to_phase || '?'}`;
1196
+ repoLines.push(` - ${failure.gate_id || 'unknown'}: ${failure.gate_type || 'unknown'} | ${request} | ${failure.queued_request ? 'queued drain' : 'direct'} | ${failure.failed_at || 'n/a'}`);
1197
+ for (const reason of failure.reasons || []) {
1198
+ repoLines.push(` reason: ${reason}`);
1199
+ }
1200
+ }
1201
+ }
1202
+ if (repo.approval_policy_events && repo.approval_policy_events.length > 0) {
1203
+ repoLines.push(' Approval Policy:');
1204
+ for (const evt of repo.approval_policy_events) {
1205
+ const transition = evt.gate_type === 'run_completion'
1206
+ ? 'run completion'
1207
+ : `${evt.from_phase || '?'} -> ${evt.to_phase || '?'}`;
1208
+ repoLines.push(` - ${evt.action || 'unknown'}: ${evt.gate_type || 'unknown'} | ${transition} | ${evt.timestamp || 'n/a'}`);
1209
+ }
1210
+ }
1211
+ if (repo.timeout_events && repo.timeout_events.length > 0) {
1212
+ repoLines.push(' Timeout Events:');
1213
+ for (const evt of repo.timeout_events) {
1214
+ const label = evt.type === 'timeout_warning' ? 'warning'
1215
+ : evt.type === 'timeout_skip' ? 'skip'
1216
+ : evt.type === 'timeout_skip_failed' ? 'skip failed'
1217
+ : 'escalation';
1218
+ const elapsed = evt.elapsed_minutes != null ? `${evt.elapsed_minutes}m` : '?';
1219
+ const limit = evt.limit_minutes != null ? `${evt.limit_minutes}m` : '?';
1220
+ repoLines.push(` - ${label}: ${evt.scope || '?'} scope | ${elapsed}/${limit} | action: ${evt.action || 'n/a'} | ${evt.timestamp || 'n/a'}`);
1221
+ }
1222
+ }
1021
1223
  if (repo.hook_summary) {
1022
1224
  repoLines.push(` Hook Activity: ${repo.hook_summary.total} total, ${repo.hook_summary.blocked} blocked`);
1023
1225
  }
@@ -1117,7 +1319,8 @@ export function formatGovernanceReportMarkdown(report) {
1117
1319
  const cost = t.cost_usd != null ? formatUsd(t.cost_usd) : 'n/a';
1118
1320
  const phase = t.phase_transition ? `${t.phase || '?'} → ${t.phase_transition}` : (t.phase || '?');
1119
1321
  const summary = (t.summary || '(no summary)').replace(/\|/g, '\\|');
1120
- lines.push(`| ${i + 1} | ${t.role} | ${phase} | ${summary} | ${t.files_changed_count} | ${cost} | ${t.accepted_at || 'n/a'} |`);
1322
+ const siblingNote = Array.isArray(t.sibling_attributed_files) ? ` (${t.sibling_attributed_files.length} sibling)` : '';
1323
+ lines.push(`| ${i + 1} | ${t.role} | ${phase} | ${summary} | ${t.files_changed_count}${siblingNote} | ${cost} | ${t.accepted_at || 'n/a'} |`);
1121
1324
  }
1122
1325
  }
1123
1326
 
@@ -1135,6 +1338,45 @@ export function formatGovernanceReportMarkdown(report) {
1135
1338
  }
1136
1339
  }
1137
1340
 
1341
+ if (run.gate_failures && run.gate_failures.length > 0) {
1342
+ lines.push('', '## Gate Failures', '');
1343
+ for (const failure of run.gate_failures) {
1344
+ const request = failure.gate_type === 'run_completion'
1345
+ ? 'run completion'
1346
+ : `${failure.from_phase || failure.phase || '?'} → ${failure.to_phase || '?'}`;
1347
+ lines.push(`- \`${failure.gate_id || 'unknown'}\` (${failure.gate_type || 'unknown'}) at \`${failure.failed_at || 'n/a'}\` via ${failure.queued_request ? 'queued drain' : 'direct'} request: ${request}`);
1348
+ for (const reason of failure.reasons || []) {
1349
+ lines.push(` - ${reason}`);
1350
+ }
1351
+ }
1352
+ }
1353
+
1354
+ if (run.approval_policy_events && run.approval_policy_events.length > 0) {
1355
+ lines.push('', '## Approval Policy', '');
1356
+ for (const evt of run.approval_policy_events) {
1357
+ const transition = evt.gate_type === 'run_completion'
1358
+ ? 'run completion'
1359
+ : `${evt.from_phase || '?'} → ${evt.to_phase || '?'}`;
1360
+ const rule = evt.matched_rule ? ` — rule: \`${typeof evt.matched_rule === 'object' ? JSON.stringify(evt.matched_rule) : evt.matched_rule}\`` : '';
1361
+ lines.push(`- **${evt.action || 'unknown'}** (${evt.gate_type || 'unknown'}) ${transition}${rule} at \`${evt.timestamp || 'n/a'}\``);
1362
+ if (evt.reason) lines.push(` - ${evt.reason}`);
1363
+ }
1364
+ }
1365
+
1366
+ if (run.timeout_events && run.timeout_events.length > 0) {
1367
+ lines.push('', '## Timeout Events', '');
1368
+ for (const evt of run.timeout_events) {
1369
+ const label = evt.type === 'timeout_warning' ? 'Warning'
1370
+ : evt.type === 'timeout_skip' ? 'Skip'
1371
+ : evt.type === 'timeout_skip_failed' ? 'Skip Failed'
1372
+ : 'Escalation';
1373
+ const elapsed = evt.elapsed_minutes != null ? `${evt.elapsed_minutes}m` : '?';
1374
+ const limit = evt.limit_minutes != null ? `${evt.limit_minutes}m` : '?';
1375
+ const exceeded = evt.exceeded_by_minutes != null ? ` (+${evt.exceeded_by_minutes}m)` : '';
1376
+ lines.push(`- **${label}** (\`${evt.scope || '?'}\` scope) — ${elapsed}/${limit}${exceeded}, action: \`${evt.action || 'n/a'}\`, phase: \`${evt.phase || 'n/a'}\` at \`${evt.timestamp || 'n/a'}\``);
1377
+ }
1378
+ }
1379
+
1138
1380
  if (run.intake_links && run.intake_links.length > 0) {
1139
1381
  lines.push('', '## Intake Linkage', '');
1140
1382
  for (const intake of run.intake_links) {
@@ -1188,7 +1430,19 @@ export function formatGovernanceReportMarkdown(report) {
1188
1430
  return lines.join('\n');
1189
1431
  }
1190
1432
 
1191
- const { coordinator, run, artifacts, repos, coordinator_timeline, barrier_summary, barrier_ledger_timeline, decision_digest, recovery_report: coordRecoveryReport } = report.subject;
1433
+ const {
1434
+ coordinator,
1435
+ run,
1436
+ artifacts,
1437
+ repos,
1438
+ coordinator_timeline,
1439
+ barrier_summary,
1440
+ barrier_ledger_timeline,
1441
+ decision_digest,
1442
+ approval_policy_events,
1443
+ timeout_events,
1444
+ recovery_report: coordRecoveryReport,
1445
+ } = report.subject;
1192
1446
  const mdLines = [
1193
1447
  '# AgentXchain Governance Report',
1194
1448
  '',
@@ -1272,6 +1526,32 @@ export function formatGovernanceReportMarkdown(report) {
1272
1526
  }
1273
1527
  }
1274
1528
 
1529
+ if (approval_policy_events && approval_policy_events.length > 0) {
1530
+ mdLines.push('', '## Approval Policy', '');
1531
+ for (const evt of approval_policy_events) {
1532
+ const transition = evt.gate_type === 'run_completion'
1533
+ ? 'run completion'
1534
+ : `${evt.from_phase || '?'} → ${evt.to_phase || '?'}`;
1535
+ const rule = evt.matched_rule ? ` — rule: \`${typeof evt.matched_rule === 'object' ? JSON.stringify(evt.matched_rule) : evt.matched_rule}\`` : '';
1536
+ mdLines.push(`- **${evt.action || 'unknown'}** (${evt.gate_type || 'unknown'}) ${transition}${rule} at \`${evt.timestamp || 'n/a'}\``);
1537
+ if (evt.reason) mdLines.push(` - ${evt.reason}`);
1538
+ }
1539
+ }
1540
+
1541
+ if (timeout_events && timeout_events.length > 0) {
1542
+ mdLines.push('', '## Timeout Events', '');
1543
+ for (const evt of timeout_events) {
1544
+ const label = evt.type === 'timeout_warning' ? 'Warning'
1545
+ : evt.type === 'timeout_skip' ? 'Skip'
1546
+ : evt.type === 'timeout_skip_failed' ? 'Skip Failed'
1547
+ : 'Escalation';
1548
+ const elapsed = evt.elapsed_minutes != null ? `${evt.elapsed_minutes}m` : '?';
1549
+ const limit = evt.limit_minutes != null ? `${evt.limit_minutes}m` : '?';
1550
+ const exceeded = evt.exceeded_by_minutes != null ? ` (+${evt.exceeded_by_minutes}m)` : '';
1551
+ mdLines.push(`- **${label}** (\`${evt.scope || '?'}\` scope) — ${elapsed}/${limit}${exceeded}, action: \`${evt.action || 'n/a'}\`, phase: \`${evt.phase || 'n/a'}\` at \`${evt.timestamp || 'n/a'}\``);
1552
+ }
1553
+ }
1554
+
1275
1555
  if (coordRecoveryReport) {
1276
1556
  mdLines.push('', '## Recovery Report', '');
1277
1557
  mdLines.push(`- **Trigger:** ${coordRecoveryReport.trigger || 'n/a'}`);
@@ -1297,7 +1577,8 @@ export function formatGovernanceReportMarkdown(report) {
1297
1577
  const cost = t.cost_usd != null ? formatUsd(t.cost_usd) : 'n/a';
1298
1578
  const phase = t.phase_transition ? `${t.phase || '?'} → ${t.phase_transition}` : (t.phase || '?');
1299
1579
  const summary = (t.summary || '(no summary)').replace(/\|/g, '\\|');
1300
- repoLines.push(`| ${i + 1} | ${t.role} | ${phase} | ${summary} | ${t.files_changed_count} | ${cost} | ${t.accepted_at || 'n/a'} |`);
1580
+ const siblingNote = Array.isArray(t.sibling_attributed_files) ? ` (${t.sibling_attributed_files.length} sibling)` : '';
1581
+ repoLines.push(`| ${i + 1} | ${t.role} | ${phase} | ${summary} | ${t.files_changed_count}${siblingNote} | ${cost} | ${t.accepted_at || 'n/a'} |`);
1301
1582
  }
1302
1583
  }
1303
1584
  if (repo.decisions && repo.decisions.length > 0) {
@@ -1312,6 +1593,42 @@ export function formatGovernanceReportMarkdown(report) {
1312
1593
  repoLines.push(`- \`${gate.gate_id}\`: \`${gate.status}\``);
1313
1594
  }
1314
1595
  }
1596
+ if (repo.gate_failures && repo.gate_failures.length > 0) {
1597
+ repoLines.push('', '#### Gate Failures', '');
1598
+ for (const failure of repo.gate_failures) {
1599
+ const request = failure.gate_type === 'run_completion'
1600
+ ? 'run completion'
1601
+ : `${failure.from_phase || failure.phase || '?'} → ${failure.to_phase || '?'}`;
1602
+ repoLines.push(`- \`${failure.gate_id || 'unknown'}\` (${failure.gate_type || 'unknown'}) at \`${failure.failed_at || 'n/a'}\` via ${failure.queued_request ? 'queued drain' : 'direct'} request: ${request}`);
1603
+ for (const reason of failure.reasons || []) {
1604
+ repoLines.push(` - ${reason}`);
1605
+ }
1606
+ }
1607
+ }
1608
+ if (repo.approval_policy_events && repo.approval_policy_events.length > 0) {
1609
+ repoLines.push('', '#### Approval Policy', '');
1610
+ for (const evt of repo.approval_policy_events) {
1611
+ const transition = evt.gate_type === 'run_completion'
1612
+ ? 'run completion'
1613
+ : `${evt.from_phase || '?'} → ${evt.to_phase || '?'}`;
1614
+ const rule = evt.matched_rule ? ` — rule: \`${typeof evt.matched_rule === 'object' ? JSON.stringify(evt.matched_rule) : evt.matched_rule}\`` : '';
1615
+ repoLines.push(`- **${evt.action || 'unknown'}** (${evt.gate_type || 'unknown'}) ${transition}${rule} at \`${evt.timestamp || 'n/a'}\``);
1616
+ if (evt.reason) repoLines.push(` - ${evt.reason}`);
1617
+ }
1618
+ }
1619
+ if (repo.timeout_events && repo.timeout_events.length > 0) {
1620
+ repoLines.push('', '#### Timeout Events', '');
1621
+ for (const evt of repo.timeout_events) {
1622
+ const label = evt.type === 'timeout_warning' ? 'Warning'
1623
+ : evt.type === 'timeout_skip' ? 'Skip'
1624
+ : evt.type === 'timeout_skip_failed' ? 'Skip Failed'
1625
+ : 'Escalation';
1626
+ const elapsed = evt.elapsed_minutes != null ? `${evt.elapsed_minutes}m` : '?';
1627
+ const limit = evt.limit_minutes != null ? `${evt.limit_minutes}m` : '?';
1628
+ const exceeded = evt.exceeded_by_minutes != null ? ` (+${evt.exceeded_by_minutes}m)` : '';
1629
+ repoLines.push(`- **${label}** (\`${evt.scope || '?'}\` scope) — ${elapsed}/${limit}${exceeded}, action: \`${evt.action || 'n/a'}\`, phase: \`${evt.phase || 'n/a'}\` at \`${evt.timestamp || 'n/a'}\``);
1630
+ }
1631
+ }
1315
1632
  if (repo.hook_summary) {
1316
1633
  repoLines.push('', '#### Hook Activity', '', `- Total: ${repo.hook_summary.total}`, `- Blocked: ${repo.hook_summary.blocked}`);
1317
1634
  const eventList = Object.entries(repo.hook_summary.events).sort(([a], [b]) => a.localeCompare(b, 'en')).map(([e, c]) => `${e}(${c})`).join(', ');
package/src/lib/schema.js CHANGED
@@ -74,6 +74,12 @@ export function validateGovernedStateSchema(data) {
74
74
  errors.push(`status must be one of: ${VALID_RUN_STATUSES.join(', ')}`);
75
75
  }
76
76
  if (typeof data.phase !== 'string' || !data.phase.trim()) errors.push('phase must be a non-empty string');
77
+ if ('created_at' in data && data.created_at !== null && (typeof data.created_at !== 'string' || !data.created_at.trim())) {
78
+ errors.push('created_at must be a non-empty string or null');
79
+ }
80
+ if ('phase_entered_at' in data && data.phase_entered_at !== null && (typeof data.phase_entered_at !== 'string' || !data.phase_entered_at.trim())) {
81
+ errors.push('phase_entered_at must be a non-empty string or null');
82
+ }
77
83
 
78
84
  if (isV1_1) {
79
85
  if (hasLegacyCurrentTurn) {
@@ -96,6 +102,47 @@ export function validateGovernedStateSchema(data) {
96
102
  if ('phase_gate_status' in data && data.phase_gate_status !== null && typeof data.phase_gate_status !== 'object') {
97
103
  errors.push('phase_gate_status must be an object');
98
104
  }
105
+ if ('last_gate_failure' in data && data.last_gate_failure !== null) {
106
+ if (typeof data.last_gate_failure !== 'object' || Array.isArray(data.last_gate_failure)) {
107
+ errors.push('last_gate_failure must be an object or null');
108
+ } else {
109
+ const failure = data.last_gate_failure;
110
+ const validGateTypes = ['phase_transition', 'run_completion'];
111
+ if (typeof failure.gate_type !== 'string' || !validGateTypes.includes(failure.gate_type)) {
112
+ errors.push(`last_gate_failure.gate_type must be one of: ${validGateTypes.join(', ')}`);
113
+ }
114
+ if (failure.gate_id !== null && failure.gate_id !== undefined && typeof failure.gate_id !== 'string') {
115
+ errors.push('last_gate_failure.gate_id must be a string or null');
116
+ }
117
+ if (typeof failure.phase !== 'string' || !failure.phase.trim()) {
118
+ errors.push('last_gate_failure.phase must be a non-empty string');
119
+ }
120
+ if (failure.from_phase !== null && failure.from_phase !== undefined && typeof failure.from_phase !== 'string') {
121
+ errors.push('last_gate_failure.from_phase must be a string or null');
122
+ }
123
+ if (failure.to_phase !== null && failure.to_phase !== undefined && typeof failure.to_phase !== 'string') {
124
+ errors.push('last_gate_failure.to_phase must be a string or null');
125
+ }
126
+ if (failure.requested_by_turn !== null && failure.requested_by_turn !== undefined && typeof failure.requested_by_turn !== 'string') {
127
+ errors.push('last_gate_failure.requested_by_turn must be a string or null');
128
+ }
129
+ if (typeof failure.failed_at !== 'string' || !failure.failed_at.trim()) {
130
+ errors.push('last_gate_failure.failed_at must be a non-empty string');
131
+ }
132
+ if (typeof failure.queued_request !== 'boolean') {
133
+ errors.push('last_gate_failure.queued_request must be a boolean');
134
+ }
135
+ if (!Array.isArray(failure.reasons) || failure.reasons.some((reason) => typeof reason !== 'string')) {
136
+ errors.push('last_gate_failure.reasons must be an array of strings');
137
+ }
138
+ if (!Array.isArray(failure.missing_files) || failure.missing_files.some((file) => typeof file !== 'string')) {
139
+ errors.push('last_gate_failure.missing_files must be an array of strings');
140
+ }
141
+ if (typeof failure.missing_verification !== 'boolean') {
142
+ errors.push('last_gate_failure.missing_verification must be a boolean');
143
+ }
144
+ }
145
+ }
99
146
  if ('budget_status' in data && data.budget_status !== null && typeof data.budget_status !== 'object') {
100
147
  errors.push('budget_status must be an object');
101
148
  }