@shapeshift-labs/frontier-swarm 0.5.21 → 0.5.23

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
@@ -1,3 +1,5 @@
1
+ import { createRunDashboardSnapshot, createRunEdgeEvent, createRunEvent, createRunNodeEvent, defineRunArtifact, defineRunAttempt, defineRunDecision, defineRunEvidence, defineRunLane, defineRunPatch, defineRunTask, defineRunVerification, linkRunNodes, replayRunEvents } from '@shapeshift-labs/frontier-run';
2
+ import { acquireSemanticLease, createSemanticLeaseState, defineSemanticLeaseScope, validateSemanticLeaseFence } from '@shapeshift-labs/frontier-lease';
1
3
  export const FRONTIER_SWARM_MANIFEST_KIND = 'frontier.swarm.manifest';
2
4
  export const FRONTIER_SWARM_MANIFEST_VERSION = 1;
3
5
  export const FRONTIER_SWARM_TASK_KIND = 'frontier.swarm.task';
@@ -10,6 +12,22 @@ export const FRONTIER_SWARM_EVENT_KIND = 'frontier.swarm.event';
10
12
  export const FRONTIER_SWARM_EVENT_VERSION = 1;
11
13
  export const FRONTIER_SWARM_EVENT_STREAM_KIND = 'frontier.swarm.event-stream';
12
14
  export const FRONTIER_SWARM_EVENT_STREAM_VERSION = 1;
15
+ export const FRONTIER_SWARM_RUN_GRAPH_KIND = 'frontier.swarm.run-graph';
16
+ export const FRONTIER_SWARM_RUN_GRAPH_VERSION = 1;
17
+ export const FRONTIER_SWARM_GRAPH_SNAPSHOT_KIND = 'frontier.swarm.graph-snapshot';
18
+ export const FRONTIER_SWARM_GRAPH_SNAPSHOT_VERSION = 1;
19
+ export const FRONTIER_SWARM_RUN_GRAPH_CHUNK_KIND = 'frontier.swarm.run-graph-chunk';
20
+ export const FRONTIER_SWARM_RUN_GRAPH_CHUNK_VERSION = 1;
21
+ export const FRONTIER_SWARM_GATE_RECORD_KIND = 'frontier.swarm.gate-record';
22
+ export const FRONTIER_SWARM_GATE_RECORD_VERSION = 1;
23
+ export const FRONTIER_SWARM_EVIDENCE_RECORD_KIND = 'frontier.swarm.evidence-record';
24
+ export const FRONTIER_SWARM_EVIDENCE_RECORD_VERSION = 1;
25
+ export const FRONTIER_SWARM_PATCH_EVENT_KIND = 'frontier.swarm.patch-event';
26
+ export const FRONTIER_SWARM_PATCH_EVENT_VERSION = 1;
27
+ export const FRONTIER_SWARM_REPLAY_RECORD_KIND = 'frontier.swarm.replay-record';
28
+ export const FRONTIER_SWARM_REPLAY_RECORD_VERSION = 1;
29
+ export const FRONTIER_SWARM_IMPROVEMENT_LOOP_KIND = 'frontier.swarm.improvement-loop';
30
+ export const FRONTIER_SWARM_IMPROVEMENT_LOOP_VERSION = 1;
13
31
  export const FRONTIER_SWARM_MAILBOX_KIND = 'frontier.swarm.mailbox';
14
32
  export const FRONTIER_SWARM_MAILBOX_VERSION = 1;
15
33
  export const FRONTIER_SWARM_PROOF_KIND = 'frontier.swarm.proof';
@@ -30,6 +48,10 @@ export const FRONTIER_SWARM_MERGE_PLAN_KIND = 'frontier.swarm.merge-plan';
30
48
  export const FRONTIER_SWARM_MERGE_PLAN_VERSION = 1;
31
49
  export const FRONTIER_SWARM_MERGE_BUNDLE_KIND = 'frontier.swarm.merge-bundle';
32
50
  export const FRONTIER_SWARM_MERGE_BUNDLE_VERSION = 1;
51
+ export const FRONTIER_SWARM_SEMANTIC_CHANGE_KIND = 'frontier.swarm.semantic-change';
52
+ export const FRONTIER_SWARM_SEMANTIC_CHANGE_VERSION = 1;
53
+ export const FRONTIER_SWARM_MERGE_CANDIDATE_KIND = 'frontier.swarm.merge-candidate';
54
+ export const FRONTIER_SWARM_MERGE_CANDIDATE_VERSION = 1;
33
55
  export const FRONTIER_SWARM_QUEUE_OVERLAY_KIND = 'frontier.swarm.queue-overlay';
34
56
  export const FRONTIER_SWARM_QUEUE_OVERLAY_VERSION = 1;
35
57
  export const FRONTIER_SWARM_MERGE_INDEX_KIND = 'frontier.swarm.merge-index';
@@ -96,6 +118,8 @@ export const FRONTIER_SWARM_QUEUE_OUTCOME_MODEL_KIND = 'frontier.swarm.queue-out
96
118
  export const FRONTIER_SWARM_QUEUE_OUTCOME_MODEL_VERSION = 1;
97
119
  export const FRONTIER_SWARM_TERMINAL_STATE_RECONCILIATION_KIND = 'frontier.swarm.terminal-state-reconciliation';
98
120
  export const FRONTIER_SWARM_TERMINAL_STATE_RECONCILIATION_VERSION = 1;
121
+ export const FRONTIER_SWARM_TERMINAL_OUTCOME_RECORD_KIND = 'frontier.swarm.terminal-outcome-record';
122
+ export const FRONTIER_SWARM_TERMINAL_OUTCOME_RECORD_VERSION = 1;
99
123
  export const FRONTIER_SWARM_PRIORITY_POLICY_KIND = 'frontier.swarm.priority-policy';
100
124
  export const FRONTIER_SWARM_PRIORITY_POLICY_VERSION = 1;
101
125
  export const FRONTIER_SWARM_MODEL_ROUTE_KIND = 'frontier.swarm.model-route';
@@ -118,6 +142,42 @@ export const FRONTIER_SWARM_VERIFICATION_CATEGORY_HINTS = [
118
142
  'browser',
119
143
  'oracle'
120
144
  ];
145
+ export const FRONTIER_SWARM_GRAPH_NODE_KINDS = [
146
+ 'intent',
147
+ 'task',
148
+ 'worker',
149
+ 'candidate',
150
+ 'evidence',
151
+ 'gate',
152
+ 'decision',
153
+ 'merge',
154
+ 'replay',
155
+ 'rsi'
156
+ ];
157
+ export const FRONTIER_SWARM_GRAPH_EDGE_KINDS = [
158
+ 'dependsOn',
159
+ 'blocks',
160
+ 'produces',
161
+ 'verifies',
162
+ 'conflictsWith',
163
+ 'supersedes',
164
+ 'mergesInto'
165
+ ];
166
+ export const FRONTIER_SWARM_MERGE_CANDIDATE_ADMISSION_STATUSES = [
167
+ 'safe',
168
+ 'safe-with-losses',
169
+ 'review-required',
170
+ 'blocked'
171
+ ];
172
+ export const FRONTIER_SWARM_MERGE_CANDIDATE_REASON_CODES = [
173
+ 'missing-sidecar',
174
+ 'empty-sidecar',
175
+ 'stale-source-hash',
176
+ 'symbol-conflict',
177
+ 'effect-conflict',
178
+ 'lossy-import',
179
+ 'tests-missing'
180
+ ];
121
181
  export const FRONTIER_SWARM_SEMANTIC_OWNERSHIP_EXPORT_STABLE_KEY_KIND = 'exported-declaration';
122
182
  export const FRONTIER_SWARM_SEMANTIC_OWNERSHIP_NAMESPACE_EXPORT_STABLE_KEY_KIND = 'namespace-export';
123
183
  export const FRONTIER_SWARM_SEMANTIC_OWNERSHIP_TYPE_STABLE_KEY_KIND = 'type-declaration';
@@ -176,6 +236,7 @@ export const FRONTIER_SWARM_TERMINAL_OUTCOME_LABELS = [
176
236
  'superseded',
177
237
  'evidence-only',
178
238
  'no-change',
239
+ 'research-complete',
179
240
  'generated-by-collector',
180
241
  'patch-missing',
181
242
  'bundle-missing',
@@ -186,6 +247,26 @@ export const FRONTIER_SWARM_TERMINAL_OUTCOME_LABELS = [
186
247
  'human-blocked',
187
248
  'coordinator-review'
188
249
  ];
250
+ export const FRONTIER_SWARM_TERMINAL_OUTCOME_STATUSES = [
251
+ 'applied',
252
+ 'rejected',
253
+ 'superseded',
254
+ 'no-change',
255
+ 'conflict',
256
+ 'human-needed',
257
+ 'research-complete',
258
+ 'rerun'
259
+ ];
260
+ export const FRONTIER_SWARM_TERMINAL_OUTCOME_REASON_CODES = [
261
+ 'accepted-by-admission',
262
+ 'failed-verification',
263
+ 'superseded-by-newer-output',
264
+ 'no-effective-change',
265
+ 'conflict-detected',
266
+ 'human-decision-required',
267
+ 'research-complete',
268
+ 'stale-rerun-required'
269
+ ];
189
270
  const DEFAULT_COMPLETED_STATUSES = ['completed', 'verified', 'done', 'verified-local-harness'];
190
271
  const DEFAULT_SWARM_EVENT_TYPES = [
191
272
  'swarm.started',
@@ -378,6 +459,1016 @@ export function createSwarmTaskSelection(manifestInput, taskInput, options = {})
378
459
  summary: summarizeTaskSelection(entries)
379
460
  };
380
461
  }
462
+ export function createSwarmRunGraph(input = {}) {
463
+ const generatedAt = input.generatedAt ?? Date.now();
464
+ const nodes = dedupeGraphNodes((input.nodes ?? []).map((node) => normalizeGraphNode(node, generatedAt)));
465
+ const knownNodeIds = new Set(nodes.map((node) => node.id));
466
+ const edges = dedupeGraphEdges((input.edges ?? [])
467
+ .map((edge) => normalizeGraphEdge(edge, generatedAt))
468
+ .filter((edge) => knownNodeIds.has(edge.from) && knownNodeIds.has(edge.to)));
469
+ const knownEdgeIds = new Set(edges.map((edge) => edge.id));
470
+ const events = (input.events ?? [])
471
+ .map((event) => normalizeGraphEvent(event, generatedAt))
472
+ .filter((event) => event.nodeId === undefined || knownNodeIds.has(event.nodeId))
473
+ .filter((event) => event.edgeId === undefined || knownEdgeIds.has(event.edgeId))
474
+ .sort((left, right) => left.timestamp - right.timestamp || left.id.localeCompare(right.id));
475
+ const graph = {
476
+ kind: FRONTIER_SWARM_RUN_GRAPH_KIND,
477
+ version: FRONTIER_SWARM_RUN_GRAPH_VERSION,
478
+ id: input.id ?? 'swarm-run-graph:' + stableHash([input.runId, nodes.map((node) => node.id), edges.map((edge) => edge.id)]),
479
+ ...(input.runId ? { runId: input.runId } : {}),
480
+ ...(input.title ? { title: input.title } : {}),
481
+ generatedAt,
482
+ nodes,
483
+ edges,
484
+ events,
485
+ summary: summarizeSwarmRunGraph(nodes, edges, events),
486
+ ...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
487
+ };
488
+ return graph;
489
+ }
490
+ export function normalizeSwarmRunGraph(input) {
491
+ if (input.kind === FRONTIER_SWARM_RUN_GRAPH_KIND) {
492
+ const graph = input;
493
+ return createSwarmRunGraph({
494
+ id: graph.id,
495
+ runId: graph.runId,
496
+ title: graph.title,
497
+ generatedAt: graph.generatedAt,
498
+ nodes: graph.nodes,
499
+ edges: graph.edges,
500
+ events: graph.events,
501
+ metadata: graph.metadata
502
+ });
503
+ }
504
+ return createSwarmRunGraph(input);
505
+ }
506
+ export function createSwarmGraphSnapshot(input) {
507
+ const graph = normalizeSwarmRunGraph(input.graph);
508
+ const generatedAt = input.generatedAt ?? Date.now();
509
+ return {
510
+ kind: FRONTIER_SWARM_GRAPH_SNAPSHOT_KIND,
511
+ version: FRONTIER_SWARM_GRAPH_SNAPSHOT_VERSION,
512
+ id: input.id ?? 'swarm-graph-snapshot:' + stableHash([graph.id, generatedAt, graph.summary]),
513
+ graphId: graph.id,
514
+ generatedAt,
515
+ summary: graph.summary,
516
+ graph,
517
+ ...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
518
+ };
519
+ }
520
+ export function createSwarmGateRecord(input) {
521
+ const metrics = toJsonObject(input.metrics);
522
+ const metadata = toJsonObject(input.metadata);
523
+ return {
524
+ kind: FRONTIER_SWARM_GATE_RECORD_KIND,
525
+ version: FRONTIER_SWARM_GATE_RECORD_VERSION,
526
+ id: input.id ?? 'swarm-gate:' + stableHash([input.type, input.command, input.path, input.jobId, input.taskId, input.startedAt, input.finishedAt]),
527
+ type: input.type,
528
+ status: input.status ?? 'unknown',
529
+ required: input.required ?? false,
530
+ ...(input.command ? { command: input.command } : {}),
531
+ ...(input.path ? { path: input.path } : {}),
532
+ ...(input.jobId ? { jobId: input.jobId } : {}),
533
+ ...(input.taskId ? { taskId: input.taskId } : {}),
534
+ ...(input.startedAt !== undefined ? { startedAt: input.startedAt } : {}),
535
+ ...(input.finishedAt !== undefined ? { finishedAt: input.finishedAt } : {}),
536
+ ...(input.failureReason ? { failureReason: input.failureReason } : {}),
537
+ ...(input.confidence ? { confidence: input.confidence } : {}),
538
+ ...(metrics ? { metrics } : {}),
539
+ ...(metadata ? { metadata } : {})
540
+ };
541
+ }
542
+ export function createSwarmEvidenceRecord(input) {
543
+ const metrics = toJsonObject(input.metrics);
544
+ const metadata = toJsonObject(input.metadata);
545
+ const producedAt = input.producedAt ?? Date.now();
546
+ return {
547
+ kind: FRONTIER_SWARM_EVIDENCE_RECORD_KIND,
548
+ version: FRONTIER_SWARM_EVIDENCE_RECORD_VERSION,
549
+ id: input.id ?? 'swarm-evidence:' + stableHash([input.type, input.path, input.ref, input.jobId, input.taskId, input.gateId]),
550
+ type: input.type,
551
+ ...(input.path ? { path: input.path } : {}),
552
+ ...(input.ref ? { ref: input.ref } : {}),
553
+ ...(input.jobId ? { jobId: input.jobId } : {}),
554
+ ...(input.taskId ? { taskId: input.taskId } : {}),
555
+ ...(input.gateId ? { gateId: input.gateId } : {}),
556
+ producedAt,
557
+ ...(input.confidence ? { confidence: input.confidence } : {}),
558
+ ...(metrics ? { metrics } : {}),
559
+ ...(metadata ? { metadata } : {})
560
+ };
561
+ }
562
+ export function createSwarmPatchEvent(input) {
563
+ const generatedAt = input.generatedAt ?? Date.now();
564
+ const graphRefs = normalizeSwarmGraphRefs(input.graphRefs ?? []);
565
+ const sequence = input.sequence !== undefined ? Math.max(0, Math.floor(input.sequence)) : undefined;
566
+ return {
567
+ kind: FRONTIER_SWARM_PATCH_EVENT_KIND,
568
+ version: FRONTIER_SWARM_PATCH_EVENT_VERSION,
569
+ id: input.id ?? 'swarm-patch-event:' + stableHash([input.replayRecordId, sequence, input.operation, input.path, input.from, input.hash]),
570
+ operation: input.operation,
571
+ path: input.path,
572
+ ...(input.from ? { from: input.from } : {}),
573
+ ...(input.hash ? { hash: input.hash } : {}),
574
+ ...(sequence !== undefined ? { sequence } : {}),
575
+ ...(input.value !== undefined ? { value: toJsonValue(input.value) } : {}),
576
+ ...(input.before !== undefined ? { before: toJsonValue(input.before) } : {}),
577
+ ...(input.after !== undefined ? { after: toJsonValue(input.after) } : {}),
578
+ ...(input.jobId ? { jobId: input.jobId } : {}),
579
+ ...(input.taskId ? { taskId: input.taskId } : {}),
580
+ ...(input.replayRecordId ? { replayRecordId: input.replayRecordId } : {}),
581
+ graphRefs,
582
+ generatedAt,
583
+ ...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
584
+ };
585
+ }
586
+ export function createSwarmReplayRecord(input = {}) {
587
+ const generatedAt = input.generatedAt ?? Date.now();
588
+ const title = input.title ?? titleFromId(input.id ?? input.subject ?? input.replayBundleId ?? 'replay record');
589
+ const evidenceRefs = normalizeNamedRefs(input.evidenceRefs ?? [], 'evidence');
590
+ const graphRefs = normalizeSwarmGraphRefs(input.graphRefs ?? []);
591
+ const replayId = input.id ?? 'swarm-replay-record:' + stableHash([
592
+ title,
593
+ input.status,
594
+ input.subject,
595
+ input.replayBundleId,
596
+ input.patchEvents ?? [],
597
+ evidenceRefs,
598
+ graphRefs,
599
+ input.jobId,
600
+ input.taskId
601
+ ]);
602
+ const patchEvents = (input.patchEvents ?? [])
603
+ .map((event, index) => normalizeSwarmPatchEvent(event, replayId, index))
604
+ .sort((left, right) => (left.sequence ?? Number.MAX_SAFE_INTEGER) - (right.sequence ?? Number.MAX_SAFE_INTEGER) || left.id.localeCompare(right.id));
605
+ return {
606
+ kind: FRONTIER_SWARM_REPLAY_RECORD_KIND,
607
+ version: FRONTIER_SWARM_REPLAY_RECORD_VERSION,
608
+ id: replayId,
609
+ title,
610
+ status: input.status ?? 'unknown',
611
+ ...(input.subject ? { subject: input.subject } : {}),
612
+ ...(input.replayBundleId ? { replayBundleId: input.replayBundleId } : {}),
613
+ patchEvents,
614
+ evidenceRefs,
615
+ graphRefs,
616
+ ...(input.jobId ? { jobId: input.jobId } : {}),
617
+ ...(input.taskId ? { taskId: input.taskId } : {}),
618
+ ...(input.startedAt !== undefined ? { startedAt: input.startedAt } : {}),
619
+ ...(input.finishedAt !== undefined ? { finishedAt: input.finishedAt } : {}),
620
+ generatedAt,
621
+ summary: {
622
+ patchEventCount: patchEvents.length,
623
+ evidenceRefCount: evidenceRefs.length,
624
+ graphRefCount: graphRefs.length
625
+ },
626
+ ...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
627
+ };
628
+ }
629
+ export function createSwarmImprovementLoop(input = {}) {
630
+ const generatedAt = input.generatedAt ?? Date.now();
631
+ const title = input.title ?? titleFromId(input.id ?? input.subject ?? 'improvement loop');
632
+ const replayRecordIds = uniqueStrings(input.replayRecordIds ?? []);
633
+ const patchEventIds = uniqueStrings(input.patchEventIds ?? []);
634
+ const evidenceRefs = normalizeNamedRefs(input.evidenceRefs ?? [], 'evidence');
635
+ const graphRefs = normalizeSwarmGraphRefs(input.graphRefs ?? []);
636
+ return {
637
+ kind: FRONTIER_SWARM_IMPROVEMENT_LOOP_KIND,
638
+ version: FRONTIER_SWARM_IMPROVEMENT_LOOP_VERSION,
639
+ id: input.id ?? 'swarm-improvement-loop:' + stableHash([
640
+ title,
641
+ input.status,
642
+ input.subject,
643
+ input.observation,
644
+ input.action,
645
+ input.result,
646
+ replayRecordIds,
647
+ patchEventIds,
648
+ evidenceRefs,
649
+ graphRefs,
650
+ input.jobId,
651
+ input.taskId
652
+ ]),
653
+ title,
654
+ status: input.status ?? inferImprovementLoopStatus(input),
655
+ ...(input.subject ? { subject: input.subject } : {}),
656
+ ...(input.observation !== undefined ? { observation: toJsonValue(input.observation) } : {}),
657
+ ...(input.action !== undefined ? { action: toJsonValue(input.action) } : {}),
658
+ ...(input.result !== undefined ? { result: toJsonValue(input.result) } : {}),
659
+ replayRecordIds,
660
+ patchEventIds,
661
+ evidenceRefs,
662
+ graphRefs,
663
+ ...(input.jobId ? { jobId: input.jobId } : {}),
664
+ ...(input.taskId ? { taskId: input.taskId } : {}),
665
+ generatedAt,
666
+ summary: {
667
+ replayRecordCount: replayRecordIds.length,
668
+ patchEventCount: patchEventIds.length,
669
+ evidenceRefCount: evidenceRefs.length,
670
+ graphRefCount: graphRefs.length
671
+ },
672
+ ...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
673
+ };
674
+ }
675
+ export function mapSwarmGateEvidenceToGraph(input) {
676
+ const generatedAt = input.generatedAt ?? Date.now();
677
+ const gateRecords = (input.gates ?? []).map((gate) => isSwarmGateRecord(gate) ? gate : createSwarmGateRecord(gate));
678
+ const evidenceRecords = (input.evidence ?? []).map((evidence) => isSwarmEvidenceRecord(evidence) ? evidence : createSwarmEvidenceRecord(evidence));
679
+ const nodes = [
680
+ ...gateRecords.map((gate) => ({
681
+ id: graphNodeId('gate', gate.id),
682
+ kind: 'gate',
683
+ title: gate.command ?? gate.path ?? gate.type,
684
+ status: gate.status,
685
+ jobId: gate.jobId,
686
+ taskId: gate.taskId,
687
+ path: gate.path,
688
+ generatedAt,
689
+ metadata: gate
690
+ })),
691
+ ...evidenceRecords.map((evidence) => ({
692
+ id: graphNodeId('evidence', evidence.id),
693
+ kind: 'evidence',
694
+ title: evidence.path ?? evidence.ref ?? evidence.type,
695
+ status: evidence.confidence,
696
+ jobId: evidence.jobId,
697
+ taskId: evidence.taskId,
698
+ path: evidence.path,
699
+ generatedAt,
700
+ metadata: evidence
701
+ }))
702
+ ];
703
+ const edges = [];
704
+ for (const evidence of evidenceRecords) {
705
+ if (evidence.gateId) {
706
+ edges.push({
707
+ kind: 'produces',
708
+ from: graphNodeId('gate', evidence.gateId),
709
+ to: graphNodeId('evidence', evidence.id),
710
+ generatedAt
711
+ });
712
+ edges.push({
713
+ kind: 'verifies',
714
+ from: graphNodeId('evidence', evidence.id),
715
+ to: graphNodeId('gate', evidence.gateId),
716
+ generatedAt
717
+ });
718
+ }
719
+ }
720
+ return createSwarmRunGraph({
721
+ runId: input.runId,
722
+ title: 'Gate and evidence graph',
723
+ generatedAt,
724
+ nodes,
725
+ edges
726
+ });
727
+ }
728
+ export function createSwarmGateEvidenceGraph(input) {
729
+ return mapSwarmGateEvidenceToGraph(input);
730
+ }
731
+ export function classifySwarmMergeCandidateAdmission(input = {}) {
732
+ const arrayInput = Array.isArray(input);
733
+ const objectInput = arrayInput
734
+ ? undefined
735
+ : input;
736
+ const status = objectInput ? normalizeOptionalString(objectInput.status) : undefined;
737
+ if (status)
738
+ return status;
739
+ const reasonCodes = arrayInput
740
+ ? normalizeSwarmMergeCandidateReasonCodes(input)
741
+ : normalizeSwarmMergeCandidateReasonCodes(objectInput?.reasonCodes ?? []);
742
+ if (reasonCodes.some((reason) => FRONTIER_SWARM_BLOCKING_MERGE_CANDIDATE_REASON_CODES.has(reason)))
743
+ return 'blocked';
744
+ if (reasonCodes.some((reason) => FRONTIER_SWARM_REVIEW_MERGE_CANDIDATE_REASON_CODES.has(reason)))
745
+ return 'review-required';
746
+ if (reasonCodes.includes('lossy-import'))
747
+ return 'safe-with-losses';
748
+ return 'safe';
749
+ }
750
+ export function createSwarmSemanticChange(input) {
751
+ if (isSwarmSemanticChange(input))
752
+ return cloneJsonValue(input);
753
+ const generatedAt = input.generatedAt ?? Date.now();
754
+ const symbolId = normalizeId(input.symbolId, 'semantic change symbol id');
755
+ const declarationKind = normalizeId(input.declarationKind, 'semantic change declaration kind');
756
+ const sourceSpan = normalizeSwarmSourceSpan(input.sourceSpan, {
757
+ sourcePath: input.sourcePath,
758
+ sourceHash: input.sourceHash,
759
+ expectedSourceHash: input.expectedSourceHash
760
+ });
761
+ const operation = normalizeOptionalString(input.operation) ?? 'unknown';
762
+ const derivedReasonCodes = deriveSwarmSemanticChangeReasonCodes(input, sourceSpan);
763
+ const reasonCodes = normalizeSwarmMergeCandidateReasonCodes([...(input.reasonCodes ?? []), ...derivedReasonCodes]);
764
+ const conflictReason = normalizeSwarmConflictReason(input.conflictReason, reasonCodes);
765
+ return {
766
+ kind: FRONTIER_SWARM_SEMANTIC_CHANGE_KIND,
767
+ version: FRONTIER_SWARM_SEMANTIC_CHANGE_VERSION,
768
+ id: input.id ?? 'swarm-semantic-change:' + stableHash([symbolId, declarationKind, sourceSpan, operation, reasonCodes]),
769
+ symbolId,
770
+ declarationKind,
771
+ sourceSpan,
772
+ operation,
773
+ confidence: normalizeSwarmSemanticConfidence(input.confidence),
774
+ ...(conflictReason ? { conflictReason } : {}),
775
+ status: classifySwarmMergeCandidateAdmission({ status: input.status, reasonCodes }),
776
+ reasonCodes,
777
+ generatedAt,
778
+ ...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
779
+ };
780
+ }
781
+ export function createSwarmMergeCandidate(input) {
782
+ if (isSwarmMergeCandidate(input))
783
+ return cloneJsonValue(input);
784
+ const generatedAt = input.generatedAt ?? Date.now();
785
+ const shorthandChange = input.symbolId || input.declarationKind || input.operation || input.sourceSpan || input.sourcePath
786
+ ? [{
787
+ symbolId: input.symbolId ?? 'unknown-symbol',
788
+ declarationKind: input.declarationKind ?? 'unknown',
789
+ operation: input.operation ?? 'unknown',
790
+ sourceSpan: input.sourceSpan,
791
+ sourcePath: input.sourcePath,
792
+ sourceHash: input.sourceHash,
793
+ expectedSourceHash: input.expectedSourceHash,
794
+ confidence: input.confidence,
795
+ conflictReason: input.conflictReason,
796
+ status: input.status,
797
+ reasonCodes: input.reasonCodes,
798
+ generatedAt
799
+ }]
800
+ : [];
801
+ const semanticChanges = [...(input.semanticChanges ?? []), ...(input.changes ?? []), ...shorthandChange]
802
+ .map((change) => createSwarmSemanticChange({ ...change, generatedAt: change.generatedAt ?? generatedAt }));
803
+ const primaryChange = semanticChanges[0];
804
+ const candidateSourceSpan = primaryChange?.sourceSpan ?? normalizeSwarmSourceSpan(input.sourceSpan, {
805
+ sourcePath: input.sourcePath,
806
+ sourceHash: input.sourceHash,
807
+ expectedSourceHash: input.expectedSourceHash
808
+ });
809
+ const derivedReasonCodes = deriveSwarmMergeCandidateReasonCodes(input, semanticChanges, candidateSourceSpan);
810
+ const reasonCodes = normalizeSwarmMergeCandidateReasonCodes([
811
+ ...(input.reasonCodes ?? []),
812
+ ...semanticChanges.flatMap((change) => change.reasonCodes),
813
+ ...derivedReasonCodes
814
+ ]);
815
+ const symbolIds = uniqueStrings([
816
+ input.symbolId,
817
+ ...semanticChanges.map((change) => change.symbolId)
818
+ ]);
819
+ const declarationKinds = uniqueStrings([
820
+ input.declarationKind,
821
+ ...semanticChanges.map((change) => change.declarationKind)
822
+ ]);
823
+ const changedPaths = uniqueStrings(semanticChanges.map((change) => change.sourceSpan.path).concat(candidateSourceSpan.path));
824
+ const conflictReason = normalizeSwarmConflictReason(input.conflictReason, reasonCodes);
825
+ const metadata = toJsonObject(input.metadata);
826
+ return {
827
+ kind: FRONTIER_SWARM_MERGE_CANDIDATE_KIND,
828
+ version: FRONTIER_SWARM_MERGE_CANDIDATE_VERSION,
829
+ id: input.id ?? 'swarm-merge-candidate:' + stableHash([input.jobId, input.taskId, symbolIds, declarationKinds, semanticChanges, reasonCodes]),
830
+ ...(input.jobId ? { jobId: input.jobId } : {}),
831
+ ...(input.taskId ? { taskId: input.taskId } : {}),
832
+ ...(input.lane ? { lane: input.lane } : {}),
833
+ ...(input.title ? { title: input.title } : {}),
834
+ ...(input.sidecarPath ? { sidecarPath: input.sidecarPath } : {}),
835
+ symbolId: symbolIds[0] ?? primaryChange?.symbolId ?? 'unknown-symbol',
836
+ symbolIds,
837
+ declarationKind: declarationKinds[0] ?? primaryChange?.declarationKind ?? 'unknown',
838
+ declarationKinds,
839
+ sourceSpan: candidateSourceSpan,
840
+ operation: input.operation ?? primaryChange?.operation ?? 'unknown',
841
+ confidence: normalizeSwarmSemanticConfidence(input.confidence ?? primaryChange?.confidence),
842
+ ...(conflictReason ? { conflictReason } : {}),
843
+ status: classifySwarmMergeCandidateAdmission({ status: input.status, reasonCodes }),
844
+ reasonCodes,
845
+ semanticChanges,
846
+ changedPaths,
847
+ evidencePaths: uniqueStrings(input.evidencePaths ?? []),
848
+ generatedAt,
849
+ summary: {
850
+ changeCount: semanticChanges.length,
851
+ symbolCount: symbolIds.length,
852
+ reasonCount: reasonCodes.length
853
+ },
854
+ ...(metadata ? { metadata } : {})
855
+ };
856
+ }
857
+ export function mapSwarmMergeCandidatesToGraph(input) {
858
+ const generatedAt = input.generatedAt ?? Date.now();
859
+ const candidates = input.candidates.map((candidate) => createSwarmMergeCandidate({ ...candidate, generatedAt: candidate.generatedAt ?? generatedAt }));
860
+ const includeChangeNodes = input.includeChangeNodes !== false;
861
+ const nodes = [];
862
+ const edges = [];
863
+ for (const candidate of candidates) {
864
+ const candidateNodeId = graphNodeId('candidate', candidate.id);
865
+ nodes.push({
866
+ id: candidateNodeId,
867
+ kind: 'candidate',
868
+ title: candidate.title ?? candidate.symbolId,
869
+ status: candidate.status,
870
+ jobId: candidate.jobId,
871
+ taskId: candidate.taskId,
872
+ path: candidate.changedPaths[0],
873
+ generatedAt,
874
+ metadata: candidate
875
+ });
876
+ if (!includeChangeNodes)
877
+ continue;
878
+ for (const change of candidate.semanticChanges) {
879
+ const changeNodeId = graphNodeId('semantic-change', change.id);
880
+ nodes.push({
881
+ id: changeNodeId,
882
+ kind: 'semantic-change',
883
+ title: `${change.operation} ${change.symbolId}`,
884
+ status: change.status,
885
+ jobId: candidate.jobId,
886
+ taskId: candidate.taskId,
887
+ path: change.sourceSpan.path,
888
+ generatedAt,
889
+ metadata: change
890
+ });
891
+ edges.push({
892
+ kind: 'produces',
893
+ from: candidateNodeId,
894
+ to: changeNodeId,
895
+ label: 'semantic-change',
896
+ generatedAt,
897
+ metadata: {
898
+ symbolId: change.symbolId,
899
+ declarationKind: change.declarationKind,
900
+ operation: change.operation,
901
+ reasonCodes: change.reasonCodes
902
+ }
903
+ });
904
+ if (change.conflictReason) {
905
+ edges.push({
906
+ kind: 'conflictsWith',
907
+ from: candidateNodeId,
908
+ to: changeNodeId,
909
+ label: change.conflictReason,
910
+ generatedAt
911
+ });
912
+ }
913
+ }
914
+ }
915
+ return { candidates, nodes, edges };
916
+ }
917
+ export function createSwarmMergeCandidateGraph(input) {
918
+ const generatedAt = input.generatedAt ?? Date.now();
919
+ const projection = mapSwarmMergeCandidatesToGraph({ ...input, generatedAt });
920
+ return createSwarmRunGraph({
921
+ id: input.id,
922
+ runId: input.runId,
923
+ title: input.title ?? 'Semantic merge candidate graph',
924
+ generatedAt,
925
+ nodes: projection.nodes,
926
+ edges: projection.edges,
927
+ metadata: input.metadata
928
+ });
929
+ }
930
+ export function mapSwarmReplayRecordsToGraph(input) {
931
+ const generatedAt = input.generatedAt ?? Date.now();
932
+ const replayRecords = (input.replayRecords ?? []).map((record) => isSwarmReplayRecord(record) ? record : createSwarmReplayRecord(record));
933
+ const nodes = [];
934
+ const edges = [];
935
+ for (const replay of replayRecords) {
936
+ const replayNodeId = graphNodeId('replay', replay.id);
937
+ nodes.push({
938
+ id: replayNodeId,
939
+ kind: 'replay',
940
+ title: replay.title,
941
+ status: replay.status,
942
+ jobId: replay.jobId,
943
+ taskId: replay.taskId,
944
+ generatedAt,
945
+ metadata: replay
946
+ });
947
+ for (const event of replay.patchEvents) {
948
+ const eventNodeId = graphNodeId('replay', event.id);
949
+ nodes.push({
950
+ id: eventNodeId,
951
+ kind: 'replay',
952
+ title: `${event.operation} ${event.path}`,
953
+ jobId: event.jobId ?? replay.jobId,
954
+ taskId: event.taskId ?? replay.taskId,
955
+ path: event.path,
956
+ generatedAt,
957
+ metadata: event
958
+ });
959
+ edges.push({
960
+ kind: 'produces',
961
+ from: replayNodeId,
962
+ to: eventNodeId,
963
+ label: event.operation,
964
+ generatedAt
965
+ });
966
+ }
967
+ for (const evidence of replay.evidenceRefs) {
968
+ const evidenceNodeId = graphNodeId('evidence', evidence.id);
969
+ nodes.push({
970
+ id: evidenceNodeId,
971
+ kind: 'evidence',
972
+ title: evidence.path ?? evidence.uri ?? evidence.id,
973
+ path: evidence.path,
974
+ generatedAt,
975
+ metadata: evidence
976
+ });
977
+ edges.push({
978
+ kind: 'verifies',
979
+ from: evidenceNodeId,
980
+ to: replayNodeId,
981
+ label: evidence.role ?? evidence.kind,
982
+ generatedAt
983
+ });
984
+ }
985
+ }
986
+ return createSwarmRunGraph({
987
+ runId: input.runId,
988
+ title: 'Replay records graph',
989
+ generatedAt,
990
+ nodes,
991
+ edges
992
+ });
993
+ }
994
+ export function createSwarmReplayRecordGraph(input) {
995
+ return mapSwarmReplayRecordsToGraph(input);
996
+ }
997
+ export function mapSwarmImprovementLoopsToGraph(input) {
998
+ const generatedAt = input.generatedAt ?? Date.now();
999
+ const loops = (input.improvementLoops ?? []).map((loop) => isSwarmImprovementLoop(loop) ? loop : createSwarmImprovementLoop(loop));
1000
+ const nodes = [];
1001
+ const edges = [];
1002
+ for (const loop of loops) {
1003
+ const phaseNodes = [];
1004
+ if (loop.observation !== undefined) {
1005
+ phaseNodes.push(createImprovementLoopPhaseNode(loop, 'observation', loop.observation, generatedAt));
1006
+ }
1007
+ if (loop.action !== undefined) {
1008
+ phaseNodes.push(createImprovementLoopPhaseNode(loop, 'action', loop.action, generatedAt));
1009
+ }
1010
+ if (loop.result !== undefined) {
1011
+ phaseNodes.push(createImprovementLoopPhaseNode(loop, 'result', loop.result, generatedAt));
1012
+ }
1013
+ if (phaseNodes.length === 0) {
1014
+ phaseNodes.push({
1015
+ id: graphNodeId('rsi', loop.id),
1016
+ kind: 'rsi',
1017
+ title: loop.title,
1018
+ status: loop.status,
1019
+ jobId: loop.jobId,
1020
+ taskId: loop.taskId,
1021
+ generatedAt,
1022
+ metadata: loop
1023
+ });
1024
+ }
1025
+ nodes.push(...phaseNodes);
1026
+ for (let index = 1; index < phaseNodes.length; index += 1) {
1027
+ edges.push({
1028
+ kind: 'produces',
1029
+ from: phaseNodes[index - 1].id,
1030
+ to: phaseNodes[index].id,
1031
+ label: index === 1 ? 'action' : 'result',
1032
+ generatedAt
1033
+ });
1034
+ }
1035
+ const entryNodeId = phaseNodes[0]?.id;
1036
+ const exitNodeId = phaseNodes.at(-1)?.id;
1037
+ for (const replayRecordId of loop.replayRecordIds) {
1038
+ const replayNodeId = graphNodeId('replay', replayRecordId);
1039
+ nodes.push({
1040
+ id: replayNodeId,
1041
+ kind: 'replay',
1042
+ title: titleFromId(replayRecordId),
1043
+ generatedAt,
1044
+ metadata: { replayRecordId }
1045
+ });
1046
+ if (entryNodeId)
1047
+ edges.push({ kind: 'produces', from: replayNodeId, to: entryNodeId, label: 'feeds-rsi', generatedAt });
1048
+ }
1049
+ for (const patchEventId of loop.patchEventIds) {
1050
+ const patchNodeId = graphNodeId('replay', patchEventId);
1051
+ nodes.push({
1052
+ id: patchNodeId,
1053
+ kind: 'replay',
1054
+ title: titleFromId(patchEventId),
1055
+ generatedAt,
1056
+ metadata: { patchEventId }
1057
+ });
1058
+ if (entryNodeId)
1059
+ edges.push({ kind: 'produces', from: patchNodeId, to: entryNodeId, label: 'feeds-rsi', generatedAt });
1060
+ }
1061
+ for (const evidence of loop.evidenceRefs) {
1062
+ const evidenceNodeId = graphNodeId('evidence', evidence.id);
1063
+ nodes.push({
1064
+ id: evidenceNodeId,
1065
+ kind: 'evidence',
1066
+ title: evidence.path ?? evidence.uri ?? evidence.id,
1067
+ path: evidence.path,
1068
+ generatedAt,
1069
+ metadata: evidence
1070
+ });
1071
+ if (exitNodeId)
1072
+ edges.push({ kind: 'verifies', from: evidenceNodeId, to: exitNodeId, label: evidence.role ?? evidence.kind, generatedAt });
1073
+ }
1074
+ }
1075
+ return createSwarmRunGraph({
1076
+ runId: input.runId,
1077
+ title: 'Improvement loop graph',
1078
+ generatedAt,
1079
+ nodes,
1080
+ edges
1081
+ });
1082
+ }
1083
+ export function createSwarmImprovementLoopGraph(input) {
1084
+ return mapSwarmImprovementLoopsToGraph(input);
1085
+ }
1086
+ export function createSwarmRunGraphChunk(input = {}) {
1087
+ const generatedAt = Date.now();
1088
+ const nodes = dedupeGraphNodes((input.nodes ?? []).map((node) => normalizeGraphNode(node, generatedAt)));
1089
+ const known = new Set(nodes.map((node) => node.id));
1090
+ const edges = dedupeGraphEdges((input.edges ?? [])
1091
+ .map((edge) => normalizeGraphEdge(edge, generatedAt))
1092
+ .filter((edge) => known.has(edge.from) && known.has(edge.to)));
1093
+ const entryNodeIds = uniqueStrings((input.entryNodeIds ?? []).filter((id) => known.has(id)));
1094
+ const exitNodeIds = uniqueStrings((input.exitNodeIds ?? []).filter((id) => known.has(id)));
1095
+ return {
1096
+ kind: FRONTIER_SWARM_RUN_GRAPH_CHUNK_KIND,
1097
+ version: FRONTIER_SWARM_RUN_GRAPH_CHUNK_VERSION,
1098
+ id: input.id ?? 'swarm-run-graph-chunk:' + stableHash([nodes.map((node) => node.id), edges.map((edge) => edge.id), entryNodeIds, exitNodeIds]),
1099
+ nodes,
1100
+ edges,
1101
+ entryNodeIds,
1102
+ exitNodeIds,
1103
+ summary: {
1104
+ nodeCount: nodes.length,
1105
+ edgeCount: edges.length,
1106
+ entryCount: entryNodeIds.length,
1107
+ exitCount: exitNodeIds.length
1108
+ },
1109
+ ...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
1110
+ };
1111
+ }
1112
+ export function createSwarmRunGraphChain(input) {
1113
+ const nodes = input.nodes.map((node) => normalizeGraphNode(node));
1114
+ const edges = [];
1115
+ for (let index = 1; index < nodes.length; index += 1) {
1116
+ edges.push({ kind: input.edgeKind ?? 'dependsOn', from: nodes[index - 1].id, to: nodes[index].id });
1117
+ }
1118
+ return createSwarmRunGraphChunk({
1119
+ id: input.id,
1120
+ nodes,
1121
+ edges,
1122
+ entryNodeIds: nodes[0] ? [nodes[0].id] : [],
1123
+ exitNodeIds: nodes.at(-1) ? [nodes.at(-1).id] : [],
1124
+ metadata: input.metadata
1125
+ });
1126
+ }
1127
+ export function createSwarmRunGraphFork(input) {
1128
+ const source = normalizeGraphNode(input.source);
1129
+ const branches = input.branches.map((node) => normalizeGraphNode(node));
1130
+ return createSwarmRunGraphChunk({
1131
+ id: input.id,
1132
+ nodes: [source, ...branches],
1133
+ edges: branches.map((branch) => ({ kind: 'dependsOn', from: source.id, to: branch.id, label: 'fork' })),
1134
+ entryNodeIds: [source.id],
1135
+ exitNodeIds: branches.map((branch) => branch.id),
1136
+ metadata: input.metadata
1137
+ });
1138
+ }
1139
+ export function createSwarmRunGraphJoin(input) {
1140
+ const branches = input.branches.map((node) => normalizeGraphNode(node));
1141
+ const join = normalizeGraphNode(input.join);
1142
+ return createSwarmRunGraphChunk({
1143
+ id: input.id,
1144
+ nodes: [...branches, join],
1145
+ edges: branches.map((branch) => ({ kind: 'dependsOn', from: branch.id, to: join.id, label: 'join' })),
1146
+ entryNodeIds: branches.map((branch) => branch.id),
1147
+ exitNodeIds: [join.id],
1148
+ metadata: input.metadata
1149
+ });
1150
+ }
1151
+ export function createSwarmRunGraphBarrier(input) {
1152
+ const prerequisites = input.prerequisites.map((node) => normalizeGraphNode(node));
1153
+ const prerequisiteIds = prerequisites.map((node) => node.id);
1154
+ const barrierMetadata = toJsonObject(input.barrier?.metadata);
1155
+ const barrier = normalizeGraphNode({
1156
+ ...(input.barrier ?? {}),
1157
+ id: input.barrier?.id ?? (input.id ? `${input.id}:barrier` : graphNodeId('gate', 'barrier:' + stableHash(prerequisiteIds))),
1158
+ kind: input.barrier?.kind ?? 'gate',
1159
+ title: input.barrier?.title ?? 'Barrier',
1160
+ metadata: {
1161
+ ...(barrierMetadata ?? {}),
1162
+ role: 'barrier',
1163
+ graphChunkKind: 'barrier',
1164
+ prerequisiteIds,
1165
+ prerequisiteCount: prerequisiteIds.length
1166
+ }
1167
+ });
1168
+ return createSwarmRunGraphChunk({
1169
+ id: input.id,
1170
+ nodes: [...prerequisites, barrier],
1171
+ edges: prerequisites.map((node, index) => ({
1172
+ kind: 'dependsOn',
1173
+ from: node.id,
1174
+ to: barrier.id,
1175
+ label: 'barrier',
1176
+ metadata: {
1177
+ role: 'barrier-prerequisite',
1178
+ barrierId: barrier.id,
1179
+ prerequisiteId: node.id,
1180
+ prerequisiteIndex: index,
1181
+ prerequisiteCount: prerequisites.length
1182
+ }
1183
+ })),
1184
+ entryNodeIds: prerequisiteIds,
1185
+ exitNodeIds: [barrier.id],
1186
+ metadata: input.metadata
1187
+ });
1188
+ }
1189
+ export function createSwarmRunGraphRaceSelect(input) {
1190
+ const baseBranches = input.branches.map((node) => normalizeGraphNode(node));
1191
+ const branchIds = baseBranches.map((node) => node.id);
1192
+ const selectedBranchId = input.selectedBranchId && branchIds.includes(input.selectedBranchId) ? input.selectedBranchId : undefined;
1193
+ const rejectedBranchIds = new Set(selectedBranchId
1194
+ ? branchIds.filter((id) => id !== selectedBranchId)
1195
+ : uniqueStrings(input.rejectedBranchIds ?? []).filter((id) => branchIds.includes(id)));
1196
+ if (selectedBranchId)
1197
+ rejectedBranchIds.delete(selectedBranchId);
1198
+ const selectorMetadata = toJsonObject(input.selector?.metadata);
1199
+ const selector = normalizeGraphNode({
1200
+ ...(input.selector ?? {}),
1201
+ id: input.selector?.id ?? (input.id ? `${input.id}:selector` : graphNodeId('decision', 'race-select:' + stableHash(branchIds))),
1202
+ kind: input.selector?.kind ?? 'decision',
1203
+ title: input.selector?.title ?? 'Race/select',
1204
+ metadata: {
1205
+ ...(selectorMetadata ?? {}),
1206
+ role: 'race-select',
1207
+ graphChunkKind: 'race-select',
1208
+ branchIds,
1209
+ branchCount: branchIds.length,
1210
+ ...(selectedBranchId ? { selectedBranchId } : {}),
1211
+ rejectedBranchIds: Array.from(rejectedBranchIds).sort()
1212
+ }
1213
+ });
1214
+ const branches = baseBranches.map((node, index) => {
1215
+ const disposition = node.id === selectedBranchId ? 'selected' : rejectedBranchIds.has(node.id) ? 'rejected' : 'candidate';
1216
+ return normalizeGraphNode({
1217
+ ...node,
1218
+ status: node.status ?? (disposition === 'selected' ? 'accepted' : disposition === 'rejected' ? 'rejected' : undefined),
1219
+ metadata: {
1220
+ ...(node.metadata ?? {}),
1221
+ raceSelect: {
1222
+ selectorId: selector.id,
1223
+ branchId: node.id,
1224
+ branchIndex: index,
1225
+ branchCount: baseBranches.length,
1226
+ disposition,
1227
+ selected: disposition === 'selected',
1228
+ rejected: disposition === 'rejected'
1229
+ }
1230
+ }
1231
+ }, node.generatedAt);
1232
+ });
1233
+ return createSwarmRunGraphChunk({
1234
+ id: input.id,
1235
+ nodes: [selector, ...branches],
1236
+ edges: branches.map((branch, index) => {
1237
+ const disposition = branch.id === selectedBranchId ? 'selected' : rejectedBranchIds.has(branch.id) ? 'rejected' : 'candidate';
1238
+ return {
1239
+ kind: disposition === 'selected' ? 'mergesInto' : disposition === 'rejected' ? 'supersedes' : 'dependsOn',
1240
+ from: branch.id,
1241
+ to: selector.id,
1242
+ label: disposition,
1243
+ metadata: {
1244
+ role: 'race-select-branch',
1245
+ selectorId: selector.id,
1246
+ branchId: branch.id,
1247
+ branchIndex: index,
1248
+ branchCount: branches.length,
1249
+ disposition,
1250
+ selected: disposition === 'selected',
1251
+ rejected: disposition === 'rejected'
1252
+ }
1253
+ };
1254
+ }),
1255
+ entryNodeIds: branchIds,
1256
+ exitNodeIds: [selector.id],
1257
+ metadata: input.metadata
1258
+ });
1259
+ }
1260
+ export function createSwarmRunGraphTournament(input) {
1261
+ const tournament = normalizeGraphNode({ id: input.id ? `${input.id}:tournament` : undefined, kind: 'decision', title: 'Tournament', metadata: input.metadata });
1262
+ const candidates = input.candidates.map((node) => normalizeGraphNode({ ...node, kind: node.kind ?? 'candidate' }));
1263
+ const rejected = new Set(input.rejectedCandidates ?? []);
1264
+ const edges = candidates.map((candidate) => ({
1265
+ kind: candidate.id === input.winner ? 'mergesInto' : rejected.has(candidate.id) ? 'supersedes' : 'dependsOn',
1266
+ from: candidate.id,
1267
+ to: tournament.id,
1268
+ label: candidate.id === input.winner ? 'winner' : rejected.has(candidate.id) ? 'rejected' : 'candidate'
1269
+ }));
1270
+ return createSwarmRunGraphChunk({
1271
+ id: input.id,
1272
+ nodes: [tournament, ...candidates],
1273
+ edges,
1274
+ entryNodeIds: candidates.map((candidate) => candidate.id),
1275
+ exitNodeIds: [tournament.id],
1276
+ metadata: input.metadata
1277
+ });
1278
+ }
1279
+ export function createSwarmRunGraphRetryLoop(input) {
1280
+ const action = normalizeGraphNode(input.action);
1281
+ const gate = normalizeGraphNode({ ...input.gate, kind: input.gate.kind ?? 'gate' });
1282
+ const retry = normalizeGraphNode(input.retry);
1283
+ const optional = [input.success, input.failure].filter((node) => Boolean(node)).map((node) => normalizeGraphNode(node));
1284
+ return createSwarmRunGraphChunk({
1285
+ id: input.id,
1286
+ nodes: [action, gate, retry, ...optional],
1287
+ edges: [
1288
+ { kind: 'verifies', from: action.id, to: gate.id },
1289
+ { kind: 'blocks', from: gate.id, to: retry.id, label: 'retry' },
1290
+ { kind: 'dependsOn', from: retry.id, to: action.id, label: 'loop' },
1291
+ ...optional.map((node) => ({ kind: node.status === 'failed' ? 'blocks' : 'mergesInto', from: gate.id, to: node.id }))
1292
+ ],
1293
+ entryNodeIds: [action.id],
1294
+ exitNodeIds: optional.length > 0 ? optional.map((node) => node.id) : [gate.id],
1295
+ metadata: input.metadata
1296
+ });
1297
+ }
1298
+ export function createSwarmRunGraphRsiLoop(input) {
1299
+ const observe = normalizeGraphNode({ ...input.observe, kind: input.observe.kind ?? 'rsi' });
1300
+ const improve = normalizeGraphNode({ ...input.improve, kind: input.improve.kind ?? 'rsi' });
1301
+ const apply = input.apply ? normalizeGraphNode(input.apply) : undefined;
1302
+ return createSwarmRunGraphChunk({
1303
+ id: input.id,
1304
+ nodes: apply ? [observe, improve, apply] : [observe, improve],
1305
+ edges: [
1306
+ { kind: 'produces', from: observe.id, to: improve.id, label: 'feedback' },
1307
+ ...(apply ? [{ kind: 'mergesInto', from: improve.id, to: apply.id, label: 'applies' }] : [])
1308
+ ],
1309
+ entryNodeIds: [observe.id],
1310
+ exitNodeIds: [apply?.id ?? improve.id],
1311
+ metadata: input.metadata
1312
+ });
1313
+ }
1314
+ export function createSwarmRunGraphSynthesisChunk(input = {}) {
1315
+ const source = input.source ? normalizeGraphNode(input.source) : undefined;
1316
+ const panel = normalizeGraphNode({
1317
+ ...input.panel,
1318
+ id: input.panel?.id ?? (input.id ? `${input.id}:panel` : undefined),
1319
+ kind: input.panel?.kind ?? 'decision',
1320
+ title: input.panel?.title ?? 'Synthesis panel'
1321
+ });
1322
+ const candidates = (input.candidates ?? []).map((candidate) => normalizeGraphNode({
1323
+ ...candidate,
1324
+ kind: candidate.kind ?? 'candidate'
1325
+ }));
1326
+ const rejectedCandidateIds = new Set(input.rejectedCandidateIds ?? candidates
1327
+ .filter((candidate) => candidate.status === 'rejected' || candidate.status === 'failed' || candidate.status === 'superseded')
1328
+ .map((candidate) => candidate.id));
1329
+ const selectedCandidateId = input.selectedCandidateId ?? candidates.find((candidate) => !rejectedCandidateIds.has(candidate.id))?.id;
1330
+ const selectedCandidate = selectedCandidateId ? candidates.find((candidate) => candidate.id === selectedCandidateId) : undefined;
1331
+ const decision = input.decision ? normalizeGraphNode({
1332
+ ...input.decision,
1333
+ id: input.decision.id ?? (input.id ? `${input.id}:decision` : undefined),
1334
+ kind: input.decision.kind ?? 'decision',
1335
+ title: input.decision.title ?? 'Coordinator synthesis decision'
1336
+ }) : undefined;
1337
+ const output = input.output ? normalizeGraphNode({
1338
+ ...input.output,
1339
+ kind: input.output.kind ?? 'candidate'
1340
+ }) : undefined;
1341
+ const edges = [];
1342
+ if (source) {
1343
+ edges.push({ kind: 'produces', from: source.id, to: panel.id, label: 'synthesis-request' });
1344
+ }
1345
+ for (const candidate of candidates) {
1346
+ edges.push({ kind: 'produces', from: panel.id, to: candidate.id, label: 'candidate' });
1347
+ }
1348
+ if (decision && selectedCandidate) {
1349
+ edges.push({ kind: 'mergesInto', from: selectedCandidate.id, to: decision.id, label: 'selected' });
1350
+ }
1351
+ for (const candidate of candidates) {
1352
+ if (!rejectedCandidateIds.has(candidate.id))
1353
+ continue;
1354
+ edges.push(selectedCandidate && selectedCandidate.id !== candidate.id
1355
+ ? { kind: 'supersedes', from: selectedCandidate.id, to: candidate.id, label: 'rejected' }
1356
+ : { kind: 'supersedes', from: candidate.id, to: decision?.id ?? panel.id, label: 'rejected' });
1357
+ }
1358
+ if (decision && output) {
1359
+ edges.push({
1360
+ kind: isBlockingGraphStatus(decision.status) ? 'blocks' : 'produces',
1361
+ from: decision.id,
1362
+ to: output.id,
1363
+ label: isBlockingGraphStatus(decision.status) ? 'blocked-output' : 'synthesized-output'
1364
+ });
1365
+ }
1366
+ else if (selectedCandidate && output) {
1367
+ edges.push({ kind: 'mergesInto', from: selectedCandidate.id, to: output.id, label: 'synthesized-output' });
1368
+ }
1369
+ const nodes = [source, panel, ...candidates, decision, output].filter((node) => Boolean(node));
1370
+ return createSwarmRunGraphChunk({
1371
+ id: input.id,
1372
+ nodes,
1373
+ edges,
1374
+ entryNodeIds: [source?.id ?? panel.id],
1375
+ exitNodeIds: [output?.id ?? decision?.id ?? selectedCandidate?.id ?? panel.id],
1376
+ metadata: input.metadata
1377
+ });
1378
+ }
1379
+ export function createSwarmRunGraphVerificationGateChunk(input) {
1380
+ const subject = normalizeGraphNode(input.subject);
1381
+ const gate = normalizeGraphNode({
1382
+ ...input.gate,
1383
+ kind: input.gate.kind ?? 'gate',
1384
+ title: input.gate.title ?? 'Verification gate'
1385
+ });
1386
+ const evidence = (input.evidence ?? []).map((node) => normalizeGraphNode({
1387
+ ...node,
1388
+ kind: node.kind ?? 'evidence'
1389
+ }));
1390
+ const pass = input.pass ? normalizeGraphNode(input.pass) : undefined;
1391
+ const block = input.block ? normalizeGraphNode(input.block) : undefined;
1392
+ const passing = isPassingGraphStatus(gate.status);
1393
+ const blocking = isBlockingGraphStatus(gate.status);
1394
+ const edges = [
1395
+ { kind: 'verifies', from: gate.id, to: subject.id, label: 'gate' },
1396
+ ...evidence.map((node) => ({ kind: 'produces', from: gate.id, to: node.id, label: 'evidence' })),
1397
+ ...evidence.map((node) => ({ kind: 'verifies', from: node.id, to: gate.id, label: 'evidence-verifies-gate' }))
1398
+ ];
1399
+ if (passing && pass) {
1400
+ edges.push({ kind: 'mergesInto', from: gate.id, to: pass.id, label: 'passed' });
1401
+ }
1402
+ if (blocking) {
1403
+ edges.push({ kind: 'blocks', from: gate.id, to: block?.id ?? subject.id, label: gate.status === 'failed' ? 'failed' : 'blocked' });
1404
+ }
1405
+ return createSwarmRunGraphChunk({
1406
+ id: input.id,
1407
+ nodes: [subject, gate, ...evidence, pass, block].filter((node) => Boolean(node)),
1408
+ edges,
1409
+ entryNodeIds: [subject.id],
1410
+ exitNodeIds: [passing && pass ? pass.id : blocking && block ? block.id : gate.id],
1411
+ metadata: input.metadata
1412
+ });
1413
+ }
1414
+ export function createSwarmRunGraphMergeGateChunk(input) {
1415
+ const candidate = normalizeGraphNode({
1416
+ ...input.candidate,
1417
+ kind: input.candidate.kind ?? 'candidate'
1418
+ });
1419
+ const target = normalizeGraphNode(input.target);
1420
+ const gate = input.gate ? normalizeGraphNode({
1421
+ ...input.gate,
1422
+ kind: input.gate.kind ?? 'gate',
1423
+ title: input.gate.title ?? 'Merge gate'
1424
+ }) : undefined;
1425
+ const decision = input.decision ? normalizeGraphNode({
1426
+ ...input.decision,
1427
+ kind: input.decision.kind ?? 'decision',
1428
+ title: input.decision.title ?? 'Coordinator merge decision'
1429
+ }) : undefined;
1430
+ const blockers = (input.blockers ?? []).map((node) => normalizeGraphNode({
1431
+ ...node,
1432
+ kind: node.kind ?? 'gate'
1433
+ }));
1434
+ const superseded = (input.superseded ?? []).map((node) => normalizeGraphNode({
1435
+ ...node,
1436
+ kind: node.kind ?? 'candidate'
1437
+ }));
1438
+ const status = input.status ?? decision?.status ?? gate?.status ?? candidate.status;
1439
+ const passing = isPassingGraphStatus(status);
1440
+ const blocking = isBlockingGraphStatus(status);
1441
+ const edges = [];
1442
+ if (gate) {
1443
+ edges.push({ kind: 'verifies', from: gate.id, to: candidate.id, label: 'merge-gate' });
1444
+ }
1445
+ if (gate && decision) {
1446
+ edges.push({ kind: blocking ? 'blocks' : 'produces', from: gate.id, to: decision.id, label: 'gate-decision' });
1447
+ }
1448
+ if (decision) {
1449
+ edges.push({ kind: 'verifies', from: decision.id, to: candidate.id, label: 'coordinator-decision' });
1450
+ }
1451
+ for (const blocker of blockers) {
1452
+ edges.push({ kind: 'blocks', from: blocker.id, to: candidate.id, label: 'merge-blocker' });
1453
+ }
1454
+ for (const node of superseded) {
1455
+ edges.push({ kind: 'supersedes', from: candidate.id, to: node.id, label: 'superseded' });
1456
+ }
1457
+ if (passing) {
1458
+ edges.push({ kind: 'mergesInto', from: candidate.id, to: target.id, label: 'accepted' });
1459
+ }
1460
+ else if (blocking) {
1461
+ edges.push({ kind: 'blocks', from: decision?.id ?? gate?.id ?? candidate.id, to: target.id, label: status === 'failed' ? 'failed' : 'blocked' });
1462
+ }
1463
+ return createSwarmRunGraphChunk({
1464
+ id: input.id,
1465
+ nodes: [candidate, target, gate, decision, ...blockers, ...superseded].filter((node) => Boolean(node)),
1466
+ edges,
1467
+ entryNodeIds: [candidate.id],
1468
+ exitNodeIds: [passing ? target.id : blocking ? decision?.id ?? gate?.id ?? candidate.id : target.id],
1469
+ metadata: input.metadata
1470
+ });
1471
+ }
381
1472
  export const FRONTIER_SWARM_TASK_MODEL_PROFILES = [
382
1473
  {
383
1474
  workKind: 'agent-task',
@@ -748,7 +1839,498 @@ export function createSwarmRun(input) {
748
1839
  summary: summarizeRun(input.plan.jobs, results),
749
1840
  ...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
750
1841
  };
751
- return run;
1842
+ return run;
1843
+ }
1844
+ export function createRunEventsFromSwarmPlan(plan, options = {}) {
1845
+ const actorId = options.actorId ?? 'frontier-swarm';
1846
+ const runId = options.runId ?? plan.runId;
1847
+ const time = swarmRunEventTime(options, plan.createdAt);
1848
+ let actorSeq = options.startActorSeq ?? 1;
1849
+ const events = [];
1850
+ const created = createRunEvent({
1851
+ runId,
1852
+ actorId,
1853
+ actorSeq: actorSeq++,
1854
+ parents: [...(options.parents ?? [])],
1855
+ time,
1856
+ type: 'run.created',
1857
+ payload: {
1858
+ goal: plan.metadata?.objective ?? plan.metadata?.goal ?? `Frontier swarm ${plan.id}`,
1859
+ metadata: pruneUndefinedJsonObject({
1860
+ source: 'frontier-swarm.plan',
1861
+ planId: plan.id,
1862
+ manifestId: plan.manifestId,
1863
+ summary: plan.summary,
1864
+ filters: plan.filters,
1865
+ limits: plan.limits,
1866
+ metadata: plan.metadata,
1867
+ adapterMetadata: toJsonObject(options.metadata)
1868
+ })
1869
+ }
1870
+ });
1871
+ events.push(created);
1872
+ const laneIds = uniqueStrings(plan.jobs.map((job) => job.lane));
1873
+ const laneEventByLane = new Map();
1874
+ for (const lane of laneIds) {
1875
+ const laneJobs = plan.jobs.filter((job) => job.lane === lane);
1876
+ const laneEvent = createRunNodeEvent(runId, actorId, actorSeq++, defineRunLane({
1877
+ id: swarmRunLaneNodeId(lane),
1878
+ title: lane,
1879
+ packageId: inferSwarmLanePackageId(laneJobs),
1880
+ allowedWrites: uniqueStrings(laneJobs.flatMap((job) => job.allowedWrites)),
1881
+ requiredChecks: uniqueStrings(laneJobs.flatMap((job) => job.verification.map(formatSwarmCommandLine))),
1882
+ maxConcurrency: plan.limits.maxLaneConcurrency[lane],
1883
+ concurrencyKey: lane,
1884
+ semanticRegions: uniqueStrings(laneJobs.flatMap((job) => [...job.ownedRegions, ...job.changedRegions])),
1885
+ status: 'ready',
1886
+ createdAt: time,
1887
+ updatedAt: time,
1888
+ metadata: pruneUndefinedJsonObject({
1889
+ source: 'frontier-swarm.plan.lane',
1890
+ jobIds: laneJobs.map((job) => job.id),
1891
+ taskIds: uniqueStrings(laneJobs.map((job) => job.taskId))
1892
+ })
1893
+ }), {
1894
+ parents: [created.id],
1895
+ time
1896
+ });
1897
+ events.push(laneEvent);
1898
+ laneEventByLane.set(lane, laneEvent);
1899
+ }
1900
+ const taskById = new Map();
1901
+ for (const job of plan.jobs)
1902
+ if (!taskById.has(job.taskId))
1903
+ taskById.set(job.taskId, job);
1904
+ for (const job of taskById.values()) {
1905
+ const task = job.task;
1906
+ const taskEvent = createRunNodeEvent(runId, actorId, actorSeq++, defineRunTask({
1907
+ id: swarmRunTaskNodeId(job.taskId),
1908
+ title: task.title || job.title,
1909
+ laneId: swarmRunLaneNodeId(job.lane),
1910
+ status: swarmStatusToRunStatus(job.status),
1911
+ targetRefs: uniqueStrings([...(task.targetRefs ?? []), ...job.allowedWrites]),
1912
+ sourceRefs: task.sourceRefs,
1913
+ allowedWrites: job.allowedWrites,
1914
+ semanticRegions: uniqueStrings([...job.ownedRegions, ...job.changedRegions]),
1915
+ acceptance: job.acceptance,
1916
+ verification: job.verification.map((command) => ({
1917
+ id: command.name,
1918
+ command: command.command,
1919
+ args: command.args,
1920
+ cwd: command.cwd,
1921
+ required: command.required,
1922
+ metadata: command.metadata
1923
+ })),
1924
+ priority: job.priority,
1925
+ concurrencyKey: job.concurrencyKey,
1926
+ createdAt: time,
1927
+ updatedAt: time,
1928
+ metadata: pruneUndefinedJsonObject({
1929
+ source: 'frontier-swarm.plan.task',
1930
+ jobIds: plan.jobs.filter((entry) => entry.taskId === job.taskId).map((entry) => entry.id),
1931
+ workKind: task.workKind,
1932
+ layer: job.layer,
1933
+ compute: job.compute.id,
1934
+ tags: job.tags,
1935
+ task: task
1936
+ })
1937
+ }), {
1938
+ parents: [laneEventByLane.get(job.lane)?.id ?? created.id],
1939
+ time
1940
+ });
1941
+ events.push(taskEvent);
1942
+ }
1943
+ return events;
1944
+ }
1945
+ export function createRunEventsFromSwarmLease(lease, options = {}) {
1946
+ const actorId = options.actorId ?? lease.workerId ?? 'frontier-swarm';
1947
+ const runId = options.runId ?? options.job?.metadata?.runId ?? 'frontier-swarm';
1948
+ const eventType = options.eventType ?? (lease.status === 'released' ? 'lease.released' : 'lease.granted');
1949
+ const time = swarmRunEventTime(options, eventType === 'lease.released' ? options.now : lease.leasedAt);
1950
+ const leaseNode = {
1951
+ kind: 'lease',
1952
+ id: swarmRunLeaseNodeId(lease.id),
1953
+ title: `Lease ${lease.jobId}`,
1954
+ scopeId: options.job ? swarmRunTaskNodeId(options.job.taskId) : swarmRunTaskNodeId(lease.jobId),
1955
+ leaseKey: options.job?.concurrencyKey ?? lease.jobId,
1956
+ holderId: lease.workerId,
1957
+ status: eventType === 'lease.released' ? 'released' : 'granted',
1958
+ requestedAt: new Date(lease.leasedAt).toISOString(),
1959
+ grantedAt: new Date(lease.leasedAt).toISOString(),
1960
+ releasedAt: eventType === 'lease.released' ? time : undefined,
1961
+ metadata: pruneUndefinedJsonObject({
1962
+ source: 'frontier-swarm.lease',
1963
+ jobId: lease.jobId,
1964
+ token: lease.token,
1965
+ fencingToken: lease.fencingToken,
1966
+ expiresAt: lease.expiresAt,
1967
+ status: lease.status
1968
+ })
1969
+ };
1970
+ return [createRunEvent({
1971
+ runId,
1972
+ actorId,
1973
+ actorSeq: options.startActorSeq ?? 1,
1974
+ parents: [...(options.parents ?? [])],
1975
+ time,
1976
+ type: eventType,
1977
+ payload: pruneUndefinedJsonObject({
1978
+ subjectId: options.job ? swarmRunTaskNodeId(options.job.taskId) : undefined,
1979
+ lease: leaseNode
1980
+ })
1981
+ })];
1982
+ }
1983
+ export function createRunEventsFromSwarmResult(resultInput, options = {}) {
1984
+ const result = isSwarmJobResult(resultInput) ? cloneJsonValue(resultInput) : normalizeResult(resultInput);
1985
+ const job = options.job;
1986
+ const runId = options.runId ?? 'frontier-swarm';
1987
+ const actorId = options.actorId ?? 'frontier-swarm-worker';
1988
+ const time = swarmRunEventTime(options, result.finishedAt ?? result.startedAt);
1989
+ let actorSeq = options.startActorSeq ?? 1;
1990
+ const parents = [...(options.parents ?? [])];
1991
+ const events = [];
1992
+ const attemptId = swarmRunAttemptNodeId(result.jobId);
1993
+ const taskId = swarmRunTaskNodeId(job?.taskId ?? result.jobId);
1994
+ const attemptEvent = createRunNodeEvent(runId, actorId, actorSeq++, defineRunAttempt({
1995
+ id: attemptId,
1996
+ title: job?.title ?? result.jobId,
1997
+ taskId,
1998
+ actorId,
1999
+ runnerId: job?.compute.kind ?? job?.compute.id,
2000
+ workspaceId: job?.worktreePath,
2001
+ status: swarmResultStatusToAttemptStatus(result.status),
2002
+ startedAt: result.startedAt ? new Date(result.startedAt).toISOString() : undefined,
2003
+ endedAt: result.finishedAt ? new Date(result.finishedAt).toISOString() : undefined,
2004
+ model: job?.compute.model,
2005
+ reason: result.error,
2006
+ metadata: pruneUndefinedJsonObject({
2007
+ source: 'frontier-swarm.result',
2008
+ jobId: result.jobId,
2009
+ mergeReadiness: result.mergeReadiness,
2010
+ mergeDisposition: result.mergeDisposition,
2011
+ riskLevel: result.riskLevel,
2012
+ exitCode: result.exitCode,
2013
+ signal: result.signal,
2014
+ queueItemIds: result.queueItemIds,
2015
+ semanticImport: result.semanticImport,
2016
+ metadata: result.metadata
2017
+ })
2018
+ }), { parents, time });
2019
+ events.push(attemptEvent);
2020
+ let latestParents = [attemptEvent.id];
2021
+ if (result.patchPath || result.changedPaths.length) {
2022
+ const patchId = swarmRunPatchNodeId(result.jobId);
2023
+ const patchEvent = createRunNodeEvent(runId, actorId, actorSeq++, defineRunPatch({
2024
+ id: patchId,
2025
+ title: `Patch ${result.jobId}`,
2026
+ changedPaths: result.changedPaths,
2027
+ artifactId: result.patchPath ? swarmRunArtifactNodeId(result.patchPath) : undefined,
2028
+ summary: result.lastMessage,
2029
+ risk: swarmRiskToRunRisk(result.riskLevel),
2030
+ metadata: pruneUndefinedJsonObject({
2031
+ source: 'frontier-swarm.result.patch',
2032
+ patchPath: result.patchPath,
2033
+ changedRegions: result.changedRegions,
2034
+ ownershipViolations: result.ownershipViolations
2035
+ })
2036
+ }), { parents: latestParents, time });
2037
+ events.push(patchEvent);
2038
+ events.push(createRunEdgeEvent(runId, actorId, actorSeq++, linkRunNodes(attemptId, patchId, 'produces-patch', { createdAt: time }), {
2039
+ parents: [patchEvent.id],
2040
+ time
2041
+ }));
2042
+ if (result.patchPath) {
2043
+ events.push(createRunEvent({
2044
+ runId,
2045
+ actorId,
2046
+ actorSeq: actorSeq++,
2047
+ parents: [patchEvent.id],
2048
+ time,
2049
+ type: 'artifact.attached',
2050
+ payload: {
2051
+ subjectId: patchId,
2052
+ artifact: toJsonValue(defineRunArtifact({
2053
+ id: swarmRunArtifactNodeId(result.patchPath),
2054
+ title: result.patchPath,
2055
+ artifactType: 'patch',
2056
+ path: result.patchPath,
2057
+ summary: `Patch artifact for ${result.jobId}`,
2058
+ metadata: { source: 'frontier-swarm.result.patch-artifact', jobId: result.jobId }
2059
+ }))
2060
+ }
2061
+ }));
2062
+ }
2063
+ latestParents = [patchEvent.id];
2064
+ }
2065
+ for (const evidencePath of result.evidencePaths) {
2066
+ const artifactId = swarmRunArtifactNodeId(evidencePath);
2067
+ const evidenceId = swarmRunEvidenceNodeId(result.jobId, evidencePath);
2068
+ const artifactEvent = createRunEvent({
2069
+ runId,
2070
+ actorId,
2071
+ actorSeq: actorSeq++,
2072
+ parents: latestParents,
2073
+ time,
2074
+ type: 'artifact.attached',
2075
+ payload: {
2076
+ subjectId: attemptId,
2077
+ artifact: toJsonValue(defineRunArtifact({
2078
+ id: artifactId,
2079
+ title: evidencePath,
2080
+ artifactType: inferSwarmArtifactType(evidencePath),
2081
+ path: evidencePath,
2082
+ summary: `Evidence artifact for ${result.jobId}`,
2083
+ metadata: { source: 'frontier-swarm.result.evidence-artifact', jobId: result.jobId }
2084
+ }))
2085
+ }
2086
+ });
2087
+ events.push(artifactEvent);
2088
+ const evidenceEvent = createRunNodeEvent(runId, actorId, actorSeq++, defineRunEvidence({
2089
+ id: evidenceId,
2090
+ title: evidencePath,
2091
+ evidenceType: inferSwarmArtifactType(evidencePath),
2092
+ result: result.status === 'completed' || result.status === 'verified' ? 'pass' : result.status === 'failed' ? 'fail' : 'unknown',
2093
+ artifactIds: [artifactId],
2094
+ summary: `Evidence from ${result.jobId}`,
2095
+ metadata: { source: 'frontier-swarm.result.evidence', jobId: result.jobId }
2096
+ }), { parents: [artifactEvent.id], time });
2097
+ events.push(evidenceEvent);
2098
+ events.push(createRunEdgeEvent(runId, actorId, actorSeq++, linkRunNodes(attemptId, evidenceId, 'produces-evidence', { createdAt: time }), {
2099
+ parents: [evidenceEvent.id],
2100
+ time
2101
+ }));
2102
+ }
2103
+ for (let index = 0; index < result.verification.length; index += 1) {
2104
+ const verification = result.verification[index];
2105
+ const verificationId = swarmRunVerificationNodeId(result.jobId, index, verification.name);
2106
+ const verificationEvent = createRunNodeEvent(runId, actorId, actorSeq++, defineRunVerification({
2107
+ id: verificationId,
2108
+ title: verification.name,
2109
+ status: verification.status === 0 ? 'passed' : verification.required === false && verification.status !== 0 ? 'skipped' : 'failed',
2110
+ command: verification.command[0],
2111
+ args: verification.command.slice(1),
2112
+ cwd: verification.cwd,
2113
+ exitCode: verification.status,
2114
+ artifactIds: [],
2115
+ required: verification.required,
2116
+ summary: verification.commandLine,
2117
+ metadata: pruneUndefinedJsonObject({
2118
+ source: 'frontier-swarm.result.verification',
2119
+ jobId: result.jobId,
2120
+ durationMs: verification.durationMs,
2121
+ stdoutTail: verification.stdoutTail,
2122
+ stderrTail: verification.stderrTail,
2123
+ category: verification.category,
2124
+ metadata: verification.metadata
2125
+ })
2126
+ }), { parents: latestParents, time });
2127
+ events.push(verificationEvent);
2128
+ events.push(createRunEdgeEvent(runId, actorId, actorSeq++, linkRunNodes(attemptId, verificationId, 'verified-by', { createdAt: time }), {
2129
+ parents: [verificationEvent.id],
2130
+ time
2131
+ }));
2132
+ }
2133
+ return events;
2134
+ }
2135
+ export function createRunEventsFromMergeBundle(bundle, options = {}) {
2136
+ const runId = options.runId ?? bundle.runId ?? 'frontier-swarm';
2137
+ const actorId = options.actorId ?? 'frontier-swarm-collector';
2138
+ const time = swarmRunEventTime(options, bundle.generatedAt);
2139
+ let actorSeq = options.startActorSeq ?? 1;
2140
+ const parents = [...(options.parents ?? [])];
2141
+ const events = [];
2142
+ const patchId = swarmRunPatchNodeId(bundle.jobId);
2143
+ const patchEvent = createRunNodeEvent(runId, actorId, actorSeq++, defineRunPatch({
2144
+ id: patchId,
2145
+ title: bundle.title ?? `Merge bundle ${bundle.jobId}`,
2146
+ changedPaths: bundle.changedPaths,
2147
+ artifactId: bundle.patchPath ? swarmRunArtifactNodeId(bundle.patchPath) : undefined,
2148
+ summary: bundle.reasons.join('; '),
2149
+ risk: swarmRiskToRunRisk(bundle.riskLevel),
2150
+ metadata: pruneUndefinedJsonObject({
2151
+ source: 'frontier-swarm.merge-bundle',
2152
+ bundleId: bundle.id,
2153
+ jobId: bundle.jobId,
2154
+ taskId: bundle.taskId,
2155
+ lane: bundle.lane,
2156
+ mergeReadiness: bundle.mergeReadiness,
2157
+ disposition: bundle.disposition,
2158
+ autoMergeable: bundle.autoMergeable,
2159
+ staleAgainstHead: bundle.staleAgainstHead,
2160
+ changedRegions: bundle.changedRegions,
2161
+ ownedFilesTouched: bundle.ownedFilesTouched,
2162
+ ownershipViolations: bundle.ownershipViolations,
2163
+ patchHash: bundle.patchHash,
2164
+ branchName: bundle.branchName,
2165
+ commit: bundle.commit,
2166
+ semanticImport: bundle.semanticImport
2167
+ })
2168
+ }), { parents, time });
2169
+ events.push(patchEvent);
2170
+ if (bundle.patchPath) {
2171
+ events.push(createRunEvent({
2172
+ runId,
2173
+ actorId,
2174
+ actorSeq: actorSeq++,
2175
+ parents: [patchEvent.id],
2176
+ time,
2177
+ type: 'artifact.attached',
2178
+ payload: {
2179
+ subjectId: patchId,
2180
+ artifact: toJsonValue(defineRunArtifact({
2181
+ id: swarmRunArtifactNodeId(bundle.patchPath),
2182
+ title: bundle.patchPath,
2183
+ artifactType: 'patch',
2184
+ path: bundle.patchPath,
2185
+ hash: bundle.patchHash,
2186
+ summary: `Merge patch for ${bundle.jobId}`,
2187
+ metadata: { source: 'frontier-swarm.merge-bundle.patch', bundleId: bundle.id }
2188
+ }))
2189
+ }
2190
+ }));
2191
+ }
2192
+ for (const evidencePath of bundle.evidencePaths) {
2193
+ events.push(createRunEvent({
2194
+ runId,
2195
+ actorId,
2196
+ actorSeq: actorSeq++,
2197
+ parents: [patchEvent.id],
2198
+ time,
2199
+ type: 'artifact.attached',
2200
+ payload: {
2201
+ subjectId: patchId,
2202
+ artifact: toJsonValue(defineRunArtifact({
2203
+ id: swarmRunArtifactNodeId(evidencePath),
2204
+ title: evidencePath,
2205
+ artifactType: inferSwarmArtifactType(evidencePath),
2206
+ path: evidencePath,
2207
+ summary: `Merge evidence for ${bundle.jobId}`,
2208
+ metadata: { source: 'frontier-swarm.merge-bundle.evidence', bundleId: bundle.id }
2209
+ }))
2210
+ }
2211
+ }));
2212
+ }
2213
+ const decisionKind = swarmDispositionToRunDecision(bundle.disposition, bundle.mergeReadiness);
2214
+ const decisionEvent = createRunEvent({
2215
+ runId,
2216
+ actorId,
2217
+ actorSeq: actorSeq++,
2218
+ parents: [patchEvent.id],
2219
+ time,
2220
+ type: 'decision.recorded',
2221
+ payload: {
2222
+ decision: toJsonValue(defineRunDecision({
2223
+ id: swarmRunDecisionNodeId(bundle.id || bundle.jobId),
2224
+ title: `Merge decision ${bundle.jobId}`,
2225
+ decision: decisionKind,
2226
+ subjectIds: uniqueStrings([patchId, bundle.taskId ? swarmRunTaskNodeId(bundle.taskId) : undefined]),
2227
+ actorId,
2228
+ reason: bundle.reasons.join('; ') || bundle.disposition,
2229
+ requiredActions: bundle.disposition === 'needs-port' ? ['human-port'] : bundle.staleAgainstHead ? ['rerun-against-head'] : [],
2230
+ metadata: pruneUndefinedJsonObject({
2231
+ source: 'frontier-swarm.merge-bundle.decision',
2232
+ bundleId: bundle.id,
2233
+ disposition: bundle.disposition,
2234
+ mergeReadiness: bundle.mergeReadiness,
2235
+ riskLevel: bundle.riskLevel,
2236
+ autoMergeable: bundle.autoMergeable
2237
+ })
2238
+ }))
2239
+ }
2240
+ });
2241
+ events.push(decisionEvent);
2242
+ return events;
2243
+ }
2244
+ export function createRunEventsFromCoordinatorDecision(decision, options = {}) {
2245
+ const normalized = createSwarmQueueOutcomeDecision(decision);
2246
+ const runId = options.runId ?? 'frontier-swarm';
2247
+ const actorId = options.actorId ?? 'frontier-swarm-coordinator';
2248
+ const time = swarmRunEventTime(options, normalized.generatedAt);
2249
+ const subjectIds = uniqueStrings([
2250
+ normalized.jobId ? swarmRunAttemptNodeId(normalized.jobId) : undefined,
2251
+ normalized.taskId ? swarmRunTaskNodeId(normalized.taskId) : undefined,
2252
+ normalized.subjectId
2253
+ ]);
2254
+ return [createRunEvent({
2255
+ runId,
2256
+ actorId,
2257
+ actorSeq: options.startActorSeq ?? 1,
2258
+ parents: [...(options.parents ?? [])],
2259
+ time,
2260
+ type: 'decision.recorded',
2261
+ payload: {
2262
+ decision: toJsonValue(defineRunDecision({
2263
+ id: swarmRunDecisionNodeId(normalized.id),
2264
+ title: `Coordinator decision ${normalized.subjectId}`,
2265
+ decision: swarmQueueOutcomeToRunDecision(normalized),
2266
+ subjectIds,
2267
+ actorId,
2268
+ reason: normalized.reasons.join('; ') || normalized.outcome,
2269
+ requiredActions: swarmQueueOutcomeRequiredActions(normalized),
2270
+ metadata: pruneUndefinedJsonObject({
2271
+ source: 'frontier-swarm.queue-outcome-decision',
2272
+ decision: normalized.decision,
2273
+ action: normalized.action,
2274
+ assignedAction: normalized.assignedAction,
2275
+ category: normalized.category,
2276
+ outcome: normalized.outcome,
2277
+ terminal: normalized.terminal,
2278
+ queueItemIds: normalized.queueItemIds,
2279
+ queueId: normalized.queueId,
2280
+ lane: normalized.lane,
2281
+ disposition: normalized.disposition,
2282
+ mergeReadiness: normalized.mergeReadiness,
2283
+ status: normalized.status,
2284
+ conflictingJobIds: normalized.conflictingJobIds,
2285
+ metadata: normalized.metadata
2286
+ })
2287
+ }))
2288
+ }
2289
+ })];
2290
+ }
2291
+ export function createRunProjectionFromSwarmRunEvents(events, options = {}) {
2292
+ return replayRunEvents(events, {
2293
+ id: options.runId,
2294
+ goal: options.goal,
2295
+ metadata: toJsonObject(options.metadata)
2296
+ });
2297
+ }
2298
+ export function createRunDashboardFromSwarmRun(input, options = {}) {
2299
+ const events = isFrontierRunEventList(input) ? input : createRunEventsFromSwarmRun(input, options);
2300
+ return createRunDashboardSnapshot(createRunProjectionFromSwarmRunEvents(events, options));
2301
+ }
2302
+ export function createRunEventsFromSwarmRun(run, options = {}) {
2303
+ const runId = options.runId ?? run.id;
2304
+ const actorId = options.actorId ?? 'frontier-swarm';
2305
+ const created = createRunEvent({
2306
+ runId,
2307
+ actorId,
2308
+ actorSeq: options.startActorSeq ?? 1,
2309
+ parents: [...(options.parents ?? [])],
2310
+ time: swarmRunEventTime(options, run.startedAt),
2311
+ type: 'run.created',
2312
+ payload: {
2313
+ goal: run.metadata?.objective ?? run.metadata?.goal ?? `Frontier swarm ${run.id}`,
2314
+ metadata: pruneUndefinedJsonObject({
2315
+ source: 'frontier-swarm.run',
2316
+ planId: run.planId,
2317
+ manifestId: run.manifestId,
2318
+ status: run.status,
2319
+ summary: run.summary,
2320
+ metadata: run.metadata
2321
+ })
2322
+ }
2323
+ });
2324
+ return [
2325
+ created,
2326
+ ...run.results.flatMap((result, index) => createRunEventsFromSwarmResult(result, {
2327
+ runId,
2328
+ actorId: 'frontier-swarm-worker',
2329
+ startActorSeq: 1000 + index * 100,
2330
+ parents: [created.id],
2331
+ job: run.jobs.find((job) => job.id === result.jobId)
2332
+ }))
2333
+ ];
752
2334
  }
753
2335
  export function recordSwarmEvent(runInput, eventInput) {
754
2336
  const run = cloneJsonValue(runInput);
@@ -1030,6 +2612,14 @@ const SEMANTIC_IMPORT_CONFLICT_HINT_KEYS = new Set([
1030
2612
  'symbols',
1031
2613
  'touchedSymbols'
1032
2614
  ]);
2615
+ const SEMANTIC_IMPORT_PUBLIC_CONTRACT_HINT_KEYS = new Set([
2616
+ 'apiContract',
2617
+ 'apiContracts',
2618
+ 'publicContract',
2619
+ 'publicContractId',
2620
+ 'publicContractIds',
2621
+ 'publicContracts'
2622
+ ]);
1033
2623
  function semanticImportExportMetadata(value, key, file) {
1034
2624
  const explicitKind = semanticImportStringValue(value.kind);
1035
2625
  const declarationKind = semanticImportStringValue(value.declarationKind)
@@ -1119,9 +2709,28 @@ function semanticImportStringValue(value) {
1119
2709
  function semanticMergeConflictKeys(semanticImport) {
1120
2710
  const records = semanticImportRecords(semanticImport);
1121
2711
  const sources = records.length > 0 ? records : [semanticImport];
1122
- return uniqueStrings(sources.flatMap((source) => semanticImportConflictHintStringsForValue('mergeCandidate' in source && source.mergeCandidate !== undefined
2712
+ return uniqueStrings(sources.flatMap((source) => semanticImportConflictKeyStringsForValue('mergeCandidate' in source && source.mergeCandidate !== undefined
1123
2713
  ? source.mergeCandidate
1124
- : source).map((symbol) => `symbol:${symbol}`)));
2714
+ : source)));
2715
+ }
2716
+ function semanticImportConflictKeyStringsForValue(value, key, depth = 0) {
2717
+ if (value === undefined || value === null)
2718
+ return [];
2719
+ if (typeof value === 'string') {
2720
+ const normalized = value.trim();
2721
+ if (!normalized)
2722
+ return [];
2723
+ if (key && SEMANTIC_IMPORT_PUBLIC_CONTRACT_HINT_KEYS.has(key))
2724
+ return [`public-contract:${publicContractConflictValue(normalized)}`];
2725
+ if (key && SEMANTIC_IMPORT_CONFLICT_HINT_KEYS.has(key))
2726
+ return [`symbol:${normalized}`];
2727
+ return [];
2728
+ }
2729
+ if (depth >= 4 || typeof value !== 'object')
2730
+ return [];
2731
+ if (Array.isArray(value))
2732
+ return value.flatMap((item) => semanticImportConflictKeyStringsForValue(item, key, depth + 1));
2733
+ return Object.entries(value).flatMap(([nextKey, nextValue]) => (semanticImportConflictKeyStringsForValue(nextValue, nextKey, depth + 1)));
1125
2734
  }
1126
2735
  function semanticImportConflictHintStringsForValue(value, key, depth = 0) {
1127
2736
  if (value === undefined || value === null)
@@ -2467,6 +4076,11 @@ export function createSwarmHierarchicalMergeQueue(input) {
2467
4076
  const admissionPressure = summarizeSwarmMergeAdmissionPressure(assignments);
2468
4077
  const orderedScopes = Array.from(scopes.values()).sort((left, right) => (mergeQueueScopeRank(left.kind) - mergeQueueScopeRank(right.kind)
2469
4078
  || left.id.localeCompare(right.id)));
4079
+ const semanticLeaseScopeMap = createSwarmSemanticLeaseScopeMap(orderedScopes, {
4080
+ rootScopeId,
4081
+ ...semanticLeaseScopeOptionsFromMetadata(input.metadata)
4082
+ });
4083
+ const scopesWithSemanticLeases = orderedScopes.map((scope) => attachSemanticLeaseScopeToMergeQueueScope(scope, semanticLeaseScopeMap));
2470
4084
  const queueId = input.id ?? 'swarm-hierarchical-merge-queue:' + stableHash([input.index.id, input.admission?.id, orderedScopes, assignments, promotions, generatedAt]);
2471
4085
  const linkedAssignments = assignments.map((assignment) => {
2472
4086
  if (!coordinatorAgentDrainActionIsTerminal(assignment.action))
@@ -2476,19 +4090,19 @@ export function createSwarmHierarchicalMergeQueue(input) {
2476
4090
  terminalDecisionId: hierarchicalQueueTerminalDecisionId(queueId, assignment),
2477
4091
  terminalDecisionQueueItemIds: [...assignment.queueItemIds]
2478
4092
  };
2479
- });
4093
+ }).map((assignment) => attachSemanticLeaseScopesToAssignment(assignment, semanticLeaseScopeMap));
2480
4094
  const leaseRecords = createHierarchicalQueueLeaseRecords({
2481
4095
  queueId,
2482
4096
  rootScopeId,
2483
4097
  generatedAt,
2484
- scopes: orderedScopes,
4098
+ scopes: scopesWithSemanticLeases,
2485
4099
  assignments: linkedAssignments,
2486
4100
  promotions,
2487
4101
  localLeader: input.localLeader,
2488
4102
  localLeaders: input.localLeaders
2489
4103
  });
2490
4104
  const byLeaseKey = groupJobIdsByMany(linkedAssignments, (assignment) => assignment.requiredLeaseKeys ?? [assignment.leaseKey]);
2491
- const scopeTree = createHierarchicalQueueScopeTree(orderedScopes, rootScopeId);
4105
+ const scopeTree = createHierarchicalQueueScopeTree(scopesWithSemanticLeases, rootScopeId);
2492
4106
  return {
2493
4107
  kind: FRONTIER_SWARM_HIERARCHICAL_MERGE_QUEUE_KIND,
2494
4108
  version: FRONTIER_SWARM_HIERARCHICAL_MERGE_QUEUE_VERSION,
@@ -2498,13 +4112,14 @@ export function createSwarmHierarchicalMergeQueue(input) {
2498
4112
  generatedAt,
2499
4113
  rootScopeId,
2500
4114
  scopeTree,
2501
- scopes: orderedScopes,
4115
+ scopes: scopesWithSemanticLeases,
2502
4116
  leaseRecords,
2503
4117
  assignments: linkedAssignments,
2504
4118
  promotions,
2505
4119
  byScope,
2506
4120
  byLeaseKey,
2507
4121
  byAction,
4122
+ semanticLeaseScopes: uniqueSemanticLeaseScopes(Array.from(semanticLeaseScopeMap.values())),
2508
4123
  summary: {
2509
4124
  scopeCount: orderedScopes.length,
2510
4125
  assignmentCount: linkedAssignments.length,
@@ -2534,6 +4149,7 @@ export function createSwarmCoordinatorAgentDrainWork(input) {
2534
4149
  title: scope.title,
2535
4150
  leaseScope: scope.leaseKey,
2536
4151
  leaseKey: scope.leaseKey,
4152
+ ...(scope.semanticLeaseScope ? { semanticLeaseScope: scope.semanticLeaseScope } : {}),
2537
4153
  ...(scope.parentId ? { parentQueueId: scope.parentId } : {}),
2538
4154
  ...(scope.lane ? { lane: scope.lane } : {}),
2539
4155
  changedPaths: [...scope.changedPaths],
@@ -2563,6 +4179,9 @@ export function createSwarmCoordinatorAgentDrainWork(input) {
2563
4179
  const requiredLeaseKeys = assignmentRequiredLeaseKeys.length
2564
4180
  ? [...assignmentRequiredLeaseKeys]
2565
4181
  : [leaseScope];
4182
+ const requiredSemanticLeaseScopes = requiredLeaseScopeIds
4183
+ .map((scopeId) => scopesById.get(scopeId)?.semanticLeaseScope)
4184
+ .filter((scope) => Boolean(scope));
2566
4185
  const decision = coordinatorAgentDrainDecisionForAction(assignment.action);
2567
4186
  const terminal = coordinatorAgentDrainActionIsTerminal(assignment.action);
2568
4187
  const parentQueueId = assignment.action === 'promote'
@@ -2599,6 +4218,7 @@ export function createSwarmCoordinatorAgentDrainWork(input) {
2599
4218
  conflictingJobIds: [...assignment.conflictingJobIds],
2600
4219
  requiredLeaseScopeIds,
2601
4220
  requiredLeaseKeys,
4221
+ ...(requiredSemanticLeaseScopes.length ? { requiredSemanticLeaseScopes } : {}),
2602
4222
  ...(assignment.retrySlices?.length ? { retrySlices: cloneMergeQueueRetrySlices(assignment.retrySlices) } : {}),
2603
4223
  ...(assignment.semanticSliceScopeIds?.length ? { semanticSliceScopeIds: [...assignment.semanticSliceScopeIds] } : {}),
2604
4224
  ...(assignment.semanticSliceLeaseKeys?.length ? { semanticSliceLeaseKeys: [...assignment.semanticSliceLeaseKeys] } : {}),
@@ -2623,6 +4243,7 @@ export function createSwarmCoordinatorAgentDrainWork(input) {
2623
4243
  reasons: [...assignment.reasons],
2624
4244
  requiredLeaseScopeIds: [...(assignment.requiredLeaseScopeIds ?? [])],
2625
4245
  requiredLeaseKeys: [...(assignment.requiredLeaseKeys ?? [])],
4246
+ ...(assignment.requiredSemanticLeaseScopes?.length ? { requiredSemanticLeaseScopes: assignment.requiredSemanticLeaseScopes.map(cloneSemanticLeaseScope) } : {}),
2626
4247
  ...(assignment.retrySlices?.length ? { retrySlices: cloneMergeQueueRetrySlices(assignment.retrySlices) } : {}),
2627
4248
  ...(assignment.semanticSliceScopeIds?.length ? { semanticSliceScopeIds: [...assignment.semanticSliceScopeIds] } : {}),
2628
4249
  ...(assignment.semanticSliceLeaseKeys?.length ? { semanticSliceLeaseKeys: [...assignment.semanticSliceLeaseKeys] } : {}),
@@ -2649,6 +4270,7 @@ export function createSwarmCoordinatorAgentDrainWork(input) {
2649
4270
  reasons: [...assignment.reasons],
2650
4271
  requiredLeaseScopeIds: [...(assignment.requiredLeaseScopeIds ?? [])],
2651
4272
  requiredLeaseKeys: [...(assignment.requiredLeaseKeys ?? [])],
4273
+ ...(assignment.requiredSemanticLeaseScopes?.length ? { requiredSemanticLeaseScopes: assignment.requiredSemanticLeaseScopes.map(cloneSemanticLeaseScope) } : {}),
2652
4274
  ...(assignment.metadata ? { metadata: cloneJsonValue(assignment.metadata) } : {})
2653
4275
  }));
2654
4276
  const activeAssignments = assignments.filter((assignment) => !coordinatorAgentDrainAssignmentIsTerminal(assignment));
@@ -2706,6 +4328,120 @@ export function createSwarmCoordinatorAgentDrainWork(input) {
2706
4328
  ...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
2707
4329
  };
2708
4330
  }
4331
+ export function createSwarmSemanticLeaseScopeForMergeQueueScope(scope, input = {}) {
4332
+ const scopesById = new Map((input.scopes ?? []).map((entry) => [entry.id, entry]));
4333
+ const parentKeys = [];
4334
+ let parentId = scope.parentId;
4335
+ const visited = new Set([scope.id]);
4336
+ while (parentId && !visited.has(parentId)) {
4337
+ const parent = scopesById.get(parentId);
4338
+ if (!parent)
4339
+ break;
4340
+ parentKeys.push(parent.leaseKey);
4341
+ visited.add(parentId);
4342
+ parentId = parent.parentId;
4343
+ }
4344
+ const regionId = scope.changedRegions.length === 1 ? scope.changedRegions[0] : scope.id;
4345
+ const path = scope.changedPaths.length === 1 ? scope.changedPaths[0] : undefined;
4346
+ const metadata = mergeSwarmMetadata([
4347
+ scope.metadata,
4348
+ toJsonObject(input.metadata),
4349
+ {
4350
+ swarmQueueScopeId: scope.id,
4351
+ swarmQueueScopeKind: scope.kind,
4352
+ swarmLeaseKey: scope.leaseKey,
4353
+ changedPaths: scope.changedPaths,
4354
+ changedRegions: scope.changedRegions
4355
+ }
4356
+ ]);
4357
+ return defineSemanticLeaseScope({
4358
+ kind: semanticLeaseScopeKindForMergeQueueScope(scope),
4359
+ key: scope.leaseKey,
4360
+ repository: input.repository,
4361
+ packageId: input.packageId,
4362
+ lane: scope.lane,
4363
+ path,
4364
+ regionId,
4365
+ name: semanticLeaseScopeNameForMergeQueueScope(scope),
4366
+ parentKeys,
4367
+ metadata
4368
+ });
4369
+ }
4370
+ export function createSwarmSemanticLeaseScopesForMergeQueue(queue, input = {}) {
4371
+ const existing = queue.scopes
4372
+ .map((scope) => scope.semanticLeaseScope)
4373
+ .filter((scope) => Boolean(scope));
4374
+ if (existing.length)
4375
+ return uniqueSemanticLeaseScopes(existing);
4376
+ return uniqueSemanticLeaseScopes(queue.scopes.map((scope) => (createSwarmSemanticLeaseScopeForMergeQueueScope(scope, {
4377
+ ...input,
4378
+ rootScopeId: input.rootScopeId ?? queue.rootScopeId,
4379
+ scopes: queue.scopes
4380
+ }))));
4381
+ }
4382
+ export function createSwarmSemanticLeaseStateForMergeQueue(queue, input = {}) {
4383
+ const semanticLeaseScopes = createSwarmSemanticLeaseScopesForMergeQueue(queue, input);
4384
+ return createSemanticLeaseState({
4385
+ id: input.id ?? `frontier-swarm-merge-queue:${queue.id}`,
4386
+ defaultTtlMs: input.defaultTtlMs,
4387
+ metadata: mergeSwarmMetadata([
4388
+ toJsonObject(input.metadata),
4389
+ {
4390
+ swarmQueueId: queue.id,
4391
+ swarmMergeIndexId: queue.mergeIndexId,
4392
+ rootScopeId: queue.rootScopeId,
4393
+ semanticLeaseScopeCount: semanticLeaseScopes.length,
4394
+ generatedAt: input.now ?? queue.generatedAt
4395
+ }
4396
+ ])
4397
+ });
4398
+ }
4399
+ export function acquireSwarmCoordinatorSemanticLease(input) {
4400
+ const scopes = semanticLeaseScopesForCoordinatorAssignment(input.queue, input.assignment, input);
4401
+ const requiredLeaseScopeIds = coordinatorAssignmentRequiredLeaseScopeIds(input.assignment);
4402
+ const requiredLeaseKeys = coordinatorAssignmentRequiredLeaseKeys(input.assignment, scopes);
4403
+ const state = input.state ?? createSwarmSemanticLeaseStateForMergeQueue(input.queue, input);
4404
+ const acquireInput = {
4405
+ ownerId: input.ownerId,
4406
+ holderId: input.holderId,
4407
+ now: input.now,
4408
+ ttlMs: input.ttlMs,
4409
+ purpose: input.purpose ?? `frontier swarm coordinator apply: ${input.assignment.jobId}`,
4410
+ reason: input.reason,
4411
+ scopes,
4412
+ metadata: mergeSwarmMetadata([
4413
+ toJsonObject(input.metadata),
4414
+ {
4415
+ swarmQueueId: input.queue.id,
4416
+ swarmJobId: input.assignment.jobId,
4417
+ swarmTaskId: input.assignment.taskId,
4418
+ requiredLeaseScopeIds,
4419
+ requiredLeaseKeys
4420
+ }
4421
+ ])
4422
+ };
4423
+ const mutation = acquireSemanticLease(state, acquireInput);
4424
+ return {
4425
+ state: mutation.state,
4426
+ mutation,
4427
+ scopes,
4428
+ requiredLeaseScopeIds,
4429
+ requiredLeaseKeys,
4430
+ ...(mutation.lease ? { lease: mutation.lease } : {})
4431
+ };
4432
+ }
4433
+ export function validateSwarmCoordinatorSemanticLeaseFence(input) {
4434
+ const requiredScopes = input.requiredSemanticLeaseScopes?.length
4435
+ ? input.requiredSemanticLeaseScopes.map(cloneSemanticLeaseScope)
4436
+ : (input.assignment.requiredSemanticLeaseScopes ?? []).map(cloneSemanticLeaseScope);
4437
+ return validateSemanticLeaseFence(input.state, {
4438
+ leaseId: input.leaseId ?? input.lease?.id ?? '',
4439
+ token: input.token,
4440
+ fencingToken: input.fencingToken,
4441
+ now: input.now,
4442
+ scopes: requiredScopes.length ? requiredScopes : undefined
4443
+ });
4444
+ }
2709
4445
  export function summarizeSwarmCoordinatorAgentDrainWork(work) {
2710
4446
  const activeAssignments = work.assignments.filter((assignment) => !coordinatorAgentDrainAssignmentIsTerminal(assignment));
2711
4447
  const terminalAssignments = work.assignments.filter((assignment) => coordinatorAgentDrainAssignmentIsTerminal(assignment));
@@ -2879,7 +4615,8 @@ export function classifySwarmQueueOutcome(input) {
2879
4615
  || action === 'reject'
2880
4616
  || action === 'record-only'
2881
4617
  || input.disposition === 'rejected'
2882
- || input.mergeReadiness === 'discovery-only') {
4618
+ || input.mergeReadiness === 'discovery-only'
4619
+ || queueOutcomeHas(search, 'research-complete', 'discovery-complete', 'synthesized')) {
2883
4620
  category = 'terminal';
2884
4621
  }
2885
4622
  else if (action === 'rerun'
@@ -3137,6 +4874,8 @@ function terminalOutcomeLabelFromText(value) {
3137
4874
  return 'evidence-only';
3138
4875
  if (token === 'no-change' || token === 'nochange' || token === 'no-op' || token === 'noop' || token === 'unchanged')
3139
4876
  return 'no-change';
4877
+ if (token === 'research-complete' || token === 'discovery-complete' || token === 'synthesized')
4878
+ return 'research-complete';
3140
4879
  if (token === 'generated-by-collector' || token === 'collector-generated' || token === 'generated-collector')
3141
4880
  return 'generated-by-collector';
3142
4881
  if (token === 'patch-missing' || token === 'missing-patch' || token === 'patchmissing')
@@ -3149,7 +4888,7 @@ function terminalOutcomeLabelFromText(value) {
3149
4888
  return 'rejected';
3150
4889
  if (token === 'conflict-blocked' || token === 'merge-conflict' || token === 'textual-conflict' || token === 'conflict')
3151
4890
  return 'conflict-blocked';
3152
- if (token === 'human-question' || token === 'human-blocked' || token === 'blocked')
4891
+ if (token === 'human-needed' || token === 'human-question' || token === 'human-blocked' || token === 'blocked')
3153
4892
  return 'human-question';
3154
4893
  if (token === 'coordinator-review' || token === 'needs-port' || token === 'escalated' || token === 'review')
3155
4894
  return 'coordinator-review';
@@ -3178,6 +4917,12 @@ export function normalizeSwarmTerminalOutcome(input = {}) {
3178
4917
  || terminalOutcomeTextMatches(input.outcome, 'no-change', 'nochange', 'no-op', 'noop', 'unchanged')
3179
4918
  || terminalOutcomeTextMatches(input.status, 'no-change', 'nochange', 'no-op', 'noop', 'unchanged')
3180
4919
  || terminalOutcomeTextMatches(input.decision, 'no-change', 'nochange', 'no-op', 'noop', 'unchanged');
4920
+ const researchComplete = typeof input === 'string'
4921
+ ? terminalOutcomeTextMatches(input, 'research-complete', 'discovery-complete', 'synthesized')
4922
+ : terminalOutcomeTextMatches(input.label, 'research-complete', 'discovery-complete', 'synthesized')
4923
+ || terminalOutcomeTextMatches(input.outcome, 'research-complete', 'discovery-complete', 'synthesized')
4924
+ || terminalOutcomeTextMatches(input.status, 'research-complete', 'discovery-complete', 'synthesized')
4925
+ || terminalOutcomeTextMatches(input.decision, 'research-complete', 'discovery-complete', 'synthesized');
3181
4926
  const patchMissing = typeof input === 'string'
3182
4927
  ? terminalOutcomeTextMatches(input, 'patch-missing', 'missing-patch', 'patchmissing')
3183
4928
  : input.patchMissing === true
@@ -3200,8 +4945,12 @@ export function normalizeSwarmTerminalOutcome(input = {}) {
3200
4945
  || terminalOutcomeTextMatches(input.status, 'conflict-blocked', 'merge-conflict', 'textual-conflict', 'conflict')
3201
4946
  || terminalOutcomeTextMatches(input.decision, 'conflict-blocked', 'merge-conflict', 'textual-conflict', 'conflict');
3202
4947
  const humanQuestion = typeof input === 'string'
3203
- ? terminalOutcomeTextMatches(input, 'human-question')
4948
+ ? terminalOutcomeTextMatches(input, 'human-needed', 'human-question')
3204
4949
  : input.humanQuestion === true
4950
+ || terminalOutcomeTextMatches(input.label, 'human-needed')
4951
+ || terminalOutcomeTextMatches(input.outcome, 'human-needed')
4952
+ || terminalOutcomeTextMatches(input.status, 'human-needed')
4953
+ || terminalOutcomeTextMatches(input.decision, 'human-needed')
3205
4954
  || terminalOutcomeTextMatches(input.label, 'human-question')
3206
4955
  || terminalOutcomeTextMatches(input.outcome, 'human-question')
3207
4956
  || terminalOutcomeTextMatches(input.status, 'human-question')
@@ -3227,18 +4976,20 @@ export function normalizeSwarmTerminalOutcome(input = {}) {
3227
4976
  ? 'patch-missing'
3228
4977
  : evidenceOnly
3229
4978
  ? 'evidence-only'
3230
- : noChange
3231
- ? 'no-change'
3232
- : generatedByCollector
3233
- ? 'generated-by-collector'
3234
- : conflictBlocked
3235
- ? 'conflict-blocked'
3236
- : humanQuestion || humanBlocked
3237
- ? 'human-question'
3238
- : coordinatorReview
3239
- ? 'coordinator-review'
3240
- : explicitLabel ?? 'unknown';
3241
- const category = label === 'applied' || label === 'committed' || label === 'checked' || label === 'superseded' || label === 'evidence-only' || label === 'no-change' || label === 'generated-by-collector'
4979
+ : researchComplete
4980
+ ? 'research-complete'
4981
+ : noChange
4982
+ ? 'no-change'
4983
+ : generatedByCollector
4984
+ ? 'generated-by-collector'
4985
+ : conflictBlocked
4986
+ ? 'conflict-blocked'
4987
+ : humanQuestion || humanBlocked
4988
+ ? 'human-question'
4989
+ : coordinatorReview
4990
+ ? 'coordinator-review'
4991
+ : explicitLabel ?? 'unknown';
4992
+ const category = label === 'applied' || label === 'committed' || label === 'checked' || label === 'superseded' || label === 'evidence-only' || label === 'no-change' || label === 'research-complete' || label === 'generated-by-collector'
3242
4993
  ? 'success'
3243
4994
  : label === 'patch-missing' || label === 'bundle-missing'
3244
4995
  ? 'incomplete'
@@ -3264,6 +5015,65 @@ export function normalizeSwarmTerminalOutcome(input = {}) {
3264
5015
  ...(typeof input !== 'string' && toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
3265
5016
  };
3266
5017
  }
5018
+ export function createSwarmTerminalOutcomeRecord(input = {}) {
5019
+ const generatedAt = input.generatedAt ?? Date.now();
5020
+ const queueItemIds = uniqueStrings([input.queueItemId, ...(input.queueItemIds ?? [])]);
5021
+ const subjectId = input.subjectId?.trim()
5022
+ || queueItemIds[0]
5023
+ || input.taskId?.trim()
5024
+ || input.jobId?.trim()
5025
+ || input.id?.trim()
5026
+ || 'unknown-subject';
5027
+ const subjectAliases = uniqueStrings([
5028
+ input.subjectId,
5029
+ ...(input.subjectAliases ?? []),
5030
+ ...queueItemIds,
5031
+ input.taskId,
5032
+ input.jobId
5033
+ ]);
5034
+ const outcome = normalizeSwarmTerminalOutcome(terminalOutcomeInputForRecord(input));
5035
+ const status = terminalOutcomeRecordStatusFromText(input.status)
5036
+ ?? terminalOutcomeRecordStatusFromOutcome(outcome);
5037
+ const reasonCodes = normalizeSwarmTerminalOutcomeReasonCodes([
5038
+ ...(input.reasonCodes ?? []),
5039
+ ...defaultTerminalOutcomeRecordReasonCodes(status)
5040
+ ]);
5041
+ const admitted = input.admitted ?? terminalOutcomeRecordStatusIsAdmitted(status);
5042
+ const evidenceRefs = normalizeNamedRefs(input.evidenceRefs ?? [], 'evidence');
5043
+ return {
5044
+ kind: FRONTIER_SWARM_TERMINAL_OUTCOME_RECORD_KIND,
5045
+ version: FRONTIER_SWARM_TERMINAL_OUTCOME_RECORD_VERSION,
5046
+ id: input.id ?? 'swarm-terminal-outcome-record:' + stableHash([subjectId, subjectAliases, status, reasonCodes, generatedAt]),
5047
+ generatedAt,
5048
+ subjectId,
5049
+ subjectAliases: subjectAliases.length ? subjectAliases : [subjectId],
5050
+ ...(input.jobId ? { jobId: input.jobId } : {}),
5051
+ ...(input.taskId ? { taskId: input.taskId } : {}),
5052
+ ...(input.queueId ? { queueId: input.queueId } : {}),
5053
+ queueItemIds,
5054
+ ...(input.lane ? { lane: input.lane } : {}),
5055
+ ...(input.source ? { source: input.source } : {}),
5056
+ status,
5057
+ outcome,
5058
+ terminal: true,
5059
+ admitted,
5060
+ success: outcome.success,
5061
+ rejected: status === 'rejected',
5062
+ conflict: status === 'conflict',
5063
+ humanNeeded: status === 'human-needed',
5064
+ rerun: status === 'rerun',
5065
+ noChange: status === 'no-change',
5066
+ researchComplete: status === 'research-complete',
5067
+ reasonCodes,
5068
+ reasons: uniqueStrings(input.reasons ?? []),
5069
+ conflictingJobIds: uniqueStrings(input.conflictingJobIds ?? []),
5070
+ supersedes: uniqueStrings(input.supersedes ?? []),
5071
+ ...(input.supersededBy ? { supersededBy: input.supersededBy } : {}),
5072
+ ...(input.rerunOf ? { rerunOf: input.rerunOf } : {}),
5073
+ evidenceRefs,
5074
+ ...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
5075
+ };
5076
+ }
3267
5077
  export function reconcileSwarmTerminalState(input = {}) {
3268
5078
  const generatedAt = input.generatedAt ?? Date.now();
3269
5079
  const doneBucket = input.doneBucket ?? 'done';
@@ -4054,13 +5864,25 @@ function createMergeIndexConflicts(entries) {
4054
5864
  continue;
4055
5865
  const keys = pairConflictKeys(left, right);
4056
5866
  for (const key of keys) {
4057
- const kind = key.startsWith('symbol:') ? 'symbol' : key.startsWith('region:') ? 'region' : 'path';
5867
+ const kind = key.startsWith('symbol:')
5868
+ ? 'symbol'
5869
+ : key.startsWith('public-contract:')
5870
+ ? 'public-contract'
5871
+ : key.startsWith('region:')
5872
+ ? 'region'
5873
+ : 'path';
4058
5874
  const value = key.slice(key.indexOf(':') + 1);
4059
5875
  conflicts.push({
4060
5876
  jobIds: [left.jobId, right.jobId].sort(),
4061
5877
  key,
4062
5878
  kind,
4063
- ...(kind === 'region' ? { region: value } : kind === 'symbol' ? { symbol: value } : { path: value })
5879
+ ...(kind === 'region'
5880
+ ? { region: value }
5881
+ : kind === 'symbol'
5882
+ ? { symbol: value }
5883
+ : kind === 'public-contract'
5884
+ ? { publicContract: value }
5885
+ : { path: value })
4064
5886
  });
4065
5887
  }
4066
5888
  }
@@ -4077,6 +5899,9 @@ function pairConflictKeys(left, right) {
4077
5899
  const sharedSymbols = leftKeys.filter((key) => key.startsWith('symbol:') && rightKeySet.has(key));
4078
5900
  if (sharedSymbols.length > 0)
4079
5901
  return sharedSymbols.sort();
5902
+ const sharedPublicContracts = leftKeys.filter((key) => key.startsWith('public-contract:') && rightKeySet.has(key));
5903
+ if (sharedPublicContracts.length > 0)
5904
+ return sharedPublicContracts.sort();
4080
5905
  const leftRegions = leftKeys.filter((key) => key.startsWith('region:'));
4081
5906
  const rightRegions = rightKeys.filter((key) => key.startsWith('region:'));
4082
5907
  if (leftRegions.length > 0 && rightRegions.length > 0) {
@@ -4137,6 +5962,11 @@ function stableIdPart(value) {
4137
5962
  return 'star';
4138
5963
  return normalized.length > 0 ? normalized : 'anonymous';
4139
5964
  }
5965
+ function publicContractConflictValue(value) {
5966
+ const trimmed = value.trim();
5967
+ const withoutPrefix = trimmed.startsWith('public-contract:') ? trimmed.slice('public-contract:'.length) : trimmed;
5968
+ return stableIdPart(withoutPrefix);
5969
+ }
4140
5970
  function reviewerLaneReasons(entry) {
4141
5971
  const reasons = [];
4142
5972
  if (entry.conflictingJobIds.length)
@@ -4186,6 +6016,154 @@ function ensureMergeQueueScope(scopes, input) {
4186
6016
  scopes.set(scope.id, scope);
4187
6017
  return scope;
4188
6018
  }
6019
+ function createSwarmSemanticLeaseScopeMap(scopes, input = {}) {
6020
+ const out = new Map();
6021
+ for (const scope of scopes) {
6022
+ out.set(scope.id, createSwarmSemanticLeaseScopeForMergeQueueScope(scope, { ...input, scopes }));
6023
+ }
6024
+ return out;
6025
+ }
6026
+ function attachSemanticLeaseScopeToMergeQueueScope(scope, semanticLeaseScopes) {
6027
+ const semanticLeaseScope = semanticLeaseScopes.get(scope.id);
6028
+ return {
6029
+ ...scope,
6030
+ ...(semanticLeaseScope ? { semanticLeaseScope: cloneSemanticLeaseScope(semanticLeaseScope) } : {})
6031
+ };
6032
+ }
6033
+ function attachSemanticLeaseScopesToAssignment(assignment, semanticLeaseScopes) {
6034
+ const requiredLeaseScopeIds = assignment.requiredLeaseScopeIds?.length ? assignment.requiredLeaseScopeIds : [assignment.scopeId];
6035
+ const requiredSemanticLeaseScopes = requiredLeaseScopeIds
6036
+ .map((scopeId) => semanticLeaseScopes.get(scopeId))
6037
+ .filter((scope) => Boolean(scope))
6038
+ .map(cloneSemanticLeaseScope);
6039
+ const retrySlices = assignment.retrySlices?.map((slice) => {
6040
+ const requiredScopeIds = slice.requiredLeaseScopeIds?.length ? slice.requiredLeaseScopeIds : [slice.scopeId];
6041
+ const requiredScopes = requiredScopeIds
6042
+ .map((scopeId) => semanticLeaseScopes.get(scopeId))
6043
+ .filter((scope) => Boolean(scope))
6044
+ .map(cloneSemanticLeaseScope);
6045
+ const semanticLeaseScope = semanticLeaseScopes.get(slice.scopeId);
6046
+ return {
6047
+ ...slice,
6048
+ ...(semanticLeaseScope ? { semanticLeaseScope: cloneSemanticLeaseScope(semanticLeaseScope) } : {}),
6049
+ ...(requiredScopes.length ? { requiredSemanticLeaseScopes: requiredScopes } : {})
6050
+ };
6051
+ });
6052
+ return {
6053
+ ...assignment,
6054
+ ...(requiredSemanticLeaseScopes.length ? { requiredSemanticLeaseScopes } : {}),
6055
+ ...(retrySlices?.length ? { retrySlices } : {})
6056
+ };
6057
+ }
6058
+ function semanticLeaseScopesForCoordinatorAssignment(queue, assignment, input = {}) {
6059
+ if (assignment.requiredSemanticLeaseScopes?.length) {
6060
+ return uniqueSemanticLeaseScopes(assignment.requiredSemanticLeaseScopes.map(cloneSemanticLeaseScope));
6061
+ }
6062
+ const queueScopes = new Map(queue.scopes.map((scope) => [scope.id, scope]));
6063
+ const semanticByKey = new Map(createSwarmSemanticLeaseScopesForMergeQueue(queue, input).map((scope) => [scope.key, scope]));
6064
+ const fromIds = coordinatorAssignmentRequiredLeaseScopeIds(assignment)
6065
+ .map((scopeId) => queueScopes.get(scopeId)?.semanticLeaseScope)
6066
+ .filter((scope) => Boolean(scope));
6067
+ if (fromIds.length)
6068
+ return uniqueSemanticLeaseScopes(fromIds);
6069
+ const fromKeys = coordinatorAssignmentRequiredLeaseKeys(assignment, [])
6070
+ .map((key) => semanticByKey.get(key))
6071
+ .filter((scope) => Boolean(scope));
6072
+ if (fromKeys.length)
6073
+ return uniqueSemanticLeaseScopes(fromKeys);
6074
+ const fallbackKey = 'leaseScope' in assignment ? assignment.leaseScope : assignment.leaseKey;
6075
+ return [defineSemanticLeaseScope({
6076
+ kind: 'custom',
6077
+ key: fallbackKey,
6078
+ repository: input.repository,
6079
+ packageId: input.packageId,
6080
+ metadata: mergeSwarmMetadata([
6081
+ toJsonObject(input.metadata),
6082
+ {
6083
+ swarmQueueId: queue.id,
6084
+ swarmJobId: assignment.jobId,
6085
+ fallbackLeaseKey: fallbackKey
6086
+ }
6087
+ ])
6088
+ })];
6089
+ }
6090
+ function coordinatorAssignmentRequiredLeaseScopeIds(assignment) {
6091
+ const explicit = assignment.requiredLeaseScopeIds ?? [];
6092
+ if (explicit.length)
6093
+ return uniqueStrings(explicit);
6094
+ return uniqueStrings(['queueId' in assignment ? assignment.queueId : assignment.scopeId]);
6095
+ }
6096
+ function coordinatorAssignmentRequiredLeaseKeys(assignment, scopes) {
6097
+ const explicit = assignment.requiredLeaseKeys ?? [];
6098
+ if (explicit.length)
6099
+ return uniqueStrings(explicit);
6100
+ if (scopes.length)
6101
+ return uniqueStrings(scopes.map((scope) => scope.key));
6102
+ return uniqueStrings(['leaseScope' in assignment ? assignment.leaseScope : assignment.leaseKey]);
6103
+ }
6104
+ function semanticLeaseScopeOptionsFromMetadata(metadata) {
6105
+ const object = toJsonObject(metadata);
6106
+ if (!object)
6107
+ return {};
6108
+ return {
6109
+ ...(typeof object.repository === 'string' ? { repository: object.repository } : {}),
6110
+ ...(typeof object.packageId === 'string' ? { packageId: object.packageId } : {})
6111
+ };
6112
+ }
6113
+ function semanticLeaseScopeKindForMergeQueueScope(scope) {
6114
+ if (scope.kind === 'root')
6115
+ return 'repository';
6116
+ if (scope.kind === 'path')
6117
+ return 'path';
6118
+ if (scope.kind === 'lane')
6119
+ return 'lane';
6120
+ if (scope.kind === 'semantic-region' || scope.kind === 'semantic')
6121
+ return semanticLeaseScopeKindForRegion(scope.changedRegions[0]);
6122
+ if (scope.kind === 'package')
6123
+ return 'package';
6124
+ return 'custom';
6125
+ }
6126
+ function semanticLeaseScopeKindForRegion(region) {
6127
+ if (!region)
6128
+ return 'semantic-region';
6129
+ const normalized = region.toLowerCase();
6130
+ if (normalized.includes(FRONTIER_SWARM_SEMANTIC_OWNERSHIP_EXPORT_STABLE_KEY_KIND) || normalized.includes('named-export') || normalized.includes('default-export'))
6131
+ return 'export';
6132
+ if (normalized.includes(FRONTIER_SWARM_SEMANTIC_OWNERSHIP_TYPE_STABLE_KEY_KIND) || normalized.includes('interface') || normalized.includes('type-alias'))
6133
+ return 'type';
6134
+ if (normalized.includes(FRONTIER_SWARM_SEMANTIC_OWNERSHIP_CLI_COMMAND_STABLE_KEY_KIND))
6135
+ return 'cli-command';
6136
+ if (normalized.includes(FRONTIER_SWARM_SEMANTIC_OWNERSHIP_DOCS_SECTION_STABLE_KEY_KIND))
6137
+ return 'docs-section';
6138
+ if (normalized.includes(FRONTIER_SWARM_SEMANTIC_OWNERSHIP_FIXTURE_FAMILY_STABLE_KEY_KIND))
6139
+ return 'test-fixture';
6140
+ if (normalized.includes(FRONTIER_SWARM_SEMANTIC_OWNERSHIP_TEST_CASE_STABLE_KEY_KIND))
6141
+ return 'test-fixture';
6142
+ if (normalized.includes('function'))
6143
+ return 'function';
6144
+ if (normalized.includes('class'))
6145
+ return 'class';
6146
+ if (normalized.includes('member'))
6147
+ return 'member';
6148
+ return 'semantic-region';
6149
+ }
6150
+ function semanticLeaseScopeNameForMergeQueueScope(scope) {
6151
+ const region = scope.changedRegions[0];
6152
+ if (region)
6153
+ return region.split(':').filter(Boolean).at(-1);
6154
+ if (scope.changedPaths.length === 1)
6155
+ return scope.changedPaths[0].split('/').at(-1);
6156
+ return scope.title || scope.id;
6157
+ }
6158
+ function uniqueSemanticLeaseScopes(scopes) {
6159
+ const out = new Map();
6160
+ for (const scope of scopes)
6161
+ out.set(scope.key, cloneSemanticLeaseScope(scope));
6162
+ return Array.from(out.values()).sort((left, right) => left.key.localeCompare(right.key));
6163
+ }
6164
+ function cloneSemanticLeaseScope(scope) {
6165
+ return cloneJsonValue(scope);
6166
+ }
4189
6167
  function mergeQueueRootLeaseKey(rootScopeId) {
4190
6168
  return `merge:root:${rootScopeId}`;
4191
6169
  }
@@ -4300,6 +6278,8 @@ function cloneMergeQueueRetrySlices(slices) {
4300
6278
  leaseKey: slice.leaseKey,
4301
6279
  ...(slice.requiredLeaseScopeIds?.length ? { requiredLeaseScopeIds: [...slice.requiredLeaseScopeIds] } : {}),
4302
6280
  ...(slice.requiredLeaseKeys?.length ? { requiredLeaseKeys: [...slice.requiredLeaseKeys] } : {}),
6281
+ ...(slice.semanticLeaseScope ? { semanticLeaseScope: cloneSemanticLeaseScope(slice.semanticLeaseScope) } : {}),
6282
+ ...(slice.requiredSemanticLeaseScopes?.length ? { requiredSemanticLeaseScopes: slice.requiredSemanticLeaseScopes.map(cloneSemanticLeaseScope) } : {}),
4303
6283
  ...(slice.lane ? { lane: slice.lane } : {}),
4304
6284
  changedPaths: [...slice.changedPaths],
4305
6285
  changedRegions: [...slice.changedRegions],
@@ -4349,6 +6329,7 @@ function createHierarchicalQueueLeaseRecords(input) {
4349
6329
  ...(scope.lane ? { lane: scope.lane } : {}),
4350
6330
  title: scope.title,
4351
6331
  leaseKey: scope.leaseKey,
6332
+ ...(scope.semanticLeaseScope ? { semanticLeaseScope: cloneSemanticLeaseScope(scope.semanticLeaseScope) } : {}),
4352
6333
  ...(hierarchicalQueueLocalLeaderForScope(input, scope) ? { localLeader: hierarchicalQueueLocalLeaderForScope(input, scope) } : {}),
4353
6334
  promotion: {
4354
6335
  state: promotionState,
@@ -5186,6 +7167,8 @@ function defaultQueueOutcomeForCategory(category, input, search) {
5186
7167
  return 'committed';
5187
7168
  if (action === 'apply-local' || input.decision === 'applied' || queueOutcomeHas(search, 'applied', 'apply-local', 'apply'))
5188
7169
  return 'applied';
7170
+ if (input.decision === 'research-complete' || queueOutcomeHas(search, 'research-complete', 'discovery-complete', 'synthesized'))
7171
+ return 'research-complete';
5189
7172
  if (input.decision === 'checked' || queueOutcomeHas(search, 'checked', 'check'))
5190
7173
  return 'checked';
5191
7174
  if (input.decision === 'superseded' || queueOutcomeHas(search, 'superseded'))
@@ -5249,6 +7232,8 @@ function canonicalizeSwarmQueueOutcome(value) {
5249
7232
  return 'superseded';
5250
7233
  if (token === 'no-change' || token === 'nochange' || token === 'no-op' || token === 'noop' || token === 'unchanged')
5251
7234
  return 'no-change';
7235
+ if (token === 'research-complete' || token === 'discovery-complete' || token === 'synthesized')
7236
+ return 'research-complete';
5252
7237
  if (token === 'rejected' || token === 'reject' || token === 'failed' || token === 'failure')
5253
7238
  return 'rejected';
5254
7239
  if (token === 'rerun' || token === 're-run' || token === 'retry' || token === 'needs-rerun' || token === 'stale-rerun')
@@ -5263,6 +7248,96 @@ function canonicalizeSwarmQueueOutcome(value) {
5263
7248
  return 'closed';
5264
7249
  return undefined;
5265
7250
  }
7251
+ function terminalOutcomeInputForRecord(input) {
7252
+ if (input.outcome && typeof input.outcome === 'object')
7253
+ return input.outcome;
7254
+ return {
7255
+ label: input.label,
7256
+ outcome: typeof input.outcome === 'string' ? input.outcome : undefined,
7257
+ status: input.status,
7258
+ decision: input.decision,
7259
+ reasons: input.reasons,
7260
+ metadata: input.metadata
7261
+ };
7262
+ }
7263
+ function terminalOutcomeRecordStatusFromText(value) {
7264
+ const normalized = normalizeOptionalString(value);
7265
+ if (!normalized)
7266
+ return undefined;
7267
+ const token = normalizeSwarmTerminalOutcomeText(normalized);
7268
+ if (token === 'applied' || token === 'apply-local' || token === 'apply' || token === 'committed' || token === 'commit' || token === 'checked' || token === 'check')
7269
+ return 'applied';
7270
+ if (token === 'rejected' || token === 'reject' || token === 'failed' || token === 'failure')
7271
+ return 'rejected';
7272
+ if (token === 'superseded')
7273
+ return 'superseded';
7274
+ if (token === 'no-change' || token === 'nochange' || token === 'no-op' || token === 'noop' || token === 'unchanged' || token === 'recorded' || token === 'closed' || token === 'evidence-only' || token === 'evidenceonly' || token === 'generated-by-collector')
7275
+ return 'no-change';
7276
+ if (token === 'conflict' || token === 'conflict-blocked' || token === 'merge-conflict' || token === 'textual-conflict')
7277
+ return 'conflict';
7278
+ if (token === 'human-needed' || token === 'human-question' || token === 'human-blocked' || token === 'blocked' || token === 'coordinator-review' || token === 'needs-port' || token === 'escalated' || token === 'review')
7279
+ return 'human-needed';
7280
+ if (token === 'research-complete' || token === 'discovery-complete' || token === 'synthesized')
7281
+ return 'research-complete';
7282
+ if (token === 'rerun' || token === 're-run' || token === 'retry' || token === 'needs-rerun' || token === 'stale-rerun')
7283
+ return 'rerun';
7284
+ return token;
7285
+ }
7286
+ function terminalOutcomeRecordStatusFromOutcome(outcome) {
7287
+ return terminalOutcomeRecordStatusFromText(outcome.label)
7288
+ ?? terminalOutcomeRecordStatusFromText(outcome.category)
7289
+ ?? 'no-change';
7290
+ }
7291
+ function terminalOutcomeRecordStatusIsAdmitted(status) {
7292
+ return status === 'applied'
7293
+ || status === 'superseded'
7294
+ || status === 'no-change'
7295
+ || status === 'research-complete';
7296
+ }
7297
+ function defaultTerminalOutcomeRecordReasonCodes(status) {
7298
+ if (status === 'applied')
7299
+ return ['accepted-by-admission'];
7300
+ if (status === 'rejected')
7301
+ return ['failed-verification'];
7302
+ if (status === 'superseded')
7303
+ return ['superseded-by-newer-output'];
7304
+ if (status === 'no-change')
7305
+ return ['no-effective-change'];
7306
+ if (status === 'conflict')
7307
+ return ['conflict-detected'];
7308
+ if (status === 'human-needed')
7309
+ return ['human-decision-required'];
7310
+ if (status === 'research-complete')
7311
+ return ['research-complete'];
7312
+ if (status === 'rerun')
7313
+ return ['stale-rerun-required'];
7314
+ return [];
7315
+ }
7316
+ function normalizeSwarmTerminalOutcomeReasonCodes(reasonCodes) {
7317
+ return uniqueStrings(uniqueStrings(reasonCodes).map(canonicalizeSwarmTerminalOutcomeReasonCode));
7318
+ }
7319
+ function canonicalizeSwarmTerminalOutcomeReasonCode(value) {
7320
+ const token = normalizeSwarmTerminalOutcomeText(value);
7321
+ if (!token)
7322
+ return value;
7323
+ if (token === 'accepted' || token === 'admitted' || token === 'accepted-by-admission')
7324
+ return 'accepted-by-admission';
7325
+ if (token === 'failed' || token === 'failed-verification' || token === 'verification-failed')
7326
+ return 'failed-verification';
7327
+ if (token === 'superseded' || token === 'superseded-by-newer-output')
7328
+ return 'superseded-by-newer-output';
7329
+ if (token === 'no-change' || token === 'nochange' || token === 'no-effective-change')
7330
+ return 'no-effective-change';
7331
+ if (token === 'conflict' || token === 'conflict-detected' || token === 'merge-conflict')
7332
+ return 'conflict-detected';
7333
+ if (token === 'human-needed' || token === 'human-decision-required' || token === 'human-question')
7334
+ return 'human-decision-required';
7335
+ if (token === 'research-complete' || token === 'discovery-complete')
7336
+ return 'research-complete';
7337
+ if (token === 'rerun' || token === 'stale-rerun-required' || token === 'needs-rerun')
7338
+ return 'stale-rerun-required';
7339
+ return token;
7340
+ }
5266
7341
  function queueOutcomeInputsFromMergeQueue(queue) {
5267
7342
  return queue.assignments.map((assignment) => ({
5268
7343
  subjectAliases: uniqueStrings([assignment.taskId, assignment.jobId, ...assignment.queueItemIds]),
@@ -5420,6 +7495,7 @@ function queueOutcomeDecisionIsResolvedOutput(decision) {
5420
7495
  || decision.outcome === 'checked'
5421
7496
  || decision.outcome === 'superseded'
5422
7497
  || decision.outcome === 'no-change'
7498
+ || decision.outcome === 'research-complete'
5423
7499
  || decision.outcome === 'recorded'
5424
7500
  || decision.outcome === 'closed';
5425
7501
  }
@@ -5867,6 +7943,253 @@ function countBy(values) {
5867
7943
  out[value] = (out[value] ?? 0) + 1;
5868
7944
  return out;
5869
7945
  }
7946
+ const FRONTIER_SWARM_BLOCKING_MERGE_CANDIDATE_REASON_CODES = new Set([
7947
+ 'missing-sidecar',
7948
+ 'empty-sidecar',
7949
+ 'stale-source-hash',
7950
+ 'symbol-conflict',
7951
+ 'effect-conflict'
7952
+ ]);
7953
+ const FRONTIER_SWARM_REVIEW_MERGE_CANDIDATE_REASON_CODES = new Set([
7954
+ 'tests-missing'
7955
+ ]);
7956
+ const FRONTIER_SWARM_CONFLICT_MERGE_CANDIDATE_REASON_CODES = new Set([
7957
+ 'symbol-conflict',
7958
+ 'effect-conflict'
7959
+ ]);
7960
+ function isSwarmSemanticChange(input) {
7961
+ return input.kind === FRONTIER_SWARM_SEMANTIC_CHANGE_KIND;
7962
+ }
7963
+ function isSwarmMergeCandidate(input) {
7964
+ return input.kind === FRONTIER_SWARM_MERGE_CANDIDATE_KIND;
7965
+ }
7966
+ function normalizeOptionalString(value) {
7967
+ if (typeof value !== 'string')
7968
+ return undefined;
7969
+ const normalized = value.trim();
7970
+ return normalized.length > 0 ? normalized : undefined;
7971
+ }
7972
+ function normalizeSwarmMergeCandidateReasonCodes(reasonCodes) {
7973
+ return uniqueStrings(reasonCodes);
7974
+ }
7975
+ function normalizeSwarmSourceSpan(input, fallback = {}) {
7976
+ const metadata = toJsonObject(input?.metadata);
7977
+ const startLine = readNonNegativeNumber(input?.startLine);
7978
+ const startColumn = readNonNegativeNumber(input?.startColumn);
7979
+ const endLine = readNonNegativeNumber(input?.endLine);
7980
+ const endColumn = readNonNegativeNumber(input?.endColumn);
7981
+ const startOffset = readNonNegativeNumber(input?.startOffset);
7982
+ const endOffset = readNonNegativeNumber(input?.endOffset);
7983
+ const path = normalizeOptionalString(input?.path) ?? normalizeOptionalString(fallback.sourcePath);
7984
+ const sourceHash = normalizeOptionalString(input?.sourceHash) ?? normalizeOptionalString(fallback.sourceHash);
7985
+ const expectedSourceHash = normalizeOptionalString(input?.expectedSourceHash) ?? normalizeOptionalString(fallback.expectedSourceHash);
7986
+ return {
7987
+ ...(path ? { path } : {}),
7988
+ ...(startLine !== undefined ? { startLine: Math.floor(startLine) } : {}),
7989
+ ...(startColumn !== undefined ? { startColumn: Math.floor(startColumn) } : {}),
7990
+ ...(endLine !== undefined ? { endLine: Math.floor(endLine) } : {}),
7991
+ ...(endColumn !== undefined ? { endColumn: Math.floor(endColumn) } : {}),
7992
+ ...(startOffset !== undefined ? { startOffset: Math.floor(startOffset) } : {}),
7993
+ ...(endOffset !== undefined ? { endOffset: Math.floor(endOffset) } : {}),
7994
+ ...(sourceHash ? { sourceHash } : {}),
7995
+ ...(expectedSourceHash ? { expectedSourceHash } : {}),
7996
+ ...(metadata ? { metadata } : {})
7997
+ };
7998
+ }
7999
+ function deriveSwarmSemanticChangeReasonCodes(input, sourceSpan) {
8000
+ const reasons = [];
8001
+ if (input.conflictReason)
8002
+ reasons.push(input.conflictReason);
8003
+ if (sourceSpan.sourceHash && sourceSpan.expectedSourceHash && sourceSpan.sourceHash !== sourceSpan.expectedSourceHash) {
8004
+ reasons.push('stale-source-hash');
8005
+ }
8006
+ return normalizeSwarmMergeCandidateReasonCodes(reasons);
8007
+ }
8008
+ function deriveSwarmMergeCandidateReasonCodes(input, semanticChanges, sourceSpan) {
8009
+ const reasons = [];
8010
+ if (input.sidecarRequired && !input.sidecarPath)
8011
+ reasons.push('missing-sidecar');
8012
+ if (input.sidecarEmpty)
8013
+ reasons.push('empty-sidecar');
8014
+ if (input.hasLosses)
8015
+ reasons.push('lossy-import');
8016
+ if (input.testsRequired && input.testsPassed !== true)
8017
+ reasons.push('tests-missing');
8018
+ if (input.conflictReason)
8019
+ reasons.push(input.conflictReason);
8020
+ if (sourceSpan.sourceHash && sourceSpan.expectedSourceHash && sourceSpan.sourceHash !== sourceSpan.expectedSourceHash) {
8021
+ reasons.push('stale-source-hash');
8022
+ }
8023
+ if (semanticChanges.length === 0 && input.sidecarPath)
8024
+ reasons.push('empty-sidecar');
8025
+ return normalizeSwarmMergeCandidateReasonCodes(reasons);
8026
+ }
8027
+ function normalizeSwarmSemanticConfidence(value) {
8028
+ if (typeof value === 'number' && Number.isFinite(value))
8029
+ return clamp01(value);
8030
+ const normalized = normalizeOptionalString(value);
8031
+ return normalized ?? 'medium';
8032
+ }
8033
+ function normalizeSwarmConflictReason(conflictReason, reasonCodes) {
8034
+ const explicit = normalizeOptionalString(conflictReason);
8035
+ if (explicit)
8036
+ return explicit;
8037
+ return reasonCodes.find((reason) => FRONTIER_SWARM_CONFLICT_MERGE_CANDIDATE_REASON_CODES.has(reason));
8038
+ }
8039
+ function normalizeGraphNode(input, generatedAt = Date.now()) {
8040
+ const metadata = toJsonObject(input.metadata);
8041
+ const id = input.id ?? graphNodeId(input.kind, input.title ?? input.jobId ?? input.taskId ?? String(generatedAt));
8042
+ return {
8043
+ id,
8044
+ kind: input.kind,
8045
+ ...(input.title ? { title: input.title } : {}),
8046
+ ...(input.status ? { status: input.status } : {}),
8047
+ ...(input.jobId ? { jobId: input.jobId } : {}),
8048
+ ...(input.taskId ? { taskId: input.taskId } : {}),
8049
+ ...(input.path ? { path: input.path } : {}),
8050
+ generatedAt: input.generatedAt ?? generatedAt,
8051
+ ...(metadata ? { metadata } : {})
8052
+ };
8053
+ }
8054
+ function normalizeGraphEdge(input, generatedAt = Date.now()) {
8055
+ const metadata = toJsonObject(input.metadata);
8056
+ return {
8057
+ id: input.id ?? `${input.kind}:${input.from}->${input.to}`,
8058
+ from: input.from,
8059
+ to: input.to,
8060
+ kind: input.kind,
8061
+ ...(input.label ? { label: input.label } : {}),
8062
+ generatedAt: input.generatedAt ?? generatedAt,
8063
+ ...(metadata ? { metadata } : {})
8064
+ };
8065
+ }
8066
+ function normalizeGraphEvent(input, generatedAt = Date.now()) {
8067
+ const timestamp = input.timestamp ?? generatedAt;
8068
+ const metadata = toJsonObject(input.metadata);
8069
+ return {
8070
+ id: input.id ?? 'swarm-graph-event:' + stableHash([input.type, input.nodeId, input.edgeId, timestamp]),
8071
+ type: input.type,
8072
+ ...(input.nodeId ? { nodeId: input.nodeId } : {}),
8073
+ ...(input.edgeId ? { edgeId: input.edgeId } : {}),
8074
+ timestamp,
8075
+ ...(metadata ? { metadata } : {})
8076
+ };
8077
+ }
8078
+ function isPassingGraphStatus(status) {
8079
+ return status === 'passed' || status === 'accepted' || status === 'verified' || status === 'completed';
8080
+ }
8081
+ function isBlockingGraphStatus(status) {
8082
+ return status === 'failed' || status === 'rejected' || status === 'blocked';
8083
+ }
8084
+ function dedupeGraphNodes(nodes) {
8085
+ const byId = new Map();
8086
+ for (const node of nodes)
8087
+ byId.set(node.id, mergeGraphNode(byId.get(node.id), node));
8088
+ return Array.from(byId.values()).sort((left, right) => left.id.localeCompare(right.id));
8089
+ }
8090
+ function mergeGraphNode(existing, node) {
8091
+ if (!existing)
8092
+ return node;
8093
+ return {
8094
+ ...existing,
8095
+ ...node,
8096
+ metadata: mergeGraphMetadata(existing.metadata, node.metadata)
8097
+ };
8098
+ }
8099
+ function dedupeGraphEdges(edges) {
8100
+ const byId = new Map();
8101
+ for (const edge of edges)
8102
+ byId.set(edge.id, edge);
8103
+ return Array.from(byId.values()).sort((left, right) => left.id.localeCompare(right.id));
8104
+ }
8105
+ function summarizeSwarmRunGraph(nodes, edges, events) {
8106
+ return {
8107
+ nodeCount: nodes.length,
8108
+ edgeCount: edges.length,
8109
+ eventCount: events.length,
8110
+ nodeKinds: countBy(nodes.map((node) => node.kind)),
8111
+ edgeKinds: countBy(edges.map((edge) => edge.kind)),
8112
+ openBlockerCount: nodes.filter((node) => node.status === 'blocked').length + edges.filter((edge) => edge.kind === 'blocks').length,
8113
+ humanQuestionCount: nodes.filter((node) => node.kind === 'decision' && /human|question/i.test(node.title ?? '')).length
8114
+ };
8115
+ }
8116
+ function graphNodeId(kind, value) {
8117
+ return `${kind}:${normalizeGraphIdPart(value)}`;
8118
+ }
8119
+ function normalizeGraphIdPart(value) {
8120
+ return value.trim().toLowerCase().replace(/[^a-z0-9_.:-]+/g, '-').replace(/^-+|-+$/g, '') || stableHash(value);
8121
+ }
8122
+ function normalizeSwarmGraphRef(input) {
8123
+ return {
8124
+ id: input.id ?? 'swarm-graph-ref:' + stableHash([input.kind, input.graphId, input.nodeId, input.edgeId, input.role]),
8125
+ kind: input.kind ?? 'graph',
8126
+ ...(input.graphId ? { graphId: input.graphId } : {}),
8127
+ ...(input.nodeId ? { nodeId: input.nodeId } : {}),
8128
+ ...(input.edgeId ? { edgeId: input.edgeId } : {}),
8129
+ ...(input.role ? { role: input.role } : {}),
8130
+ ...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
8131
+ };
8132
+ }
8133
+ function normalizeSwarmGraphRefs(input) {
8134
+ return input.map(normalizeSwarmGraphRef).sort((left, right) => left.id.localeCompare(right.id));
8135
+ }
8136
+ function normalizeSwarmPatchEvent(input, replayRecordId, index) {
8137
+ if (isSwarmPatchEvent(input) && (!replayRecordId || input.replayRecordId) && (index === undefined || input.sequence !== undefined)) {
8138
+ return input;
8139
+ }
8140
+ return createSwarmPatchEvent({
8141
+ ...input,
8142
+ replayRecordId: input.replayRecordId ?? replayRecordId,
8143
+ sequence: input.sequence ?? index
8144
+ });
8145
+ }
8146
+ function inferImprovementLoopStatus(input) {
8147
+ if (input.result !== undefined)
8148
+ return 'applied';
8149
+ if (input.action !== undefined)
8150
+ return 'planned';
8151
+ if (input.observation !== undefined)
8152
+ return 'observed';
8153
+ return 'unknown';
8154
+ }
8155
+ function createImprovementLoopPhaseNode(loop, phase, value, generatedAt) {
8156
+ return {
8157
+ id: graphNodeId('rsi', `${loop.id}:${phase}`),
8158
+ kind: 'rsi',
8159
+ title: `${loop.title} ${phase}`,
8160
+ status: loop.status,
8161
+ jobId: loop.jobId,
8162
+ taskId: loop.taskId,
8163
+ generatedAt,
8164
+ metadata: {
8165
+ phase,
8166
+ value,
8167
+ loop
8168
+ }
8169
+ };
8170
+ }
8171
+ function isSwarmGateRecord(input) {
8172
+ return input.kind === FRONTIER_SWARM_GATE_RECORD_KIND;
8173
+ }
8174
+ function isSwarmEvidenceRecord(input) {
8175
+ return input.kind === FRONTIER_SWARM_EVIDENCE_RECORD_KIND;
8176
+ }
8177
+ function isSwarmPatchEvent(input) {
8178
+ return input.kind === FRONTIER_SWARM_PATCH_EVENT_KIND;
8179
+ }
8180
+ function isSwarmReplayRecord(input) {
8181
+ return input.kind === FRONTIER_SWARM_REPLAY_RECORD_KIND;
8182
+ }
8183
+ function isSwarmImprovementLoop(input) {
8184
+ return input.kind === FRONTIER_SWARM_IMPROVEMENT_LOOP_KIND;
8185
+ }
8186
+ function mergeGraphMetadata(left, right) {
8187
+ if (!left)
8188
+ return right;
8189
+ if (!right)
8190
+ return left;
8191
+ return { ...left, ...right };
8192
+ }
5870
8193
  function hasJobDependencyCycle(start, dependenciesByJobId) {
5871
8194
  const visiting = new Set();
5872
8195
  const visited = new Set();
@@ -8123,6 +10446,141 @@ function joinPathParts(...parts) {
8123
10446
  .filter(Boolean)
8124
10447
  .join('/');
8125
10448
  }
10449
+ function swarmRunEventTime(options, fallback) {
10450
+ if (options.time)
10451
+ return options.time;
10452
+ const value = options.now ?? fallback;
10453
+ return new Date(Number.isFinite(value) ? value : Date.now()).toISOString();
10454
+ }
10455
+ function isFrontierRunEventList(input) {
10456
+ return Array.isArray(input);
10457
+ }
10458
+ function swarmRunLaneNodeId(lane) {
10459
+ return String(lane).startsWith('lane:') ? String(lane) : `lane:${lane}`;
10460
+ }
10461
+ function swarmRunTaskNodeId(taskId) {
10462
+ return String(taskId).startsWith('task:') ? String(taskId) : `task:${taskId}`;
10463
+ }
10464
+ function swarmRunAttemptNodeId(jobId) {
10465
+ return String(jobId).startsWith('attempt:') ? String(jobId) : `attempt:${jobId}`;
10466
+ }
10467
+ function swarmRunLeaseNodeId(leaseId) {
10468
+ return String(leaseId).startsWith('lease:') ? String(leaseId) : `lease:${leaseId}`;
10469
+ }
10470
+ function swarmRunPatchNodeId(jobId) {
10471
+ return String(jobId).startsWith('patch:') ? String(jobId) : `patch:${jobId}`;
10472
+ }
10473
+ function swarmRunArtifactNodeId(artifactPath) {
10474
+ return `artifact:${slug(artifactPath)}:${stableHash(artifactPath).slice('fnv1a32:'.length)}`;
10475
+ }
10476
+ function swarmRunEvidenceNodeId(jobId, evidencePath) {
10477
+ return `evidence:${slug(jobId)}:${stableHash(evidencePath).slice('fnv1a32:'.length)}`;
10478
+ }
10479
+ function swarmRunVerificationNodeId(jobId, index, name) {
10480
+ return `verification:${slug(jobId)}:${index}:${slug(name || 'command')}`;
10481
+ }
10482
+ function swarmRunDecisionNodeId(id) {
10483
+ return String(id).startsWith('decision:') ? String(id) : `decision:${id}`;
10484
+ }
10485
+ function inferSwarmLanePackageId(jobs) {
10486
+ for (const job of jobs) {
10487
+ const candidate = job.task.metadata?.packageId ?? job.metadata?.packageId ?? job.task.metadata?.package ?? job.metadata?.package;
10488
+ if (typeof candidate === 'string' && candidate)
10489
+ return candidate;
10490
+ }
10491
+ return undefined;
10492
+ }
10493
+ function formatSwarmCommandLine(command) {
10494
+ return [command.command, ...command.args].join(' ');
10495
+ }
10496
+ function swarmStatusToRunStatus(status) {
10497
+ if (status === 'completed' || status === 'verified')
10498
+ return 'done';
10499
+ if (status === 'failed' || status === 'rejected')
10500
+ return 'rejected';
10501
+ if (status === 'blocked')
10502
+ return 'blocked';
10503
+ if (status === 'running')
10504
+ return 'running';
10505
+ if (status === 'planned')
10506
+ return 'planned';
10507
+ return 'ready';
10508
+ }
10509
+ function swarmResultStatusToAttemptStatus(status) {
10510
+ if (status === 'completed' || status === 'verified')
10511
+ return 'completed';
10512
+ if (status === 'running')
10513
+ return 'running';
10514
+ if (status === 'cancelled')
10515
+ return 'cancelled';
10516
+ if (status === 'timed-out' || status === 'timeout')
10517
+ return 'timed-out';
10518
+ if (status === 'queued' || status === 'ready' || status === 'planned')
10519
+ return 'queued';
10520
+ return 'failed';
10521
+ }
10522
+ function swarmRiskToRunRisk(risk) {
10523
+ if (risk === 'low' || risk === 'medium' || risk === 'high')
10524
+ return risk;
10525
+ return 'medium';
10526
+ }
10527
+ function inferSwarmArtifactType(file) {
10528
+ const lower = file.toLowerCase();
10529
+ if (lower.endsWith('.patch') || lower.endsWith('.diff'))
10530
+ return 'patch';
10531
+ if (lower.endsWith('.json') || lower.endsWith('.jsonl'))
10532
+ return 'json';
10533
+ if (lower.endsWith('.log') || lower.endsWith('.txt') || lower.includes('stdout') || lower.includes('stderr'))
10534
+ return 'log';
10535
+ if (lower.endsWith('.png') || lower.endsWith('.jpg') || lower.endsWith('.jpeg') || lower.endsWith('.webp'))
10536
+ return 'image';
10537
+ return 'artifact';
10538
+ }
10539
+ function swarmDispositionToRunDecision(disposition, readiness) {
10540
+ if (disposition === 'auto-mergeable' || readiness === 'verified-patch')
10541
+ return 'apply';
10542
+ if (disposition === 'stale-against-head')
10543
+ return 'rerun';
10544
+ if (disposition === 'rejected' || readiness === 'rejected')
10545
+ return 'reject';
10546
+ if (disposition === 'needs-port' || disposition === 'blocked' || readiness === 'blocked')
10547
+ return 'human-question';
10548
+ return 'record-only';
10549
+ }
10550
+ function swarmQueueOutcomeToRunDecision(decision) {
10551
+ if (decision.outcome === 'applied' || decision.decision === 'applied' || decision.action === 'apply-local')
10552
+ return 'apply';
10553
+ if (decision.outcome === 'rerun' || decision.decision === 'rerun' || decision.staleOrRerun)
10554
+ return 'rerun';
10555
+ if (decision.outcome === 'rejected' || decision.decision === 'rejected')
10556
+ return 'reject';
10557
+ if (decision.action === 'promote')
10558
+ return 'promote';
10559
+ if (decision.humanBlocked || decision.outcome === 'human-blocked')
10560
+ return 'human-question';
10561
+ return 'record-only';
10562
+ }
10563
+ function swarmQueueOutcomeRequiredActions(decision) {
10564
+ const actions = [];
10565
+ if (decision.coordinatorReview || decision.reviewDebt)
10566
+ actions.push('coordinator-review');
10567
+ if (decision.humanBlocked)
10568
+ actions.push('human-answer');
10569
+ if (decision.staleOrRerun)
10570
+ actions.push('rerun');
10571
+ if (decision.conflict)
10572
+ actions.push('resolve-conflict');
10573
+ return uniqueStrings(actions);
10574
+ }
10575
+ function pruneUndefinedJsonObject(value) {
10576
+ const out = {};
10577
+ for (const [key, entry] of Object.entries(value)) {
10578
+ if (entry === undefined)
10579
+ continue;
10580
+ out[key] = toJsonValue(entry);
10581
+ }
10582
+ return out;
10583
+ }
8126
10584
  function normalizeId(value, label) {
8127
10585
  const id = String(value || '').trim();
8128
10586
  if (!id)