@shapeshift-labs/frontier-swarm 0.3.0 → 0.5.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/README.md +34 -5
- package/benchmarks/package-bench.mjs +107 -0
- package/dist/index.d.ts +1369 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1741 -23
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -30,6 +30,64 @@ export const FRONTIER_SWARM_MERGE_PLAN_KIND = 'frontier.swarm.merge-plan';
|
|
|
30
30
|
export const FRONTIER_SWARM_MERGE_PLAN_VERSION = 1;
|
|
31
31
|
export const FRONTIER_SWARM_MERGE_BUNDLE_KIND = 'frontier.swarm.merge-bundle';
|
|
32
32
|
export const FRONTIER_SWARM_MERGE_BUNDLE_VERSION = 1;
|
|
33
|
+
export const FRONTIER_SWARM_QUEUE_OVERLAY_KIND = 'frontier.swarm.queue-overlay';
|
|
34
|
+
export const FRONTIER_SWARM_QUEUE_OVERLAY_VERSION = 1;
|
|
35
|
+
export const FRONTIER_SWARM_MERGE_INDEX_KIND = 'frontier.swarm.merge-index';
|
|
36
|
+
export const FRONTIER_SWARM_MERGE_INDEX_VERSION = 1;
|
|
37
|
+
export const FRONTIER_SWARM_HOTSPOT_REPORT_KIND = 'frontier.swarm.hotspot-report';
|
|
38
|
+
export const FRONTIER_SWARM_HOTSPOT_REPORT_VERSION = 1;
|
|
39
|
+
export const FRONTIER_SWARM_REVIEWER_LANE_PLAN_KIND = 'frontier.swarm.reviewer-lane-plan';
|
|
40
|
+
export const FRONTIER_SWARM_REVIEWER_LANE_PLAN_VERSION = 1;
|
|
41
|
+
export const FRONTIER_SWARM_RUN_STORE_SHARDS_KIND = 'frontier.swarm.run-store-shards';
|
|
42
|
+
export const FRONTIER_SWARM_RUN_STORE_SHARDS_VERSION = 1;
|
|
43
|
+
export const FRONTIER_SWARM_MERGE_ADMISSION_KIND = 'frontier.swarm.merge-admission';
|
|
44
|
+
export const FRONTIER_SWARM_MERGE_ADMISSION_VERSION = 1;
|
|
45
|
+
export const FRONTIER_SWARM_CONTEXT_PACK_KIND = 'frontier.swarm.context-pack';
|
|
46
|
+
export const FRONTIER_SWARM_CONTEXT_PACK_VERSION = 1;
|
|
47
|
+
export const FRONTIER_SWARM_ORACLE_CORPUS_KIND = 'frontier.swarm.oracle-corpus';
|
|
48
|
+
export const FRONTIER_SWARM_ORACLE_CORPUS_VERSION = 1;
|
|
49
|
+
export const FRONTIER_SWARM_REPLAY_BUNDLE_KIND = 'frontier.swarm.replay-bundle';
|
|
50
|
+
export const FRONTIER_SWARM_REPLAY_BUNDLE_VERSION = 1;
|
|
51
|
+
export const FRONTIER_SWARM_PARITY_ORACLE_KIND = 'frontier.swarm.parity-oracle';
|
|
52
|
+
export const FRONTIER_SWARM_PARITY_ORACLE_VERSION = 1;
|
|
53
|
+
export const FRONTIER_SWARM_DIVERGENCE_REPORT_KIND = 'frontier.swarm.divergence-report';
|
|
54
|
+
export const FRONTIER_SWARM_DIVERGENCE_REPORT_VERSION = 1;
|
|
55
|
+
export const FRONTIER_SWARM_OBSERVABILITY_POINT_KIND = 'frontier.swarm.observability-point';
|
|
56
|
+
export const FRONTIER_SWARM_OBSERVABILITY_POINT_VERSION = 1;
|
|
57
|
+
export const FRONTIER_SWARM_WATCHPOINT_PLAN_KIND = 'frontier.swarm.watchpoint-plan';
|
|
58
|
+
export const FRONTIER_SWARM_WATCHPOINT_PLAN_VERSION = 1;
|
|
59
|
+
export const FRONTIER_SWARM_DEBUG_HANDOFF_KIND = 'frontier.swarm.debug-handoff';
|
|
60
|
+
export const FRONTIER_SWARM_DEBUG_HANDOFF_VERSION = 1;
|
|
61
|
+
export const FRONTIER_SWARM_INSTRUMENTATION_BUDGET_KIND = 'frontier.swarm.instrumentation-budget';
|
|
62
|
+
export const FRONTIER_SWARM_INSTRUMENTATION_BUDGET_VERSION = 1;
|
|
63
|
+
export const FRONTIER_SWARM_BOTTLENECK_REPORT_KIND = 'frontier.swarm.bottleneck-report';
|
|
64
|
+
export const FRONTIER_SWARM_BOTTLENECK_REPORT_VERSION = 1;
|
|
65
|
+
export const FRONTIER_SWARM_EVIDENCE_INDEX_KIND = 'frontier.swarm.evidence-index';
|
|
66
|
+
export const FRONTIER_SWARM_EVIDENCE_INDEX_VERSION = 1;
|
|
67
|
+
export const FRONTIER_SWARM_BLACKBOARD_KIND = 'frontier.swarm.blackboard';
|
|
68
|
+
export const FRONTIER_SWARM_BLACKBOARD_VERSION = 1;
|
|
69
|
+
export const FRONTIER_SWARM_REFERENCE_ORACLE_PLAN_KIND = 'frontier.swarm.reference-oracle-plan';
|
|
70
|
+
export const FRONTIER_SWARM_REFERENCE_ORACLE_PLAN_VERSION = 1;
|
|
71
|
+
export const FRONTIER_SWARM_REFERENCE_ORACLE_RESPONSE_KIND = 'frontier.swarm.reference-oracle-response';
|
|
72
|
+
export const FRONTIER_SWARM_REFERENCE_ORACLE_RESPONSE_VERSION = 1;
|
|
73
|
+
export const FRONTIER_SWARM_ARTIFACT_ROUTING_PLAN_KIND = 'frontier.swarm.artifact-routing-plan';
|
|
74
|
+
export const FRONTIER_SWARM_ARTIFACT_ROUTING_PLAN_VERSION = 1;
|
|
75
|
+
export const FRONTIER_SWARM_SCHEDULER_RECOMMENDATIONS_KIND = 'frontier.swarm.scheduler-recommendations';
|
|
76
|
+
export const FRONTIER_SWARM_SCHEDULER_RECOMMENDATIONS_VERSION = 1;
|
|
77
|
+
export const FRONTIER_SWARM_FIXTURE_CATALOG_KIND = 'frontier.swarm.fixture-catalog';
|
|
78
|
+
export const FRONTIER_SWARM_FIXTURE_CATALOG_VERSION = 1;
|
|
79
|
+
export const FRONTIER_SWARM_PROGRESS_MODEL_KIND = 'frontier.swarm.progress-model';
|
|
80
|
+
export const FRONTIER_SWARM_PROGRESS_MODEL_VERSION = 1;
|
|
81
|
+
export const FRONTIER_SWARM_AUTO_REVIEW_REPORT_KIND = 'frontier.swarm.auto-review-report';
|
|
82
|
+
export const FRONTIER_SWARM_AUTO_REVIEW_REPORT_VERSION = 1;
|
|
83
|
+
export const FRONTIER_SWARM_REBASE_REPORT_KIND = 'frontier.swarm.rebase-report';
|
|
84
|
+
export const FRONTIER_SWARM_REBASE_REPORT_VERSION = 1;
|
|
85
|
+
export const FRONTIER_SWARM_USAGE_GOVERNOR_KIND = 'frontier.swarm.usage-governor';
|
|
86
|
+
export const FRONTIER_SWARM_USAGE_GOVERNOR_VERSION = 1;
|
|
87
|
+
export const FRONTIER_SWARM_LANE_PLAYBOOK_KIND = 'frontier.swarm.lane-playbook';
|
|
88
|
+
export const FRONTIER_SWARM_LANE_PLAYBOOK_VERSION = 1;
|
|
89
|
+
export const FRONTIER_SWARM_PATCH_STACK_PLAN_KIND = 'frontier.swarm.patch-stack-plan';
|
|
90
|
+
export const FRONTIER_SWARM_PATCH_STACK_PLAN_VERSION = 1;
|
|
33
91
|
export const FRONTIER_SWARM_DEFAULT_CODEX_COMPUTE_ID = 'codex.gpt-5.5.xhigh';
|
|
34
92
|
export const FRONTIER_SWARM_DEFAULT_MODEL = 'gpt-5.5';
|
|
35
93
|
export const FRONTIER_SWARM_DEFAULT_REASONING_EFFORT = 'xhigh';
|
|
@@ -426,6 +484,1121 @@ export function createSwarmMergeBundle(input) {
|
|
|
426
484
|
...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
|
|
427
485
|
};
|
|
428
486
|
}
|
|
487
|
+
export function createSwarmQueueOverlay(input = {}) {
|
|
488
|
+
const generatedAt = input.generatedAt ?? Date.now();
|
|
489
|
+
const entries = [];
|
|
490
|
+
for (const bundle of input.bundles ?? []) {
|
|
491
|
+
const status = queueOverlayStatusFromBundle(bundle);
|
|
492
|
+
const queueItemIds = bundle.queueItemIds.length ? bundle.queueItemIds : [bundle.taskId ?? bundle.jobId];
|
|
493
|
+
for (const queueItemId of queueItemIds) {
|
|
494
|
+
entries.push({
|
|
495
|
+
queueItemId,
|
|
496
|
+
jobId: bundle.jobId,
|
|
497
|
+
status,
|
|
498
|
+
mergeReadiness: bundle.mergeReadiness,
|
|
499
|
+
disposition: bundle.disposition,
|
|
500
|
+
riskLevel: bundle.riskLevel,
|
|
501
|
+
...(bundle.patchPath ? { patchPath: bundle.patchPath } : {}),
|
|
502
|
+
evidencePaths: [...bundle.evidencePaths],
|
|
503
|
+
changedPaths: [...bundle.changedPaths],
|
|
504
|
+
changedRegions: [...bundle.changedRegions],
|
|
505
|
+
reasons: [...bundle.reasons],
|
|
506
|
+
generatedAt: bundle.generatedAt
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
for (const raw of input.results ?? []) {
|
|
511
|
+
const result = isSwarmJobResult(raw) ? cloneJsonValue(raw) : normalizeResult(raw);
|
|
512
|
+
const queueItemIds = result.queueItemIds.length ? result.queueItemIds : [result.jobId];
|
|
513
|
+
for (const queueItemId of queueItemIds) {
|
|
514
|
+
entries.push({
|
|
515
|
+
queueItemId,
|
|
516
|
+
jobId: result.jobId,
|
|
517
|
+
status: queueOverlayStatusFromResult(result),
|
|
518
|
+
mergeReadiness: result.mergeReadiness,
|
|
519
|
+
disposition: result.mergeDisposition,
|
|
520
|
+
riskLevel: result.riskLevel,
|
|
521
|
+
...(result.patchPath ? { patchPath: result.patchPath } : {}),
|
|
522
|
+
evidencePaths: [...result.evidencePaths],
|
|
523
|
+
changedPaths: [...result.changedPaths],
|
|
524
|
+
changedRegions: [...result.changedRegions],
|
|
525
|
+
reasons: result.error ? [result.error] : [],
|
|
526
|
+
generatedAt
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
const byQueueItemId = groupOverlayEntries(entries);
|
|
531
|
+
return {
|
|
532
|
+
kind: FRONTIER_SWARM_QUEUE_OVERLAY_KIND,
|
|
533
|
+
version: FRONTIER_SWARM_QUEUE_OVERLAY_VERSION,
|
|
534
|
+
id: input.id ?? 'swarm-queue-overlay:' + stableHash([input.runId, entries, generatedAt]),
|
|
535
|
+
...(input.runId ? { runId: input.runId } : {}),
|
|
536
|
+
generatedAt,
|
|
537
|
+
entries,
|
|
538
|
+
byQueueItemId,
|
|
539
|
+
summary: {
|
|
540
|
+
entryCount: entries.length,
|
|
541
|
+
queueItemCount: Object.keys(byQueueItemId).length,
|
|
542
|
+
readyToApplyCount: entries.filter((entry) => entry.status === 'ready-to-apply').length,
|
|
543
|
+
needsHumanPortCount: entries.filter((entry) => entry.status === 'needs-human-port').length,
|
|
544
|
+
failedEvidenceCount: entries.filter((entry) => entry.status === 'failed-evidence').length,
|
|
545
|
+
staleAgainstHeadCount: entries.filter((entry) => entry.status === 'stale-against-head').length,
|
|
546
|
+
discoveryOnlyCount: entries.filter((entry) => entry.status === 'discovery-only').length
|
|
547
|
+
},
|
|
548
|
+
...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
export function deriveSwarmQueueStatus(input) {
|
|
552
|
+
const generatedAt = input.generatedAt ?? Date.now();
|
|
553
|
+
const latestByQueueItem = new Map();
|
|
554
|
+
for (const overlay of input.overlays ?? []) {
|
|
555
|
+
for (const entry of overlay.entries) {
|
|
556
|
+
const existing = latestByQueueItem.get(entry.queueItemId);
|
|
557
|
+
if (!existing || entry.generatedAt >= existing.generatedAt)
|
|
558
|
+
latestByQueueItem.set(entry.queueItemId, entry);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
const jobs = input.snapshot.jobs.map((job) => {
|
|
562
|
+
const overlay = latestByQueueItem.get(job.taskId ?? job.jobId) ?? latestByQueueItem.get(job.jobId);
|
|
563
|
+
if (!overlay)
|
|
564
|
+
return cloneJsonValue(job);
|
|
565
|
+
return {
|
|
566
|
+
...cloneJsonValue(job),
|
|
567
|
+
status: queueJobStatusFromOverlay(overlay),
|
|
568
|
+
lastError: overlay.status === 'failed-evidence' || overlay.status === 'stale-against-head' ? overlay.reasons.join(', ') : job.lastError,
|
|
569
|
+
metadata: toJsonObject({
|
|
570
|
+
...(job.metadata ?? {}),
|
|
571
|
+
overlayStatus: overlay.status,
|
|
572
|
+
mergeDisposition: overlay.disposition,
|
|
573
|
+
mergeReadiness: overlay.mergeReadiness,
|
|
574
|
+
evidencePaths: overlay.evidencePaths
|
|
575
|
+
})
|
|
576
|
+
};
|
|
577
|
+
});
|
|
578
|
+
const byStatus = groupIds(jobs, (job) => job.status);
|
|
579
|
+
return {
|
|
580
|
+
generatedAt,
|
|
581
|
+
jobs,
|
|
582
|
+
byStatus,
|
|
583
|
+
summary: {
|
|
584
|
+
jobCount: jobs.length,
|
|
585
|
+
leaseCount: input.snapshot.leases.length,
|
|
586
|
+
readyCount: byStatus.ready?.length ?? 0,
|
|
587
|
+
leasedCount: byStatus.leased?.length ?? 0,
|
|
588
|
+
completedCount: byStatus.completed?.length ?? 0,
|
|
589
|
+
failedCount: byStatus.failed?.length ?? 0,
|
|
590
|
+
deadLetterCount: byStatus['dead-letter']?.length ?? 0
|
|
591
|
+
}
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
export function createSwarmMergeIndex(input) {
|
|
595
|
+
const generatedAt = input.generatedAt ?? Date.now();
|
|
596
|
+
const entries = input.bundles.map((bundle) => {
|
|
597
|
+
const patchStatus = input.patchStatuses?.[bundle.jobId] ?? (bundle.staleAgainstHead ? 'stale' : bundle.patchPath ? 'unknown' : 'missing');
|
|
598
|
+
const staleAgainstHead = bundle.staleAgainstHead || patchStatus === 'stale' || patchStatus === 'failed-check';
|
|
599
|
+
return {
|
|
600
|
+
jobId: bundle.jobId,
|
|
601
|
+
...(bundle.taskId ? { taskId: bundle.taskId } : {}),
|
|
602
|
+
...(bundle.lane ? { lane: bundle.lane } : {}),
|
|
603
|
+
...(bundle.title ? { title: bundle.title } : {}),
|
|
604
|
+
status: bundle.status,
|
|
605
|
+
mergeReadiness: bundle.mergeReadiness,
|
|
606
|
+
disposition: staleAgainstHead ? 'stale-against-head' : bundle.disposition,
|
|
607
|
+
riskLevel: bundle.riskLevel,
|
|
608
|
+
patchStatus,
|
|
609
|
+
staleAgainstHead,
|
|
610
|
+
autoMergeable: bundle.autoMergeable && !staleAgainstHead,
|
|
611
|
+
changedPaths: [...bundle.changedPaths],
|
|
612
|
+
changedRegions: [...bundle.changedRegions],
|
|
613
|
+
conflictKeys: mergeIndexConflictKeys(bundle),
|
|
614
|
+
conflictingJobIds: [],
|
|
615
|
+
ownedFilesTouched: [...bundle.ownedFilesTouched],
|
|
616
|
+
ownershipViolations: [...bundle.ownershipViolations],
|
|
617
|
+
...(bundle.patchPath ? { patchPath: bundle.patchPath } : {}),
|
|
618
|
+
...(bundle.patchHash ? { patchHash: bundle.patchHash } : {}),
|
|
619
|
+
evidencePaths: [...bundle.evidencePaths],
|
|
620
|
+
queueItemIds: [...bundle.queueItemIds],
|
|
621
|
+
reasons: uniqueStrings([...bundle.reasons, ...(staleAgainstHead ? ['stale-against-head'] : [])]),
|
|
622
|
+
generatedAt: bundle.generatedAt
|
|
623
|
+
};
|
|
624
|
+
});
|
|
625
|
+
const conflicts = createMergeIndexConflicts(entries);
|
|
626
|
+
const conflictsByJob = new Map();
|
|
627
|
+
for (const conflict of conflicts) {
|
|
628
|
+
for (const jobId of conflict.jobIds) {
|
|
629
|
+
const set = conflictsByJob.get(jobId) ?? new Set();
|
|
630
|
+
for (const other of conflict.jobIds)
|
|
631
|
+
if (other !== jobId)
|
|
632
|
+
set.add(other);
|
|
633
|
+
conflictsByJob.set(jobId, set);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
const indexed = entries.map((entry) => ({
|
|
637
|
+
...entry,
|
|
638
|
+
conflictingJobIds: Array.from(conflictsByJob.get(entry.jobId) ?? []).sort()
|
|
639
|
+
}));
|
|
640
|
+
const byDisposition = groupJobIdsBy(indexed, (entry) => entry.disposition);
|
|
641
|
+
const byPath = groupJobIdsByMany(indexed, (entry) => entry.changedPaths);
|
|
642
|
+
const byRegion = groupJobIdsByMany(indexed, (entry) => entry.changedRegions);
|
|
643
|
+
return {
|
|
644
|
+
kind: FRONTIER_SWARM_MERGE_INDEX_KIND,
|
|
645
|
+
version: FRONTIER_SWARM_MERGE_INDEX_VERSION,
|
|
646
|
+
id: input.id ?? 'swarm-merge-index:' + stableHash([input.runId, input.planId, indexed, conflicts, generatedAt]),
|
|
647
|
+
...(input.runId ? { runId: input.runId } : {}),
|
|
648
|
+
...(input.planId ? { planId: input.planId } : {}),
|
|
649
|
+
generatedAt,
|
|
650
|
+
entries: indexed,
|
|
651
|
+
conflicts,
|
|
652
|
+
byDisposition,
|
|
653
|
+
byPath,
|
|
654
|
+
byRegion,
|
|
655
|
+
summary: {
|
|
656
|
+
entryCount: indexed.length,
|
|
657
|
+
readyToApplyCount: indexed.filter((entry) => entry.disposition === 'auto-mergeable' && entry.autoMergeable && !entry.conflictingJobIds.length).length,
|
|
658
|
+
needsHumanPortCount: indexed.filter((entry) => entry.disposition === 'needs-port').length,
|
|
659
|
+
failedEvidenceCount: indexed.filter((entry) => entry.disposition === 'rejected' || entry.disposition === 'blocked' || entry.ownershipViolations.length > 0).length,
|
|
660
|
+
staleAgainstHeadCount: indexed.filter((entry) => entry.staleAgainstHead || entry.disposition === 'stale-against-head').length,
|
|
661
|
+
discoveryOnlyCount: indexed.filter((entry) => entry.disposition === 'discovery-only').length,
|
|
662
|
+
conflictCount: conflicts.length,
|
|
663
|
+
conflictedJobCount: conflictsByJob.size
|
|
664
|
+
},
|
|
665
|
+
...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
export function checkSwarmRegionOwnership(job, input = {}) {
|
|
669
|
+
const changedPaths = uniqueStrings(input.changedPaths ?? []);
|
|
670
|
+
const resolvedRegions = resolveSwarmChangedRegions(job, changedPaths);
|
|
671
|
+
const changedRegions = uniqueStrings([...(input.changedRegions ?? []), ...resolvedRegions]);
|
|
672
|
+
const ownedRegions = new Set(job.ownedRegions);
|
|
673
|
+
const regionViolations = changedRegions.filter((region) => !ownedRegions.has(region));
|
|
674
|
+
const classifiedPaths = new Set();
|
|
675
|
+
for (const region of job.ownershipRegions) {
|
|
676
|
+
for (const file of changedPaths) {
|
|
677
|
+
if (region.globs.some((glob) => matchesGlob(file, glob)))
|
|
678
|
+
classifiedPaths.add(file);
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
const unclassifiedChangedPaths = changedPaths.filter((file) => !classifiedPaths.has(file));
|
|
682
|
+
return {
|
|
683
|
+
ok: regionViolations.length === 0 && (job.ownershipRegions.length === 0 || unclassifiedChangedPaths.length === 0),
|
|
684
|
+
jobId: job.id,
|
|
685
|
+
changedPaths,
|
|
686
|
+
changedRegions,
|
|
687
|
+
ownedRegions: [...job.ownedRegions],
|
|
688
|
+
regionViolations,
|
|
689
|
+
unclassifiedChangedPaths
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
export function createSwarmHotspotReport(input = {}) {
|
|
693
|
+
const generatedAt = input.generatedAt ?? Date.now();
|
|
694
|
+
const threshold = Math.max(2, Math.floor(input.threshold ?? 3));
|
|
695
|
+
const byPath = new Map();
|
|
696
|
+
for (const bundle of input.bundles ?? []) {
|
|
697
|
+
for (const file of bundle.changedPaths) {
|
|
698
|
+
const current = byPath.get(file) ?? {
|
|
699
|
+
path: file,
|
|
700
|
+
touchCount: 0,
|
|
701
|
+
jobIds: [],
|
|
702
|
+
regions: [],
|
|
703
|
+
dispositions: [],
|
|
704
|
+
riskLevels: []
|
|
705
|
+
};
|
|
706
|
+
current.touchCount += 1;
|
|
707
|
+
current.jobIds = uniqueStrings([...current.jobIds, bundle.jobId]);
|
|
708
|
+
current.regions = uniqueStrings([...current.regions, ...bundle.changedRegions]);
|
|
709
|
+
current.dispositions = uniqueStrings([...current.dispositions, bundle.disposition]);
|
|
710
|
+
current.riskLevels = uniqueStrings([...current.riskLevels, bundle.riskLevel]);
|
|
711
|
+
byPath.set(file, current);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
for (const raw of input.results ?? []) {
|
|
715
|
+
const result = isSwarmJobResult(raw) ? raw : normalizeResult(raw);
|
|
716
|
+
for (const file of result.changedPaths) {
|
|
717
|
+
const current = byPath.get(file) ?? {
|
|
718
|
+
path: file,
|
|
719
|
+
touchCount: 0,
|
|
720
|
+
jobIds: [],
|
|
721
|
+
regions: [],
|
|
722
|
+
dispositions: [],
|
|
723
|
+
riskLevels: []
|
|
724
|
+
};
|
|
725
|
+
current.touchCount += 1;
|
|
726
|
+
current.jobIds = uniqueStrings([...current.jobIds, result.jobId]);
|
|
727
|
+
current.regions = uniqueStrings([...current.regions, ...result.changedRegions]);
|
|
728
|
+
current.dispositions = uniqueStrings([...current.dispositions, result.mergeDisposition]);
|
|
729
|
+
current.riskLevels = uniqueStrings([...current.riskLevels, result.riskLevel]);
|
|
730
|
+
byPath.set(file, current);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
const entries = Array.from(byPath.values()).sort((left, right) => right.touchCount - left.touchCount || left.path.localeCompare(right.path));
|
|
734
|
+
const recommendations = entries
|
|
735
|
+
.filter((entry) => entry.touchCount >= threshold || entry.regions.length > 1)
|
|
736
|
+
.map((entry) => ({
|
|
737
|
+
path: entry.path,
|
|
738
|
+
reason: entry.regions.length > 1 ? 'region-overlap' : 'hot-file',
|
|
739
|
+
suggestedModuleId: suggestedModuleId(entry.path),
|
|
740
|
+
suggestedOwnershipRegions: entry.regions.length ? entry.regions : [`${suggestedModuleId(entry.path)}.*`],
|
|
741
|
+
jobIds: [...entry.jobIds]
|
|
742
|
+
}));
|
|
743
|
+
return {
|
|
744
|
+
kind: FRONTIER_SWARM_HOTSPOT_REPORT_KIND,
|
|
745
|
+
version: FRONTIER_SWARM_HOTSPOT_REPORT_VERSION,
|
|
746
|
+
id: input.id ?? 'swarm-hotspot-report:' + stableHash([entries, threshold, generatedAt]),
|
|
747
|
+
generatedAt,
|
|
748
|
+
threshold,
|
|
749
|
+
entries,
|
|
750
|
+
recommendations,
|
|
751
|
+
summary: {
|
|
752
|
+
pathCount: entries.length,
|
|
753
|
+
hotspotCount: entries.filter((entry) => entry.touchCount >= threshold).length,
|
|
754
|
+
recommendationCount: recommendations.length
|
|
755
|
+
},
|
|
756
|
+
...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
export function createSwarmReviewerLanePlan(input) {
|
|
760
|
+
const generatedAt = input.generatedAt ?? Date.now();
|
|
761
|
+
const reviewerLane = input.reviewerLane ?? 'review';
|
|
762
|
+
const reviewers = uniqueStrings(input.reviewers ?? []);
|
|
763
|
+
const deferralsByJob = new Map((input.admission?.deferred ?? []).map((entry) => [entry.jobId, entry.reasons]));
|
|
764
|
+
const candidates = input.index.entries.filter((entry) => input.includeAutoMergeable
|
|
765
|
+
|| deferralsByJob.has(entry.jobId)
|
|
766
|
+
|| entry.conflictingJobIds.length > 0
|
|
767
|
+
|| entry.riskLevel === 'high'
|
|
768
|
+
|| entry.disposition !== 'auto-mergeable'
|
|
769
|
+
|| !entry.autoMergeable);
|
|
770
|
+
const assignments = candidates.map((entry) => ({
|
|
771
|
+
jobId: entry.jobId,
|
|
772
|
+
reviewers: selectReviewers(reviewers, reviewers.length ? 1 : 0, entry.jobId),
|
|
773
|
+
required: deferralsByJob.has(entry.jobId) || entry.conflictingJobIds.length > 0 || entry.riskLevel === 'high' || entry.disposition !== 'auto-mergeable',
|
|
774
|
+
reasons: uniqueStrings([...reviewerLaneReasons(entry), ...(deferralsByJob.get(entry.jobId) ?? [])])
|
|
775
|
+
}));
|
|
776
|
+
const tasks = candidates.map((entry) => ({
|
|
777
|
+
id: `review-${slug(entry.jobId)}`,
|
|
778
|
+
lane: reviewerLane,
|
|
779
|
+
kind: 'review',
|
|
780
|
+
title: `Review ${entry.title ?? entry.jobId}`,
|
|
781
|
+
objective: `Review swarm merge bundle ${entry.jobId}.`,
|
|
782
|
+
sourceRefs: entry.evidencePaths,
|
|
783
|
+
targetRefs: entry.changedPaths,
|
|
784
|
+
ownedRegions: entry.changedRegions,
|
|
785
|
+
acceptance: [
|
|
786
|
+
'Review evidence, patch applicability, ownership, conflicts, and risk.',
|
|
787
|
+
`Merge disposition: ${entry.disposition}.`
|
|
788
|
+
],
|
|
789
|
+
metadata: {
|
|
790
|
+
mergeJobId: entry.jobId,
|
|
791
|
+
conflictingJobIds: entry.conflictingJobIds,
|
|
792
|
+
reasons: uniqueStrings([...reviewerLaneReasons(entry), ...(deferralsByJob.get(entry.jobId) ?? [])])
|
|
793
|
+
}
|
|
794
|
+
}));
|
|
795
|
+
return {
|
|
796
|
+
kind: FRONTIER_SWARM_REVIEWER_LANE_PLAN_KIND,
|
|
797
|
+
version: FRONTIER_SWARM_REVIEWER_LANE_PLAN_VERSION,
|
|
798
|
+
id: input.id ?? 'swarm-reviewer-lane-plan:' + stableHash([input.index.id, assignments, generatedAt]),
|
|
799
|
+
mergeIndexId: input.index.id,
|
|
800
|
+
generatedAt,
|
|
801
|
+
reviewerLane,
|
|
802
|
+
assignments,
|
|
803
|
+
tasks,
|
|
804
|
+
summary: {
|
|
805
|
+
assignmentCount: assignments.length,
|
|
806
|
+
taskCount: tasks.length
|
|
807
|
+
},
|
|
808
|
+
...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
export function createSwarmRunStoreShards(input = {}) {
|
|
812
|
+
const generatedAt = input.generatedAt ?? Date.now();
|
|
813
|
+
const root = input.root ?? 'agent-runs/shards';
|
|
814
|
+
const shardSize = Math.max(1, Math.floor(input.shardSize ?? 100));
|
|
815
|
+
const groupBy = input.groupBy ?? 'lane';
|
|
816
|
+
const jobs = input.run?.jobs ?? input.plan?.jobs ?? [];
|
|
817
|
+
const groups = new Map();
|
|
818
|
+
for (const job of jobs) {
|
|
819
|
+
const key = groupBy === 'none' ? 'all' : groupBy === 'hash' ? String(hashBucket(job.id, shardSize)) : job.lane;
|
|
820
|
+
groups.set(key, [...(groups.get(key) ?? []), job]);
|
|
821
|
+
}
|
|
822
|
+
const shards = [];
|
|
823
|
+
for (const [group, groupJobs] of Array.from(groups.entries()).sort((left, right) => left[0].localeCompare(right[0]))) {
|
|
824
|
+
for (let index = 0; index < groupJobs.length; index += shardSize) {
|
|
825
|
+
const slice = groupJobs.slice(index, index + shardSize);
|
|
826
|
+
const suffix = `${slug(group)}-${Math.floor(index / shardSize)}`;
|
|
827
|
+
const shardRoot = joinPathParts(root, suffix);
|
|
828
|
+
shards.push({
|
|
829
|
+
id: 'swarm-run-store-shard:' + stableHash([input.run?.id, input.plan?.id, group, index, slice.map((job) => job.id)]),
|
|
830
|
+
...(groupBy === 'lane' ? { lane: group } : {}),
|
|
831
|
+
path: shardRoot,
|
|
832
|
+
eventPath: joinPathParts(shardRoot, 'events.jsonl'),
|
|
833
|
+
resultPath: joinPathParts(shardRoot, 'results.jsonl'),
|
|
834
|
+
checkpointPath: joinPathParts(shardRoot, 'checkpoint.json'),
|
|
835
|
+
jobIds: slice.map((job) => job.id)
|
|
836
|
+
});
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
return {
|
|
840
|
+
kind: FRONTIER_SWARM_RUN_STORE_SHARDS_KIND,
|
|
841
|
+
version: FRONTIER_SWARM_RUN_STORE_SHARDS_VERSION,
|
|
842
|
+
id: input.id ?? 'swarm-run-store-shards:' + stableHash([input.run?.id, input.plan?.id, root, shardSize, groupBy, shards, generatedAt]),
|
|
843
|
+
...(input.run ? { runId: input.run.id } : {}),
|
|
844
|
+
...(input.plan ? { planId: input.plan.id } : {}),
|
|
845
|
+
root,
|
|
846
|
+
generatedAt,
|
|
847
|
+
groupBy,
|
|
848
|
+
shardSize,
|
|
849
|
+
shards,
|
|
850
|
+
summary: {
|
|
851
|
+
shardCount: shards.length,
|
|
852
|
+
jobCount: jobs.length
|
|
853
|
+
},
|
|
854
|
+
...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
export function createSwarmMergeAdmission(input) {
|
|
858
|
+
const generatedAt = input.generatedAt ?? Date.now();
|
|
859
|
+
const maxReady = Math.max(0, Math.floor(input.maxReady ?? input.index.entries.length));
|
|
860
|
+
const maxChangedPaths = input.maxChangedPaths === undefined ? undefined : Math.max(0, Math.floor(input.maxChangedPaths));
|
|
861
|
+
const maxChangedRegions = input.maxChangedRegions === undefined ? undefined : Math.max(0, Math.floor(input.maxChangedRegions));
|
|
862
|
+
const maxHighRisk = input.maxHighRisk === undefined ? undefined : Math.max(0, Math.floor(input.maxHighRisk));
|
|
863
|
+
const allowRisks = uniqueStrings(input.allowRisks ?? ['low', 'medium']);
|
|
864
|
+
const admitted = [];
|
|
865
|
+
const deferred = [];
|
|
866
|
+
const usedPaths = new Set();
|
|
867
|
+
const usedRegions = new Set();
|
|
868
|
+
let highRiskCount = 0;
|
|
869
|
+
for (const entry of input.index.entries) {
|
|
870
|
+
const reasons = [];
|
|
871
|
+
if (entry.disposition !== 'auto-mergeable' || !entry.autoMergeable)
|
|
872
|
+
reasons.push('not-auto-mergeable');
|
|
873
|
+
if (entry.staleAgainstHead)
|
|
874
|
+
reasons.push('stale-against-head');
|
|
875
|
+
if (entry.conflictingJobIds.length)
|
|
876
|
+
reasons.push('conflicting-changes');
|
|
877
|
+
if (!allowRisks.includes(entry.riskLevel))
|
|
878
|
+
reasons.push('risk-not-admitted');
|
|
879
|
+
if (admitted.length >= maxReady)
|
|
880
|
+
reasons.push('max-ready');
|
|
881
|
+
const nextPaths = new Set([...usedPaths, ...entry.changedPaths]);
|
|
882
|
+
const nextRegions = new Set([...usedRegions, ...entry.changedRegions]);
|
|
883
|
+
const nextHighRiskCount = highRiskCount + (entry.riskLevel === 'high' ? 1 : 0);
|
|
884
|
+
if (maxChangedPaths !== undefined && nextPaths.size > maxChangedPaths)
|
|
885
|
+
reasons.push('max-changed-paths');
|
|
886
|
+
if (maxChangedRegions !== undefined && nextRegions.size > maxChangedRegions)
|
|
887
|
+
reasons.push('max-changed-regions');
|
|
888
|
+
if (maxHighRisk !== undefined && nextHighRiskCount > maxHighRisk)
|
|
889
|
+
reasons.push('max-high-risk');
|
|
890
|
+
if (reasons.length) {
|
|
891
|
+
deferred.push({ jobId: entry.jobId, reasons: uniqueStrings(reasons) });
|
|
892
|
+
continue;
|
|
893
|
+
}
|
|
894
|
+
admitted.push(entry.jobId);
|
|
895
|
+
for (const file of entry.changedPaths)
|
|
896
|
+
usedPaths.add(file);
|
|
897
|
+
for (const region of entry.changedRegions)
|
|
898
|
+
usedRegions.add(region);
|
|
899
|
+
highRiskCount = nextHighRiskCount;
|
|
900
|
+
}
|
|
901
|
+
return {
|
|
902
|
+
kind: FRONTIER_SWARM_MERGE_ADMISSION_KIND,
|
|
903
|
+
version: FRONTIER_SWARM_MERGE_ADMISSION_VERSION,
|
|
904
|
+
id: input.id ?? 'swarm-merge-admission:' + stableHash([input.index.id, admitted, deferred, generatedAt]),
|
|
905
|
+
mergeIndexId: input.index.id,
|
|
906
|
+
generatedAt,
|
|
907
|
+
admitted,
|
|
908
|
+
deferred,
|
|
909
|
+
budget: {
|
|
910
|
+
maxReady,
|
|
911
|
+
...(maxChangedPaths !== undefined ? { maxChangedPaths } : {}),
|
|
912
|
+
...(maxChangedRegions !== undefined ? { maxChangedRegions } : {}),
|
|
913
|
+
...(maxHighRisk !== undefined ? { maxHighRisk } : {}),
|
|
914
|
+
allowRisks
|
|
915
|
+
},
|
|
916
|
+
summary: {
|
|
917
|
+
admittedCount: admitted.length,
|
|
918
|
+
deferredCount: deferred.length,
|
|
919
|
+
changedPathCount: usedPaths.size,
|
|
920
|
+
changedRegionCount: usedRegions.size,
|
|
921
|
+
highRiskCount
|
|
922
|
+
},
|
|
923
|
+
...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
|
|
924
|
+
};
|
|
925
|
+
}
|
|
926
|
+
export function createSwarmContextPack(input = {}) {
|
|
927
|
+
const generatedAt = input.generatedAt ?? Date.now();
|
|
928
|
+
const task = input.job?.task ?? (input.task ? isSwarmTask(input.task) ? input.task : normalizeTask(input.task) : undefined);
|
|
929
|
+
const files = uniqueStrings([
|
|
930
|
+
...(input.files ?? []),
|
|
931
|
+
...(input.job?.task.sourceRefs ?? []),
|
|
932
|
+
...(input.job?.task.targetRefs ?? []),
|
|
933
|
+
...(task?.sourceRefs ?? []),
|
|
934
|
+
...(task?.targetRefs ?? [])
|
|
935
|
+
]);
|
|
936
|
+
const apiMap = Object.fromEntries(Object.entries(input.apiMap ?? {}).map(([key, values]) => [key, uniqueStrings(values)]));
|
|
937
|
+
const commands = normalizeCommands([
|
|
938
|
+
...(input.commands ?? []),
|
|
939
|
+
...(input.oracleCommands ?? []),
|
|
940
|
+
...(input.job?.verification ?? [])
|
|
941
|
+
]);
|
|
942
|
+
const expectedEvidence = uniqueStrings([
|
|
943
|
+
...(input.expectedEvidence ?? []),
|
|
944
|
+
...(input.job?.evidencePrefix ? [joinPathParts(input.job.evidencePrefix, 'evidence.json')] : [])
|
|
945
|
+
]);
|
|
946
|
+
return {
|
|
947
|
+
kind: FRONTIER_SWARM_CONTEXT_PACK_KIND,
|
|
948
|
+
version: FRONTIER_SWARM_CONTEXT_PACK_VERSION,
|
|
949
|
+
id: input.id ?? 'swarm-context-pack:' + stableHash([input.job?.id, task?.id, files, apiMap, generatedAt]),
|
|
950
|
+
...(input.job ? { jobId: input.job.id } : {}),
|
|
951
|
+
...(task ? { taskId: task.id } : {}),
|
|
952
|
+
...(input.job?.lane ?? task?.lane ? { lane: input.job?.lane ?? task?.lane } : {}),
|
|
953
|
+
title: input.title ?? input.job?.title ?? task?.title ?? 'Swarm Context Pack',
|
|
954
|
+
generatedAt,
|
|
955
|
+
files,
|
|
956
|
+
apiMap,
|
|
957
|
+
knownFailures: uniqueStrings(input.knownFailures ?? []),
|
|
958
|
+
commands,
|
|
959
|
+
oracleCommands: commands,
|
|
960
|
+
...(input.evidenceSchema !== undefined ? { evidenceSchema: toJsonValue(input.evidenceSchema) } : {}),
|
|
961
|
+
expectedEvidence,
|
|
962
|
+
exclusions: uniqueStrings(input.exclusions ?? []),
|
|
963
|
+
avoidInvestigating: uniqueStrings(input.avoidInvestigating ?? []),
|
|
964
|
+
playbookIds: uniqueStrings(input.playbookIds ?? []),
|
|
965
|
+
...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
|
|
966
|
+
};
|
|
967
|
+
}
|
|
968
|
+
export function createSwarmOracleCorpus(input = {}) {
|
|
969
|
+
const generatedAt = input.generatedAt ?? Date.now();
|
|
970
|
+
const artifacts = (input.artifacts ?? []).map(normalizeOracleArtifact).sort((left, right) => left.id.localeCompare(right.id));
|
|
971
|
+
const byKind = groupArtifactIdsBy(artifacts, (artifact) => [artifact.kind]);
|
|
972
|
+
const byTag = groupArtifactIdsBy(artifacts, (artifact) => artifact.tags);
|
|
973
|
+
return {
|
|
974
|
+
kind: FRONTIER_SWARM_ORACLE_CORPUS_KIND,
|
|
975
|
+
version: FRONTIER_SWARM_ORACLE_CORPUS_VERSION,
|
|
976
|
+
id: input.id ?? 'swarm-oracle-corpus:' + stableHash([artifacts, generatedAt]),
|
|
977
|
+
title: input.title ?? titleFromId(input.id ?? 'oracle corpus'),
|
|
978
|
+
generatedAt,
|
|
979
|
+
artifacts,
|
|
980
|
+
byKind,
|
|
981
|
+
byTag,
|
|
982
|
+
summary: {
|
|
983
|
+
artifactCount: artifacts.length,
|
|
984
|
+
kindCount: Object.keys(byKind).length,
|
|
985
|
+
tagCount: Object.keys(byTag).length
|
|
986
|
+
},
|
|
987
|
+
...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
|
|
988
|
+
};
|
|
989
|
+
}
|
|
990
|
+
export function createSwarmObservabilityPoint(input = {}) {
|
|
991
|
+
const eventRefs = normalizeNamedRefs(input.eventRefs ?? [], 'event');
|
|
992
|
+
return {
|
|
993
|
+
kind: FRONTIER_SWARM_OBSERVABILITY_POINT_KIND,
|
|
994
|
+
version: FRONTIER_SWARM_OBSERVABILITY_POINT_VERSION,
|
|
995
|
+
id: input.id ?? 'swarm-observability-point:' + stableHash([input.subject, input.scope, input.operationIndex, input.at, input.path, input.selector, eventRefs]),
|
|
996
|
+
...(input.subject ? { subject: input.subject } : {}),
|
|
997
|
+
...(input.scope ? { scope: input.scope } : {}),
|
|
998
|
+
...(input.operationIndex !== undefined ? { operationIndex: Math.max(0, Math.floor(input.operationIndex)) } : {}),
|
|
999
|
+
...(input.at !== undefined ? { at: input.at } : {}),
|
|
1000
|
+
...(input.path ? { path: input.path } : {}),
|
|
1001
|
+
...(input.selector ? { selector: input.selector } : {}),
|
|
1002
|
+
...(input.before !== undefined ? { before: toJsonValue(input.before) } : {}),
|
|
1003
|
+
...(input.after !== undefined ? { after: toJsonValue(input.after) } : {}),
|
|
1004
|
+
eventRefs,
|
|
1005
|
+
...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
|
|
1006
|
+
};
|
|
1007
|
+
}
|
|
1008
|
+
export function createSwarmReplayBundle(input = {}) {
|
|
1009
|
+
const generatedAt = input.generatedAt ?? Date.now();
|
|
1010
|
+
const commands = normalizeCommands(input.commands ?? []);
|
|
1011
|
+
const inputs = normalizeNamedRefs(input.inputs ?? [], 'input');
|
|
1012
|
+
const artifacts = normalizeNamedRefs(input.artifacts ?? [], 'artifact');
|
|
1013
|
+
const sourceRefs = normalizeNamedRefs(input.sourceRefs ?? [], 'source');
|
|
1014
|
+
const seeds = normalizeSeedRefs(input.seeds ?? []);
|
|
1015
|
+
const expectedEvidence = uniqueStrings(input.expectedEvidence ?? []);
|
|
1016
|
+
const title = input.title ?? titleFromId(input.id ?? input.subject ?? 'replay bundle');
|
|
1017
|
+
return {
|
|
1018
|
+
kind: FRONTIER_SWARM_REPLAY_BUNDLE_KIND,
|
|
1019
|
+
version: FRONTIER_SWARM_REPLAY_BUNDLE_VERSION,
|
|
1020
|
+
id: input.id ?? 'swarm-replay-bundle:' + stableHash([title, input.subject, commands, inputs, artifacts, sourceRefs, seeds, expectedEvidence, generatedAt]),
|
|
1021
|
+
title,
|
|
1022
|
+
...(input.subject ? { subject: input.subject } : {}),
|
|
1023
|
+
generatedAt,
|
|
1024
|
+
commands,
|
|
1025
|
+
inputs,
|
|
1026
|
+
artifacts,
|
|
1027
|
+
sourceRefs,
|
|
1028
|
+
seeds,
|
|
1029
|
+
...(toJsonObject(input.environment) ? { environment: toJsonObject(input.environment) } : {}),
|
|
1030
|
+
expectedEvidence,
|
|
1031
|
+
summary: {
|
|
1032
|
+
commandCount: commands.length,
|
|
1033
|
+
inputCount: inputs.length,
|
|
1034
|
+
artifactCount: artifacts.length,
|
|
1035
|
+
sourceRefCount: sourceRefs.length
|
|
1036
|
+
},
|
|
1037
|
+
...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
export function createSwarmParityOracle(input = {}) {
|
|
1041
|
+
const generatedAt = input.generatedAt ?? Date.now();
|
|
1042
|
+
const referenceCommands = normalizeCommands(input.referenceCommands ?? []);
|
|
1043
|
+
const testCommands = normalizeCommands(input.testCommands ?? []);
|
|
1044
|
+
const comparators = (input.comparators ?? []).map(normalizeParityComparator);
|
|
1045
|
+
const artifacts = normalizeNamedRefs(input.artifacts ?? [], 'parity-artifact');
|
|
1046
|
+
const status = input.status ?? inferParityStatus(comparators);
|
|
1047
|
+
return {
|
|
1048
|
+
kind: FRONTIER_SWARM_PARITY_ORACLE_KIND,
|
|
1049
|
+
version: FRONTIER_SWARM_PARITY_ORACLE_VERSION,
|
|
1050
|
+
id: input.id ?? 'swarm-parity-oracle:' + stableHash([input.title, input.subject, referenceCommands, testCommands, comparators, artifacts, generatedAt]),
|
|
1051
|
+
title: input.title ?? titleFromId(input.id ?? input.subject ?? 'parity oracle'),
|
|
1052
|
+
status,
|
|
1053
|
+
...(input.subject ? { subject: input.subject } : {}),
|
|
1054
|
+
generatedAt,
|
|
1055
|
+
referenceCommands,
|
|
1056
|
+
testCommands,
|
|
1057
|
+
comparators,
|
|
1058
|
+
artifacts,
|
|
1059
|
+
replayBundleIds: uniqueStrings(input.replayBundleIds ?? []),
|
|
1060
|
+
summary: {
|
|
1061
|
+
comparatorCount: comparators.length,
|
|
1062
|
+
passedCount: comparators.filter((comparator) => comparator.status === 'passed').length,
|
|
1063
|
+
failedCount: comparators.filter((comparator) => comparator.status === 'failed').length,
|
|
1064
|
+
blockedCount: comparators.filter((comparator) => comparator.status === 'blocked').length
|
|
1065
|
+
},
|
|
1066
|
+
...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
|
|
1067
|
+
};
|
|
1068
|
+
}
|
|
1069
|
+
export function createSwarmDivergenceReport(input = {}) {
|
|
1070
|
+
const generatedAt = input.generatedAt ?? Date.now();
|
|
1071
|
+
const observabilityPoints = (input.observabilityPoints ?? []).map((point) => isSwarmObservabilityPoint(point) ? point : createSwarmObservabilityPoint(point));
|
|
1072
|
+
const earliest = observabilityPoints
|
|
1073
|
+
.filter((point) => point.operationIndex !== undefined)
|
|
1074
|
+
.sort((left, right) => left.operationIndex - right.operationIndex)[0];
|
|
1075
|
+
const divergesAt = input.divergesAt ?? earliest?.path;
|
|
1076
|
+
const operationIndex = input.operationIndex ?? earliest?.operationIndex;
|
|
1077
|
+
return {
|
|
1078
|
+
kind: FRONTIER_SWARM_DIVERGENCE_REPORT_KIND,
|
|
1079
|
+
version: FRONTIER_SWARM_DIVERGENCE_REPORT_VERSION,
|
|
1080
|
+
id: input.id ?? 'swarm-divergence-report:' + stableHash([input.subject, input.divergesAt, input.operationIndex, observabilityPoints, generatedAt]),
|
|
1081
|
+
title: input.title ?? titleFromId(input.id ?? input.subject ?? 'divergence report'),
|
|
1082
|
+
status: input.status ?? 'failed',
|
|
1083
|
+
severity: input.severity ?? 'error',
|
|
1084
|
+
...(input.subject ? { subject: input.subject } : {}),
|
|
1085
|
+
confidence: input.confidence ?? 'medium',
|
|
1086
|
+
...(divergesAt ? { divergesAt } : {}),
|
|
1087
|
+
...(operationIndex !== undefined ? { operationIndex } : {}),
|
|
1088
|
+
...(input.expected !== undefined ? { expected: toJsonValue(input.expected) } : {}),
|
|
1089
|
+
...(input.actual !== undefined ? { actual: toJsonValue(input.actual) } : {}),
|
|
1090
|
+
observabilityPoints,
|
|
1091
|
+
traceRefs: normalizeNamedRefs(input.traceRefs ?? [], 'trace'),
|
|
1092
|
+
replayBundleIds: uniqueStrings(input.replayBundleIds ?? []),
|
|
1093
|
+
evidenceRefs: normalizeNamedRefs(input.evidenceRefs ?? [], 'evidence'),
|
|
1094
|
+
generatedAt,
|
|
1095
|
+
...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
|
|
1096
|
+
};
|
|
1097
|
+
}
|
|
1098
|
+
export function createSwarmWatchpointPlan(input = {}) {
|
|
1099
|
+
const generatedAt = input.generatedAt ?? Date.now();
|
|
1100
|
+
const watchpoints = (input.watchpoints ?? []).map(normalizeWatchpoint);
|
|
1101
|
+
const commands = normalizeCommands(input.commands ?? []);
|
|
1102
|
+
return {
|
|
1103
|
+
kind: FRONTIER_SWARM_WATCHPOINT_PLAN_KIND,
|
|
1104
|
+
version: FRONTIER_SWARM_WATCHPOINT_PLAN_VERSION,
|
|
1105
|
+
id: input.id ?? 'swarm-watchpoint-plan:' + stableHash([input.subject, watchpoints, commands, generatedAt]),
|
|
1106
|
+
title: input.title ?? titleFromId(input.id ?? input.subject ?? 'watchpoint plan'),
|
|
1107
|
+
...(input.subject ? { subject: input.subject } : {}),
|
|
1108
|
+
matchMode: input.matchMode ?? 'all',
|
|
1109
|
+
generatedAt,
|
|
1110
|
+
watchpoints,
|
|
1111
|
+
commands,
|
|
1112
|
+
replayBundleIds: uniqueStrings(input.replayBundleIds ?? []),
|
|
1113
|
+
divergenceReportIds: uniqueStrings(input.divergenceReportIds ?? []),
|
|
1114
|
+
summary: {
|
|
1115
|
+
watchpointCount: watchpoints.length,
|
|
1116
|
+
commandCount: commands.length
|
|
1117
|
+
},
|
|
1118
|
+
...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1121
|
+
export function createSwarmDebugHandoff(input = {}) {
|
|
1122
|
+
const generatedAt = input.generatedAt ?? Date.now();
|
|
1123
|
+
const focus = input.focus ? (isSwarmObservabilityPoint(input.focus) ? input.focus : createSwarmObservabilityPoint(input.focus)) : undefined;
|
|
1124
|
+
return {
|
|
1125
|
+
kind: FRONTIER_SWARM_DEBUG_HANDOFF_KIND,
|
|
1126
|
+
version: FRONTIER_SWARM_DEBUG_HANDOFF_VERSION,
|
|
1127
|
+
id: input.id ?? 'swarm-debug-handoff:' + stableHash([input.subject, focus, input.replayBundleIds, input.divergenceReportIds, input.watchpointPlanIds, generatedAt]),
|
|
1128
|
+
title: input.title ?? titleFromId(input.id ?? input.subject ?? 'debug handoff'),
|
|
1129
|
+
status: input.status ?? 'ready',
|
|
1130
|
+
...(input.subject ? { subject: input.subject } : {}),
|
|
1131
|
+
...(focus ? { focus } : {}),
|
|
1132
|
+
replayBundleIds: uniqueStrings(input.replayBundleIds ?? []),
|
|
1133
|
+
divergenceReportIds: uniqueStrings(input.divergenceReportIds ?? []),
|
|
1134
|
+
watchpointPlanIds: uniqueStrings(input.watchpointPlanIds ?? []),
|
|
1135
|
+
commands: normalizeCommands(input.commands ?? []),
|
|
1136
|
+
files: normalizeNamedRefs(input.files ?? [], 'file'),
|
|
1137
|
+
artifacts: normalizeNamedRefs(input.artifacts ?? [], 'artifact'),
|
|
1138
|
+
comparisons: (input.comparisons ?? []).map(normalizeParityComparator),
|
|
1139
|
+
...(toJsonObject(input.environment) ? { environment: toJsonObject(input.environment) } : {}),
|
|
1140
|
+
generatedAt,
|
|
1141
|
+
...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
|
|
1142
|
+
};
|
|
1143
|
+
}
|
|
1144
|
+
export function createSwarmInstrumentationBudget(input = {}) {
|
|
1145
|
+
const generatedAt = input.generatedAt ?? Date.now();
|
|
1146
|
+
return {
|
|
1147
|
+
kind: FRONTIER_SWARM_INSTRUMENTATION_BUDGET_KIND,
|
|
1148
|
+
version: FRONTIER_SWARM_INSTRUMENTATION_BUDGET_VERSION,
|
|
1149
|
+
id: input.id ?? 'swarm-instrumentation-budget:' + stableHash([input.lane, input.maxEvents, input.maxBytes, input.maxDurationMs, input.maxOverheadRatio, generatedAt]),
|
|
1150
|
+
title: input.title ?? titleFromId(input.id ?? input.lane ?? 'instrumentation budget'),
|
|
1151
|
+
...(input.lane ? { lane: input.lane } : {}),
|
|
1152
|
+
generatedAt,
|
|
1153
|
+
...(positiveNumber(input.maxEvents) ? { maxEvents: Math.floor(input.maxEvents) } : {}),
|
|
1154
|
+
...(positiveNumber(input.maxBytes) ? { maxBytes: Math.floor(input.maxBytes) } : {}),
|
|
1155
|
+
...(positiveNumber(input.maxDurationMs) ? { maxDurationMs: Math.floor(input.maxDurationMs) } : {}),
|
|
1156
|
+
...(positiveNumber(input.maxOverheadRatio) ? { maxOverheadRatio: input.maxOverheadRatio } : {}),
|
|
1157
|
+
captureKinds: uniqueStrings(input.captureKinds ?? []),
|
|
1158
|
+
sampling: {
|
|
1159
|
+
mode: input.sampling?.mode ?? 'adaptive',
|
|
1160
|
+
...(positiveNumber(input.sampling?.rate) ? { rate: input.sampling?.rate } : {}),
|
|
1161
|
+
...(toJsonObject(input.sampling?.metadata) ? { metadata: toJsonObject(input.sampling?.metadata) } : {})
|
|
1162
|
+
},
|
|
1163
|
+
...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
|
|
1164
|
+
};
|
|
1165
|
+
}
|
|
1166
|
+
export function checkSwarmInstrumentationBudget(budgetInput, usageInput = {}) {
|
|
1167
|
+
const budget = isSwarmInstrumentationBudget(budgetInput) ? budgetInput : createSwarmInstrumentationBudget(budgetInput);
|
|
1168
|
+
const usage = {
|
|
1169
|
+
events: Math.max(0, Math.floor(usageInput.events ?? 0)),
|
|
1170
|
+
bytes: Math.max(0, Math.floor(usageInput.bytes ?? 0)),
|
|
1171
|
+
durationMs: Math.max(0, Math.floor(usageInput.durationMs ?? 0)),
|
|
1172
|
+
overheadRatio: Math.max(0, usageInput.overheadRatio ?? 0),
|
|
1173
|
+
captureKinds: uniqueStrings(usageInput.captureKinds ?? []),
|
|
1174
|
+
...(toJsonObject(usageInput.metadata) ? { metadata: toJsonObject(usageInput.metadata) } : {})
|
|
1175
|
+
};
|
|
1176
|
+
const violations = [];
|
|
1177
|
+
if (budget.maxEvents !== undefined && usage.events > budget.maxEvents)
|
|
1178
|
+
violations.push('max-events');
|
|
1179
|
+
if (budget.maxBytes !== undefined && usage.bytes > budget.maxBytes)
|
|
1180
|
+
violations.push('max-bytes');
|
|
1181
|
+
if (budget.maxDurationMs !== undefined && usage.durationMs > budget.maxDurationMs)
|
|
1182
|
+
violations.push('max-duration-ms');
|
|
1183
|
+
if (budget.maxOverheadRatio !== undefined && usage.overheadRatio > budget.maxOverheadRatio)
|
|
1184
|
+
violations.push('max-overhead-ratio');
|
|
1185
|
+
for (const kind of usage.captureKinds.filter((kind) => budget.captureKinds.length > 0 && !budget.captureKinds.includes(kind))) {
|
|
1186
|
+
violations.push(`capture-kind:${kind}`);
|
|
1187
|
+
}
|
|
1188
|
+
return { ok: violations.length === 0, budgetId: budget.id, usage, violations: uniqueStrings(violations) };
|
|
1189
|
+
}
|
|
1190
|
+
export function classifySwarmBottleneck(input) {
|
|
1191
|
+
const source = normalizeBottleneckSource(input);
|
|
1192
|
+
const text = [source.text, source.status, ...(source.reasons ?? []), ...(source.evidencePaths ?? []), ...(source.changedPaths ?? [])].join(' ').toLowerCase();
|
|
1193
|
+
const verification = source.verification ?? [];
|
|
1194
|
+
let kind = 'queue';
|
|
1195
|
+
let confidence = 'medium';
|
|
1196
|
+
if (/missing.*oracle|no oracle|needs-fixture|fixture/.test(text))
|
|
1197
|
+
kind = 'missing-oracle';
|
|
1198
|
+
else if (/flaky|timeout|browser|playwright|chrome|port/.test(text))
|
|
1199
|
+
kind = 'flaky-harness';
|
|
1200
|
+
else if (/instrument|logging|trace|telemetry|overhead/.test(text))
|
|
1201
|
+
kind = 'instrumentation-overhead';
|
|
1202
|
+
else if (/merge|conflict|review|needs-port|ownership/.test(text))
|
|
1203
|
+
kind = 'merge-review';
|
|
1204
|
+
else if (/dependency|depends|blocked/.test(text))
|
|
1205
|
+
kind = 'blocked-dependency';
|
|
1206
|
+
else if (/perf|slow|latency|throughput|cpu|memory|resource-capacity/.test(text))
|
|
1207
|
+
kind = 'performance';
|
|
1208
|
+
else if (/diverg|correct|parity|oracle failed|regression/.test(text))
|
|
1209
|
+
kind = 'correctness';
|
|
1210
|
+
if (verification.some((entry) => entry.status !== undefined && entry.status !== 0 && entry.required !== false))
|
|
1211
|
+
confidence = 'high';
|
|
1212
|
+
if ((source.evidencePaths?.length ?? 0) === 0 && (source.changedPaths?.length ?? 0) > 0)
|
|
1213
|
+
confidence = 'low';
|
|
1214
|
+
return {
|
|
1215
|
+
kind,
|
|
1216
|
+
confidence,
|
|
1217
|
+
reasons: uniqueStrings([kind, ...(source.reasons ?? []), ...verification.filter((entry) => entry.status !== undefined && entry.status !== 0).map((entry) => entry.name ?? 'failed-verification')]),
|
|
1218
|
+
route: routeForBottleneck(kind, source.lane)
|
|
1219
|
+
};
|
|
1220
|
+
}
|
|
1221
|
+
export function createSwarmBottleneckReport(input = {}) {
|
|
1222
|
+
const generatedAt = input.generatedAt ?? Date.now();
|
|
1223
|
+
const classifications = (input.sources ?? []).map(classifySwarmBottleneck);
|
|
1224
|
+
const byKind = groupObjects(classifications, (classification) => classification.kind);
|
|
1225
|
+
return {
|
|
1226
|
+
kind: FRONTIER_SWARM_BOTTLENECK_REPORT_KIND,
|
|
1227
|
+
version: FRONTIER_SWARM_BOTTLENECK_REPORT_VERSION,
|
|
1228
|
+
id: input.id ?? 'swarm-bottleneck-report:' + stableHash([classifications, generatedAt]),
|
|
1229
|
+
generatedAt,
|
|
1230
|
+
classifications,
|
|
1231
|
+
byKind,
|
|
1232
|
+
summary: {
|
|
1233
|
+
sourceCount: input.sources?.length ?? 0,
|
|
1234
|
+
kindCount: Object.keys(byKind).length
|
|
1235
|
+
},
|
|
1236
|
+
...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
|
|
1237
|
+
};
|
|
1238
|
+
}
|
|
1239
|
+
export function createSwarmEvidenceIndex(input = {}) {
|
|
1240
|
+
const source = isSwarmRun(input) ? { run: input } : input;
|
|
1241
|
+
const generatedAt = source.generatedAt ?? Date.now();
|
|
1242
|
+
const runEntries = source.run?.results.flatMap((result) => result.evidencePaths.map((path) => normalizeEvidenceIndexEntry({
|
|
1243
|
+
jobId: result.jobId,
|
|
1244
|
+
queueItemId: result.queueItemIds[0],
|
|
1245
|
+
path,
|
|
1246
|
+
kind: evidenceKindFromPath(path),
|
|
1247
|
+
status: result.status,
|
|
1248
|
+
confidence: result.status === 'verified' || result.mergeReadiness === 'verified-patch' ? 1 : 0.65,
|
|
1249
|
+
generatedAt
|
|
1250
|
+
}))) ?? [];
|
|
1251
|
+
const entries = [...runEntries, ...(source.entries ?? []).map((entry) => normalizeEvidenceIndexEntry({ ...entry, generatedAt: entry.generatedAt ?? generatedAt }))];
|
|
1252
|
+
const byJobId = groupObjects(entries.filter((entry) => entry.jobId), (entry) => entry.jobId);
|
|
1253
|
+
const byTopic = groupObjects(entries.filter((entry) => entry.topic), (entry) => entry.topic);
|
|
1254
|
+
const byPath = groupObjects(entries.filter((entry) => entry.path), (entry) => entry.path);
|
|
1255
|
+
return {
|
|
1256
|
+
kind: FRONTIER_SWARM_EVIDENCE_INDEX_KIND,
|
|
1257
|
+
version: FRONTIER_SWARM_EVIDENCE_INDEX_VERSION,
|
|
1258
|
+
id: source.id ?? 'swarm-evidence-index:' + stableHash([source.run?.id, entries, generatedAt]),
|
|
1259
|
+
...(source.run?.id ? { runId: source.run.id } : {}),
|
|
1260
|
+
generatedAt,
|
|
1261
|
+
entries,
|
|
1262
|
+
byJobId,
|
|
1263
|
+
byTopic,
|
|
1264
|
+
byPath,
|
|
1265
|
+
summary: {
|
|
1266
|
+
entryCount: entries.length,
|
|
1267
|
+
jobCount: Object.keys(byJobId).length,
|
|
1268
|
+
topicCount: Object.keys(byTopic).length,
|
|
1269
|
+
pathCount: Object.keys(byPath).length
|
|
1270
|
+
},
|
|
1271
|
+
...(toJsonObject(source.metadata) ? { metadata: toJsonObject(source.metadata) } : {})
|
|
1272
|
+
};
|
|
1273
|
+
}
|
|
1274
|
+
export function querySwarmEvidenceIndex(index, query = {}) {
|
|
1275
|
+
const entries = index.entries.filter((entry) => ((query.jobId === undefined || entry.jobId === query.jobId)
|
|
1276
|
+
&& (query.lane === undefined || entry.lane === query.lane)
|
|
1277
|
+
&& (query.topic === undefined || entry.topic === query.topic)
|
|
1278
|
+
&& (query.pathIncludes === undefined || (entry.path ?? '').includes(query.pathIncludes))
|
|
1279
|
+
&& (query.kind === undefined || entry.kind === query.kind)
|
|
1280
|
+
&& (query.status === undefined || entry.status === query.status)
|
|
1281
|
+
&& (query.tag === undefined || entry.tags.includes(query.tag))
|
|
1282
|
+
&& (query.minConfidence === undefined || entry.confidence >= query.minConfidence)
|
|
1283
|
+
&& matchesFacetQuery(entry.facets, query.facet)));
|
|
1284
|
+
return { entries, summary: { entryCount: entries.length } };
|
|
1285
|
+
}
|
|
1286
|
+
export function createSwarmBlackboard(input = {}) {
|
|
1287
|
+
const generatedAt = input.generatedAt ?? Date.now();
|
|
1288
|
+
const entries = (input.entries ?? []).map((entry) => normalizeBlackboardEntry({ ...entry, generatedAt: entry.generatedAt ?? generatedAt }));
|
|
1289
|
+
const byTopic = groupObjects(entries, (entry) => entry.topic);
|
|
1290
|
+
const byKind = groupObjects(entries, (entry) => entry.kind);
|
|
1291
|
+
return {
|
|
1292
|
+
kind: FRONTIER_SWARM_BLACKBOARD_KIND,
|
|
1293
|
+
version: FRONTIER_SWARM_BLACKBOARD_VERSION,
|
|
1294
|
+
id: input.id ?? 'swarm-blackboard:' + stableHash([input.runId, entries, generatedAt]),
|
|
1295
|
+
...(input.runId ? { runId: input.runId } : {}),
|
|
1296
|
+
generatedAt,
|
|
1297
|
+
entries,
|
|
1298
|
+
byTopic,
|
|
1299
|
+
byKind,
|
|
1300
|
+
summary: {
|
|
1301
|
+
entryCount: entries.length,
|
|
1302
|
+
topicCount: Object.keys(byTopic).length,
|
|
1303
|
+
kindCount: Object.keys(byKind).length
|
|
1304
|
+
},
|
|
1305
|
+
...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
|
|
1306
|
+
};
|
|
1307
|
+
}
|
|
1308
|
+
export function querySwarmBlackboard(blackboard, query = {}) {
|
|
1309
|
+
const textIncludes = query.textIncludes?.toLowerCase();
|
|
1310
|
+
const entries = blackboard.entries.filter((entry) => ((query.kind === undefined || entry.kind === query.kind)
|
|
1311
|
+
&& (query.topic === undefined || entry.topic === query.topic)
|
|
1312
|
+
&& (query.status === undefined || entry.status === query.status)
|
|
1313
|
+
&& (query.lane === undefined || entry.lane === query.lane)
|
|
1314
|
+
&& (query.jobId === undefined || entry.jobId === query.jobId)
|
|
1315
|
+
&& (query.owner === undefined || entry.owner === query.owner)
|
|
1316
|
+
&& (query.tag === undefined || entry.tags.includes(query.tag))
|
|
1317
|
+
&& (textIncludes === undefined || entry.text.toLowerCase().includes(textIncludes))));
|
|
1318
|
+
return { entries, summary: { entryCount: entries.length } };
|
|
1319
|
+
}
|
|
1320
|
+
export function createSwarmReferenceOraclePlan(input = {}) {
|
|
1321
|
+
const generatedAt = input.generatedAt ?? Date.now();
|
|
1322
|
+
const targets = (input.targets ?? []).map((target) => ({
|
|
1323
|
+
id: target.id,
|
|
1324
|
+
role: target.role ?? 'candidate',
|
|
1325
|
+
...(target.command ? { command: normalizeCommand(target.command) } : {}),
|
|
1326
|
+
...(toJsonObject(target.metadata) ? { metadata: toJsonObject(target.metadata) } : {})
|
|
1327
|
+
}));
|
|
1328
|
+
return {
|
|
1329
|
+
kind: FRONTIER_SWARM_REFERENCE_ORACLE_PLAN_KIND,
|
|
1330
|
+
version: FRONTIER_SWARM_REFERENCE_ORACLE_PLAN_VERSION,
|
|
1331
|
+
id: input.id ?? 'swarm-reference-oracle-plan:' + stableHash([input.serviceId, input.subject, input.fixtureId, targets, input.window, generatedAt]),
|
|
1332
|
+
...(input.serviceId ? { serviceId: input.serviceId } : {}),
|
|
1333
|
+
...(input.subject ? { subject: input.subject } : {}),
|
|
1334
|
+
...(input.fixtureId ? { fixtureId: input.fixtureId } : {}),
|
|
1335
|
+
generatedAt,
|
|
1336
|
+
targets,
|
|
1337
|
+
...(input.window ? { window: normalizeReferenceWindow(input.window) } : {}),
|
|
1338
|
+
watchpoints: (input.watchpoints ?? []).map(normalizeWatchpoint),
|
|
1339
|
+
artifactKinds: uniqueStrings(input.artifactKinds ?? []),
|
|
1340
|
+
...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
|
|
1341
|
+
};
|
|
1342
|
+
}
|
|
1343
|
+
export function createSwarmReferenceOracleResponse(input = {}) {
|
|
1344
|
+
const generatedAt = input.generatedAt ?? Date.now();
|
|
1345
|
+
return {
|
|
1346
|
+
kind: FRONTIER_SWARM_REFERENCE_ORACLE_RESPONSE_KIND,
|
|
1347
|
+
version: FRONTIER_SWARM_REFERENCE_ORACLE_RESPONSE_VERSION,
|
|
1348
|
+
id: input.id ?? 'swarm-reference-oracle-response:' + stableHash([input.planId, input.status, input.targetResults, input.divergence, generatedAt]),
|
|
1349
|
+
...(input.planId ? { planId: input.planId } : {}),
|
|
1350
|
+
status: input.status ?? (input.divergence ? 'failed' : 'pending'),
|
|
1351
|
+
...(input.subject ? { subject: input.subject } : {}),
|
|
1352
|
+
generatedAt,
|
|
1353
|
+
targetResults: (input.targetResults ?? []).map((target) => ({
|
|
1354
|
+
targetId: target.targetId,
|
|
1355
|
+
status: target.status ?? 'pending',
|
|
1356
|
+
artifacts: normalizeNamedRefs(target.artifacts ?? [], 'reference-oracle-artifact'),
|
|
1357
|
+
...(toJsonObject(target.metadata) ? { metadata: toJsonObject(target.metadata) } : {})
|
|
1358
|
+
})),
|
|
1359
|
+
...(input.divergence ? { divergence: createSwarmDivergenceReport(input.divergence) } : {}),
|
|
1360
|
+
...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
|
|
1361
|
+
};
|
|
1362
|
+
}
|
|
1363
|
+
export function createSwarmArtifactRoutingPlan(input = {}) {
|
|
1364
|
+
const generatedAt = input.generatedAt ?? Date.now();
|
|
1365
|
+
const artifacts = [
|
|
1366
|
+
...normalizeNamedRefs(input.artifacts ?? [], 'artifact'),
|
|
1367
|
+
...(input.bundles ?? []).flatMap((bundle) => bundle.evidencePaths.map((path) => normalizeNamedRef({ path, kind: evidenceKindFromPath(path), role: bundle.disposition }, 'evidence')))
|
|
1368
|
+
];
|
|
1369
|
+
const hints = (input.hints ?? []).map(normalizeRoutingHint);
|
|
1370
|
+
const routes = artifacts.map((artifact) => {
|
|
1371
|
+
const matched = hints.filter((hint) => ((hint.artifactKind === undefined || hint.artifactKind === artifact.kind)
|
|
1372
|
+
&& (hint.pathPattern === undefined || (artifact.path ?? artifact.uri ?? '').includes(hint.pathPattern))));
|
|
1373
|
+
const bucket = matched[0]?.bucket ?? defaultArtifactBucket(artifact);
|
|
1374
|
+
return {
|
|
1375
|
+
artifact,
|
|
1376
|
+
bucket,
|
|
1377
|
+
...(matched[0]?.lane ? { lane: matched[0].lane } : {}),
|
|
1378
|
+
reasons: uniqueStrings(matched.map((hint) => hint.reason))
|
|
1379
|
+
};
|
|
1380
|
+
});
|
|
1381
|
+
const byBucket = groupIds(routes.map((route) => ({ id: route.artifact.id, bucket: route.bucket })), (route) => route.bucket);
|
|
1382
|
+
return {
|
|
1383
|
+
kind: FRONTIER_SWARM_ARTIFACT_ROUTING_PLAN_KIND,
|
|
1384
|
+
version: FRONTIER_SWARM_ARTIFACT_ROUTING_PLAN_VERSION,
|
|
1385
|
+
id: input.id ?? 'swarm-artifact-routing-plan:' + stableHash([routes, generatedAt]),
|
|
1386
|
+
generatedAt,
|
|
1387
|
+
routes,
|
|
1388
|
+
byBucket,
|
|
1389
|
+
summary: {
|
|
1390
|
+
routeCount: routes.length,
|
|
1391
|
+
bucketCount: Object.keys(byBucket).length
|
|
1392
|
+
},
|
|
1393
|
+
...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
|
|
1394
|
+
};
|
|
1395
|
+
}
|
|
1396
|
+
export function createSwarmFixtureCatalog(input = {}) {
|
|
1397
|
+
const generatedAt = input.generatedAt ?? Date.now();
|
|
1398
|
+
const fixtures = (input.fixtures ?? []).map(normalizeFixture);
|
|
1399
|
+
const byTag = {};
|
|
1400
|
+
for (const fixture of fixtures) {
|
|
1401
|
+
for (const tag of fixture.tags)
|
|
1402
|
+
byTag[tag] = uniqueStrings([...(byTag[tag] ?? []), fixture.id]);
|
|
1403
|
+
}
|
|
1404
|
+
return {
|
|
1405
|
+
kind: FRONTIER_SWARM_FIXTURE_CATALOG_KIND,
|
|
1406
|
+
version: FRONTIER_SWARM_FIXTURE_CATALOG_VERSION,
|
|
1407
|
+
id: input.id ?? 'swarm-fixture-catalog:' + stableHash([fixtures, generatedAt]),
|
|
1408
|
+
generatedAt,
|
|
1409
|
+
fixtures,
|
|
1410
|
+
byTag,
|
|
1411
|
+
summary: {
|
|
1412
|
+
fixtureCount: fixtures.length,
|
|
1413
|
+
tagCount: Object.keys(byTag).length
|
|
1414
|
+
},
|
|
1415
|
+
...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
|
|
1416
|
+
};
|
|
1417
|
+
}
|
|
1418
|
+
export function createSwarmProgressModel(input = {}) {
|
|
1419
|
+
const generatedAt = input.generatedAt ?? Date.now();
|
|
1420
|
+
const items = (input.items ?? []).map((item) => ({
|
|
1421
|
+
id: item.id,
|
|
1422
|
+
...(item.surface ? { surface: item.surface } : {}),
|
|
1423
|
+
status: item.status ?? 'not-started',
|
|
1424
|
+
evidencePaths: uniqueStrings(item.evidencePaths ?? []),
|
|
1425
|
+
blockers: uniqueStrings(item.blockers ?? []),
|
|
1426
|
+
...(toJsonObject(item.metadata) ? { metadata: toJsonObject(item.metadata) } : {})
|
|
1427
|
+
}));
|
|
1428
|
+
const byStatus = groupIds(items, (item) => item.status);
|
|
1429
|
+
return {
|
|
1430
|
+
kind: FRONTIER_SWARM_PROGRESS_MODEL_KIND,
|
|
1431
|
+
version: FRONTIER_SWARM_PROGRESS_MODEL_VERSION,
|
|
1432
|
+
id: input.id ?? 'swarm-progress-model:' + stableHash([items, generatedAt]),
|
|
1433
|
+
generatedAt,
|
|
1434
|
+
items,
|
|
1435
|
+
byStatus,
|
|
1436
|
+
summary: {
|
|
1437
|
+
itemCount: items.length,
|
|
1438
|
+
acceptedCount: byStatus.accepted?.length ?? 0,
|
|
1439
|
+
blockedCount: byStatus.blocked?.length ?? 0
|
|
1440
|
+
},
|
|
1441
|
+
...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
|
|
1442
|
+
};
|
|
1443
|
+
}
|
|
1444
|
+
export function createSwarmAutoReviewReport(input = {}) {
|
|
1445
|
+
const generatedAt = input.generatedAt ?? Date.now();
|
|
1446
|
+
const derived = (input.bundles ?? []).flatMap((bundle) => deriveAutoReviewFindings(bundle, generatedAt));
|
|
1447
|
+
const findings = [...derived, ...(input.findings ?? []).map((finding) => normalizeAutoReviewFinding(finding, generatedAt))];
|
|
1448
|
+
const byKind = groupObjects(findings, (finding) => finding.kind);
|
|
1449
|
+
return {
|
|
1450
|
+
kind: FRONTIER_SWARM_AUTO_REVIEW_REPORT_KIND,
|
|
1451
|
+
version: FRONTIER_SWARM_AUTO_REVIEW_REPORT_VERSION,
|
|
1452
|
+
id: input.id ?? 'swarm-auto-review-report:' + stableHash([findings, generatedAt]),
|
|
1453
|
+
generatedAt,
|
|
1454
|
+
findings,
|
|
1455
|
+
byKind,
|
|
1456
|
+
summary: {
|
|
1457
|
+
findingCount: findings.length,
|
|
1458
|
+
highSeverityCount: findings.filter((finding) => finding.severity === 'error' || finding.severity === 'critical').length
|
|
1459
|
+
},
|
|
1460
|
+
...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
|
|
1461
|
+
};
|
|
1462
|
+
}
|
|
1463
|
+
export function createSwarmRebaseReport(input = {}) {
|
|
1464
|
+
const generatedAt = input.generatedAt ?? Date.now();
|
|
1465
|
+
const fromIndex = input.mergeIndex?.entries.map((entry) => ({
|
|
1466
|
+
jobId: entry.jobId,
|
|
1467
|
+
status: entry.staleAgainstHead ? 'stale-evidence' : entry.conflictingJobIds.length ? 'semantic-overlap' : 'clean-apply',
|
|
1468
|
+
reasons: uniqueStrings([...entry.reasons, ...entry.conflictKeys])
|
|
1469
|
+
})) ?? [];
|
|
1470
|
+
const fromBundles = !input.mergeIndex && input.bundles ? input.bundles.map((bundle) => ({
|
|
1471
|
+
jobId: bundle.jobId,
|
|
1472
|
+
status: bundle.staleAgainstHead ? 'stale-evidence' : 'clean-apply',
|
|
1473
|
+
reasons: [...bundle.reasons]
|
|
1474
|
+
})) : [];
|
|
1475
|
+
const entries = [...fromIndex, ...fromBundles, ...(input.entries ?? [])].map((entry) => ({
|
|
1476
|
+
jobId: entry.jobId,
|
|
1477
|
+
status: entry.status ?? 'clean-apply',
|
|
1478
|
+
reasons: uniqueStrings(entry.reasons ?? []),
|
|
1479
|
+
...('metadata' in entry && toJsonObject(entry.metadata) ? { metadata: toJsonObject(entry.metadata) } : {})
|
|
1480
|
+
}));
|
|
1481
|
+
const byStatus = groupIds(entries, (entry) => entry.status);
|
|
1482
|
+
return {
|
|
1483
|
+
kind: FRONTIER_SWARM_REBASE_REPORT_KIND,
|
|
1484
|
+
version: FRONTIER_SWARM_REBASE_REPORT_VERSION,
|
|
1485
|
+
id: input.id ?? 'swarm-rebase-report:' + stableHash([input.currentHead, entries, generatedAt]),
|
|
1486
|
+
...(input.currentHead ? { currentHead: input.currentHead } : {}),
|
|
1487
|
+
generatedAt,
|
|
1488
|
+
entries,
|
|
1489
|
+
byStatus,
|
|
1490
|
+
summary: {
|
|
1491
|
+
entryCount: entries.length,
|
|
1492
|
+
cleanCount: byStatus['clean-apply']?.length ?? 0,
|
|
1493
|
+
conflictCount: (byStatus['textual-conflict']?.length ?? 0) + (byStatus['semantic-overlap']?.length ?? 0),
|
|
1494
|
+
staleCount: byStatus['stale-evidence']?.length ?? 0
|
|
1495
|
+
},
|
|
1496
|
+
...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
|
|
1497
|
+
};
|
|
1498
|
+
}
|
|
1499
|
+
export function createSwarmUsageGovernor(input = {}) {
|
|
1500
|
+
const generatedAt = input.generatedAt ?? Date.now();
|
|
1501
|
+
return {
|
|
1502
|
+
kind: FRONTIER_SWARM_USAGE_GOVERNOR_KIND,
|
|
1503
|
+
version: FRONTIER_SWARM_USAGE_GOVERNOR_VERSION,
|
|
1504
|
+
id: input.id ?? 'swarm-usage-governor:' + stableHash([input.maxWorkers, input.maxTokensByLane, input.maxCostUsd, input.retryBudget, generatedAt]),
|
|
1505
|
+
generatedAt,
|
|
1506
|
+
...(positiveNumber(input.maxWorkers) ? { maxWorkers: Math.floor(input.maxWorkers) } : {}),
|
|
1507
|
+
maxTokensByLane: { ...(input.maxTokensByLane ?? {}) },
|
|
1508
|
+
...(positiveNumber(input.maxCostUsd) ? { maxCostUsd: input.maxCostUsd } : {}),
|
|
1509
|
+
retryBudget: Math.max(0, Math.floor(input.retryBudget ?? 0)),
|
|
1510
|
+
stopConditions: uniqueStrings(input.stopConditions ?? []),
|
|
1511
|
+
preferStaticWhenLowBudget: input.preferStaticWhenLowBudget ?? true,
|
|
1512
|
+
...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
|
|
1513
|
+
};
|
|
1514
|
+
}
|
|
1515
|
+
export function checkSwarmUsageGovernor(governorInput, usage = {}) {
|
|
1516
|
+
const governor = isSwarmUsageGovernor(governorInput) ? governorInput : createSwarmUsageGovernor(governorInput);
|
|
1517
|
+
const reasons = [];
|
|
1518
|
+
if (governor.maxWorkers !== undefined && (usage.activeWorkers ?? 0) > governor.maxWorkers)
|
|
1519
|
+
reasons.push('max-workers');
|
|
1520
|
+
if (governor.maxCostUsd !== undefined && (usage.costUsd ?? 0) > governor.maxCostUsd)
|
|
1521
|
+
reasons.push('max-cost-usd');
|
|
1522
|
+
if ((usage.retriesUsed ?? 0) > governor.retryBudget)
|
|
1523
|
+
reasons.push('retry-budget');
|
|
1524
|
+
for (const [lane, maxTokens] of Object.entries(governor.maxTokensByLane)) {
|
|
1525
|
+
if ((usage.tokensByLane?.[lane] ?? 0) > maxTokens)
|
|
1526
|
+
reasons.push(`max-tokens:${lane}`);
|
|
1527
|
+
}
|
|
1528
|
+
return {
|
|
1529
|
+
ok: reasons.length === 0,
|
|
1530
|
+
reasons,
|
|
1531
|
+
...(governor.maxWorkers !== undefined ? { recommendedMaxWorkers: Math.max(1, governor.maxWorkers - (reasons.length ? 1 : 0)) } : {}),
|
|
1532
|
+
preferStatic: governor.preferStaticWhenLowBudget && reasons.some((reason) => reason.startsWith('max-tokens') || reason === 'max-cost-usd')
|
|
1533
|
+
};
|
|
1534
|
+
}
|
|
1535
|
+
export function createSwarmLanePlaybook(input) {
|
|
1536
|
+
const generatedAt = input.generatedAt ?? Date.now();
|
|
1537
|
+
const successful = (input.successfulBundles ?? []).filter((bundle) => bundle.status === 'completed' || bundle.status === 'verified' || bundle.autoMergeable);
|
|
1538
|
+
const hotPaths = createSwarmHotspotReport({ bundles: successful, threshold: 2, generatedAt }).entries
|
|
1539
|
+
.filter((entry) => entry.touchCount >= 2)
|
|
1540
|
+
.map((entry) => entry.path);
|
|
1541
|
+
return {
|
|
1542
|
+
kind: FRONTIER_SWARM_LANE_PLAYBOOK_KIND,
|
|
1543
|
+
version: FRONTIER_SWARM_LANE_PLAYBOOK_VERSION,
|
|
1544
|
+
id: input.id ?? 'swarm-lane-playbook:' + stableHash([input.lane, successful.map((bundle) => bundle.jobId), input.notes, generatedAt]),
|
|
1545
|
+
lane: normalizeId(input.lane, 'playbook lane'),
|
|
1546
|
+
title: input.title ?? `${titleFromId(input.lane)} Playbook`,
|
|
1547
|
+
generatedAt,
|
|
1548
|
+
notes: uniqueStrings(input.notes ?? []),
|
|
1549
|
+
commands: normalizeCommands(input.commands ?? []),
|
|
1550
|
+
avoidInvestigating: uniqueStrings(input.avoidInvestigating ?? []),
|
|
1551
|
+
evidencePatterns: uniqueStrings(input.evidencePatterns ?? successful.flatMap((bundle) => bundle.evidencePaths)),
|
|
1552
|
+
successfulJobIds: uniqueStrings(successful.map((bundle) => bundle.jobId)),
|
|
1553
|
+
hotPaths,
|
|
1554
|
+
changedRegions: uniqueStrings(successful.flatMap((bundle) => bundle.changedRegions)),
|
|
1555
|
+
...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
|
|
1556
|
+
};
|
|
1557
|
+
}
|
|
1558
|
+
export function createSwarmPatchStackPlan(input) {
|
|
1559
|
+
const generatedAt = input.generatedAt ?? Date.now();
|
|
1560
|
+
const maxStackSize = Math.max(1, Math.floor(input.maxStackSize ?? 8));
|
|
1561
|
+
const groups = new Map();
|
|
1562
|
+
for (const entry of input.index.entries) {
|
|
1563
|
+
const key = patchStackKey(entry);
|
|
1564
|
+
groups.set(key, [...(groups.get(key) ?? []), entry]);
|
|
1565
|
+
}
|
|
1566
|
+
const stacks = [];
|
|
1567
|
+
for (const [key, entries] of Array.from(groups.entries()).sort((left, right) => left[0].localeCompare(right[0]))) {
|
|
1568
|
+
const sorted = [...entries].sort((left, right) => riskRank(left.riskLevel) - riskRank(right.riskLevel) || left.jobId.localeCompare(right.jobId));
|
|
1569
|
+
for (let index = 0; index < sorted.length; index += maxStackSize) {
|
|
1570
|
+
const slice = sorted.slice(index, index + maxStackSize);
|
|
1571
|
+
const jobIds = slice.map((entry) => entry.jobId);
|
|
1572
|
+
const conflicts = input.index.conflicts.filter((conflict) => conflict.jobIds.some((jobId) => jobIds.includes(jobId)));
|
|
1573
|
+
stacks.push({
|
|
1574
|
+
id: 'swarm-patch-stack:' + stableHash([input.index.id, key, index, jobIds]),
|
|
1575
|
+
title: titleFromId(key),
|
|
1576
|
+
...(slice[0]?.lane ? { lane: slice[0].lane } : {}),
|
|
1577
|
+
jobIds,
|
|
1578
|
+
changedPaths: uniqueStrings(slice.flatMap((entry) => entry.changedPaths)),
|
|
1579
|
+
changedRegions: uniqueStrings(slice.flatMap((entry) => entry.changedRegions)),
|
|
1580
|
+
riskLevels: uniqueStrings(slice.map((entry) => entry.riskLevel)),
|
|
1581
|
+
dispositions: uniqueStrings(slice.map((entry) => entry.disposition)),
|
|
1582
|
+
conflicts,
|
|
1583
|
+
gateHints: uniqueStrings(slice.flatMap((entry) => entry.evidencePaths.filter((file) => file.endsWith('.json') || file.endsWith('.jsonl'))))
|
|
1584
|
+
});
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
return {
|
|
1588
|
+
kind: FRONTIER_SWARM_PATCH_STACK_PLAN_KIND,
|
|
1589
|
+
version: FRONTIER_SWARM_PATCH_STACK_PLAN_VERSION,
|
|
1590
|
+
id: input.id ?? 'swarm-patch-stack-plan:' + stableHash([input.index.id, stacks, generatedAt]),
|
|
1591
|
+
mergeIndexId: input.index.id,
|
|
1592
|
+
generatedAt,
|
|
1593
|
+
stacks,
|
|
1594
|
+
summary: {
|
|
1595
|
+
stackCount: stacks.length,
|
|
1596
|
+
jobCount: input.index.entries.length,
|
|
1597
|
+
conflictedStackCount: stacks.filter((stack) => stack.conflicts.length > 0).length
|
|
1598
|
+
},
|
|
1599
|
+
...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
|
|
1600
|
+
};
|
|
1601
|
+
}
|
|
429
1602
|
export function resolveSwarmCompute(manifestInput, taskInput) {
|
|
430
1603
|
const compiled = compileSwarm(manifestInput);
|
|
431
1604
|
const task = isSwarmTask(taskInput) ? taskInput : normalizeTask(taskInput);
|
|
@@ -474,6 +1647,7 @@ export function createSwarmSchedule(input) {
|
|
|
474
1647
|
const runningByLane = countBy(runningJobs.map((job) => job.lane));
|
|
475
1648
|
const runningByKey = countBy(runningJobs.map((job) => job.concurrencyKey));
|
|
476
1649
|
const runningByCompute = countBy(runningJobs.map((job) => job.compute));
|
|
1650
|
+
const runningByResource = resourceUsageFromScheduled(runningJobs);
|
|
477
1651
|
const ready = [];
|
|
478
1652
|
const blocked = [];
|
|
479
1653
|
const sortedJobs = [...plan.jobs].sort((left, right) => left.priority - right.priority || left.id.localeCompare(right.id));
|
|
@@ -495,6 +1669,9 @@ export function createSwarmSchedule(input) {
|
|
|
495
1669
|
if ((runningByCompute[job.compute.id] ?? 0) >= computeMax)
|
|
496
1670
|
reasons.push('compute-capacity');
|
|
497
1671
|
const scheduled = scheduleJob(job, dependencyIds);
|
|
1672
|
+
for (const resource of resourceQuotaViolations(scheduled, runningByResource, limits.resourceQuotas)) {
|
|
1673
|
+
reasons.push(`resource-capacity:${resource}`);
|
|
1674
|
+
}
|
|
498
1675
|
if (reasons.length) {
|
|
499
1676
|
blocked.push({ ...scheduled, reasons, waitingFor });
|
|
500
1677
|
continue;
|
|
@@ -507,6 +1684,7 @@ export function createSwarmSchedule(input) {
|
|
|
507
1684
|
runningByLane[job.lane] = (runningByLane[job.lane] ?? 0) + 1;
|
|
508
1685
|
runningByKey[job.concurrencyKey] = (runningByKey[job.concurrencyKey] ?? 0) + 1;
|
|
509
1686
|
runningByCompute[job.compute.id] = (runningByCompute[job.compute.id] ?? 0) + 1;
|
|
1687
|
+
addResourceUsage(runningByResource, scheduled);
|
|
510
1688
|
}
|
|
511
1689
|
return {
|
|
512
1690
|
kind: FRONTIER_SWARM_SCHEDULE_KIND,
|
|
@@ -530,6 +1708,46 @@ export function createSwarmSchedule(input) {
|
|
|
530
1708
|
}
|
|
531
1709
|
};
|
|
532
1710
|
}
|
|
1711
|
+
export function createSwarmSchedulerRecommendations(input) {
|
|
1712
|
+
const generatedAt = input.generatedAt ?? Date.now();
|
|
1713
|
+
const recommendations = [];
|
|
1714
|
+
const blockedByReason = new Map();
|
|
1715
|
+
for (const job of input.schedule.blocked) {
|
|
1716
|
+
for (const reason of job.reasons)
|
|
1717
|
+
blockedByReason.set(reason, [...(blockedByReason.get(reason) ?? []), job]);
|
|
1718
|
+
}
|
|
1719
|
+
for (const [reason, jobs] of blockedByReason) {
|
|
1720
|
+
const resource = reason.startsWith('resource-capacity:') ? reason.slice('resource-capacity:'.length) : undefined;
|
|
1721
|
+
recommendations.push({
|
|
1722
|
+
id: 'swarm-scheduler-recommendation:' + stableHash([input.schedule.id, reason, jobs.map((job) => job.jobId), generatedAt]),
|
|
1723
|
+
reason,
|
|
1724
|
+
jobIds: jobs.map((job) => job.jobId).sort(),
|
|
1725
|
+
...(resource ? { resource } : {}),
|
|
1726
|
+
...(jobs[0]?.lane ? { lane: jobs[0].lane } : {}),
|
|
1727
|
+
action: schedulerActionForReason(reason),
|
|
1728
|
+
priority: schedulerPriorityForReason(reason)
|
|
1729
|
+
});
|
|
1730
|
+
}
|
|
1731
|
+
for (const conflict of input.mergeIndex?.conflicts ?? []) {
|
|
1732
|
+
recommendations.push({
|
|
1733
|
+
id: 'swarm-scheduler-recommendation:' + stableHash([input.schedule.id, conflict.key, conflict.jobIds, generatedAt]),
|
|
1734
|
+
reason: `merge-conflict:${conflict.kind}`,
|
|
1735
|
+
jobIds: [...conflict.jobIds],
|
|
1736
|
+
action: 'serialize-conflicting-surface-or-split-ownership',
|
|
1737
|
+
priority: 15
|
|
1738
|
+
});
|
|
1739
|
+
}
|
|
1740
|
+
return {
|
|
1741
|
+
kind: FRONTIER_SWARM_SCHEDULER_RECOMMENDATIONS_KIND,
|
|
1742
|
+
version: FRONTIER_SWARM_SCHEDULER_RECOMMENDATIONS_VERSION,
|
|
1743
|
+
id: input.id ?? 'swarm-scheduler-recommendations:' + stableHash([input.schedule.id, recommendations, generatedAt]),
|
|
1744
|
+
scheduleId: input.schedule.id,
|
|
1745
|
+
generatedAt,
|
|
1746
|
+
recommendations: recommendations.sort((left, right) => left.priority - right.priority || left.id.localeCompare(right.id)),
|
|
1747
|
+
summary: { recommendationCount: recommendations.length },
|
|
1748
|
+
...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
|
|
1749
|
+
};
|
|
1750
|
+
}
|
|
533
1751
|
export function createSwarmLeases(input) {
|
|
534
1752
|
const now = input.now ?? Date.now();
|
|
535
1753
|
const leaseMs = Math.max(1, Math.floor(input.leaseMs ?? 900000));
|
|
@@ -813,7 +2031,8 @@ function normalizeScheduleLimits(manifest, options) {
|
|
|
813
2031
|
...(positiveNumber(options.maxReadyJobs) ? { maxReadyJobs: Math.floor(options.maxReadyJobs) } : {}),
|
|
814
2032
|
maxLaneConcurrency: { ...maxLaneConcurrency, ...(options.maxLaneConcurrency ?? {}) },
|
|
815
2033
|
maxConcurrencyKeyConcurrency: { ...(options.maxConcurrencyKeyConcurrency ?? {}) },
|
|
816
|
-
maxComputeConcurrency: { ...(options.maxComputeConcurrency ?? {}) }
|
|
2034
|
+
maxComputeConcurrency: { ...(options.maxComputeConcurrency ?? {}) },
|
|
2035
|
+
resourceQuotas: normalizeResourceQuotas(options.resourceQuotas ?? {})
|
|
817
2036
|
};
|
|
818
2037
|
}
|
|
819
2038
|
function mergeScheduleLimits(base, override) {
|
|
@@ -821,7 +2040,8 @@ function mergeScheduleLimits(base, override) {
|
|
|
821
2040
|
maxReadyJobs: positiveNumber(override.maxReadyJobs) ? Math.floor(override.maxReadyJobs) : base.maxReadyJobs,
|
|
822
2041
|
maxLaneConcurrency: { ...base.maxLaneConcurrency, ...(override.maxLaneConcurrency ?? {}) },
|
|
823
2042
|
maxConcurrencyKeyConcurrency: { ...base.maxConcurrencyKeyConcurrency, ...(override.maxConcurrencyKeyConcurrency ?? {}) },
|
|
824
|
-
maxComputeConcurrency: { ...base.maxComputeConcurrency, ...(override.maxComputeConcurrency ?? {}) }
|
|
2043
|
+
maxComputeConcurrency: { ...base.maxComputeConcurrency, ...(override.maxComputeConcurrency ?? {}) },
|
|
2044
|
+
resourceQuotas: { ...base.resourceQuotas, ...normalizeResourceQuotas(override.resourceQuotas ?? {}) }
|
|
825
2045
|
};
|
|
826
2046
|
}
|
|
827
2047
|
function scheduleJob(job, dependsOn = job.dependsOn) {
|
|
@@ -837,6 +2057,63 @@ function scheduleJob(job, dependsOn = job.dependsOn) {
|
|
|
837
2057
|
...(job.resourceRequirements ? { resourceRequirements: cloneJsonValue(job.resourceRequirements) } : {})
|
|
838
2058
|
};
|
|
839
2059
|
}
|
|
2060
|
+
function normalizeResourceQuotas(input) {
|
|
2061
|
+
const quotas = {};
|
|
2062
|
+
for (const [resource, value] of Object.entries(input)) {
|
|
2063
|
+
if (positiveNumber(value))
|
|
2064
|
+
quotas[resource] = value;
|
|
2065
|
+
}
|
|
2066
|
+
return quotas;
|
|
2067
|
+
}
|
|
2068
|
+
function resourceUsageFromScheduled(jobs) {
|
|
2069
|
+
const usage = {};
|
|
2070
|
+
for (const job of jobs)
|
|
2071
|
+
addResourceUsage(usage, job);
|
|
2072
|
+
return usage;
|
|
2073
|
+
}
|
|
2074
|
+
function addResourceUsage(usage, job) {
|
|
2075
|
+
for (const [resource, amount] of Object.entries(job.resourceRequirements?.resources ?? {})) {
|
|
2076
|
+
usage[resource] = (usage[resource] ?? 0) + amount;
|
|
2077
|
+
}
|
|
2078
|
+
if (job.resourceRequirements?.browser?.required) {
|
|
2079
|
+
if (job.resourceRequirements.resources.browser === undefined)
|
|
2080
|
+
usage.browser = (usage.browser ?? 0) + 1;
|
|
2081
|
+
if (job.resourceRequirements.resources['browser-port'] === undefined)
|
|
2082
|
+
usage['browser-port'] = (usage['browser-port'] ?? 0) + 1;
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
2085
|
+
function resourceQuotaViolations(job, usage, quotas) {
|
|
2086
|
+
const next = { ...usage };
|
|
2087
|
+
addResourceUsage(next, job);
|
|
2088
|
+
return Object.entries(quotas)
|
|
2089
|
+
.filter(([resource, quota]) => (next[resource] ?? 0) > quota)
|
|
2090
|
+
.map(([resource]) => resource)
|
|
2091
|
+
.sort();
|
|
2092
|
+
}
|
|
2093
|
+
function schedulerActionForReason(reason) {
|
|
2094
|
+
if (reason.startsWith('resource-capacity:'))
|
|
2095
|
+
return 'lower-concurrency-or-add-resource-pool';
|
|
2096
|
+
if (reason === 'lane-capacity')
|
|
2097
|
+
return 'increase-lane-capacity-or-split-lane';
|
|
2098
|
+
if (reason === 'compute-capacity')
|
|
2099
|
+
return 'increase-compute-capacity-or-use-another-compute';
|
|
2100
|
+
if (reason === 'concurrency-key-capacity')
|
|
2101
|
+
return 'serialize-or-split-concurrency-key';
|
|
2102
|
+
if (reason === 'waiting-for-dependencies')
|
|
2103
|
+
return 'prioritize-dependency-chain';
|
|
2104
|
+
if (reason === 'ready-capacity')
|
|
2105
|
+
return 'raise-ready-window-or-drain-ready-jobs';
|
|
2106
|
+
return 'review-scheduler-blocker';
|
|
2107
|
+
}
|
|
2108
|
+
function schedulerPriorityForReason(reason) {
|
|
2109
|
+
if (reason.startsWith('resource-capacity:'))
|
|
2110
|
+
return 10;
|
|
2111
|
+
if (reason === 'waiting-for-dependencies')
|
|
2112
|
+
return 20;
|
|
2113
|
+
if (reason === 'ready-capacity')
|
|
2114
|
+
return 25;
|
|
2115
|
+
return 30;
|
|
2116
|
+
}
|
|
840
2117
|
function normalizeBudget(input = {}) {
|
|
841
2118
|
return {
|
|
842
2119
|
...(positiveNumber(input.maxCostUsd) ? { maxCostUsd: input.maxCostUsd } : {}),
|
|
@@ -900,31 +2177,469 @@ function selectReviewers(pool, required, salt) {
|
|
|
900
2177
|
return sorted.slice(0, Math.min(required, sorted.length));
|
|
901
2178
|
}
|
|
902
2179
|
function conflictMap(results) {
|
|
903
|
-
const
|
|
904
|
-
for (
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
2180
|
+
const conflicts = new Map();
|
|
2181
|
+
for (let leftIndex = 0; leftIndex < results.length; leftIndex += 1) {
|
|
2182
|
+
for (let rightIndex = leftIndex + 1; rightIndex < results.length; rightIndex += 1) {
|
|
2183
|
+
const left = results[leftIndex];
|
|
2184
|
+
const right = results[rightIndex];
|
|
2185
|
+
if (!left || !right || pairConflictKeys(left, right).length === 0)
|
|
2186
|
+
continue;
|
|
2187
|
+
const leftConflicts = conflicts.get(left.jobId) ?? new Set();
|
|
2188
|
+
const rightConflicts = conflicts.get(right.jobId) ?? new Set();
|
|
2189
|
+
leftConflicts.add(right.jobId);
|
|
2190
|
+
rightConflicts.add(left.jobId);
|
|
2191
|
+
conflicts.set(left.jobId, leftConflicts);
|
|
2192
|
+
conflicts.set(right.jobId, rightConflicts);
|
|
912
2193
|
}
|
|
913
2194
|
}
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
2195
|
+
return conflicts;
|
|
2196
|
+
}
|
|
2197
|
+
function queueOverlayStatusFromBundle(bundle) {
|
|
2198
|
+
if (bundle.staleAgainstHead || bundle.disposition === 'stale-against-head')
|
|
2199
|
+
return 'stale-against-head';
|
|
2200
|
+
if (bundle.disposition === 'rejected' || bundle.disposition === 'blocked' || bundle.status === 'failed' || bundle.commandsFailed.length > 0) {
|
|
2201
|
+
return 'failed-evidence';
|
|
2202
|
+
}
|
|
2203
|
+
if (bundle.disposition === 'auto-mergeable' && bundle.autoMergeable)
|
|
2204
|
+
return 'ready-to-apply';
|
|
2205
|
+
if (bundle.disposition === 'needs-port')
|
|
2206
|
+
return 'needs-human-port';
|
|
2207
|
+
if (bundle.disposition === 'discovery-only')
|
|
2208
|
+
return 'discovery-only';
|
|
2209
|
+
if (bundle.mergeReadiness === 'blocked')
|
|
2210
|
+
return 'blocked';
|
|
2211
|
+
if (bundle.mergeReadiness === 'rejected')
|
|
2212
|
+
return 'rejected';
|
|
2213
|
+
return 'unknown';
|
|
2214
|
+
}
|
|
2215
|
+
function queueOverlayStatusFromResult(result) {
|
|
2216
|
+
if (result.mergeDisposition === 'stale-against-head')
|
|
2217
|
+
return 'stale-against-head';
|
|
2218
|
+
if (result.status === 'failed' || result.exitCode !== undefined && result.exitCode !== 0 || result.ownershipViolations.length > 0)
|
|
2219
|
+
return 'failed-evidence';
|
|
2220
|
+
if (result.mergeDisposition === 'auto-mergeable')
|
|
2221
|
+
return 'ready-to-apply';
|
|
2222
|
+
if (result.mergeDisposition === 'needs-port')
|
|
2223
|
+
return 'needs-human-port';
|
|
2224
|
+
if (result.mergeDisposition === 'discovery-only')
|
|
2225
|
+
return 'discovery-only';
|
|
2226
|
+
if (result.status === 'blocked')
|
|
2227
|
+
return 'blocked';
|
|
2228
|
+
return 'unknown';
|
|
2229
|
+
}
|
|
2230
|
+
function queueJobStatusFromOverlay(entry) {
|
|
2231
|
+
if (entry.status === 'ready-to-apply' || entry.status === 'discovery-only')
|
|
2232
|
+
return 'completed';
|
|
2233
|
+
if (entry.status === 'needs-human-port')
|
|
2234
|
+
return 'blocked';
|
|
2235
|
+
if (entry.status === 'failed-evidence' || entry.status === 'rejected' || entry.status === 'stale-against-head')
|
|
2236
|
+
return 'failed';
|
|
2237
|
+
if (entry.status === 'blocked')
|
|
2238
|
+
return 'blocked';
|
|
2239
|
+
return 'completed';
|
|
2240
|
+
}
|
|
2241
|
+
function groupOverlayEntries(entries) {
|
|
2242
|
+
const out = {};
|
|
2243
|
+
for (const entry of entries)
|
|
2244
|
+
out[entry.queueItemId] = [...(out[entry.queueItemId] ?? []), entry];
|
|
2245
|
+
for (const key of Object.keys(out)) {
|
|
2246
|
+
out[key] = [...(out[key] ?? [])].sort((left, right) => right.generatedAt - left.generatedAt || left.jobId.localeCompare(right.jobId));
|
|
2247
|
+
}
|
|
2248
|
+
return out;
|
|
2249
|
+
}
|
|
2250
|
+
function mergeIndexConflictKeys(bundle) {
|
|
2251
|
+
return bundle.changedRegions.length
|
|
2252
|
+
? bundle.changedRegions.map((region) => `region:${region}`).sort()
|
|
2253
|
+
: bundle.changedPaths.map((file) => `path:${file}`).sort();
|
|
2254
|
+
}
|
|
2255
|
+
function createMergeIndexConflicts(entries) {
|
|
2256
|
+
const conflicts = [];
|
|
2257
|
+
for (let leftIndex = 0; leftIndex < entries.length; leftIndex += 1) {
|
|
2258
|
+
for (let rightIndex = leftIndex + 1; rightIndex < entries.length; rightIndex += 1) {
|
|
2259
|
+
const left = entries[leftIndex];
|
|
2260
|
+
const right = entries[rightIndex];
|
|
2261
|
+
if (!left || !right)
|
|
2262
|
+
continue;
|
|
2263
|
+
const keys = pairConflictKeys(left, right);
|
|
2264
|
+
for (const key of keys) {
|
|
2265
|
+
const kind = key.startsWith('region:') ? 'region' : 'path';
|
|
2266
|
+
const value = key.slice(key.indexOf(':') + 1);
|
|
2267
|
+
conflicts.push({
|
|
2268
|
+
jobIds: [left.jobId, right.jobId].sort(),
|
|
2269
|
+
key,
|
|
2270
|
+
kind,
|
|
2271
|
+
...(kind === 'region' ? { region: value } : { path: value })
|
|
2272
|
+
});
|
|
923
2273
|
}
|
|
924
|
-
conflicts.set(jobId, set);
|
|
925
2274
|
}
|
|
926
2275
|
}
|
|
927
|
-
|
|
2276
|
+
const deduped = new Map();
|
|
2277
|
+
for (const conflict of conflicts)
|
|
2278
|
+
deduped.set(`${conflict.key}:${conflict.jobIds.join(',')}`, conflict);
|
|
2279
|
+
return Array.from(deduped.values()).sort((left, right) => left.key.localeCompare(right.key) || left.jobIds.join(',').localeCompare(right.jobIds.join(',')));
|
|
2280
|
+
}
|
|
2281
|
+
function pairConflictKeys(left, right) {
|
|
2282
|
+
if (left.changedRegions.length > 0 && right.changedRegions.length > 0) {
|
|
2283
|
+
const rightRegions = new Set(right.changedRegions);
|
|
2284
|
+
return left.changedRegions.filter((region) => rightRegions.has(region)).map((region) => `region:${region}`).sort();
|
|
2285
|
+
}
|
|
2286
|
+
const rightPaths = new Set(right.changedPaths);
|
|
2287
|
+
return left.changedPaths.filter((file) => rightPaths.has(file)).map((file) => `path:${file}`).sort();
|
|
2288
|
+
}
|
|
2289
|
+
function groupJobIdsBy(items, key) {
|
|
2290
|
+
const out = {};
|
|
2291
|
+
for (const item of items)
|
|
2292
|
+
out[key(item)] = uniqueStrings([...(out[key(item)] ?? []), item.jobId]);
|
|
2293
|
+
return out;
|
|
2294
|
+
}
|
|
2295
|
+
function groupJobIdsByMany(items, key) {
|
|
2296
|
+
const out = {};
|
|
2297
|
+
for (const item of items) {
|
|
2298
|
+
for (const value of key(item))
|
|
2299
|
+
out[value] = uniqueStrings([...(out[value] ?? []), item.jobId]);
|
|
2300
|
+
}
|
|
2301
|
+
return out;
|
|
2302
|
+
}
|
|
2303
|
+
function suggestedModuleId(file) {
|
|
2304
|
+
const base = file.split('/').pop()?.replace(/\.[^.]+$/, '') ?? file;
|
|
2305
|
+
return slug(base).replace(/-/g, '.');
|
|
2306
|
+
}
|
|
2307
|
+
function reviewerLaneReasons(entry) {
|
|
2308
|
+
const reasons = [];
|
|
2309
|
+
if (entry.conflictingJobIds.length)
|
|
2310
|
+
reasons.push('conflicting-changes');
|
|
2311
|
+
if (entry.riskLevel === 'high')
|
|
2312
|
+
reasons.push('high-risk');
|
|
2313
|
+
if (entry.disposition !== 'auto-mergeable')
|
|
2314
|
+
reasons.push(entry.disposition);
|
|
2315
|
+
if (!entry.autoMergeable)
|
|
2316
|
+
reasons.push('not-auto-mergeable');
|
|
2317
|
+
if (entry.staleAgainstHead)
|
|
2318
|
+
reasons.push('stale-against-head');
|
|
2319
|
+
return uniqueStrings(reasons);
|
|
2320
|
+
}
|
|
2321
|
+
function hashBucket(value, buckets) {
|
|
2322
|
+
const hex = stableHash(value).split(':')[1] ?? '0';
|
|
2323
|
+
return parseInt(hex, 16) % Math.max(1, buckets);
|
|
2324
|
+
}
|
|
2325
|
+
function normalizeOracleArtifact(input) {
|
|
2326
|
+
return {
|
|
2327
|
+
id: normalizeId(input.id, 'oracle artifact id'),
|
|
2328
|
+
path: normalizeId(input.path, 'oracle artifact path'),
|
|
2329
|
+
kind: input.kind ?? 'oracle',
|
|
2330
|
+
...(input.command ? { command: typeof input.command === 'string' ? normalizeCommands([input.command])[0] : normalizeCommands([input.command])[0] } : {}),
|
|
2331
|
+
...(input.hash ? { hash: input.hash } : {}),
|
|
2332
|
+
...(input.sourceRef ? { sourceRef: input.sourceRef } : {}),
|
|
2333
|
+
tags: uniqueStrings(input.tags ?? []),
|
|
2334
|
+
...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
|
|
2335
|
+
};
|
|
2336
|
+
}
|
|
2337
|
+
function normalizeCommand(input) {
|
|
2338
|
+
return normalizeCommands([input])[0];
|
|
2339
|
+
}
|
|
2340
|
+
function normalizeNamedRef(input, fallbackKind) {
|
|
2341
|
+
if (typeof input === 'string' || typeof input === 'number') {
|
|
2342
|
+
const value = String(input);
|
|
2343
|
+
return { id: value, kind: fallbackKind, path: value, tags: [] };
|
|
2344
|
+
}
|
|
2345
|
+
const path = input.path ?? input.uri;
|
|
2346
|
+
const id = input.id ?? path ?? stableHash(input);
|
|
2347
|
+
return {
|
|
2348
|
+
id,
|
|
2349
|
+
kind: input.kind ?? fallbackKind,
|
|
2350
|
+
...(input.path ? { path: input.path } : {}),
|
|
2351
|
+
...(input.uri ? { uri: input.uri } : {}),
|
|
2352
|
+
...(input.role ? { role: input.role } : {}),
|
|
2353
|
+
...(input.hash ? { hash: input.hash } : {}),
|
|
2354
|
+
...(positiveNumber(input.bytes) ? { bytes: Math.floor(input.bytes) } : {}),
|
|
2355
|
+
tags: uniqueStrings(input.tags ?? []),
|
|
2356
|
+
...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
|
|
2357
|
+
};
|
|
2358
|
+
}
|
|
2359
|
+
function normalizeNamedRefs(input, fallbackKind) {
|
|
2360
|
+
return input.map((entry) => normalizeNamedRef(entry, fallbackKind)).sort((left, right) => left.id.localeCompare(right.id));
|
|
2361
|
+
}
|
|
2362
|
+
function normalizeSeedRefs(input) {
|
|
2363
|
+
return input.map((entry) => normalizeNamedRef(entry, 'seed')).sort((left, right) => left.id.localeCompare(right.id));
|
|
2364
|
+
}
|
|
2365
|
+
function normalizeParityComparator(input) {
|
|
2366
|
+
const title = input.title ?? titleFromId(input.id ?? input.path ?? 'comparator');
|
|
2367
|
+
return {
|
|
2368
|
+
id: input.id ?? 'swarm-parity-comparator:' + stableHash([title, input.status, input.expected, input.actual, input.path, input.operationIndex]),
|
|
2369
|
+
title,
|
|
2370
|
+
status: input.status ?? (input.expected !== undefined && input.actual !== undefined && stableStringify(input.expected) === stableStringify(input.actual) ? 'passed' : 'unknown'),
|
|
2371
|
+
...(input.expected !== undefined ? { expected: toJsonValue(input.expected) } : {}),
|
|
2372
|
+
...(input.actual !== undefined ? { actual: toJsonValue(input.actual) } : {}),
|
|
2373
|
+
...(input.path ? { path: input.path } : {}),
|
|
2374
|
+
...(input.operationIndex !== undefined ? { operationIndex: Math.max(0, Math.floor(input.operationIndex)) } : {}),
|
|
2375
|
+
evidenceRefs: normalizeNamedRefs(input.evidenceRefs ?? [], 'evidence'),
|
|
2376
|
+
...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
|
|
2377
|
+
};
|
|
2378
|
+
}
|
|
2379
|
+
function inferParityStatus(comparators) {
|
|
2380
|
+
if (comparators.some((comparator) => comparator.status === 'failed'))
|
|
2381
|
+
return 'failed';
|
|
2382
|
+
if (comparators.some((comparator) => comparator.status === 'blocked'))
|
|
2383
|
+
return 'blocked';
|
|
2384
|
+
if (comparators.length > 0 && comparators.every((comparator) => comparator.status === 'passed'))
|
|
2385
|
+
return 'passed';
|
|
2386
|
+
return 'pending';
|
|
2387
|
+
}
|
|
2388
|
+
function normalizeWatchpoint(input) {
|
|
2389
|
+
const title = input.title ?? titleFromId(input.id ?? input.path ?? input.selector ?? input.target ?? 'watchpoint');
|
|
2390
|
+
return {
|
|
2391
|
+
id: input.id ?? 'swarm-watchpoint:' + stableHash([input.target, input.path, input.selector, input.operator, input.value, input.action]),
|
|
2392
|
+
title,
|
|
2393
|
+
...(input.target ? { target: input.target } : {}),
|
|
2394
|
+
...(input.path ? { path: input.path } : {}),
|
|
2395
|
+
...(input.selector ? { selector: input.selector } : {}),
|
|
2396
|
+
operator: input.operator ?? 'changed',
|
|
2397
|
+
...(input.value !== undefined ? { value: toJsonValue(input.value) } : {}),
|
|
2398
|
+
action: input.action ?? 'capture',
|
|
2399
|
+
...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
|
|
2400
|
+
};
|
|
2401
|
+
}
|
|
2402
|
+
function normalizeBottleneckSource(input) {
|
|
2403
|
+
if (input.kind === FRONTIER_SWARM_MERGE_BUNDLE_KIND) {
|
|
2404
|
+
const bundle = input;
|
|
2405
|
+
return {
|
|
2406
|
+
jobId: bundle.jobId,
|
|
2407
|
+
lane: bundle.lane,
|
|
2408
|
+
status: bundle.status,
|
|
2409
|
+
reasons: bundle.reasons,
|
|
2410
|
+
evidencePaths: bundle.evidencePaths,
|
|
2411
|
+
changedPaths: bundle.changedPaths,
|
|
2412
|
+
text: bundle.title,
|
|
2413
|
+
metadata: bundle.metadata
|
|
2414
|
+
};
|
|
2415
|
+
}
|
|
2416
|
+
if ('text' in input || 'reasons' in input || 'lane' in input)
|
|
2417
|
+
return input;
|
|
2418
|
+
const result = input;
|
|
2419
|
+
if (result.jobId) {
|
|
2420
|
+
return {
|
|
2421
|
+
jobId: result.jobId,
|
|
2422
|
+
status: result.status,
|
|
2423
|
+
reasons: result.error !== undefined ? [stringifyError(result.error)] : [],
|
|
2424
|
+
verification: result.verification,
|
|
2425
|
+
evidencePaths: result.evidencePaths,
|
|
2426
|
+
changedPaths: result.changedPaths,
|
|
2427
|
+
text: result.lastMessage,
|
|
2428
|
+
metadata: result.metadata
|
|
2429
|
+
};
|
|
2430
|
+
}
|
|
2431
|
+
return input;
|
|
2432
|
+
}
|
|
2433
|
+
function routeForBottleneck(kind, lane) {
|
|
2434
|
+
if (kind === 'missing-oracle')
|
|
2435
|
+
return { lane: lane ?? 'verification', workKind: 'oracle', priority: 20 };
|
|
2436
|
+
if (kind === 'flaky-harness')
|
|
2437
|
+
return { lane: lane ?? 'evidence', workKind: 'harness', priority: 25 };
|
|
2438
|
+
if (kind === 'merge-review')
|
|
2439
|
+
return { lane: lane ?? 'review', workKind: 'review', priority: 10 };
|
|
2440
|
+
if (kind === 'instrumentation-overhead')
|
|
2441
|
+
return { lane: lane ?? 'diagnostics', workKind: 'instrumentation', priority: 30 };
|
|
2442
|
+
if (kind === 'performance')
|
|
2443
|
+
return { lane: lane ?? 'performance', workKind: 'benchmark', priority: 35 };
|
|
2444
|
+
if (kind === 'correctness')
|
|
2445
|
+
return { lane: lane ?? 'implementation', workKind: 'debug', priority: 15 };
|
|
2446
|
+
return { ...(lane ? { lane } : {}), workKind: 'triage', priority: 50 };
|
|
2447
|
+
}
|
|
2448
|
+
function evidenceKindFromPath(path) {
|
|
2449
|
+
const lower = path.toLowerCase();
|
|
2450
|
+
if (lower.endsWith('.patch') || lower.endsWith('.diff'))
|
|
2451
|
+
return 'patch';
|
|
2452
|
+
if (lower.endsWith('.jsonl'))
|
|
2453
|
+
return 'jsonl';
|
|
2454
|
+
if (lower.endsWith('.json'))
|
|
2455
|
+
return 'json';
|
|
2456
|
+
if (lower.includes('trace'))
|
|
2457
|
+
return 'trace';
|
|
2458
|
+
if (lower.includes('screenshot'))
|
|
2459
|
+
return 'screenshot';
|
|
2460
|
+
if (lower.includes('last-message'))
|
|
2461
|
+
return 'handoff';
|
|
2462
|
+
return 'evidence';
|
|
2463
|
+
}
|
|
2464
|
+
function normalizeEvidenceIndexEntry(input) {
|
|
2465
|
+
const generatedAt = input.generatedAt ?? Date.now();
|
|
2466
|
+
return {
|
|
2467
|
+
id: input.id ?? 'swarm-evidence-entry:' + stableHash([input.jobId, input.queueItemId, input.path, input.topic, input.kind, generatedAt]),
|
|
2468
|
+
...(input.jobId ? { jobId: input.jobId } : {}),
|
|
2469
|
+
...(input.queueItemId ? { queueItemId: input.queueItemId } : {}),
|
|
2470
|
+
...(input.lane ? { lane: input.lane } : {}),
|
|
2471
|
+
...(input.topic ? { topic: input.topic } : {}),
|
|
2472
|
+
...(input.path ? { path: input.path } : {}),
|
|
2473
|
+
kind: input.kind ?? (input.path ? evidenceKindFromPath(input.path) : 'evidence'),
|
|
2474
|
+
status: input.status ?? 'unknown',
|
|
2475
|
+
confidence: clamp01(input.confidence ?? 0.5),
|
|
2476
|
+
tags: uniqueStrings(input.tags ?? []),
|
|
2477
|
+
facets: normalizeFacets(input.facets ?? {}),
|
|
2478
|
+
generatedAt,
|
|
2479
|
+
...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
|
|
2480
|
+
};
|
|
2481
|
+
}
|
|
2482
|
+
function matchesFacetQuery(facets, query) {
|
|
2483
|
+
if (!query)
|
|
2484
|
+
return true;
|
|
2485
|
+
return Object.entries(query).every(([key, value]) => facets[key] === value);
|
|
2486
|
+
}
|
|
2487
|
+
function normalizeFacets(input) {
|
|
2488
|
+
return Object.fromEntries(Object.entries(input).filter(([, value]) => ['string', 'number', 'boolean'].includes(typeof value)));
|
|
2489
|
+
}
|
|
2490
|
+
function normalizeBlackboardEntry(input) {
|
|
2491
|
+
const generatedAt = input.generatedAt ?? Date.now();
|
|
2492
|
+
const topic = input.topic ?? input.kind ?? 'general';
|
|
2493
|
+
return {
|
|
2494
|
+
id: input.id ?? 'swarm-blackboard-entry:' + stableHash([input.kind, topic, input.text, input.sourceIds, generatedAt]),
|
|
2495
|
+
kind: input.kind ?? 'fact',
|
|
2496
|
+
topic,
|
|
2497
|
+
status: input.status ?? 'fresh',
|
|
2498
|
+
text: input.text ?? '',
|
|
2499
|
+
...(input.lane ? { lane: input.lane } : {}),
|
|
2500
|
+
...(input.jobId ? { jobId: input.jobId } : {}),
|
|
2501
|
+
...(input.owner ? { owner: input.owner } : {}),
|
|
2502
|
+
confidence: input.confidence ?? 'medium',
|
|
2503
|
+
sourceIds: uniqueStrings(input.sourceIds ?? []),
|
|
2504
|
+
paths: uniqueStrings(input.paths ?? []),
|
|
2505
|
+
tags: uniqueStrings(input.tags ?? []),
|
|
2506
|
+
supersedes: uniqueStrings(input.supersedes ?? []),
|
|
2507
|
+
generatedAt,
|
|
2508
|
+
...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
|
|
2509
|
+
};
|
|
2510
|
+
}
|
|
2511
|
+
function normalizeReferenceWindow(input) {
|
|
2512
|
+
return {
|
|
2513
|
+
...(input.start !== undefined ? { start: Math.max(0, Math.floor(input.start)) } : {}),
|
|
2514
|
+
...(input.end !== undefined ? { end: Math.max(0, Math.floor(input.end)) } : {}),
|
|
2515
|
+
...(input.focus ? { focus: input.focus } : {}),
|
|
2516
|
+
...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
|
|
2517
|
+
};
|
|
2518
|
+
}
|
|
2519
|
+
function normalizeRoutingHint(input) {
|
|
2520
|
+
return {
|
|
2521
|
+
...(input.artifactKind ? { artifactKind: input.artifactKind } : {}),
|
|
2522
|
+
...(input.pathPattern ? { pathPattern: input.pathPattern } : {}),
|
|
2523
|
+
...(input.lane ? { lane: input.lane } : {}),
|
|
2524
|
+
bucket: input.bucket ?? 'needs-human-port',
|
|
2525
|
+
reason: input.reason ?? 'matched-routing-hint',
|
|
2526
|
+
...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
|
|
2527
|
+
};
|
|
2528
|
+
}
|
|
2529
|
+
function defaultArtifactBucket(artifact) {
|
|
2530
|
+
if (artifact.kind === 'patch')
|
|
2531
|
+
return 'ready-to-apply';
|
|
2532
|
+
if (artifact.kind === 'handoff' || artifact.kind === 'trace' || artifact.kind === 'jsonl')
|
|
2533
|
+
return 'discovery-only';
|
|
2534
|
+
return 'needs-human-port';
|
|
2535
|
+
}
|
|
2536
|
+
function normalizeFixture(input) {
|
|
2537
|
+
return {
|
|
2538
|
+
id: normalizeId(input.id, 'fixture id'),
|
|
2539
|
+
title: input.title ?? titleFromId(input.id),
|
|
2540
|
+
...(input.description ? { description: input.description } : {}),
|
|
2541
|
+
...(input.state !== undefined ? { state: toJsonValue(input.state) } : {}),
|
|
2542
|
+
artifacts: normalizeNamedRefs(input.artifacts ?? [], 'fixture-artifact'),
|
|
2543
|
+
setupCommands: normalizeCommands(input.setupCommands ?? []),
|
|
2544
|
+
tags: uniqueStrings(input.tags ?? []),
|
|
2545
|
+
...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
|
|
2546
|
+
};
|
|
2547
|
+
}
|
|
2548
|
+
function deriveAutoReviewFindings(bundle, generatedAt) {
|
|
2549
|
+
const findings = [];
|
|
2550
|
+
if (bundle.ownershipViolations.length) {
|
|
2551
|
+
findings.push(normalizeAutoReviewFinding({
|
|
2552
|
+
jobId: bundle.jobId,
|
|
2553
|
+
kind: 'ownership-violation',
|
|
2554
|
+
severity: 'error',
|
|
2555
|
+
message: 'Bundle changed paths outside its ownership lease.',
|
|
2556
|
+
paths: bundle.ownershipViolations,
|
|
2557
|
+
evidencePaths: bundle.evidencePaths
|
|
2558
|
+
}, generatedAt));
|
|
2559
|
+
}
|
|
2560
|
+
if (bundle.evidencePaths.length === 0 && bundle.changedPaths.length > 0) {
|
|
2561
|
+
findings.push(normalizeAutoReviewFinding({
|
|
2562
|
+
jobId: bundle.jobId,
|
|
2563
|
+
kind: 'missing-evidence',
|
|
2564
|
+
severity: 'warning',
|
|
2565
|
+
message: 'Patch bundle has changed paths but no evidence paths.',
|
|
2566
|
+
paths: bundle.changedPaths
|
|
2567
|
+
}, generatedAt));
|
|
2568
|
+
}
|
|
2569
|
+
if (bundle.changedPaths.length > 12) {
|
|
2570
|
+
findings.push(normalizeAutoReviewFinding({
|
|
2571
|
+
jobId: bundle.jobId,
|
|
2572
|
+
kind: 'overlarge-patch',
|
|
2573
|
+
severity: 'warning',
|
|
2574
|
+
message: 'Patch bundle touches many files and should be split or reviewed manually.',
|
|
2575
|
+
paths: bundle.changedPaths,
|
|
2576
|
+
evidencePaths: bundle.evidencePaths
|
|
2577
|
+
}, generatedAt));
|
|
2578
|
+
}
|
|
2579
|
+
return findings;
|
|
2580
|
+
}
|
|
2581
|
+
function normalizeAutoReviewFinding(input, generatedAt) {
|
|
2582
|
+
return {
|
|
2583
|
+
id: input.id ?? 'swarm-auto-review-finding:' + stableHash([input.jobId, input.kind, input.message, input.paths, generatedAt]),
|
|
2584
|
+
...(input.jobId ? { jobId: input.jobId } : {}),
|
|
2585
|
+
kind: input.kind ?? 'weak-evidence',
|
|
2586
|
+
severity: input.severity ?? 'warning',
|
|
2587
|
+
message: input.message,
|
|
2588
|
+
paths: uniqueStrings(input.paths ?? []),
|
|
2589
|
+
evidencePaths: uniqueStrings(input.evidencePaths ?? []),
|
|
2590
|
+
...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
|
|
2591
|
+
};
|
|
2592
|
+
}
|
|
2593
|
+
function isSwarmObservabilityPoint(value) {
|
|
2594
|
+
return !!value && typeof value === 'object' && value.kind === FRONTIER_SWARM_OBSERVABILITY_POINT_KIND;
|
|
2595
|
+
}
|
|
2596
|
+
function isSwarmInstrumentationBudget(value) {
|
|
2597
|
+
return !!value && typeof value === 'object' && value.kind === FRONTIER_SWARM_INSTRUMENTATION_BUDGET_KIND;
|
|
2598
|
+
}
|
|
2599
|
+
function isSwarmUsageGovernor(value) {
|
|
2600
|
+
return !!value && typeof value === 'object' && value.kind === FRONTIER_SWARM_USAGE_GOVERNOR_KIND;
|
|
2601
|
+
}
|
|
2602
|
+
function isSwarmRun(value) {
|
|
2603
|
+
return !!value && typeof value === 'object' && value.kind === FRONTIER_SWARM_RUN_KIND;
|
|
2604
|
+
}
|
|
2605
|
+
function groupObjects(items, key) {
|
|
2606
|
+
const out = {};
|
|
2607
|
+
for (const item of items) {
|
|
2608
|
+
const group = key(item);
|
|
2609
|
+
out[group] = [...(out[group] ?? []), item];
|
|
2610
|
+
}
|
|
2611
|
+
return out;
|
|
2612
|
+
}
|
|
2613
|
+
function clamp01(value) {
|
|
2614
|
+
if (!Number.isFinite(value))
|
|
2615
|
+
return 0;
|
|
2616
|
+
return Math.max(0, Math.min(1, value));
|
|
2617
|
+
}
|
|
2618
|
+
function groupArtifactIdsBy(artifacts, key) {
|
|
2619
|
+
const out = {};
|
|
2620
|
+
for (const artifact of artifacts) {
|
|
2621
|
+
for (const value of key(artifact))
|
|
2622
|
+
out[value] = uniqueStrings([...(out[value] ?? []), artifact.id]);
|
|
2623
|
+
}
|
|
2624
|
+
return out;
|
|
2625
|
+
}
|
|
2626
|
+
function patchStackKey(entry) {
|
|
2627
|
+
const lane = entry.lane ?? 'unassigned';
|
|
2628
|
+
if (entry.changedRegions.length)
|
|
2629
|
+
return `${lane}:${entry.changedRegions[0]}`;
|
|
2630
|
+
const firstPath = entry.changedPaths[0] ?? 'evidence-only';
|
|
2631
|
+
return `${lane}:${firstPath.split('/').slice(0, 2).join('/') || firstPath}`;
|
|
2632
|
+
}
|
|
2633
|
+
function riskRank(risk) {
|
|
2634
|
+
if (risk === 'low')
|
|
2635
|
+
return 0;
|
|
2636
|
+
if (risk === 'medium')
|
|
2637
|
+
return 1;
|
|
2638
|
+
if (risk === 'unknown')
|
|
2639
|
+
return 2;
|
|
2640
|
+
if (risk === 'high')
|
|
2641
|
+
return 3;
|
|
2642
|
+
return 4;
|
|
928
2643
|
}
|
|
929
2644
|
function groupMergeReadyJobs(ready, results) {
|
|
930
2645
|
const byJob = new Map(results.map((result) => [result.jobId, result]));
|
|
@@ -949,7 +2664,10 @@ function groupIds(items, key) {
|
|
|
949
2664
|
const out = {};
|
|
950
2665
|
for (const item of items) {
|
|
951
2666
|
const group = key(item);
|
|
952
|
-
|
|
2667
|
+
const id = item.jobId ?? item.id;
|
|
2668
|
+
if (!id)
|
|
2669
|
+
continue;
|
|
2670
|
+
out[group] = [...(out[group] ?? []), id];
|
|
953
2671
|
}
|
|
954
2672
|
for (const ids of Object.values(out))
|
|
955
2673
|
ids.sort();
|