@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/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 byPath = new Map();
904
- for (const result of results) {
905
- const keys = result.changedRegions.length
906
- ? result.changedRegions.map((region) => `region:${region}`)
907
- : result.changedPaths.map((file) => `file:${file}`);
908
- for (const key of keys) {
909
- const list = byPath.get(key) ?? [];
910
- list.push(result.jobId);
911
- byPath.set(key, list);
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
- const conflicts = new Map();
915
- for (const jobIds of byPath.values()) {
916
- if (jobIds.length < 2)
917
- continue;
918
- for (const jobId of jobIds) {
919
- const set = conflicts.get(jobId) ?? new Set();
920
- for (const other of jobIds) {
921
- if (other !== jobId)
922
- set.add(other);
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
- return conflicts;
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
- out[group] = [...(out[group] ?? []), item.jobId];
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();