@shapeshift-labs/frontier-swarm 0.5.4 → 0.5.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -3
- package/benchmarks/package-bench.mjs +13 -0
- package/dist/index.d.ts +247 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +970 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -88,6 +88,10 @@ export const FRONTIER_SWARM_LANE_PLAYBOOK_KIND = 'frontier.swarm.lane-playbook';
|
|
|
88
88
|
export const FRONTIER_SWARM_LANE_PLAYBOOK_VERSION = 1;
|
|
89
89
|
export const FRONTIER_SWARM_PATCH_STACK_PLAN_KIND = 'frontier.swarm.patch-stack-plan';
|
|
90
90
|
export const FRONTIER_SWARM_PATCH_STACK_PLAN_VERSION = 1;
|
|
91
|
+
export const FRONTIER_SWARM_COORDINATOR_DASHBOARD_KIND = 'frontier.swarm.coordinator-dashboard';
|
|
92
|
+
export const FRONTIER_SWARM_COORDINATOR_DASHBOARD_VERSION = 1;
|
|
93
|
+
export const FRONTIER_SWARM_ADAPTIVE_LOAD_PLAN_KIND = 'frontier.swarm.adaptive-load-plan';
|
|
94
|
+
export const FRONTIER_SWARM_ADAPTIVE_LOAD_PLAN_VERSION = 1;
|
|
91
95
|
export const FRONTIER_SWARM_DEFAULT_CODEX_COMPUTE_ID = 'codex.gpt-5.5.xhigh';
|
|
92
96
|
export const FRONTIER_SWARM_DEFAULT_MODEL = 'gpt-5.5';
|
|
93
97
|
export const FRONTIER_SWARM_DEFAULT_REASONING_EFFORT = 'xhigh';
|
|
@@ -1606,6 +1610,235 @@ export function createSwarmPatchStackPlan(input) {
|
|
|
1606
1610
|
...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
|
|
1607
1611
|
};
|
|
1608
1612
|
}
|
|
1613
|
+
export function createSwarmCoordinatorDashboard(input = {}) {
|
|
1614
|
+
const generatedAt = input.generatedAt ?? Date.now();
|
|
1615
|
+
const runId = input.run?.id ?? input.mergeIndex?.runId ?? input.queueOverlay?.runId;
|
|
1616
|
+
const planId = input.plan?.id ?? input.mergeIndex?.planId;
|
|
1617
|
+
const bundles = input.bundles ?? input.run?.results.map((result) => createSwarmMergeBundle({
|
|
1618
|
+
runId,
|
|
1619
|
+
planId,
|
|
1620
|
+
job: input.plan?.jobs.find((job) => job.id === result.jobId),
|
|
1621
|
+
result,
|
|
1622
|
+
generatedAt
|
|
1623
|
+
})) ?? [];
|
|
1624
|
+
const mergeIndex = input.mergeIndex ?? (bundles.length ? createSwarmMergeIndex({ runId, planId, bundles, generatedAt }) : undefined);
|
|
1625
|
+
const queueOverlay = input.queueOverlay ?? (bundles.length ? createSwarmQueueOverlay({ runId, bundles, generatedAt }) : undefined);
|
|
1626
|
+
const evidenceIndex = input.evidenceIndex ?? (input.run ? createSwarmEvidenceIndex({ run: input.run, generatedAt }) : undefined);
|
|
1627
|
+
const admission = input.admission;
|
|
1628
|
+
const jobsById = new Map((input.plan?.jobs ?? []).map((job) => [job.id, job]));
|
|
1629
|
+
const resultsById = new Map((input.run?.results ?? []).map((result) => [result.jobId, result]));
|
|
1630
|
+
const entriesById = new Map((mergeIndex?.entries ?? []).map((entry) => [entry.jobId, entry]));
|
|
1631
|
+
const bundlesById = new Map(bundles.map((bundle) => [bundle.jobId, bundle]));
|
|
1632
|
+
const processes = normalizeCoordinatorProcesses(input.processes ?? []);
|
|
1633
|
+
const processesByJob = groupObjects(processes.filter((process) => process.jobId), (process) => process.jobId);
|
|
1634
|
+
const duplicateGroups = createCoordinatorDuplicateGroups(mergeIndex?.entries ?? []);
|
|
1635
|
+
const duplicateByJob = new Map();
|
|
1636
|
+
for (const group of duplicateGroups) {
|
|
1637
|
+
for (const jobId of group.jobIds)
|
|
1638
|
+
duplicateByJob.set(jobId, group);
|
|
1639
|
+
}
|
|
1640
|
+
const admissionDeferred = new Map((admission?.deferred ?? []).map((entry) => [entry.jobId, entry.reasons]));
|
|
1641
|
+
const admitted = new Set(admission?.admitted ?? []);
|
|
1642
|
+
const jobIds = uniqueStrings([
|
|
1643
|
+
...(input.plan?.jobs ?? []).map((job) => job.id),
|
|
1644
|
+
...(input.run?.results ?? []).map((result) => result.jobId),
|
|
1645
|
+
...(mergeIndex?.entries ?? []).map((entry) => entry.jobId),
|
|
1646
|
+
...processes.map((process) => process.jobId)
|
|
1647
|
+
]).sort();
|
|
1648
|
+
const jobs = jobIds.map((jobId) => {
|
|
1649
|
+
const planJob = jobsById.get(jobId);
|
|
1650
|
+
const result = resultsById.get(jobId);
|
|
1651
|
+
const entry = entriesById.get(jobId);
|
|
1652
|
+
const bundle = bundlesById.get(jobId);
|
|
1653
|
+
const processList = processesByJob[jobId] ?? [];
|
|
1654
|
+
const duplicateGroup = duplicateByJob.get(jobId);
|
|
1655
|
+
const admissionReasons = admissionDeferred.get(jobId) ?? [];
|
|
1656
|
+
const admissionStatus = admitted.has(jobId)
|
|
1657
|
+
? 'admitted'
|
|
1658
|
+
: admissionReasons.length
|
|
1659
|
+
? 'deferred'
|
|
1660
|
+
: entry?.autoMergeable
|
|
1661
|
+
? 'not-admissible'
|
|
1662
|
+
: 'unknown';
|
|
1663
|
+
const score = scoreCoordinatorMergeJob(entry, bundle, evidenceIndex?.byJobId[jobId]?.length ?? 0, duplicateGroup, admissionStatus, admissionReasons);
|
|
1664
|
+
const evidencePaths = uniqueStrings([
|
|
1665
|
+
...(entry?.evidencePaths ?? []),
|
|
1666
|
+
...(result?.evidencePaths ?? []),
|
|
1667
|
+
...(bundle?.evidencePaths ?? [])
|
|
1668
|
+
]);
|
|
1669
|
+
const changedRegions = uniqueStrings([...(entry?.changedRegions ?? []), ...(result?.changedRegions ?? []), ...(bundle?.changedRegions ?? [])]);
|
|
1670
|
+
return {
|
|
1671
|
+
jobId,
|
|
1672
|
+
...(entry?.taskId ?? result?.queueItemIds[0] ?? planJob?.taskId ? { taskId: entry?.taskId ?? result?.queueItemIds[0] ?? planJob?.taskId } : {}),
|
|
1673
|
+
...(entry?.lane ?? planJob?.lane ? { lane: entry?.lane ?? planJob?.lane } : {}),
|
|
1674
|
+
...(entry?.title ?? planJob?.title ? { title: entry?.title ?? planJob?.title } : {}),
|
|
1675
|
+
status: entry?.status ?? result?.status ?? planJob?.status ?? 'planned',
|
|
1676
|
+
liveness: coordinatorJobLiveness(result, entry, processList),
|
|
1677
|
+
mergeReadiness: entry?.mergeReadiness ?? result?.mergeReadiness ?? 'blocked',
|
|
1678
|
+
disposition: entry?.disposition ?? result?.mergeDisposition ?? 'blocked',
|
|
1679
|
+
riskLevel: entry?.riskLevel ?? result?.riskLevel ?? 'unknown',
|
|
1680
|
+
mergeScore: score.score,
|
|
1681
|
+
mergeScoreReasons: score.reasons,
|
|
1682
|
+
admissionStatus,
|
|
1683
|
+
admissionReasons,
|
|
1684
|
+
staleAgainstHead: Boolean(entry?.staleAgainstHead || bundle?.staleAgainstHead),
|
|
1685
|
+
...(duplicateGroup ? { duplicateGroupId: duplicateGroup.id, ...(duplicateGroup.jobIds[0] !== jobId ? { duplicateOf: duplicateGroup.jobIds[0] } : {}) } : {}),
|
|
1686
|
+
changedPaths: uniqueStrings([...(entry?.changedPaths ?? []), ...(result?.changedPaths ?? []), ...(bundle?.changedPaths ?? [])]),
|
|
1687
|
+
changedRegions,
|
|
1688
|
+
semanticRegions: changedRegions,
|
|
1689
|
+
ownershipViolations: uniqueStrings([...(entry?.ownershipViolations ?? []), ...(result?.ownershipViolations ?? []), ...(bundle?.ownershipViolations ?? [])]),
|
|
1690
|
+
...(entry?.patchPath ?? result?.patchPath ?? bundle?.patchPath ? { patchPath: entry?.patchPath ?? result?.patchPath ?? bundle?.patchPath } : {}),
|
|
1691
|
+
evidencePaths,
|
|
1692
|
+
...(primaryEvidencePath(evidencePaths) ? { primaryEvidencePath: primaryEvidencePath(evidencePaths) } : {}),
|
|
1693
|
+
sourceCitations: createCoordinatorSourceCitations(entry, evidenceIndex),
|
|
1694
|
+
tests: {
|
|
1695
|
+
passed: bundle?.commandsPassed.length ?? result?.verification.filter((test) => test.status === 0).length ?? 0,
|
|
1696
|
+
failed: bundle?.commandsFailed.length ?? result?.verification.filter((test) => test.status !== undefined && test.status !== 0).length ?? 0,
|
|
1697
|
+
requiredFailed: bundle?.commandsFailed.length ?? result?.verification.filter((test) => test.required !== false && test.status !== undefined && test.status !== 0).length ?? 0
|
|
1698
|
+
},
|
|
1699
|
+
...(entry?.semanticImport ?? result?.semanticImport ?? bundle?.semanticImport ? {
|
|
1700
|
+
semanticImport: cloneJsonValue(entry?.semanticImport ?? result?.semanticImport ?? bundle?.semanticImport)
|
|
1701
|
+
} : {}),
|
|
1702
|
+
generatedAt
|
|
1703
|
+
};
|
|
1704
|
+
}).sort((left, right) => right.mergeScore - left.mergeScore || left.jobId.localeCompare(right.jobId));
|
|
1705
|
+
const byLane = groupIds(jobs, (job) => job.lane ?? 'unassigned');
|
|
1706
|
+
const byDisposition = groupIds(jobs, (job) => job.disposition);
|
|
1707
|
+
const byLiveness = groupIds(jobs, (job) => job.liveness);
|
|
1708
|
+
return {
|
|
1709
|
+
kind: FRONTIER_SWARM_COORDINATOR_DASHBOARD_KIND,
|
|
1710
|
+
version: FRONTIER_SWARM_COORDINATOR_DASHBOARD_VERSION,
|
|
1711
|
+
id: input.id ?? 'swarm-coordinator-dashboard:' + stableHash([runId, planId, jobs, duplicateGroups, generatedAt]),
|
|
1712
|
+
...(runId ? { runId } : {}),
|
|
1713
|
+
...(planId ? { planId } : {}),
|
|
1714
|
+
generatedAt,
|
|
1715
|
+
jobs,
|
|
1716
|
+
duplicateGroups,
|
|
1717
|
+
processes,
|
|
1718
|
+
byLane,
|
|
1719
|
+
byDisposition,
|
|
1720
|
+
byLiveness,
|
|
1721
|
+
...(mergeIndex ? { mergeIndex } : {}),
|
|
1722
|
+
...(queueOverlay ? { queueOverlay } : {}),
|
|
1723
|
+
...(evidenceIndex ? { evidenceIndex } : {}),
|
|
1724
|
+
...(admission ? { admission } : {}),
|
|
1725
|
+
summary: {
|
|
1726
|
+
jobCount: jobs.length,
|
|
1727
|
+
readyToApplyCount: jobs.filter((job) => job.disposition === 'auto-mergeable' && job.admissionStatus !== 'deferred').length,
|
|
1728
|
+
needsHumanPortCount: jobs.filter((job) => job.disposition === 'needs-port').length,
|
|
1729
|
+
failedEvidenceCount: jobs.filter((job) => job.disposition === 'rejected' || job.disposition === 'blocked' || job.tests.requiredFailed > 0).length,
|
|
1730
|
+
staleAgainstHeadCount: jobs.filter((job) => job.staleAgainstHead).length,
|
|
1731
|
+
duplicateGroupCount: duplicateGroups.length,
|
|
1732
|
+
semanticSidecarCount: jobs.filter((job) => job.semanticImport && job.semanticImport.total > 0).length,
|
|
1733
|
+
semanticRegionCount: jobs.reduce((total, job) => total + job.semanticRegions.length, 0),
|
|
1734
|
+
averageMergeScore: averageScore(jobs.map((job) => job.mergeScore))
|
|
1735
|
+
},
|
|
1736
|
+
...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
|
|
1737
|
+
};
|
|
1738
|
+
}
|
|
1739
|
+
export function querySwarmCoordinatorDashboard(dashboard, query = {}) {
|
|
1740
|
+
const jobs = dashboard.jobs.filter((job) => ((query.jobId === undefined || job.jobId === query.jobId)
|
|
1741
|
+
&& (query.lane === undefined || job.lane === query.lane)
|
|
1742
|
+
&& (query.disposition === undefined || job.disposition === query.disposition)
|
|
1743
|
+
&& (query.liveness === undefined || job.liveness === query.liveness)
|
|
1744
|
+
&& (query.admissionStatus === undefined || job.admissionStatus === query.admissionStatus)
|
|
1745
|
+
&& (query.pathIncludes === undefined || job.changedPaths.concat(job.evidencePaths).some((entry) => entry.includes(query.pathIncludes)))
|
|
1746
|
+
&& (query.region === undefined || job.changedRegions.includes(query.region) || job.semanticRegions.includes(query.region))
|
|
1747
|
+
&& (query.hasSemanticImport === undefined || Boolean(job.semanticImport && job.semanticImport.total > 0) === query.hasSemanticImport)
|
|
1748
|
+
&& (query.hasSemanticRegions === undefined || (job.semanticRegions.length > 0) === query.hasSemanticRegions)
|
|
1749
|
+
&& (query.staleAgainstHead === undefined || job.staleAgainstHead === query.staleAgainstHead)
|
|
1750
|
+
&& (query.duplicateOnly !== true || Boolean(job.duplicateGroupId))
|
|
1751
|
+
&& (query.minMergeScore === undefined || job.mergeScore >= query.minMergeScore)
|
|
1752
|
+
&& (query.maxMergeScore === undefined || job.mergeScore <= query.maxMergeScore)));
|
|
1753
|
+
return {
|
|
1754
|
+
jobs,
|
|
1755
|
+
summary: {
|
|
1756
|
+
jobCount: jobs.length,
|
|
1757
|
+
averageMergeScore: averageScore(jobs.map((job) => job.mergeScore))
|
|
1758
|
+
}
|
|
1759
|
+
};
|
|
1760
|
+
}
|
|
1761
|
+
export function createSwarmAdaptiveLoadPlan(input = {}) {
|
|
1762
|
+
const generatedAt = input.generatedAt ?? Date.now();
|
|
1763
|
+
const mode = input.mode ?? 'balanced';
|
|
1764
|
+
const maxLimits = createAdaptiveMaxLimits(input);
|
|
1765
|
+
const minLimits = createAdaptiveMinLimits(input.minLimits, maxLimits);
|
|
1766
|
+
const currentLimits = clampAdaptiveLimits(createAdaptiveCurrentLimits(input.currentLimits, maxLimits), minLimits, maxLimits);
|
|
1767
|
+
const effectiveLimits = mode === 'off'
|
|
1768
|
+
? cloneJsonValue(maxLimits)
|
|
1769
|
+
: cloneJsonValue(currentLimits);
|
|
1770
|
+
const schedule = input.schedule ?? (input.plan ? createSwarmSchedule({ plan: input.plan, run: input.run }) : undefined);
|
|
1771
|
+
const observations = normalizeAdaptiveObservations([
|
|
1772
|
+
...deriveAdaptiveScheduleObservations(schedule, generatedAt),
|
|
1773
|
+
...deriveAdaptiveRunObservations(input.run, generatedAt),
|
|
1774
|
+
...deriveAdaptiveMergeIndexObservations(input.mergeIndex, generatedAt),
|
|
1775
|
+
...deriveAdaptiveDashboardObservations(input.dashboard, generatedAt),
|
|
1776
|
+
...deriveAdaptiveAdmissionObservations(input.admission, generatedAt),
|
|
1777
|
+
...(input.observations ?? [])
|
|
1778
|
+
], generatedAt);
|
|
1779
|
+
const decisions = [];
|
|
1780
|
+
const bottlenecks = observations.filter((observation) => adaptiveObservationIsBottleneck(observation));
|
|
1781
|
+
if (mode === 'observe') {
|
|
1782
|
+
for (const observation of bottlenecks) {
|
|
1783
|
+
decisions.push(createAdaptiveDecision({
|
|
1784
|
+
action: 'observe',
|
|
1785
|
+
target: adaptiveDecisionTargetForObservation(observation),
|
|
1786
|
+
key: adaptiveDecisionKeyForObservation(observation),
|
|
1787
|
+
reason: observation.reasons[0] ?? observation.kind,
|
|
1788
|
+
observationIds: [observation.id]
|
|
1789
|
+
}));
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
else if (mode !== 'off') {
|
|
1793
|
+
for (const observation of bottlenecks) {
|
|
1794
|
+
applyAdaptiveObservation(effectiveLimits, minLimits, maxLimits, mode, observation, decisions);
|
|
1795
|
+
}
|
|
1796
|
+
const healthy = observations.filter((observation) => observation.kind === 'healthy-throughput');
|
|
1797
|
+
if (bottlenecks.length === 0 && healthy.length > 0) {
|
|
1798
|
+
for (const observation of healthy) {
|
|
1799
|
+
applyAdaptiveRecovery(effectiveLimits, maxLimits, observation, decisions);
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
const summary = {
|
|
1804
|
+
observationCount: observations.length,
|
|
1805
|
+
bottleneckCount: bottlenecks.length,
|
|
1806
|
+
decisionCount: decisions.length,
|
|
1807
|
+
reducedCount: decisions.filter((decision) => decision.action === 'decrease').length,
|
|
1808
|
+
increasedCount: decisions.filter((decision) => decision.action === 'increase').length,
|
|
1809
|
+
...(effectiveLimits.maxReadyJobs !== undefined ? { effectiveMaxReadyJobs: effectiveLimits.maxReadyJobs } : {}),
|
|
1810
|
+
...(maxLimits.maxReadyJobs !== undefined ? { maxReadyJobs: maxLimits.maxReadyJobs } : {})
|
|
1811
|
+
};
|
|
1812
|
+
return {
|
|
1813
|
+
kind: FRONTIER_SWARM_ADAPTIVE_LOAD_PLAN_KIND,
|
|
1814
|
+
version: FRONTIER_SWARM_ADAPTIVE_LOAD_PLAN_VERSION,
|
|
1815
|
+
id: input.id ?? 'swarm-adaptive-load-plan:' + stableHash([input.plan?.id, input.run?.id, mode, observations, decisions, generatedAt]),
|
|
1816
|
+
...(input.plan?.id ? { planId: input.plan.id } : {}),
|
|
1817
|
+
...(input.run?.id ? { runId: input.run.id } : {}),
|
|
1818
|
+
mode,
|
|
1819
|
+
generatedAt,
|
|
1820
|
+
maxLimits,
|
|
1821
|
+
currentLimits,
|
|
1822
|
+
minLimits,
|
|
1823
|
+
effectiveLimits,
|
|
1824
|
+
observations,
|
|
1825
|
+
decisions,
|
|
1826
|
+
summary,
|
|
1827
|
+
...(toJsonObject(input.metadata) ? { metadata: toJsonObject(input.metadata) } : {})
|
|
1828
|
+
};
|
|
1829
|
+
}
|
|
1830
|
+
export function createSwarmScheduleInputFromAdaptiveLoadPlan(plan, adaptive, input = {}) {
|
|
1831
|
+
return {
|
|
1832
|
+
plan,
|
|
1833
|
+
...(input.run ? { run: input.run } : {}),
|
|
1834
|
+
...(input.now !== undefined ? { now: input.now } : {}),
|
|
1835
|
+
...(adaptive.effectiveLimits.maxReadyJobs !== undefined ? { maxReadyJobs: adaptive.effectiveLimits.maxReadyJobs } : {}),
|
|
1836
|
+
maxLaneConcurrency: adaptive.effectiveLimits.maxLaneConcurrency,
|
|
1837
|
+
maxConcurrencyKeyConcurrency: adaptive.effectiveLimits.maxConcurrencyKeyConcurrency,
|
|
1838
|
+
maxComputeConcurrency: adaptive.effectiveLimits.maxComputeConcurrency,
|
|
1839
|
+
resourceQuotas: adaptive.effectiveLimits.resourceQuotas
|
|
1840
|
+
};
|
|
1841
|
+
}
|
|
1609
1842
|
export function resolveSwarmCompute(manifestInput, taskInput) {
|
|
1610
1843
|
const compiled = compileSwarm(manifestInput);
|
|
1611
1844
|
const task = isSwarmTask(taskInput) ? taskInput : normalizeTask(taskInput);
|
|
@@ -2121,6 +2354,577 @@ function schedulerPriorityForReason(reason) {
|
|
|
2121
2354
|
return 25;
|
|
2122
2355
|
return 30;
|
|
2123
2356
|
}
|
|
2357
|
+
function createAdaptiveMaxLimits(input) {
|
|
2358
|
+
const plan = input.plan;
|
|
2359
|
+
const jobCount = Math.max(1, plan?.jobs.length ?? input.schedule?.summary.jobCount ?? input.dashboard?.summary.jobCount ?? 1);
|
|
2360
|
+
const raw = mergeAdaptiveScheduleLimitInputs(plan?.limits, input.maxLimits);
|
|
2361
|
+
const maxReadyJobs = positiveNumber(raw.maxReadyJobs) ? Math.floor(raw.maxReadyJobs) : jobCount;
|
|
2362
|
+
const maxLaneConcurrency = { ...raw.maxLaneConcurrency };
|
|
2363
|
+
const maxConcurrencyKeyConcurrency = { ...raw.maxConcurrencyKeyConcurrency };
|
|
2364
|
+
const maxComputeConcurrency = { ...raw.maxComputeConcurrency };
|
|
2365
|
+
const resourceQuotas = { ...raw.resourceQuotas };
|
|
2366
|
+
for (const job of plan?.jobs ?? []) {
|
|
2367
|
+
maxLaneConcurrency[job.lane] = adaptivePositiveLimit(maxLaneConcurrency[job.lane], job.compute.maxConcurrency ?? maxReadyJobs);
|
|
2368
|
+
maxConcurrencyKeyConcurrency[job.concurrencyKey] = adaptivePositiveLimit(maxConcurrencyKeyConcurrency[job.concurrencyKey], maxReadyJobs);
|
|
2369
|
+
maxComputeConcurrency[job.compute.id] = adaptivePositiveLimit(maxComputeConcurrency[job.compute.id], job.compute.maxConcurrency ?? maxReadyJobs);
|
|
2370
|
+
for (const [resource, amount] of Object.entries(job.resourceRequirements?.resources ?? {})) {
|
|
2371
|
+
resourceQuotas[resource] = adaptivePositiveLimit(resourceQuotas[resource], Math.max(1, Math.ceil(amount), maxReadyJobs));
|
|
2372
|
+
}
|
|
2373
|
+
if (job.resourceRequirements?.browser?.required) {
|
|
2374
|
+
resourceQuotas.browser = adaptivePositiveLimit(resourceQuotas.browser, maxReadyJobs);
|
|
2375
|
+
resourceQuotas['browser-port'] = adaptivePositiveLimit(resourceQuotas['browser-port'], maxReadyJobs);
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
return {
|
|
2379
|
+
maxReadyJobs,
|
|
2380
|
+
maxLaneConcurrency: normalizeAdaptiveLimitRecord(maxLaneConcurrency),
|
|
2381
|
+
maxConcurrencyKeyConcurrency: normalizeAdaptiveLimitRecord(maxConcurrencyKeyConcurrency),
|
|
2382
|
+
maxComputeConcurrency: normalizeAdaptiveLimitRecord(maxComputeConcurrency),
|
|
2383
|
+
resourceQuotas: normalizeAdaptiveLimitRecord(resourceQuotas)
|
|
2384
|
+
};
|
|
2385
|
+
}
|
|
2386
|
+
function createAdaptiveCurrentLimits(input, maxLimits) {
|
|
2387
|
+
if (!input)
|
|
2388
|
+
return cloneJsonValue(maxLimits);
|
|
2389
|
+
const raw = mergeAdaptiveScheduleLimitInputs(maxLimits, input);
|
|
2390
|
+
return {
|
|
2391
|
+
...(positiveNumber(raw.maxReadyJobs) ? { maxReadyJobs: Math.floor(raw.maxReadyJobs) } : {}),
|
|
2392
|
+
maxLaneConcurrency: normalizeAdaptiveLimitRecord(raw.maxLaneConcurrency),
|
|
2393
|
+
maxConcurrencyKeyConcurrency: normalizeAdaptiveLimitRecord(raw.maxConcurrencyKeyConcurrency),
|
|
2394
|
+
maxComputeConcurrency: normalizeAdaptiveLimitRecord(raw.maxComputeConcurrency),
|
|
2395
|
+
resourceQuotas: normalizeAdaptiveLimitRecord(raw.resourceQuotas)
|
|
2396
|
+
};
|
|
2397
|
+
}
|
|
2398
|
+
function createAdaptiveMinLimits(input, maxLimits) {
|
|
2399
|
+
const raw = mergeAdaptiveScheduleLimitInputs(undefined, input);
|
|
2400
|
+
const laneMinimums = Object.fromEntries(Object.keys(maxLimits.maxLaneConcurrency).map((key) => [key, 1]));
|
|
2401
|
+
const keyMinimums = Object.fromEntries(Object.keys(maxLimits.maxConcurrencyKeyConcurrency).map((key) => [key, 1]));
|
|
2402
|
+
const computeMinimums = Object.fromEntries(Object.keys(maxLimits.maxComputeConcurrency).map((key) => [key, 1]));
|
|
2403
|
+
const resourceMinimums = Object.fromEntries(Object.keys(maxLimits.resourceQuotas).map((key) => [key, 1]));
|
|
2404
|
+
return {
|
|
2405
|
+
maxReadyJobs: adaptivePositiveLimit(raw.maxReadyJobs, 1),
|
|
2406
|
+
maxLaneConcurrency: normalizeAdaptiveLimitRecord({ ...laneMinimums, ...raw.maxLaneConcurrency }),
|
|
2407
|
+
maxConcurrencyKeyConcurrency: normalizeAdaptiveLimitRecord({ ...keyMinimums, ...raw.maxConcurrencyKeyConcurrency }),
|
|
2408
|
+
maxComputeConcurrency: normalizeAdaptiveLimitRecord({ ...computeMinimums, ...raw.maxComputeConcurrency }),
|
|
2409
|
+
resourceQuotas: normalizeAdaptiveLimitRecord({ ...resourceMinimums, ...raw.resourceQuotas })
|
|
2410
|
+
};
|
|
2411
|
+
}
|
|
2412
|
+
function mergeAdaptiveScheduleLimitInputs(left, right) {
|
|
2413
|
+
return {
|
|
2414
|
+
...(right?.maxReadyJobs !== undefined ? { maxReadyJobs: right.maxReadyJobs } : left?.maxReadyJobs !== undefined ? { maxReadyJobs: left.maxReadyJobs } : {}),
|
|
2415
|
+
maxLaneConcurrency: { ...(left?.maxLaneConcurrency ?? {}), ...(right?.maxLaneConcurrency ?? {}) },
|
|
2416
|
+
maxConcurrencyKeyConcurrency: { ...(left?.maxConcurrencyKeyConcurrency ?? {}), ...(right?.maxConcurrencyKeyConcurrency ?? {}) },
|
|
2417
|
+
maxComputeConcurrency: { ...(left?.maxComputeConcurrency ?? {}), ...(right?.maxComputeConcurrency ?? {}) },
|
|
2418
|
+
resourceQuotas: { ...(left?.resourceQuotas ?? {}), ...(right?.resourceQuotas ?? {}) }
|
|
2419
|
+
};
|
|
2420
|
+
}
|
|
2421
|
+
function clampAdaptiveLimits(value, minLimits, maxLimits) {
|
|
2422
|
+
return {
|
|
2423
|
+
...(value.maxReadyJobs !== undefined || maxLimits.maxReadyJobs !== undefined ? {
|
|
2424
|
+
maxReadyJobs: clampAdaptiveLimit(value.maxReadyJobs ?? maxLimits.maxReadyJobs ?? 1, minLimits.maxReadyJobs ?? 1, maxLimits.maxReadyJobs ?? value.maxReadyJobs ?? 1)
|
|
2425
|
+
} : {}),
|
|
2426
|
+
maxLaneConcurrency: clampAdaptiveRecord(value.maxLaneConcurrency, minLimits.maxLaneConcurrency, maxLimits.maxLaneConcurrency),
|
|
2427
|
+
maxConcurrencyKeyConcurrency: clampAdaptiveRecord(value.maxConcurrencyKeyConcurrency, minLimits.maxConcurrencyKeyConcurrency, maxLimits.maxConcurrencyKeyConcurrency),
|
|
2428
|
+
maxComputeConcurrency: clampAdaptiveRecord(value.maxComputeConcurrency, minLimits.maxComputeConcurrency, maxLimits.maxComputeConcurrency),
|
|
2429
|
+
resourceQuotas: clampAdaptiveRecord(value.resourceQuotas, minLimits.resourceQuotas, maxLimits.resourceQuotas)
|
|
2430
|
+
};
|
|
2431
|
+
}
|
|
2432
|
+
function normalizeAdaptiveObservations(input, generatedAt) {
|
|
2433
|
+
const out = [];
|
|
2434
|
+
input.forEach((entry, index) => {
|
|
2435
|
+
const reasons = uniqueStrings([...(entry.reason ? [entry.reason] : []), ...(entry.reasons ?? [])]);
|
|
2436
|
+
const at = entry.at ?? generatedAt;
|
|
2437
|
+
const observation = {
|
|
2438
|
+
id: entry.id ?? 'swarm-adaptive-observation:' + stableHash([entry.kind, entry.jobId, entry.lane, entry.resource, reasons, index, at]),
|
|
2439
|
+
kind: entry.kind,
|
|
2440
|
+
severity: entry.severity ?? adaptiveDefaultSeverity(entry.kind),
|
|
2441
|
+
at,
|
|
2442
|
+
value: Number.isFinite(entry.value) ? Number(entry.value) : 1,
|
|
2443
|
+
...(entry.jobId ? { jobId: entry.jobId } : {}),
|
|
2444
|
+
...(entry.taskId ? { taskId: entry.taskId } : {}),
|
|
2445
|
+
...(entry.lane ? { lane: entry.lane } : {}),
|
|
2446
|
+
...(entry.compute ? { compute: entry.compute } : {}),
|
|
2447
|
+
...(entry.concurrencyKey ? { concurrencyKey: entry.concurrencyKey } : {}),
|
|
2448
|
+
...(entry.resource ? { resource: entry.resource } : {}),
|
|
2449
|
+
...(entry.path ? { path: entry.path } : {}),
|
|
2450
|
+
...(entry.region ? { region: entry.region } : {}),
|
|
2451
|
+
reasons: reasons.length ? reasons : [entry.kind],
|
|
2452
|
+
...(toJsonObject(entry.metadata) ? { metadata: toJsonObject(entry.metadata) } : {})
|
|
2453
|
+
};
|
|
2454
|
+
out.push(observation);
|
|
2455
|
+
});
|
|
2456
|
+
return dedupeAdaptiveObservations(out);
|
|
2457
|
+
}
|
|
2458
|
+
function deriveAdaptiveScheduleObservations(schedule, at) {
|
|
2459
|
+
if (!schedule)
|
|
2460
|
+
return [];
|
|
2461
|
+
const observations = [];
|
|
2462
|
+
for (const blocked of schedule.blocked) {
|
|
2463
|
+
for (const reason of blocked.reasons) {
|
|
2464
|
+
if (reason === 'waiting-for-dependencies')
|
|
2465
|
+
continue;
|
|
2466
|
+
const resource = reason.startsWith('resource-capacity:') ? reason.slice('resource-capacity:'.length) : undefined;
|
|
2467
|
+
observations.push({
|
|
2468
|
+
kind: resource ? 'resource-capacity' : reason,
|
|
2469
|
+
severity: reason === 'ready-capacity' ? 'info' : 'warning',
|
|
2470
|
+
at,
|
|
2471
|
+
jobId: blocked.jobId,
|
|
2472
|
+
taskId: blocked.taskId,
|
|
2473
|
+
lane: blocked.lane,
|
|
2474
|
+
compute: blocked.compute,
|
|
2475
|
+
concurrencyKey: blocked.concurrencyKey,
|
|
2476
|
+
...(resource ? { resource } : {}),
|
|
2477
|
+
reason
|
|
2478
|
+
});
|
|
2479
|
+
}
|
|
2480
|
+
}
|
|
2481
|
+
return observations;
|
|
2482
|
+
}
|
|
2483
|
+
function deriveAdaptiveRunObservations(run, at) {
|
|
2484
|
+
if (!run)
|
|
2485
|
+
return [];
|
|
2486
|
+
const observations = [];
|
|
2487
|
+
for (const result of run.results) {
|
|
2488
|
+
if (result.status === 'failed' || result.exitCode !== undefined && result.exitCode !== 0 || result.verification.some((entry) => entry.required !== false && entry.status !== undefined && entry.status !== 0)) {
|
|
2489
|
+
observations.push({
|
|
2490
|
+
kind: 'evidence-failure',
|
|
2491
|
+
severity: 'error',
|
|
2492
|
+
at,
|
|
2493
|
+
jobId: result.jobId,
|
|
2494
|
+
reason: 'worker failed or required evidence command failed'
|
|
2495
|
+
});
|
|
2496
|
+
}
|
|
2497
|
+
if (result.mergeDisposition === 'discovery-only' || result.mergeReadiness === 'discovery-only') {
|
|
2498
|
+
observations.push({
|
|
2499
|
+
kind: 'discovery-only-output',
|
|
2500
|
+
severity: 'info',
|
|
2501
|
+
at,
|
|
2502
|
+
jobId: result.jobId,
|
|
2503
|
+
reason: 'worker produced discovery output instead of a mergeable patch'
|
|
2504
|
+
});
|
|
2505
|
+
}
|
|
2506
|
+
if (semanticSummaryIsEmpty(result.semanticImport)) {
|
|
2507
|
+
observations.push({
|
|
2508
|
+
kind: 'semantic-empty',
|
|
2509
|
+
severity: 'warning',
|
|
2510
|
+
at,
|
|
2511
|
+
jobId: result.jobId,
|
|
2512
|
+
reason: 'semantic import emitted no selected/imported files or symbols'
|
|
2513
|
+
});
|
|
2514
|
+
}
|
|
2515
|
+
else if (semanticSummaryIsWeak(result.semanticImport)) {
|
|
2516
|
+
observations.push({
|
|
2517
|
+
kind: 'semantic-weak',
|
|
2518
|
+
severity: 'info',
|
|
2519
|
+
at,
|
|
2520
|
+
jobId: result.jobId,
|
|
2521
|
+
reason: 'semantic import has limited source maps, regions, or patch hints'
|
|
2522
|
+
});
|
|
2523
|
+
}
|
|
2524
|
+
if (result.durationMs !== undefined && result.durationMs > 900000) {
|
|
2525
|
+
observations.push({
|
|
2526
|
+
kind: 'slow-job',
|
|
2527
|
+
severity: 'warning',
|
|
2528
|
+
at,
|
|
2529
|
+
jobId: result.jobId,
|
|
2530
|
+
value: result.durationMs,
|
|
2531
|
+
reason: 'worker duration exceeded adaptive slow-job threshold'
|
|
2532
|
+
});
|
|
2533
|
+
}
|
|
2534
|
+
if ((result.status === 'completed' || result.status === 'verified') && result.exitCode === 0 && result.changedPaths.length > 0 && result.mergeDisposition !== 'discovery-only') {
|
|
2535
|
+
observations.push({
|
|
2536
|
+
kind: 'healthy-throughput',
|
|
2537
|
+
severity: 'info',
|
|
2538
|
+
at,
|
|
2539
|
+
jobId: result.jobId,
|
|
2540
|
+
reason: 'worker completed with changed paths'
|
|
2541
|
+
});
|
|
2542
|
+
}
|
|
2543
|
+
}
|
|
2544
|
+
return observations;
|
|
2545
|
+
}
|
|
2546
|
+
function deriveAdaptiveMergeIndexObservations(index, at) {
|
|
2547
|
+
if (!index)
|
|
2548
|
+
return [];
|
|
2549
|
+
const observations = [];
|
|
2550
|
+
for (const entry of index.entries) {
|
|
2551
|
+
if (entry.staleAgainstHead || entry.disposition === 'stale-against-head') {
|
|
2552
|
+
observations.push({
|
|
2553
|
+
kind: 'stale-patch',
|
|
2554
|
+
severity: 'warning',
|
|
2555
|
+
at,
|
|
2556
|
+
jobId: entry.jobId,
|
|
2557
|
+
lane: entry.lane,
|
|
2558
|
+
path: entry.changedPaths[0],
|
|
2559
|
+
region: entry.changedRegions[0],
|
|
2560
|
+
reason: 'patch is stale against coordinator head'
|
|
2561
|
+
});
|
|
2562
|
+
}
|
|
2563
|
+
if (entry.conflictingJobIds.length) {
|
|
2564
|
+
observations.push({
|
|
2565
|
+
kind: 'merge-conflict',
|
|
2566
|
+
severity: 'warning',
|
|
2567
|
+
at,
|
|
2568
|
+
jobId: entry.jobId,
|
|
2569
|
+
lane: entry.lane,
|
|
2570
|
+
path: entry.changedPaths[0],
|
|
2571
|
+
region: entry.changedRegions[0],
|
|
2572
|
+
value: entry.conflictingJobIds.length,
|
|
2573
|
+
reason: 'merge index found conflicting changed paths or regions'
|
|
2574
|
+
});
|
|
2575
|
+
}
|
|
2576
|
+
if (entry.disposition === 'discovery-only') {
|
|
2577
|
+
observations.push({
|
|
2578
|
+
kind: 'discovery-only-output',
|
|
2579
|
+
severity: 'info',
|
|
2580
|
+
at,
|
|
2581
|
+
jobId: entry.jobId,
|
|
2582
|
+
lane: entry.lane,
|
|
2583
|
+
reason: 'merge index classified the bundle as discovery-only'
|
|
2584
|
+
});
|
|
2585
|
+
}
|
|
2586
|
+
if (semanticSummaryIsEmpty(entry.semanticImport)) {
|
|
2587
|
+
observations.push({
|
|
2588
|
+
kind: 'semantic-empty',
|
|
2589
|
+
severity: 'warning',
|
|
2590
|
+
at,
|
|
2591
|
+
jobId: entry.jobId,
|
|
2592
|
+
lane: entry.lane,
|
|
2593
|
+
reason: 'semantic sidecar is present but empty'
|
|
2594
|
+
});
|
|
2595
|
+
}
|
|
2596
|
+
else if (semanticSummaryIsWeak(entry.semanticImport)) {
|
|
2597
|
+
observations.push({
|
|
2598
|
+
kind: 'semantic-weak',
|
|
2599
|
+
severity: 'info',
|
|
2600
|
+
at,
|
|
2601
|
+
jobId: entry.jobId,
|
|
2602
|
+
lane: entry.lane,
|
|
2603
|
+
reason: 'semantic sidecar lacks merge-useful structure'
|
|
2604
|
+
});
|
|
2605
|
+
}
|
|
2606
|
+
}
|
|
2607
|
+
return observations;
|
|
2608
|
+
}
|
|
2609
|
+
function deriveAdaptiveDashboardObservations(dashboard, at) {
|
|
2610
|
+
if (!dashboard)
|
|
2611
|
+
return [];
|
|
2612
|
+
const observations = [];
|
|
2613
|
+
for (const job of dashboard.jobs) {
|
|
2614
|
+
if (job.duplicateGroupId) {
|
|
2615
|
+
observations.push({
|
|
2616
|
+
kind: 'duplicate-output',
|
|
2617
|
+
severity: 'info',
|
|
2618
|
+
at,
|
|
2619
|
+
jobId: job.jobId,
|
|
2620
|
+
lane: job.lane,
|
|
2621
|
+
path: job.changedPaths[0],
|
|
2622
|
+
region: job.changedRegions[0],
|
|
2623
|
+
reason: 'coordinator dashboard found duplicate worker output'
|
|
2624
|
+
});
|
|
2625
|
+
}
|
|
2626
|
+
if (job.tests.requiredFailed > 0) {
|
|
2627
|
+
observations.push({
|
|
2628
|
+
kind: 'evidence-failure',
|
|
2629
|
+
severity: 'error',
|
|
2630
|
+
at,
|
|
2631
|
+
jobId: job.jobId,
|
|
2632
|
+
lane: job.lane,
|
|
2633
|
+
reason: 'dashboard shows required evidence failures'
|
|
2634
|
+
});
|
|
2635
|
+
}
|
|
2636
|
+
if (job.staleAgainstHead) {
|
|
2637
|
+
observations.push({
|
|
2638
|
+
kind: 'stale-patch',
|
|
2639
|
+
severity: 'warning',
|
|
2640
|
+
at,
|
|
2641
|
+
jobId: job.jobId,
|
|
2642
|
+
lane: job.lane,
|
|
2643
|
+
path: job.changedPaths[0],
|
|
2644
|
+
reason: 'dashboard marks patch stale against head'
|
|
2645
|
+
});
|
|
2646
|
+
}
|
|
2647
|
+
if (semanticSummaryIsEmpty(job.semanticImport)) {
|
|
2648
|
+
observations.push({
|
|
2649
|
+
kind: 'semantic-empty',
|
|
2650
|
+
severity: 'warning',
|
|
2651
|
+
at,
|
|
2652
|
+
jobId: job.jobId,
|
|
2653
|
+
lane: job.lane,
|
|
2654
|
+
reason: 'dashboard semantic import summary is empty'
|
|
2655
|
+
});
|
|
2656
|
+
}
|
|
2657
|
+
}
|
|
2658
|
+
return observations;
|
|
2659
|
+
}
|
|
2660
|
+
function deriveAdaptiveAdmissionObservations(admission, at) {
|
|
2661
|
+
if (!admission)
|
|
2662
|
+
return [];
|
|
2663
|
+
const observations = [];
|
|
2664
|
+
for (const deferral of admission.deferred) {
|
|
2665
|
+
for (const reason of deferral.reasons) {
|
|
2666
|
+
const kind = reason === 'stale-against-head'
|
|
2667
|
+
? 'stale-patch'
|
|
2668
|
+
: reason === 'conflicting-changes'
|
|
2669
|
+
? 'merge-conflict'
|
|
2670
|
+
: reason === 'not-auto-mergeable'
|
|
2671
|
+
? 'discovery-only-output'
|
|
2672
|
+
: reason === 'max-ready'
|
|
2673
|
+
? 'ready-capacity'
|
|
2674
|
+
: 'budget-pressure';
|
|
2675
|
+
observations.push({
|
|
2676
|
+
kind,
|
|
2677
|
+
severity: kind === 'ready-capacity' ? 'info' : 'warning',
|
|
2678
|
+
at,
|
|
2679
|
+
jobId: deferral.jobId,
|
|
2680
|
+
reason: `merge admission deferred: ${reason}`
|
|
2681
|
+
});
|
|
2682
|
+
}
|
|
2683
|
+
}
|
|
2684
|
+
return observations;
|
|
2685
|
+
}
|
|
2686
|
+
function applyAdaptiveObservation(limits, minLimits, maxLimits, mode, observation, decisions) {
|
|
2687
|
+
if (observation.kind === 'ready-capacity') {
|
|
2688
|
+
decisions.push(createAdaptiveDecision({
|
|
2689
|
+
action: 'hold',
|
|
2690
|
+
target: 'max-ready-jobs',
|
|
2691
|
+
previous: limits.maxReadyJobs,
|
|
2692
|
+
next: limits.maxReadyJobs,
|
|
2693
|
+
max: maxLimits.maxReadyJobs,
|
|
2694
|
+
min: minLimits.maxReadyJobs,
|
|
2695
|
+
reason: observation.reasons[0] ?? 'ready-capacity',
|
|
2696
|
+
observationIds: [observation.id]
|
|
2697
|
+
}));
|
|
2698
|
+
return;
|
|
2699
|
+
}
|
|
2700
|
+
const target = adaptiveDecisionTargetForObservation(observation);
|
|
2701
|
+
const key = adaptiveDecisionKeyForObservation(observation);
|
|
2702
|
+
if (target === 'lane' && key) {
|
|
2703
|
+
decreaseAdaptiveRecordLimit(limits.maxLaneConcurrency, minLimits.maxLaneConcurrency, maxLimits.maxLaneConcurrency, key, mode, observation, decisions, target);
|
|
2704
|
+
}
|
|
2705
|
+
else if (target === 'concurrency-key' && key) {
|
|
2706
|
+
decreaseAdaptiveRecordLimit(limits.maxConcurrencyKeyConcurrency, minLimits.maxConcurrencyKeyConcurrency, maxLimits.maxConcurrencyKeyConcurrency, key, mode, observation, decisions, target);
|
|
2707
|
+
}
|
|
2708
|
+
else if (target === 'compute' && key) {
|
|
2709
|
+
decreaseAdaptiveRecordLimit(limits.maxComputeConcurrency, minLimits.maxComputeConcurrency, maxLimits.maxComputeConcurrency, key, mode, observation, decisions, target);
|
|
2710
|
+
}
|
|
2711
|
+
else if (target === 'resource' && key) {
|
|
2712
|
+
decreaseAdaptiveRecordLimit(limits.resourceQuotas, minLimits.resourceQuotas, maxLimits.resourceQuotas, key, mode, observation, decisions, target);
|
|
2713
|
+
}
|
|
2714
|
+
if (adaptiveObservationShouldReduceReadyWindow(observation)) {
|
|
2715
|
+
const previous = limits.maxReadyJobs ?? maxLimits.maxReadyJobs ?? 1;
|
|
2716
|
+
const min = minLimits.maxReadyJobs ?? 1;
|
|
2717
|
+
const max = maxLimits.maxReadyJobs ?? previous;
|
|
2718
|
+
const next = adaptiveReducedValue(previous, min, mode, observation);
|
|
2719
|
+
limits.maxReadyJobs = clampAdaptiveLimit(next, min, max);
|
|
2720
|
+
decisions.push(createAdaptiveDecision({
|
|
2721
|
+
action: limits.maxReadyJobs < previous ? 'decrease' : 'hold',
|
|
2722
|
+
target: 'max-ready-jobs',
|
|
2723
|
+
previous,
|
|
2724
|
+
next: limits.maxReadyJobs,
|
|
2725
|
+
max,
|
|
2726
|
+
min,
|
|
2727
|
+
reason: observation.reasons[0] ?? observation.kind,
|
|
2728
|
+
observationIds: [observation.id]
|
|
2729
|
+
}));
|
|
2730
|
+
}
|
|
2731
|
+
}
|
|
2732
|
+
function applyAdaptiveRecovery(limits, maxLimits, observation, decisions) {
|
|
2733
|
+
if (limits.maxReadyJobs !== undefined && maxLimits.maxReadyJobs !== undefined && limits.maxReadyJobs < maxLimits.maxReadyJobs) {
|
|
2734
|
+
const previous = limits.maxReadyJobs;
|
|
2735
|
+
limits.maxReadyJobs = Math.min(maxLimits.maxReadyJobs, previous + 1);
|
|
2736
|
+
decisions.push(createAdaptiveDecision({
|
|
2737
|
+
action: 'increase',
|
|
2738
|
+
target: 'max-ready-jobs',
|
|
2739
|
+
previous,
|
|
2740
|
+
next: limits.maxReadyJobs,
|
|
2741
|
+
max: maxLimits.maxReadyJobs,
|
|
2742
|
+
reason: observation.reasons[0] ?? observation.kind,
|
|
2743
|
+
observationIds: [observation.id]
|
|
2744
|
+
}));
|
|
2745
|
+
}
|
|
2746
|
+
if (observation.lane) {
|
|
2747
|
+
increaseAdaptiveRecordLimit(limits.maxLaneConcurrency, maxLimits.maxLaneConcurrency, observation.lane, observation, decisions, 'lane');
|
|
2748
|
+
}
|
|
2749
|
+
if (observation.compute) {
|
|
2750
|
+
increaseAdaptiveRecordLimit(limits.maxComputeConcurrency, maxLimits.maxComputeConcurrency, observation.compute, observation, decisions, 'compute');
|
|
2751
|
+
}
|
|
2752
|
+
}
|
|
2753
|
+
function decreaseAdaptiveRecordLimit(record, minRecord, maxRecord, key, mode, observation, decisions, target) {
|
|
2754
|
+
const previous = record[key] ?? maxRecord[key];
|
|
2755
|
+
if (!positiveNumber(previous))
|
|
2756
|
+
return;
|
|
2757
|
+
const min = minRecord[key] ?? 1;
|
|
2758
|
+
const max = maxRecord[key] ?? previous;
|
|
2759
|
+
const next = clampAdaptiveLimit(adaptiveReducedValue(previous, min, mode, observation), min, max);
|
|
2760
|
+
record[key] = next;
|
|
2761
|
+
decisions.push(createAdaptiveDecision({
|
|
2762
|
+
action: next < previous ? 'decrease' : 'hold',
|
|
2763
|
+
target,
|
|
2764
|
+
key,
|
|
2765
|
+
previous,
|
|
2766
|
+
next,
|
|
2767
|
+
max,
|
|
2768
|
+
min,
|
|
2769
|
+
reason: observation.reasons[0] ?? observation.kind,
|
|
2770
|
+
observationIds: [observation.id]
|
|
2771
|
+
}));
|
|
2772
|
+
}
|
|
2773
|
+
function increaseAdaptiveRecordLimit(record, maxRecord, key, observation, decisions, target) {
|
|
2774
|
+
const previous = record[key];
|
|
2775
|
+
const max = maxRecord[key];
|
|
2776
|
+
if (!positiveNumber(previous) || !positiveNumber(max) || previous >= max)
|
|
2777
|
+
return;
|
|
2778
|
+
record[key] = Math.min(max, previous + 1);
|
|
2779
|
+
decisions.push(createAdaptiveDecision({
|
|
2780
|
+
action: 'increase',
|
|
2781
|
+
target,
|
|
2782
|
+
key,
|
|
2783
|
+
previous,
|
|
2784
|
+
next: record[key],
|
|
2785
|
+
max,
|
|
2786
|
+
reason: observation.reasons[0] ?? observation.kind,
|
|
2787
|
+
observationIds: [observation.id]
|
|
2788
|
+
}));
|
|
2789
|
+
}
|
|
2790
|
+
function adaptiveReducedValue(previous, min, mode, observation) {
|
|
2791
|
+
if (observation.kind === 'merge-conflict' || observation.kind === 'duplicate-output' || observation.kind === 'concurrency-key-capacity')
|
|
2792
|
+
return min;
|
|
2793
|
+
const severityFactor = observation.severity === 'critical' ? 0.45 : observation.severity === 'error' ? 0.55 : observation.severity === 'warning' ? 0.7 : 0.85;
|
|
2794
|
+
const modeFactor = mode === 'conservative' ? 0.6 : mode === 'aggressive' ? 0.85 : 0.75;
|
|
2795
|
+
return Math.max(min, Math.floor(previous * Math.min(severityFactor, modeFactor)));
|
|
2796
|
+
}
|
|
2797
|
+
function adaptiveObservationShouldReduceReadyWindow(observation) {
|
|
2798
|
+
return observation.kind === 'evidence-failure'
|
|
2799
|
+
|| observation.kind === 'stale-patch'
|
|
2800
|
+
|| observation.kind === 'browser-contention'
|
|
2801
|
+
|| observation.kind === 'semantic-empty'
|
|
2802
|
+
|| observation.kind === 'log-noise'
|
|
2803
|
+
|| observation.kind === 'discovery-only-output'
|
|
2804
|
+
|| observation.kind === 'budget-pressure'
|
|
2805
|
+
|| observation.kind === 'slow-job';
|
|
2806
|
+
}
|
|
2807
|
+
function adaptiveDecisionTargetForObservation(observation) {
|
|
2808
|
+
if (observation.kind === 'resource-capacity' || observation.kind === 'browser-contention')
|
|
2809
|
+
return 'resource';
|
|
2810
|
+
if (observation.kind === 'lane-capacity')
|
|
2811
|
+
return 'lane';
|
|
2812
|
+
if (observation.kind === 'concurrency-key-capacity' || observation.kind === 'merge-conflict' || observation.kind === 'duplicate-output')
|
|
2813
|
+
return 'concurrency-key';
|
|
2814
|
+
if (observation.kind === 'compute-capacity')
|
|
2815
|
+
return 'compute';
|
|
2816
|
+
if (observation.lane)
|
|
2817
|
+
return 'lane';
|
|
2818
|
+
return 'max-ready-jobs';
|
|
2819
|
+
}
|
|
2820
|
+
function adaptiveDecisionKeyForObservation(observation) {
|
|
2821
|
+
const target = adaptiveDecisionTargetForObservation(observation);
|
|
2822
|
+
if (target === 'resource')
|
|
2823
|
+
return observation.resource ?? (observation.kind === 'browser-contention' ? 'browser' : undefined);
|
|
2824
|
+
if (target === 'lane')
|
|
2825
|
+
return observation.lane;
|
|
2826
|
+
if (target === 'concurrency-key')
|
|
2827
|
+
return observation.concurrencyKey ?? observation.region ?? observation.path;
|
|
2828
|
+
if (target === 'compute')
|
|
2829
|
+
return observation.compute;
|
|
2830
|
+
return undefined;
|
|
2831
|
+
}
|
|
2832
|
+
function adaptiveObservationIsBottleneck(observation) {
|
|
2833
|
+
if (observation.kind === 'healthy-throughput' || observation.kind === 'ready-capacity')
|
|
2834
|
+
return false;
|
|
2835
|
+
return observation.severity !== 'info'
|
|
2836
|
+
|| observation.kind === 'merge-conflict'
|
|
2837
|
+
|| observation.kind === 'stale-patch'
|
|
2838
|
+
|| observation.kind === 'semantic-empty'
|
|
2839
|
+
|| observation.kind === 'log-noise'
|
|
2840
|
+
|| observation.kind === 'duplicate-output';
|
|
2841
|
+
}
|
|
2842
|
+
function adaptiveDefaultSeverity(kind) {
|
|
2843
|
+
if (kind === 'evidence-failure' || kind === 'budget-pressure')
|
|
2844
|
+
return 'error';
|
|
2845
|
+
if (kind === 'merge-conflict' || kind === 'stale-patch' || kind === 'semantic-empty' || kind === 'browser-contention' || kind.endsWith('-capacity'))
|
|
2846
|
+
return 'warning';
|
|
2847
|
+
return 'info';
|
|
2848
|
+
}
|
|
2849
|
+
function createAdaptiveDecision(input) {
|
|
2850
|
+
return {
|
|
2851
|
+
id: 'swarm-adaptive-decision:' + stableHash([input.action, input.target, input.key, input.previous, input.next, input.reason, input.observationIds]),
|
|
2852
|
+
...input
|
|
2853
|
+
};
|
|
2854
|
+
}
|
|
2855
|
+
function semanticSummaryIsEmpty(summary) {
|
|
2856
|
+
if (!summary)
|
|
2857
|
+
return false;
|
|
2858
|
+
return summary.total === 0
|
|
2859
|
+
|| summary.selected === 0 && summary.eligible === 0 && summary.imported === 0 && summary.semanticIndex.symbols === 0 && summary.semanticSidecars.symbols === 0;
|
|
2860
|
+
}
|
|
2861
|
+
function semanticSummaryIsWeak(summary) {
|
|
2862
|
+
if (!summary || semanticSummaryIsEmpty(summary))
|
|
2863
|
+
return false;
|
|
2864
|
+
return summary.imported === 0
|
|
2865
|
+
|| summary.semanticIndex.symbols === 0
|
|
2866
|
+
|| summary.semanticSidecars.ownershipRegions === 0
|
|
2867
|
+
|| summary.sourceMapMappingCount === 0;
|
|
2868
|
+
}
|
|
2869
|
+
function dedupeAdaptiveObservations(observations) {
|
|
2870
|
+
const byKey = new Map();
|
|
2871
|
+
for (const observation of observations) {
|
|
2872
|
+
const key = [
|
|
2873
|
+
observation.kind,
|
|
2874
|
+
observation.jobId ?? '',
|
|
2875
|
+
observation.lane ?? '',
|
|
2876
|
+
observation.compute ?? '',
|
|
2877
|
+
observation.concurrencyKey ?? '',
|
|
2878
|
+
observation.resource ?? '',
|
|
2879
|
+
observation.path ?? '',
|
|
2880
|
+
observation.region ?? '',
|
|
2881
|
+
observation.reasons.join('|')
|
|
2882
|
+
].join('\0');
|
|
2883
|
+
const existing = byKey.get(key);
|
|
2884
|
+
if (!existing || adaptiveSeverityRank(observation.severity) > adaptiveSeverityRank(existing.severity))
|
|
2885
|
+
byKey.set(key, observation);
|
|
2886
|
+
}
|
|
2887
|
+
return Array.from(byKey.values()).sort((left, right) => adaptiveSeverityRank(right.severity) - adaptiveSeverityRank(left.severity) || left.kind.localeCompare(right.kind) || (left.jobId ?? '').localeCompare(right.jobId ?? ''));
|
|
2888
|
+
}
|
|
2889
|
+
function adaptiveSeverityRank(severity) {
|
|
2890
|
+
if (severity === 'critical')
|
|
2891
|
+
return 4;
|
|
2892
|
+
if (severity === 'error')
|
|
2893
|
+
return 3;
|
|
2894
|
+
if (severity === 'warning')
|
|
2895
|
+
return 2;
|
|
2896
|
+
if (severity === 'info')
|
|
2897
|
+
return 1;
|
|
2898
|
+
return 0;
|
|
2899
|
+
}
|
|
2900
|
+
function normalizeAdaptiveLimitRecord(input = {}) {
|
|
2901
|
+
const out = {};
|
|
2902
|
+
for (const [key, value] of Object.entries(input)) {
|
|
2903
|
+
if (positiveNumber(value))
|
|
2904
|
+
out[key] = Math.max(1, Math.floor(value));
|
|
2905
|
+
}
|
|
2906
|
+
return out;
|
|
2907
|
+
}
|
|
2908
|
+
function clampAdaptiveRecord(value, minRecord, maxRecord) {
|
|
2909
|
+
const keys = uniqueStrings([...Object.keys(value), ...Object.keys(minRecord), ...Object.keys(maxRecord)]);
|
|
2910
|
+
const out = {};
|
|
2911
|
+
for (const key of keys) {
|
|
2912
|
+
const max = maxRecord[key];
|
|
2913
|
+
if (!positiveNumber(max))
|
|
2914
|
+
continue;
|
|
2915
|
+
const min = minRecord[key] ?? 1;
|
|
2916
|
+
out[key] = clampAdaptiveLimit(value[key] ?? max, min, max);
|
|
2917
|
+
}
|
|
2918
|
+
return out;
|
|
2919
|
+
}
|
|
2920
|
+
function clampAdaptiveLimit(value, min, max) {
|
|
2921
|
+
const upper = Math.max(1, Math.floor(max));
|
|
2922
|
+
const lower = Math.min(upper, Math.max(1, Math.floor(min)));
|
|
2923
|
+
return Math.min(upper, Math.max(lower, Math.floor(value)));
|
|
2924
|
+
}
|
|
2925
|
+
function adaptivePositiveLimit(value, fallback) {
|
|
2926
|
+
return positiveNumber(value) ? Math.max(1, Math.floor(value)) : Math.max(1, Math.floor(fallback));
|
|
2927
|
+
}
|
|
2124
2928
|
function normalizeBudget(input = {}) {
|
|
2125
2929
|
return {
|
|
2126
2930
|
...(positiveNumber(input.maxCostUsd) ? { maxCostUsd: input.maxCostUsd } : {}),
|
|
@@ -2637,6 +3441,172 @@ function patchStackKey(entry) {
|
|
|
2637
3441
|
const firstPath = entry.changedPaths[0] ?? 'evidence-only';
|
|
2638
3442
|
return `${lane}:${firstPath.split('/').slice(0, 2).join('/') || firstPath}`;
|
|
2639
3443
|
}
|
|
3444
|
+
function normalizeCoordinatorProcesses(input) {
|
|
3445
|
+
return input.map((entry) => ({
|
|
3446
|
+
...(entry.pid !== undefined ? { pid: Math.floor(entry.pid) } : {}),
|
|
3447
|
+
role: entry.role ?? 'worker',
|
|
3448
|
+
...(entry.jobId ? { jobId: entry.jobId } : {}),
|
|
3449
|
+
...(entry.runId ? { runId: entry.runId } : {}),
|
|
3450
|
+
status: entry.status ?? 'unknown',
|
|
3451
|
+
...(entry.startedAt !== undefined ? { startedAt: entry.startedAt } : {}),
|
|
3452
|
+
...(entry.lastSeenAt !== undefined ? { lastSeenAt: entry.lastSeenAt } : {}),
|
|
3453
|
+
command: [...(entry.command ?? [])],
|
|
3454
|
+
...(toJsonObject(entry.metadata) ? { metadata: toJsonObject(entry.metadata) } : {})
|
|
3455
|
+
})).sort((left, right) => (left.jobId ?? '').localeCompare(right.jobId ?? '') || (left.pid ?? 0) - (right.pid ?? 0));
|
|
3456
|
+
}
|
|
3457
|
+
function createCoordinatorDuplicateGroups(entries) {
|
|
3458
|
+
const groups = new Map();
|
|
3459
|
+
for (const entry of entries) {
|
|
3460
|
+
for (const key of coordinatorDuplicateKeys(entry))
|
|
3461
|
+
groups.set(key, [...(groups.get(key) ?? []), entry]);
|
|
3462
|
+
}
|
|
3463
|
+
return Array.from(groups.entries())
|
|
3464
|
+
.filter(([, groupEntries]) => groupEntries.length > 1)
|
|
3465
|
+
.map(([key, groupEntries]) => {
|
|
3466
|
+
const jobIds = groupEntries.map((entry) => entry.jobId).sort();
|
|
3467
|
+
return {
|
|
3468
|
+
id: 'swarm-duplicate-group:' + stableHash([key, jobIds]),
|
|
3469
|
+
key,
|
|
3470
|
+
jobIds,
|
|
3471
|
+
reason: key.startsWith('queue:') ? 'same-queue-item' : key.startsWith('region:') ? 'same-semantic-region' : 'same-changed-paths'
|
|
3472
|
+
};
|
|
3473
|
+
})
|
|
3474
|
+
.sort((left, right) => left.key.localeCompare(right.key));
|
|
3475
|
+
}
|
|
3476
|
+
function coordinatorDuplicateKeys(entry) {
|
|
3477
|
+
const keys = [];
|
|
3478
|
+
if (entry.queueItemIds.length)
|
|
3479
|
+
keys.push(`queue:${entry.queueItemIds.slice().sort().join('|')}`);
|
|
3480
|
+
if (entry.changedRegions.length)
|
|
3481
|
+
keys.push(`region:${entry.changedRegions.slice().sort().join('|')}`);
|
|
3482
|
+
if (entry.changedPaths.length)
|
|
3483
|
+
keys.push(`path:${entry.changedPaths.slice().sort().join('|')}`);
|
|
3484
|
+
return keys;
|
|
3485
|
+
}
|
|
3486
|
+
function scoreCoordinatorMergeJob(entry, bundle, evidenceEntryCount, duplicateGroup, admissionStatus, admissionReasons) {
|
|
3487
|
+
if (!entry)
|
|
3488
|
+
return { score: 10, reasons: ['no-merge-index-entry'] };
|
|
3489
|
+
let score = entry.disposition === 'auto-mergeable' && entry.autoMergeable ? 85 : entry.disposition === 'needs-port' ? 60 : entry.disposition === 'discovery-only' ? 35 : 15;
|
|
3490
|
+
const reasons = [];
|
|
3491
|
+
if (entry.staleAgainstHead) {
|
|
3492
|
+
score -= 45;
|
|
3493
|
+
reasons.push('stale-against-head');
|
|
3494
|
+
}
|
|
3495
|
+
if (entry.conflictingJobIds.length) {
|
|
3496
|
+
score -= Math.min(35, 15 + entry.conflictingJobIds.length * 5);
|
|
3497
|
+
reasons.push('conflicting-changes');
|
|
3498
|
+
}
|
|
3499
|
+
if (entry.ownershipViolations.length) {
|
|
3500
|
+
score -= 40;
|
|
3501
|
+
reasons.push('ownership-violations');
|
|
3502
|
+
}
|
|
3503
|
+
if (bundle?.commandsFailed.length) {
|
|
3504
|
+
score -= Math.min(35, 15 + bundle.commandsFailed.length * 10);
|
|
3505
|
+
reasons.push('failed-required-commands');
|
|
3506
|
+
}
|
|
3507
|
+
if (entry.evidencePaths.length === 0 && evidenceEntryCount === 0) {
|
|
3508
|
+
score -= 15;
|
|
3509
|
+
reasons.push('missing-evidence');
|
|
3510
|
+
}
|
|
3511
|
+
if (entry.riskLevel === 'high') {
|
|
3512
|
+
score -= 15;
|
|
3513
|
+
reasons.push('high-risk');
|
|
3514
|
+
}
|
|
3515
|
+
else if (entry.riskLevel === 'unknown') {
|
|
3516
|
+
score -= 8;
|
|
3517
|
+
reasons.push('unknown-risk');
|
|
3518
|
+
}
|
|
3519
|
+
else if (entry.riskLevel === 'medium') {
|
|
3520
|
+
score -= 5;
|
|
3521
|
+
}
|
|
3522
|
+
if (entry.disposition === 'needs-port')
|
|
3523
|
+
reasons.push('needs-human-port');
|
|
3524
|
+
if (entry.disposition === 'discovery-only')
|
|
3525
|
+
reasons.push('discovery-only');
|
|
3526
|
+
if (duplicateGroup) {
|
|
3527
|
+
score -= 12;
|
|
3528
|
+
reasons.push('duplicate-candidate');
|
|
3529
|
+
}
|
|
3530
|
+
if (admissionStatus === 'deferred') {
|
|
3531
|
+
score -= 10;
|
|
3532
|
+
reasons.push(...admissionReasons);
|
|
3533
|
+
}
|
|
3534
|
+
if (entry.semanticImport && entry.changedPaths.length > 0) {
|
|
3535
|
+
const symbols = entry.semanticImport.semanticIndex.symbols;
|
|
3536
|
+
const regions = entry.semanticImport.semanticSidecars.ownershipRegions;
|
|
3537
|
+
const errors = entry.semanticImport.errors;
|
|
3538
|
+
if (symbols > 0 && regions > 0) {
|
|
3539
|
+
score += 8;
|
|
3540
|
+
reasons.push('semantic-sidecar-usable');
|
|
3541
|
+
}
|
|
3542
|
+
if (entry.semanticImport.semanticSidecars.empty > 0 || symbols === 0) {
|
|
3543
|
+
score -= 8;
|
|
3544
|
+
reasons.push('weak-semantic-sidecar');
|
|
3545
|
+
}
|
|
3546
|
+
if (errors > 0) {
|
|
3547
|
+
score -= Math.min(25, errors * 10);
|
|
3548
|
+
reasons.push('semantic-import-errors');
|
|
3549
|
+
}
|
|
3550
|
+
}
|
|
3551
|
+
else if (entry.changedPaths.length > 0) {
|
|
3552
|
+
score -= 5;
|
|
3553
|
+
reasons.push('missing-semantic-sidecar');
|
|
3554
|
+
}
|
|
3555
|
+
if (bundle?.commandsPassed.length)
|
|
3556
|
+
score += Math.min(8, bundle.commandsPassed.length * 2);
|
|
3557
|
+
return { score: clampScore(score), reasons: uniqueStrings(reasons) };
|
|
3558
|
+
}
|
|
3559
|
+
function coordinatorJobLiveness(result, entry, processes) {
|
|
3560
|
+
if (result || entry)
|
|
3561
|
+
return 'finished';
|
|
3562
|
+
if (processes.some((process) => process.status === 'running'))
|
|
3563
|
+
return 'running';
|
|
3564
|
+
if (processes.some((process) => process.status === 'missing'))
|
|
3565
|
+
return 'missing';
|
|
3566
|
+
return 'unknown';
|
|
3567
|
+
}
|
|
3568
|
+
function primaryEvidencePath(paths) {
|
|
3569
|
+
return paths.find((entry) => entry.endsWith('/evidence.json') || entry === 'evidence.json')
|
|
3570
|
+
?? paths.find((entry) => entry.endsWith('/merge.json') || entry === 'merge.json')
|
|
3571
|
+
?? paths.find((entry) => entry.endsWith('/last-message.md') || entry.endsWith('/last.md'))
|
|
3572
|
+
?? paths[0];
|
|
3573
|
+
}
|
|
3574
|
+
function createCoordinatorSourceCitations(entry, evidenceIndex) {
|
|
3575
|
+
const citations = [];
|
|
3576
|
+
for (const file of entry?.changedPaths ?? [])
|
|
3577
|
+
citations.push({ path: file, kind: 'changed-source' });
|
|
3578
|
+
for (const region of entry?.changedRegions ?? [])
|
|
3579
|
+
citations.push({ path: region, kind: 'semantic-region', region });
|
|
3580
|
+
for (const evidence of evidenceIndex?.byJobId[entry?.jobId ?? ''] ?? []) {
|
|
3581
|
+
if (!evidence.path)
|
|
3582
|
+
continue;
|
|
3583
|
+
citations.push({
|
|
3584
|
+
path: evidence.path,
|
|
3585
|
+
kind: evidence.kind,
|
|
3586
|
+
...(evidence.topic ? { symbol: evidence.topic } : {}),
|
|
3587
|
+
confidence: evidence.confidence,
|
|
3588
|
+
...(Object.keys(evidence.facets).length ? { metadata: { facets: evidence.facets } } : {})
|
|
3589
|
+
});
|
|
3590
|
+
}
|
|
3591
|
+
const seen = new Set();
|
|
3592
|
+
return citations.filter((citation) => {
|
|
3593
|
+
const key = `${citation.kind}:${citation.path}:${citation.symbol ?? ''}:${citation.region ?? ''}`;
|
|
3594
|
+
if (seen.has(key))
|
|
3595
|
+
return false;
|
|
3596
|
+
seen.add(key);
|
|
3597
|
+
return true;
|
|
3598
|
+
}).sort((left, right) => left.kind.localeCompare(right.kind) || left.path.localeCompare(right.path));
|
|
3599
|
+
}
|
|
3600
|
+
function averageScore(scores) {
|
|
3601
|
+
if (scores.length === 0)
|
|
3602
|
+
return 0;
|
|
3603
|
+
return Math.round(scores.reduce((sum, score) => sum + score, 0) / scores.length);
|
|
3604
|
+
}
|
|
3605
|
+
function clampScore(value) {
|
|
3606
|
+
if (!Number.isFinite(value))
|
|
3607
|
+
return 0;
|
|
3608
|
+
return Math.max(0, Math.min(100, Math.round(value)));
|
|
3609
|
+
}
|
|
2640
3610
|
function riskRank(risk) {
|
|
2641
3611
|
if (risk === 'low')
|
|
2642
3612
|
return 0;
|