agentic-orchestrator 0.1.25 → 0.1.26
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/agentic/orchestrator/defaults/policy.defaults.yaml +1 -0
- package/agentic/orchestrator/policy.yaml +1 -0
- package/agentic/orchestrator/schemas/policy.schema.json +4 -0
- package/agentic/orchestrator/schemas/policy.user.schema.json +4 -0
- package/apps/control-plane/src/application/services/activity-monitor-service.ts +42 -0
- package/apps/control-plane/src/application/services/collision-queue-service.ts +48 -0
- package/apps/control-plane/src/application/services/cost-tracking-service.ts +80 -0
- package/apps/control-plane/src/application/services/feature-deletion-service.ts +57 -0
- package/apps/control-plane/src/application/services/feature-lifecycle-service.ts +65 -0
- package/apps/control-plane/src/application/services/feature-state-service.ts +87 -0
- package/apps/control-plane/src/application/services/gate-selection-service.ts +28 -0
- package/apps/control-plane/src/application/services/gate-service.ts +148 -0
- package/apps/control-plane/src/application/services/issue-tracker-service.ts +28 -0
- package/apps/control-plane/src/application/services/lock-service.ts +88 -0
- package/apps/control-plane/src/application/services/merge-service.ts +63 -0
- package/apps/control-plane/src/application/services/notifier-service.ts +70 -0
- package/apps/control-plane/src/application/services/patch-service.ts +95 -0
- package/apps/control-plane/src/application/services/performance-analytics-service.ts +90 -0
- package/apps/control-plane/src/application/services/plan-service.ts +184 -0
- package/apps/control-plane/src/application/services/pr-monitor-service.ts +58 -0
- package/apps/control-plane/src/application/services/qa-index-service.ts +68 -0
- package/apps/control-plane/src/application/services/reactions-service.ts +77 -0
- package/apps/control-plane/src/application/services/reporting-service.ts +79 -0
- package/apps/control-plane/src/application/services/run-lease-service.ts +79 -0
- package/apps/control-plane/src/application/tools/tool-router.ts +76 -0
- package/apps/control-plane/src/mcp/tool-runtime.ts +4 -0
- package/apps/control-plane/src/providers/providers.ts +96 -0
- package/apps/control-plane/src/providers/worker-provider-factory.ts +4 -0
- package/apps/control-plane/src/supervisor/build-wave-executor.ts +4 -0
- package/apps/control-plane/src/supervisor/planning-wave-executor.ts +4 -0
- package/apps/control-plane/src/supervisor/qa-wave-executor.ts +4 -0
- package/apps/control-plane/src/supervisor/run-coordinator.ts +4 -0
- package/apps/control-plane/src/supervisor/runtime.ts +4 -0
- package/apps/control-plane/src/supervisor/session-orchestrator.ts +4 -0
- package/apps/control-plane/test/dashboard-api.integration.spec.ts +310 -1
- package/apps/control-plane/test/dashboard-client.spec.ts +136 -1
- package/apps/control-plane/test/dashboard-live-feed.spec.ts +153 -0
- package/apps/control-plane/test/dashboard-ui-utils.spec.ts +132 -0
- package/apps/control-plane/test/mcp.spec.ts +96 -0
- package/apps/control-plane/test/tool-runtime.spec.ts +68 -0
- package/config/agentic/orchestrator/policy.yaml +1 -0
- package/dist/apps/control-plane/application/services/activity-monitor-service.d.ts +42 -0
- package/dist/apps/control-plane/application/services/activity-monitor-service.js +42 -0
- package/dist/apps/control-plane/application/services/activity-monitor-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/collision-queue-service.d.ts +48 -0
- package/dist/apps/control-plane/application/services/collision-queue-service.js +48 -0
- package/dist/apps/control-plane/application/services/collision-queue-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/cost-tracking-service.d.ts +79 -0
- package/dist/apps/control-plane/application/services/cost-tracking-service.js +76 -0
- package/dist/apps/control-plane/application/services/cost-tracking-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/feature-deletion-service.d.ts +57 -0
- package/dist/apps/control-plane/application/services/feature-deletion-service.js +57 -0
- package/dist/apps/control-plane/application/services/feature-deletion-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/feature-lifecycle-service.d.ts +64 -0
- package/dist/apps/control-plane/application/services/feature-lifecycle-service.js +61 -0
- package/dist/apps/control-plane/application/services/feature-lifecycle-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/feature-state-service.d.ts +86 -0
- package/dist/apps/control-plane/application/services/feature-state-service.js +83 -0
- package/dist/apps/control-plane/application/services/feature-state-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/gate-selection-service.d.ts +28 -0
- package/dist/apps/control-plane/application/services/gate-selection-service.js +28 -0
- package/dist/apps/control-plane/application/services/gate-selection-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/gate-service.d.ts +148 -0
- package/dist/apps/control-plane/application/services/gate-service.js +120 -0
- package/dist/apps/control-plane/application/services/gate-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/issue-tracker-service.d.ts +28 -0
- package/dist/apps/control-plane/application/services/issue-tracker-service.js +28 -0
- package/dist/apps/control-plane/application/services/issue-tracker-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/lock-service.d.ts +88 -0
- package/dist/apps/control-plane/application/services/lock-service.js +64 -0
- package/dist/apps/control-plane/application/services/lock-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/merge-service.d.ts +62 -0
- package/dist/apps/control-plane/application/services/merge-service.js +59 -0
- package/dist/apps/control-plane/application/services/merge-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/notifier-service.d.ts +70 -0
- package/dist/apps/control-plane/application/services/notifier-service.js +70 -0
- package/dist/apps/control-plane/application/services/notifier-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/patch-service.d.ts +94 -0
- package/dist/apps/control-plane/application/services/patch-service.js +91 -0
- package/dist/apps/control-plane/application/services/patch-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/performance-analytics-service.d.ts +89 -0
- package/dist/apps/control-plane/application/services/performance-analytics-service.js +86 -0
- package/dist/apps/control-plane/application/services/performance-analytics-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/plan-service.d.ts +287 -0
- package/dist/apps/control-plane/application/services/plan-service.js +207 -0
- package/dist/apps/control-plane/application/services/plan-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/pr-monitor-service.d.ts +58 -0
- package/dist/apps/control-plane/application/services/pr-monitor-service.js +58 -0
- package/dist/apps/control-plane/application/services/pr-monitor-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/qa-index-service.d.ts +67 -0
- package/dist/apps/control-plane/application/services/qa-index-service.js +64 -0
- package/dist/apps/control-plane/application/services/qa-index-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/reactions-service.d.ts +76 -0
- package/dist/apps/control-plane/application/services/reactions-service.js +73 -0
- package/dist/apps/control-plane/application/services/reactions-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/reporting-service.d.ts +78 -0
- package/dist/apps/control-plane/application/services/reporting-service.js +75 -0
- package/dist/apps/control-plane/application/services/reporting-service.js.map +1 -1
- package/dist/apps/control-plane/application/services/run-lease-service.d.ts +79 -0
- package/dist/apps/control-plane/application/services/run-lease-service.js +79 -0
- package/dist/apps/control-plane/application/services/run-lease-service.js.map +1 -1
- package/dist/apps/control-plane/application/tools/tool-router.d.ts +76 -0
- package/dist/apps/control-plane/application/tools/tool-router.js +62 -0
- package/dist/apps/control-plane/application/tools/tool-router.js.map +1 -1
- package/dist/apps/control-plane/mcp/tool-runtime.d.ts +3 -0
- package/dist/apps/control-plane/mcp/tool-runtime.js +3 -0
- package/dist/apps/control-plane/mcp/tool-runtime.js.map +1 -1
- package/dist/apps/control-plane/providers/providers.d.ts +88 -0
- package/dist/apps/control-plane/providers/providers.js.map +1 -1
- package/dist/apps/control-plane/providers/worker-provider-factory.d.ts +3 -0
- package/dist/apps/control-plane/providers/worker-provider-factory.js.map +1 -1
- package/dist/apps/control-plane/supervisor/build-wave-executor.d.ts +3 -0
- package/dist/apps/control-plane/supervisor/build-wave-executor.js +3 -0
- package/dist/apps/control-plane/supervisor/build-wave-executor.js.map +1 -1
- package/dist/apps/control-plane/supervisor/planning-wave-executor.d.ts +3 -0
- package/dist/apps/control-plane/supervisor/planning-wave-executor.js +3 -0
- package/dist/apps/control-plane/supervisor/planning-wave-executor.js.map +1 -1
- package/dist/apps/control-plane/supervisor/qa-wave-executor.d.ts +3 -0
- package/dist/apps/control-plane/supervisor/qa-wave-executor.js +3 -0
- package/dist/apps/control-plane/supervisor/qa-wave-executor.js.map +1 -1
- package/dist/apps/control-plane/supervisor/run-coordinator.d.ts +3 -0
- package/dist/apps/control-plane/supervisor/run-coordinator.js +3 -0
- package/dist/apps/control-plane/supervisor/run-coordinator.js.map +1 -1
- package/dist/apps/control-plane/supervisor/runtime.d.ts +3 -0
- package/dist/apps/control-plane/supervisor/runtime.js +3 -0
- package/dist/apps/control-plane/supervisor/runtime.js.map +1 -1
- package/dist/apps/control-plane/supervisor/session-orchestrator.d.ts +3 -0
- package/dist/apps/control-plane/supervisor/session-orchestrator.js +3 -0
- package/dist/apps/control-plane/supervisor/session-orchestrator.js.map +1 -1
- package/package.json +1 -1
- package/packages/web-dashboard/src/app/analytics/page.tsx +451 -0
- package/packages/web-dashboard/src/app/api/analytics/route.ts +62 -0
- package/packages/web-dashboard/src/app/api/collisions/route.ts +63 -0
- package/packages/web-dashboard/src/app/api/features/[id]/cost/route.ts +29 -0
- package/packages/web-dashboard/src/app/api/features/[id]/review-brief/route.ts +31 -0
- package/packages/web-dashboard/src/app/api/features/[id]/route.ts +5 -8
- package/packages/web-dashboard/src/app/api/features/[id]/test-index/route.ts +31 -0
- package/packages/web-dashboard/src/app/api/flaky/route.ts +20 -0
- package/packages/web-dashboard/src/app/api/policy/budget/route.ts +31 -0
- package/packages/web-dashboard/src/app/api/run/route.ts +162 -0
- package/packages/web-dashboard/src/app/api/status/route.ts +2 -5
- package/packages/web-dashboard/src/app/feature/[id]/page.tsx +112 -0
- package/packages/web-dashboard/src/app/page.tsx +379 -77
- package/packages/web-dashboard/src/components/agent-pipeline-stepper.tsx +100 -0
- package/packages/web-dashboard/src/components/dependency-chains.tsx +58 -0
- package/packages/web-dashboard/src/components/detail-panel.tsx +215 -5
- package/packages/web-dashboard/src/components/feature-card.tsx +5 -0
- package/packages/web-dashboard/src/components/feature-cost-panel.tsx +66 -0
- package/packages/web-dashboard/src/components/feature-list-view.tsx +336 -0
- package/packages/web-dashboard/src/components/gate-step-drilldown.tsx +92 -0
- package/packages/web-dashboard/src/components/kanban-board.tsx +3 -0
- package/packages/web-dashboard/src/components/live-event-feed.tsx +60 -0
- package/packages/web-dashboard/src/components/lock-resource-map.tsx +73 -0
- package/packages/web-dashboard/src/components/plan-revision-timeline.tsx +55 -0
- package/packages/web-dashboard/src/components/plan-risk-annotations.tsx +63 -0
- package/packages/web-dashboard/src/components/plan-scope-tree.tsx +285 -0
- package/packages/web-dashboard/src/components/qa-coverage-map.tsx +174 -0
- package/packages/web-dashboard/src/components/quick-launch-panel.tsx +70 -0
- package/packages/web-dashboard/src/components/review-brief-panel.tsx +75 -0
- package/packages/web-dashboard/src/components/run-health-panel.tsx +85 -0
- package/packages/web-dashboard/src/components/summary-bar.tsx +59 -2
- package/packages/web-dashboard/src/components/verification-signals.tsx +167 -0
- package/packages/web-dashboard/src/lib/analytics-utils.ts +3 -0
- package/packages/web-dashboard/src/lib/aop-client.ts +510 -11
- package/packages/web-dashboard/src/lib/api-envelope.ts +48 -0
- package/packages/web-dashboard/src/lib/authz-adapter.ts +33 -0
- package/packages/web-dashboard/src/lib/dashboard-utils.ts +304 -0
- package/packages/web-dashboard/src/lib/live-feed.ts +218 -0
- package/packages/web-dashboard/src/lib/policy-reader.ts +22 -0
- package/packages/web-dashboard/src/lib/types.ts +114 -0
- package/packages/web-dashboard/src/styles/dashboard.module.css +341 -1
- package/spec-files/{outstanding → completed}/agentic_orchestrator_dashboard_advanced_ux_spec.md +1 -1
- package/spec-files/outstanding/agentic_orchestrator_evidence_integrity_doctor_spec.md +561 -0
- package/spec-files/outstanding/agentic_orchestrator_kernel_simplification_spec.md +834 -0
- package/spec-files/outstanding/agentic_orchestrator_observability_integrity_diagnostics_spec.md +12 -5
- package/spec-files/outstanding/agentic_orchestrator_observability_replay_spec.md +442 -0
- package/spec-files/progress.md +174 -1
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { RuntimeSession } from '@/lib/types.js';
|
|
2
|
+
import styles from '@/styles/dashboard.module.css';
|
|
3
|
+
|
|
4
|
+
interface RunHealthPanelProps {
|
|
5
|
+
runtime: RuntimeSession | undefined;
|
|
6
|
+
nowMs: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function ageSeconds(iso: string | null | undefined, nowMs: number): number | null {
|
|
10
|
+
if (!iso) {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
const parsed = Date.parse(iso);
|
|
14
|
+
if (Number.isNaN(parsed)) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
return Math.max(0, Math.floor((nowMs - parsed) / 1000));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function formatSeconds(value: number | null): string {
|
|
21
|
+
if (value == null) {
|
|
22
|
+
return '-';
|
|
23
|
+
}
|
|
24
|
+
const minutes = Math.floor(value / 60);
|
|
25
|
+
const seconds = value % 60;
|
|
26
|
+
return `${minutes}m ${seconds}s`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function healthFromHeartbeat(heartbeatAgeSec: number | null): {
|
|
30
|
+
label: 'Healthy' | 'Degraded' | 'Critical';
|
|
31
|
+
className: string;
|
|
32
|
+
} {
|
|
33
|
+
if (heartbeatAgeSec == null) {
|
|
34
|
+
return { label: 'Critical', className: styles.badgeFail };
|
|
35
|
+
}
|
|
36
|
+
if (heartbeatAgeSec > 120) {
|
|
37
|
+
return { label: 'Critical', className: styles.badgeFail };
|
|
38
|
+
}
|
|
39
|
+
if (heartbeatAgeSec >= 30) {
|
|
40
|
+
return { label: 'Degraded', className: styles.badgePending };
|
|
41
|
+
}
|
|
42
|
+
return { label: 'Healthy', className: styles.badgePass };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function RunHealthPanel({ runtime, nowMs }: RunHealthPanelProps) {
|
|
46
|
+
if (!runtime) {
|
|
47
|
+
return (
|
|
48
|
+
<section className={styles.section}>
|
|
49
|
+
<h2 className={styles.sectionTitle}>Run Health</h2>
|
|
50
|
+
<p className={styles.metaText}>No active orchestrator run.</p>
|
|
51
|
+
</section>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const heartbeatAgeSec = ageSeconds(runtime.last_heartbeat_at ?? null, nowMs);
|
|
56
|
+
const startedAgeSec = ageSeconds(runtime.started_at ?? null, nowMs);
|
|
57
|
+
const leaseRemainingSec = (() => {
|
|
58
|
+
if (!runtime.lease_expires_at) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
const parsed = Date.parse(runtime.lease_expires_at);
|
|
62
|
+
if (Number.isNaN(parsed)) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
return Math.max(0, Math.floor((parsed - nowMs) / 1000));
|
|
66
|
+
})();
|
|
67
|
+
const health = healthFromHeartbeat(heartbeatAgeSec);
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<section className={styles.section}>
|
|
71
|
+
<h2 className={styles.sectionTitle}>Run Health</h2>
|
|
72
|
+
<div className={styles.badgeRow}>
|
|
73
|
+
<span className={`${styles.badge} ${health.className}`}>{health.label}</span>
|
|
74
|
+
<span className={`${styles.badge} ${styles.badgeNeutral}`}>
|
|
75
|
+
{runtime.provider ?? 'unknown'} / {runtime.model ?? 'unknown'}
|
|
76
|
+
</span>
|
|
77
|
+
</div>
|
|
78
|
+
<p className={styles.metaText}>Run: {runtime.run_id ?? '-'}</p>
|
|
79
|
+
<p className={styles.metaText}>Owner: {runtime.owner_instance_id ?? '-'}</p>
|
|
80
|
+
<p className={styles.metaText}>Uptime: {formatSeconds(startedAgeSec)}</p>
|
|
81
|
+
<p className={styles.metaText}>Heartbeat age: {formatSeconds(heartbeatAgeSec)}</p>
|
|
82
|
+
<p className={styles.metaText}>Lease remaining: {formatSeconds(leaseRemainingSec)}</p>
|
|
83
|
+
</section>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
@@ -1,13 +1,37 @@
|
|
|
1
|
+
import Link from 'next/link';
|
|
1
2
|
import styles from '@/styles/dashboard.module.css';
|
|
2
|
-
import
|
|
3
|
+
import {
|
|
4
|
+
sparklinePath,
|
|
5
|
+
sparklinePoints,
|
|
6
|
+
type DashboardSummaryCounts,
|
|
7
|
+
type QuickFilterKey,
|
|
8
|
+
} from '@/lib/dashboard-utils.js';
|
|
3
9
|
|
|
4
10
|
interface SummaryBarProps {
|
|
5
11
|
counts: DashboardSummaryCounts;
|
|
6
12
|
activeQuickFilter: QuickFilterKey | null;
|
|
7
13
|
onQuickFilter: (key: QuickFilterKey) => void;
|
|
14
|
+
mergeHistogram14d?: number[];
|
|
15
|
+
totalCostTodayUsd?: number;
|
|
16
|
+
analyticsHref: string;
|
|
8
17
|
}
|
|
9
18
|
|
|
10
|
-
|
|
19
|
+
function formatCost(value: number | undefined): string {
|
|
20
|
+
if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) {
|
|
21
|
+
return '$0.00';
|
|
22
|
+
}
|
|
23
|
+
return `$${value.toFixed(2)}`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function SummaryBar({
|
|
27
|
+
counts,
|
|
28
|
+
activeQuickFilter,
|
|
29
|
+
onQuickFilter,
|
|
30
|
+
mergeHistogram14d = [],
|
|
31
|
+
totalCostTodayUsd,
|
|
32
|
+
analyticsHref,
|
|
33
|
+
}: SummaryBarProps) {
|
|
34
|
+
const trendPoints = sparklinePoints(mergeHistogram14d, 180, 40);
|
|
11
35
|
const tiles: Array<{
|
|
12
36
|
key: QuickFilterKey;
|
|
13
37
|
label: string;
|
|
@@ -60,6 +84,39 @@ export function SummaryBar({ counts, activeQuickFilter, onQuickFilter }: Summary
|
|
|
60
84
|
</button>
|
|
61
85
|
);
|
|
62
86
|
})}
|
|
87
|
+
<Link href={analyticsHref} className={styles.summaryTile} aria-label="Open cost analytics">
|
|
88
|
+
<span className={styles.summaryLabel}>Cost Today</span>
|
|
89
|
+
<span className={styles.summaryValue}>{formatCost(totalCostTodayUsd)}</span>
|
|
90
|
+
<span className={styles.summaryLabel}>Server aggregate</span>
|
|
91
|
+
</Link>
|
|
92
|
+
<Link href={analyticsHref} className={styles.summaryTile} aria-label="Open analytics">
|
|
93
|
+
<span className={styles.summaryLabel}>Throughput (14d)</span>
|
|
94
|
+
{mergeHistogram14d.length > 0 ? (
|
|
95
|
+
<svg
|
|
96
|
+
viewBox="0 0 180 40"
|
|
97
|
+
width="100%"
|
|
98
|
+
height={40}
|
|
99
|
+
role="img"
|
|
100
|
+
aria-label="14 day merge trend sparkline"
|
|
101
|
+
>
|
|
102
|
+
<path d={sparklinePath(mergeHistogram14d, 180, 40)} fill="none" stroke="#7fc6ff" />
|
|
103
|
+
{trendPoints.map((point) => (
|
|
104
|
+
<circle
|
|
105
|
+
key={`trend-point-${point.index}`}
|
|
106
|
+
className={styles.sparklinePoint}
|
|
107
|
+
cx={point.x}
|
|
108
|
+
cy={point.y}
|
|
109
|
+
r={3}
|
|
110
|
+
>
|
|
111
|
+
<title>{`Day ${point.index + 1}: ${point.value} merges`}</title>
|
|
112
|
+
</circle>
|
|
113
|
+
))}
|
|
114
|
+
</svg>
|
|
115
|
+
) : (
|
|
116
|
+
<span className={styles.metaText}>No merge trend yet</span>
|
|
117
|
+
)}
|
|
118
|
+
<span className={styles.summaryLabel}>Open analytics</span>
|
|
119
|
+
</Link>
|
|
63
120
|
</section>
|
|
64
121
|
);
|
|
65
122
|
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import type { FeatureSummary } from '@/lib/types.js';
|
|
2
|
+
import styles from '@/styles/dashboard.module.css';
|
|
3
|
+
|
|
4
|
+
interface VerificationSignalsProps {
|
|
5
|
+
feature: FeatureSummary;
|
|
6
|
+
plan: Record<string, unknown> | null;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
type VerificationState = 'verified' | 'at_risk' | 'unverified';
|
|
10
|
+
|
|
11
|
+
interface SignalItem {
|
|
12
|
+
label: string;
|
|
13
|
+
status: VerificationState;
|
|
14
|
+
reason: string;
|
|
15
|
+
evidence_ref?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function parseCriteria(
|
|
19
|
+
plan: Record<string, unknown> | null,
|
|
20
|
+
): Array<string | Record<string, unknown>> {
|
|
21
|
+
if (!plan) {
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
const candidates = [plan.acceptance_criteria, plan.acceptance, plan.criteria, plan.outcomes];
|
|
25
|
+
for (const candidate of candidates) {
|
|
26
|
+
if (Array.isArray(candidate)) {
|
|
27
|
+
return candidate.filter(
|
|
28
|
+
(item): item is string | Record<string, unknown> =>
|
|
29
|
+
typeof item === 'string' || (item != null && typeof item === 'object'),
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function gateHasFailure(feature: FeatureSummary): boolean {
|
|
37
|
+
const gates = feature.gates;
|
|
38
|
+
if (!gates || typeof gates !== 'object') {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
return Object.values(gates).some((value) => String(value).toLowerCase() === 'fail');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function baseStatus(feature: FeatureSummary): VerificationState {
|
|
45
|
+
if (gateHasFailure(feature) || feature.phase === 'blocked') {
|
|
46
|
+
return 'at_risk';
|
|
47
|
+
}
|
|
48
|
+
if (feature.phase === 'ready_to_merge' || feature.phase === 'merged') {
|
|
49
|
+
return 'verified';
|
|
50
|
+
}
|
|
51
|
+
return 'unverified';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function parseSignalItem(
|
|
55
|
+
item: string | Record<string, unknown>,
|
|
56
|
+
fallbackState: VerificationState,
|
|
57
|
+
): SignalItem {
|
|
58
|
+
if (typeof item === 'string') {
|
|
59
|
+
return {
|
|
60
|
+
label: item,
|
|
61
|
+
status: fallbackState,
|
|
62
|
+
reason:
|
|
63
|
+
fallbackState === 'verified'
|
|
64
|
+
? 'Verified by current artifact evidence.'
|
|
65
|
+
: fallbackState === 'at_risk'
|
|
66
|
+
? 'Gate or status signals indicate risk.'
|
|
67
|
+
: 'Insufficient artifact evidence recorded yet.',
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const label =
|
|
72
|
+
typeof item.label === 'string'
|
|
73
|
+
? item.label
|
|
74
|
+
: typeof item.criterion === 'string'
|
|
75
|
+
? item.criterion
|
|
76
|
+
: typeof item.text === 'string'
|
|
77
|
+
? item.text
|
|
78
|
+
: 'Acceptance criterion';
|
|
79
|
+
const statusRaw = typeof item.status === 'string' ? item.status.toLowerCase() : '';
|
|
80
|
+
const status: VerificationState =
|
|
81
|
+
statusRaw === 'verified' || statusRaw === 'pass'
|
|
82
|
+
? 'verified'
|
|
83
|
+
: statusRaw === 'at_risk' || statusRaw === 'risk' || statusRaw === 'fail'
|
|
84
|
+
? 'at_risk'
|
|
85
|
+
: statusRaw === 'unverified' || statusRaw === 'unknown'
|
|
86
|
+
? 'unverified'
|
|
87
|
+
: fallbackState;
|
|
88
|
+
const reason =
|
|
89
|
+
typeof item.reason === 'string'
|
|
90
|
+
? item.reason
|
|
91
|
+
: status === 'verified'
|
|
92
|
+
? 'Verified by current artifact evidence.'
|
|
93
|
+
: status === 'at_risk'
|
|
94
|
+
? 'Gate or status signals indicate risk.'
|
|
95
|
+
: 'Insufficient artifact evidence recorded yet.';
|
|
96
|
+
const evidenceRef =
|
|
97
|
+
typeof item.evidence_ref === 'string'
|
|
98
|
+
? item.evidence_ref
|
|
99
|
+
: typeof item.evidence === 'string'
|
|
100
|
+
? item.evidence
|
|
101
|
+
: undefined;
|
|
102
|
+
return { label, status, reason, evidence_ref: evidenceRef };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function statusClass(status: VerificationState): string {
|
|
106
|
+
if (status === 'verified') {
|
|
107
|
+
return styles.badgePass;
|
|
108
|
+
}
|
|
109
|
+
if (status === 'at_risk') {
|
|
110
|
+
return styles.badgeFail;
|
|
111
|
+
}
|
|
112
|
+
return styles.badgeNeutral;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function statusLabel(status: VerificationState): string {
|
|
116
|
+
if (status === 'verified') {
|
|
117
|
+
return 'Verified';
|
|
118
|
+
}
|
|
119
|
+
if (status === 'at_risk') {
|
|
120
|
+
return 'At Risk';
|
|
121
|
+
}
|
|
122
|
+
return 'Unverified';
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function VerificationSignals({ feature, plan }: VerificationSignalsProps) {
|
|
126
|
+
const fallbackState = baseStatus(feature);
|
|
127
|
+
const criteria = parseCriteria(plan);
|
|
128
|
+
const signals =
|
|
129
|
+
criteria.length > 0
|
|
130
|
+
? criteria.map((item) => parseSignalItem(item, fallbackState))
|
|
131
|
+
: [
|
|
132
|
+
{
|
|
133
|
+
label: 'Acceptance criteria evidence',
|
|
134
|
+
status: fallbackState,
|
|
135
|
+
reason: 'No explicit acceptance criteria list found in plan.',
|
|
136
|
+
} satisfies SignalItem,
|
|
137
|
+
];
|
|
138
|
+
|
|
139
|
+
const verifiedCount = signals.filter((item) => item.status === 'verified').length;
|
|
140
|
+
const atRiskCount = signals.filter((item) => item.status === 'at_risk').length;
|
|
141
|
+
const unverifiedCount = signals.filter((item) => item.status === 'unverified').length;
|
|
142
|
+
|
|
143
|
+
return (
|
|
144
|
+
<details className={styles.section} open>
|
|
145
|
+
<summary className={styles.sectionSummary}>Verification Signals</summary>
|
|
146
|
+
<p className={styles.metaText}>
|
|
147
|
+
{verifiedCount} verified, {atRiskCount} at risk, {unverifiedCount} unverified.
|
|
148
|
+
</p>
|
|
149
|
+
<ul className={styles.planList}>
|
|
150
|
+
{signals.map((signal, index) => (
|
|
151
|
+
<li key={`${signal.label}-${index}`} className={styles.planListItem}>
|
|
152
|
+
<div className={styles.badgeRow}>
|
|
153
|
+
<span className={`${styles.badge} ${statusClass(signal.status)}`}>
|
|
154
|
+
{statusLabel(signal.status)}
|
|
155
|
+
</span>
|
|
156
|
+
<span>{signal.label}</span>
|
|
157
|
+
</div>
|
|
158
|
+
<p className={styles.metaText}>{signal.reason}</p>
|
|
159
|
+
{signal.evidence_ref ? (
|
|
160
|
+
<p className={styles.metaText}>Evidence: {signal.evidence_ref}</p>
|
|
161
|
+
) : null}
|
|
162
|
+
</li>
|
|
163
|
+
))}
|
|
164
|
+
</ul>
|
|
165
|
+
</details>
|
|
166
|
+
);
|
|
167
|
+
}
|