@martinloop/mcp 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/package-version.d.ts +1 -1
- package/dist/package-version.js +1 -1
- package/dist/vendor/adapters/claude-cli.d.ts +11 -1
- package/dist/vendor/adapters/claude-cli.js +285 -71
- package/dist/vendor/adapters/cli-bridge.d.ts +12 -3
- package/dist/vendor/adapters/cli-bridge.js +129 -149
- package/dist/vendor/adapters/codex-cli.d.ts +3 -3
- package/dist/vendor/adapters/codex-cli.js +10 -24
- package/dist/vendor/adapters/codex-host.js +127 -132
- package/dist/vendor/adapters/index.d.ts +2 -1
- package/dist/vendor/adapters/index.js +2 -1
- package/dist/vendor/adapters/openai-compatible.js +88 -6
- package/dist/vendor/adapters/process-supervisor.d.ts +60 -0
- package/dist/vendor/adapters/process-supervisor.js +541 -0
- package/dist/vendor/adapters/verifier-only.d.ts +10 -1
- package/dist/vendor/adapters/verifier-only.js +43 -1
- package/dist/vendor/contracts/external-outcome.d.ts +102 -0
- package/dist/vendor/contracts/external-outcome.js +226 -0
- package/dist/vendor/contracts/index.d.ts +8 -1
- package/dist/vendor/contracts/index.js +2 -0
- package/dist/vendor/contracts/swarm-hosted.d.ts +204 -0
- package/dist/vendor/contracts/swarm-hosted.js +629 -0
- package/dist/vendor/contracts/swarm.d.ts +449 -0
- package/dist/vendor/contracts/swarm.js +550 -0
- package/dist/vendor/core/external-outcome.d.ts +34 -0
- package/dist/vendor/core/external-outcome.js +515 -0
- package/dist/vendor/core/grounding.js +1 -1
- package/dist/vendor/core/index.d.ts +47 -1
- package/dist/vendor/core/index.js +53 -3
- package/dist/vendor/core/leash.d.ts +1 -0
- package/dist/vendor/core/leash.js +13 -0
- package/dist/vendor/core/persistence/integrity.d.ts +4 -0
- package/dist/vendor/core/persistence/integrity.js +19 -6
- package/dist/vendor/core/persistence/swarm-integrity.d.ts +63 -0
- package/dist/vendor/core/persistence/swarm-integrity.js +309 -0
- package/dist/vendor/core/swarm/candidates.d.ts +78 -0
- package/dist/vendor/core/swarm/candidates.js +810 -0
- package/dist/vendor/core/swarm/evidence-projection.d.ts +87 -0
- package/dist/vendor/core/swarm/evidence-projection.js +128 -0
- package/dist/vendor/core/swarm/evidence.d.ts +40 -0
- package/dist/vendor/core/swarm/evidence.js +575 -0
- package/dist/vendor/core/swarm/hosted-export.d.ts +48 -0
- package/dist/vendor/core/swarm/hosted-export.js +362 -0
- package/dist/vendor/core/swarm/hosted-projection.d.ts +5 -0
- package/dist/vendor/core/swarm/hosted-projection.js +258 -0
- package/dist/vendor/core/swarm/index.d.ts +110 -0
- package/dist/vendor/core/swarm/index.js +1060 -0
- package/dist/vendor/core/swarm/integration.d.ts +76 -0
- package/dist/vendor/core/swarm/integration.js +615 -0
- package/dist/vendor/core/swarm/lifecycle.d.ts +72 -0
- package/dist/vendor/core/swarm/lifecycle.js +252 -0
- package/dist/vendor/core/swarm/live-runtime.d.ts +78 -0
- package/dist/vendor/core/swarm/live-runtime.js +1482 -0
- package/dist/vendor/core/swarm/live-store.d.ts +99 -0
- package/dist/vendor/core/swarm/live-store.js +860 -0
- package/dist/vendor/core/swarm/operational-reader.d.ts +17 -0
- package/dist/vendor/core/swarm/operational-reader.js +96 -0
- package/dist/vendor/core/swarm/operations.d.ts +29 -0
- package/dist/vendor/core/swarm/operations.js +128 -0
- package/dist/vendor/core/swarm/parent-receipt.d.ts +13 -0
- package/dist/vendor/core/swarm/parent-receipt.js +489 -0
- package/dist/vendor/core/swarm/receipt-projection.d.ts +26 -0
- package/dist/vendor/core/swarm/receipt-projection.js +120 -0
- package/dist/vendor/core/swarm/scheduler.d.ts +92 -0
- package/dist/vendor/core/swarm/scheduler.js +555 -0
- package/dist/vendor/core/swarm/verification.d.ts +70 -0
- package/dist/vendor/core/swarm/verification.js +295 -0
- package/dist/vendor/core/swarm/workspaces.d.ts +77 -0
- package/dist/vendor/core/swarm/workspaces.js +488 -0
- package/dist/vendor/core/verified-handoff.d.ts +4 -0
- package/dist/vendor/core/verified-handoff.js +4 -0
- package/package.json +1 -1
- package/server.json +2 -2
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { SwarmBudgetLedger, SwarmBudgetLease, SwarmDependencyWaiver, SwarmParentContract, SwarmTaskNode } from "../../contracts/index.js";
|
|
2
|
+
export interface SwarmSchedulerError {
|
|
3
|
+
code: string;
|
|
4
|
+
taskId?: string;
|
|
5
|
+
message: string;
|
|
6
|
+
}
|
|
7
|
+
export type SwarmGraphValidationResult = {
|
|
8
|
+
ok: true;
|
|
9
|
+
errors: [];
|
|
10
|
+
} | {
|
|
11
|
+
ok: false;
|
|
12
|
+
errors: SwarmSchedulerError[];
|
|
13
|
+
};
|
|
14
|
+
export type SwarmBatchResult = {
|
|
15
|
+
ok: true;
|
|
16
|
+
tasks: SwarmTaskNode[];
|
|
17
|
+
errors: [];
|
|
18
|
+
} | {
|
|
19
|
+
ok: false;
|
|
20
|
+
tasks: [];
|
|
21
|
+
errors: SwarmSchedulerError[];
|
|
22
|
+
};
|
|
23
|
+
export type SwarmBudgetTransitionResult = {
|
|
24
|
+
ok: true;
|
|
25
|
+
ledger: SwarmBudgetLedger;
|
|
26
|
+
lease: SwarmBudgetLease;
|
|
27
|
+
errors: [];
|
|
28
|
+
} | {
|
|
29
|
+
ok: false;
|
|
30
|
+
ledger: SwarmBudgetLedger;
|
|
31
|
+
errors: SwarmSchedulerError[];
|
|
32
|
+
};
|
|
33
|
+
export interface SwarmBudgetProtectedUsage {
|
|
34
|
+
readonly usd: number;
|
|
35
|
+
readonly tokens: number;
|
|
36
|
+
}
|
|
37
|
+
export type ParentDependencyWaiverRegistry = Readonly<{
|
|
38
|
+
kind: "parent_dependency_waiver_registry";
|
|
39
|
+
}>;
|
|
40
|
+
export type IssuedDependencyWaiverCapability = Readonly<{
|
|
41
|
+
kind: "issued_dependency_waiver";
|
|
42
|
+
}>;
|
|
43
|
+
export type DependencyWaiverIssuanceResult = {
|
|
44
|
+
ok: true;
|
|
45
|
+
capability: IssuedDependencyWaiverCapability;
|
|
46
|
+
evidence: SwarmDependencyWaiver;
|
|
47
|
+
errors: [];
|
|
48
|
+
} | {
|
|
49
|
+
ok: false;
|
|
50
|
+
errors: SwarmSchedulerError[];
|
|
51
|
+
};
|
|
52
|
+
export declare function createParentDependencyWaiverRegistry(input: {
|
|
53
|
+
swarmId: string;
|
|
54
|
+
parentPolicyVersion: string;
|
|
55
|
+
dependencyWaiversAllowed: boolean;
|
|
56
|
+
}): ParentDependencyWaiverRegistry;
|
|
57
|
+
export declare function issueParentDependencyWaiver(registry: ParentDependencyWaiverRegistry, evidence: SwarmDependencyWaiver): DependencyWaiverIssuanceResult;
|
|
58
|
+
export declare function createSwarmBudgetLedger(input: {
|
|
59
|
+
capUsd: number;
|
|
60
|
+
capTokens?: number;
|
|
61
|
+
}): SwarmBudgetLedger;
|
|
62
|
+
export declare function reserveSwarmBudgetLease(ledger: SwarmBudgetLedger, input: Omit<SwarmBudgetLease, "status" | "actualUsage">): SwarmBudgetTransitionResult;
|
|
63
|
+
/**
|
|
64
|
+
* Extends one live lease by atomically reclaiming only the unobserved portion
|
|
65
|
+
* of sibling reservations. This keeps initial fair-share reservations intact
|
|
66
|
+
* until a child actually needs more capacity, while ensuring every live
|
|
67
|
+
* observation remains protected and total granted capacity never exceeds the
|
|
68
|
+
* parent cap.
|
|
69
|
+
*/
|
|
70
|
+
export declare function extendSwarmBudgetLease(ledger: SwarmBudgetLedger, input: {
|
|
71
|
+
leaseId: string;
|
|
72
|
+
requiredUsd: number;
|
|
73
|
+
requiredTokens: number;
|
|
74
|
+
protectedUsage: Readonly<Record<string, SwarmBudgetProtectedUsage>>;
|
|
75
|
+
}): SwarmBudgetTransitionResult;
|
|
76
|
+
export declare function settleSwarmBudgetLease(ledger: SwarmBudgetLedger, input: {
|
|
77
|
+
leaseId: string;
|
|
78
|
+
actualUsd: number;
|
|
79
|
+
actualTokens: number;
|
|
80
|
+
provenance?: string;
|
|
81
|
+
}): SwarmBudgetTransitionResult;
|
|
82
|
+
export declare function releaseSwarmBudgetLease(ledger: SwarmBudgetLedger, leaseId: string): SwarmBudgetTransitionResult;
|
|
83
|
+
export declare function validateSwarmTaskGraph(tasks: readonly SwarmTaskNode[]): SwarmGraphValidationResult;
|
|
84
|
+
export declare function selectNextSwarmBatch(input: {
|
|
85
|
+
swarmId: string;
|
|
86
|
+
tasks: readonly SwarmTaskNode[];
|
|
87
|
+
maxConcurrency: number;
|
|
88
|
+
parentContract: Pick<SwarmParentContract, "policyVersion" | "recoveryPolicy" | "maxConcurrency">;
|
|
89
|
+
dependencyWaiverRegistry?: ParentDependencyWaiverRegistry;
|
|
90
|
+
dependencyWaiverCapabilities?: readonly IssuedDependencyWaiverCapability[];
|
|
91
|
+
}): SwarmBatchResult;
|
|
92
|
+
export declare function tasksHaveScopeCollision(left: SwarmTaskNode, right: SwarmTaskNode): boolean;
|
|
@@ -0,0 +1,555 @@
|
|
|
1
|
+
import { normalizeSwarmPathPattern, swarmPathPatternsOverlap } from "../../contracts/index.js";
|
|
2
|
+
const parentWaiverRegistries = new WeakMap();
|
|
3
|
+
const issuedWaiverCapabilities = new WeakMap();
|
|
4
|
+
export function createParentDependencyWaiverRegistry(input) {
|
|
5
|
+
const registry = Object.freeze({
|
|
6
|
+
kind: "parent_dependency_waiver_registry"
|
|
7
|
+
});
|
|
8
|
+
parentWaiverRegistries.set(registry, { ...input });
|
|
9
|
+
return registry;
|
|
10
|
+
}
|
|
11
|
+
export function issueParentDependencyWaiver(registry, evidence) {
|
|
12
|
+
const registryState = parentWaiverRegistries.get(registry);
|
|
13
|
+
if (!registryState) {
|
|
14
|
+
return {
|
|
15
|
+
ok: false,
|
|
16
|
+
errors: [{
|
|
17
|
+
code: "UNISSUED_PARENT_WAIVER_REGISTRY",
|
|
18
|
+
message: "Dependency waivers require an internally issued parent registry."
|
|
19
|
+
}]
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
if (!registryState.dependencyWaiversAllowed) {
|
|
23
|
+
return {
|
|
24
|
+
ok: false,
|
|
25
|
+
errors: [{
|
|
26
|
+
code: "DEPENDENCY_WAIVERS_DISABLED",
|
|
27
|
+
message: "Parent recovery policy does not allow dependency waivers."
|
|
28
|
+
}]
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
if (evidence.parentPolicyVersion !== registryState.parentPolicyVersion) {
|
|
32
|
+
return {
|
|
33
|
+
ok: false,
|
|
34
|
+
errors: [{
|
|
35
|
+
code: "WAIVER_POLICY_VERSION_MISMATCH",
|
|
36
|
+
taskId: evidence.taskId,
|
|
37
|
+
message: "Dependency waiver is not bound to the issuing parent policy version."
|
|
38
|
+
}]
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
if (!hasText(evidence.taskId)
|
|
42
|
+
|| !hasText(evidence.dependencyTaskId)
|
|
43
|
+
|| !hasText(evidence.approvedBy)
|
|
44
|
+
|| !hasText(evidence.approvedAt)
|
|
45
|
+
|| Number.isNaN(Date.parse(evidence.approvedAt))) {
|
|
46
|
+
return {
|
|
47
|
+
ok: false,
|
|
48
|
+
errors: [{
|
|
49
|
+
code: "INVALID_DEPENDENCY_WAIVER",
|
|
50
|
+
taskId: evidence.taskId,
|
|
51
|
+
message: "Dependency waiver evidence requires task, dependency, approval identity, and approval time."
|
|
52
|
+
}]
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
const capability = Object.freeze({
|
|
56
|
+
kind: "issued_dependency_waiver"
|
|
57
|
+
});
|
|
58
|
+
const clonedEvidence = { ...evidence };
|
|
59
|
+
issuedWaiverCapabilities.set(capability, { registry, evidence: clonedEvidence });
|
|
60
|
+
return { ok: true, capability, evidence: clonedEvidence, errors: [] };
|
|
61
|
+
}
|
|
62
|
+
export function createSwarmBudgetLedger(input) {
|
|
63
|
+
return {
|
|
64
|
+
capUsd: input.capUsd,
|
|
65
|
+
settledUsd: 0,
|
|
66
|
+
settledTokens: 0,
|
|
67
|
+
leases: [],
|
|
68
|
+
...(input.capTokens !== undefined ? { capTokens: input.capTokens } : {})
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
export function reserveSwarmBudgetLease(ledger, input) {
|
|
72
|
+
if (!validateBudgetLedger(ledger)) {
|
|
73
|
+
return budgetError(ledger, "INVALID_BUDGET_LEDGER", "Budget caps and settled totals must be nonnegative finite values within their caps.");
|
|
74
|
+
}
|
|
75
|
+
if (!hasText(input.leaseId) || !hasText(input.agentId) || !hasText(input.taskId)) {
|
|
76
|
+
return budgetError(ledger, "INVALID_LEASE_IDENTITY", "Lease, agent, and task identifiers must be nonempty.");
|
|
77
|
+
}
|
|
78
|
+
if (!isNonnegativeFinite(input.reservedUsd) || !isNonnegativeInteger(input.reservedTokens)) {
|
|
79
|
+
return budgetError(ledger, "INVALID_LEASE_AMOUNT", "Lease reservations must be nonnegative finite values.");
|
|
80
|
+
}
|
|
81
|
+
if (ledger.leases.some((lease) => lease.leaseId === input.leaseId)) {
|
|
82
|
+
return budgetError(ledger, "DUPLICATE_LEASE_ID", `Lease ${input.leaseId} already exists.`);
|
|
83
|
+
}
|
|
84
|
+
const active = activeReservations(ledger);
|
|
85
|
+
if (ledger.settledUsd + active.usd + input.reservedUsd > ledger.capUsd
|
|
86
|
+
|| (ledger.capTokens !== undefined
|
|
87
|
+
&& ledger.settledTokens + active.tokens + input.reservedTokens > ledger.capTokens)) {
|
|
88
|
+
return budgetError(ledger, "GLOBAL_BUDGET_EXCEEDED", "The reservation would exceed the parent budget cap.");
|
|
89
|
+
}
|
|
90
|
+
const lease = { ...input, status: "reserved" };
|
|
91
|
+
return {
|
|
92
|
+
ok: true,
|
|
93
|
+
ledger: { ...ledger, leases: [...ledger.leases.map(cloneLease), lease] },
|
|
94
|
+
lease,
|
|
95
|
+
errors: []
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Extends one live lease by atomically reclaiming only the unobserved portion
|
|
100
|
+
* of sibling reservations. This keeps initial fair-share reservations intact
|
|
101
|
+
* until a child actually needs more capacity, while ensuring every live
|
|
102
|
+
* observation remains protected and total granted capacity never exceeds the
|
|
103
|
+
* parent cap.
|
|
104
|
+
*/
|
|
105
|
+
export function extendSwarmBudgetLease(ledger, input) {
|
|
106
|
+
if (!validateBudgetLedger(ledger)) {
|
|
107
|
+
return budgetError(ledger, "INVALID_BUDGET_LEDGER", "Budget caps and settled totals must be nonnegative finite values within their caps.");
|
|
108
|
+
}
|
|
109
|
+
const target = ledger.leases.find((lease) => lease.leaseId === input.leaseId);
|
|
110
|
+
if (!target)
|
|
111
|
+
return budgetError(ledger, "LEASE_NOT_FOUND", `Lease ${input.leaseId} does not exist.`);
|
|
112
|
+
if (target.status !== "reserved") {
|
|
113
|
+
return budgetError(ledger, "LEASE_ALREADY_FINAL", `Lease ${input.leaseId} is already ${target.status}.`);
|
|
114
|
+
}
|
|
115
|
+
if (!isNonnegativeFinite(input.requiredUsd) || !isNonnegativeInteger(input.requiredTokens)) {
|
|
116
|
+
return budgetError(ledger, "INVALID_LEASE_AMOUNT", "Required live usage must use nonnegative finite values.");
|
|
117
|
+
}
|
|
118
|
+
for (const [leaseId, usage] of Object.entries(input.protectedUsage)) {
|
|
119
|
+
const lease = ledger.leases.find((candidate) => candidate.leaseId === leaseId);
|
|
120
|
+
if (!lease || lease.status !== "reserved") {
|
|
121
|
+
return budgetError(ledger, "INVALID_PROTECTED_USAGE", `Protected usage references non-active lease ${leaseId}.`);
|
|
122
|
+
}
|
|
123
|
+
if (!isNonnegativeFinite(usage.usd) || !isNonnegativeInteger(usage.tokens)) {
|
|
124
|
+
return budgetError(ledger, "INVALID_PROTECTED_USAGE", `Protected usage for lease ${leaseId} is invalid.`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
const protectedTarget = input.protectedUsage[input.leaseId];
|
|
128
|
+
const requiredUsd = Math.max(input.requiredUsd, protectedTarget?.usd ?? 0, target.reservedUsd);
|
|
129
|
+
const requiredTokens = Math.max(input.requiredTokens, protectedTarget?.tokens ?? 0, target.reservedTokens);
|
|
130
|
+
if (requiredUsd > ledger.capUsd || (ledger.capTokens !== undefined && requiredTokens > ledger.capTokens)) {
|
|
131
|
+
return budgetError(ledger, "GLOBAL_BUDGET_EXCEEDED", "The live usage observation exceeds the parent budget cap.");
|
|
132
|
+
}
|
|
133
|
+
const nextLeases = ledger.leases.map(cloneLease);
|
|
134
|
+
let usdNeeded = requiredUsd - target.reservedUsd;
|
|
135
|
+
let tokensNeeded = requiredTokens - target.reservedTokens;
|
|
136
|
+
const active = activeReservations(ledger);
|
|
137
|
+
usdNeeded = Math.max(0, usdNeeded - Math.max(0, ledger.capUsd - ledger.settledUsd - active.usd));
|
|
138
|
+
tokensNeeded = Math.max(0, tokensNeeded - Math.max(0, (ledger.capTokens ?? Number.MAX_SAFE_INTEGER) - ledger.settledTokens - active.tokens));
|
|
139
|
+
for (const sibling of [...nextLeases]
|
|
140
|
+
.filter((lease) => lease.status === "reserved" && lease.leaseId !== input.leaseId)
|
|
141
|
+
.sort((left, right) => left.leaseId.localeCompare(right.leaseId))) {
|
|
142
|
+
const floor = input.protectedUsage[sibling.leaseId] ?? { usd: 0, tokens: 0 };
|
|
143
|
+
const reclaimedUsd = Math.min(usdNeeded, Math.max(0, sibling.reservedUsd - floor.usd));
|
|
144
|
+
const reclaimedTokens = Math.min(tokensNeeded, Math.max(0, sibling.reservedTokens - floor.tokens));
|
|
145
|
+
sibling.reservedUsd -= reclaimedUsd;
|
|
146
|
+
sibling.reservedTokens -= reclaimedTokens;
|
|
147
|
+
usdNeeded -= reclaimedUsd;
|
|
148
|
+
tokensNeeded -= reclaimedTokens;
|
|
149
|
+
if (nearlyEqual(usdNeeded, 0) && tokensNeeded === 0)
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
if (!nearlyEqual(usdNeeded, 0) || tokensNeeded !== 0) {
|
|
153
|
+
return budgetError(ledger, "GLOBAL_BUDGET_EXCEEDED", "Observed live usage cannot be granted without exceeding the parent budget cap.");
|
|
154
|
+
}
|
|
155
|
+
const nextTarget = nextLeases.find((lease) => lease.leaseId === input.leaseId);
|
|
156
|
+
nextTarget.reservedUsd = requiredUsd;
|
|
157
|
+
nextTarget.reservedTokens = requiredTokens;
|
|
158
|
+
const nextLedger = { ...ledger, leases: nextLeases };
|
|
159
|
+
if (!validateBudgetLedger(nextLedger)) {
|
|
160
|
+
return budgetError(ledger, "GLOBAL_BUDGET_EXCEEDED", "The elastic lease transition would violate the parent budget cap.");
|
|
161
|
+
}
|
|
162
|
+
return { ok: true, ledger: nextLedger, lease: cloneLease(nextTarget), errors: [] };
|
|
163
|
+
}
|
|
164
|
+
export function settleSwarmBudgetLease(ledger, input) {
|
|
165
|
+
if (!validateBudgetLedger(ledger)) {
|
|
166
|
+
return budgetError(ledger, "INVALID_BUDGET_LEDGER", "Budget caps and settled totals must be nonnegative finite values within their caps.");
|
|
167
|
+
}
|
|
168
|
+
const lease = ledger.leases.find((candidate) => candidate.leaseId === input.leaseId);
|
|
169
|
+
if (!lease)
|
|
170
|
+
return budgetError(ledger, "LEASE_NOT_FOUND", `Lease ${input.leaseId} does not exist.`);
|
|
171
|
+
if (lease.status !== "reserved") {
|
|
172
|
+
return budgetError(ledger, "LEASE_ALREADY_FINAL", `Lease ${input.leaseId} is already ${lease.status}.`);
|
|
173
|
+
}
|
|
174
|
+
if (!isNonnegativeFinite(input.actualUsd) || !isNonnegativeInteger(input.actualTokens)) {
|
|
175
|
+
return budgetError(ledger, "INVALID_SETTLEMENT_AMOUNT", "Actual usage must use nonnegative finite values.");
|
|
176
|
+
}
|
|
177
|
+
const leaseOverspent = input.actualUsd > lease.reservedUsd || input.actualTokens > lease.reservedTokens;
|
|
178
|
+
const settledLease = {
|
|
179
|
+
...lease,
|
|
180
|
+
status: leaseOverspent ? "overspent" : "settled",
|
|
181
|
+
actualUsage: {
|
|
182
|
+
usd: input.actualUsd,
|
|
183
|
+
tokens: input.actualTokens,
|
|
184
|
+
...(input.provenance !== undefined ? { provenance: input.provenance } : {})
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
const nextLedger = {
|
|
188
|
+
...ledger,
|
|
189
|
+
settledUsd: ledger.settledUsd + input.actualUsd,
|
|
190
|
+
settledTokens: ledger.settledTokens + input.actualTokens,
|
|
191
|
+
leases: ledger.leases.map((candidate) => candidate.leaseId === input.leaseId ? settledLease : cloneLease(candidate))
|
|
192
|
+
};
|
|
193
|
+
if (leaseOverspent) {
|
|
194
|
+
return budgetError(nextLedger, "LEASE_OVERSPEND", "Actual usage exceeded the reserved lease and was recorded as terminal spend.");
|
|
195
|
+
}
|
|
196
|
+
return { ok: true, ledger: nextLedger, lease: settledLease, errors: [] };
|
|
197
|
+
}
|
|
198
|
+
export function releaseSwarmBudgetLease(ledger, leaseId) {
|
|
199
|
+
if (!validateBudgetLedger(ledger)) {
|
|
200
|
+
return budgetError(ledger, "INVALID_BUDGET_LEDGER", "Budget caps and settled totals must be nonnegative finite values within their caps.");
|
|
201
|
+
}
|
|
202
|
+
const lease = ledger.leases.find((candidate) => candidate.leaseId === leaseId);
|
|
203
|
+
if (!lease)
|
|
204
|
+
return budgetError(ledger, "LEASE_NOT_FOUND", `Lease ${leaseId} does not exist.`);
|
|
205
|
+
if (lease.status !== "reserved") {
|
|
206
|
+
return budgetError(ledger, "LEASE_ALREADY_FINAL", `Lease ${leaseId} is already ${lease.status}.`);
|
|
207
|
+
}
|
|
208
|
+
const releasedLease = { ...lease, status: "released" };
|
|
209
|
+
return {
|
|
210
|
+
ok: true,
|
|
211
|
+
ledger: {
|
|
212
|
+
...ledger,
|
|
213
|
+
leases: ledger.leases.map((candidate) => candidate.leaseId === leaseId ? releasedLease : cloneLease(candidate))
|
|
214
|
+
},
|
|
215
|
+
lease: releasedLease,
|
|
216
|
+
errors: []
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
export function validateSwarmTaskGraph(tasks) {
|
|
220
|
+
const errors = [];
|
|
221
|
+
const byId = new Map();
|
|
222
|
+
for (const task of tasks) {
|
|
223
|
+
if (byId.has(task.taskId)) {
|
|
224
|
+
errors.push({
|
|
225
|
+
code: "DUPLICATE_TASK_ID",
|
|
226
|
+
taskId: task.taskId,
|
|
227
|
+
message: `Duplicate task identifier: ${task.taskId}`
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
byId.set(task.taskId, task);
|
|
232
|
+
}
|
|
233
|
+
if (task.mutationMode === "read_only" && task.writeScope.length > 0) {
|
|
234
|
+
errors.push({
|
|
235
|
+
code: "READ_ONLY_SCOPE_CONFLICT",
|
|
236
|
+
taskId: task.taskId,
|
|
237
|
+
message: "Read-only tasks cannot declare write scope."
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
if (task.mutationMode !== "read_only" && task.mutationMode !== "write") {
|
|
241
|
+
errors.push({
|
|
242
|
+
code: "INVALID_MUTATION_MODE",
|
|
243
|
+
taskId: task.taskId,
|
|
244
|
+
message: "Tasks must explicitly declare read_only or write mutation mode."
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
else if (task.mutationMode === "write" && task.writeScope.length === 0) {
|
|
248
|
+
errors.push({
|
|
249
|
+
code: "WRITE_SCOPE_REQUIRED",
|
|
250
|
+
taskId: task.taskId,
|
|
251
|
+
message: "Write tasks must declare at least one write scope pattern."
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
if (task.writeScope.some((pattern) => !normalizeSwarmPathPattern(pattern).ok)) {
|
|
255
|
+
errors.push({
|
|
256
|
+
code: "INVALID_WRITE_SCOPE",
|
|
257
|
+
taskId: task.taskId,
|
|
258
|
+
message: "Task write scope contains an unsafe or malformed repository path pattern."
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
for (const task of tasks) {
|
|
263
|
+
for (const dependencyId of task.dependsOn) {
|
|
264
|
+
if (dependencyId === task.taskId) {
|
|
265
|
+
errors.push({
|
|
266
|
+
code: "SELF_DEPENDENCY",
|
|
267
|
+
taskId: task.taskId,
|
|
268
|
+
message: `Task ${task.taskId} cannot depend on itself.`
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
else if (!byId.has(dependencyId)) {
|
|
272
|
+
errors.push({
|
|
273
|
+
code: "MISSING_DEPENDENCY",
|
|
274
|
+
taskId: task.taskId,
|
|
275
|
+
message: `Task ${task.taskId} depends on missing task ${dependencyId}.`
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
if (!errors.some((error) => error.code === "DUPLICATE_TASK_ID" || error.code === "MISSING_DEPENDENCY")) {
|
|
281
|
+
const visiting = new Set();
|
|
282
|
+
const visited = new Set();
|
|
283
|
+
const visit = (taskId) => {
|
|
284
|
+
if (visiting.has(taskId))
|
|
285
|
+
return true;
|
|
286
|
+
if (visited.has(taskId))
|
|
287
|
+
return false;
|
|
288
|
+
visiting.add(taskId);
|
|
289
|
+
for (const dependencyId of byId.get(taskId)?.dependsOn ?? []) {
|
|
290
|
+
if (visit(dependencyId))
|
|
291
|
+
return true;
|
|
292
|
+
}
|
|
293
|
+
visiting.delete(taskId);
|
|
294
|
+
visited.add(taskId);
|
|
295
|
+
return false;
|
|
296
|
+
};
|
|
297
|
+
for (const taskId of [...byId.keys()].sort()) {
|
|
298
|
+
if (visit(taskId)) {
|
|
299
|
+
errors.push({
|
|
300
|
+
code: "TASK_CYCLE",
|
|
301
|
+
taskId,
|
|
302
|
+
message: `Task graph contains a dependency cycle involving ${taskId}.`
|
|
303
|
+
});
|
|
304
|
+
break;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return errors.length === 0 ? { ok: true, errors: [] } : { ok: false, errors };
|
|
309
|
+
}
|
|
310
|
+
export function selectNextSwarmBatch(input) {
|
|
311
|
+
const graph = validateSwarmTaskGraph(input.tasks);
|
|
312
|
+
const errors = [...graph.errors];
|
|
313
|
+
if (!hasText(input.swarmId)) {
|
|
314
|
+
errors.push({
|
|
315
|
+
code: "INVALID_SWARM_ID",
|
|
316
|
+
message: "Active swarmId is required for scheduler authority binding."
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
if (!Number.isInteger(input.maxConcurrency) || input.maxConcurrency <= 0) {
|
|
320
|
+
errors.push({
|
|
321
|
+
code: "INVALID_CONCURRENCY",
|
|
322
|
+
message: "maxConcurrency must be a positive integer."
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
if (!Number.isInteger(input.parentContract.maxConcurrency) || input.parentContract.maxConcurrency <= 0) {
|
|
326
|
+
errors.push({
|
|
327
|
+
code: "INVALID_PARENT_CONCURRENCY",
|
|
328
|
+
message: "Parent maxConcurrency must be a positive integer."
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
const waiverValidation = validateDependencyWaiverCapabilities(input.swarmId, input.tasks, input.parentContract, input.dependencyWaiverRegistry, input.dependencyWaiverCapabilities ?? []);
|
|
332
|
+
errors.push(...waiverValidation.errors);
|
|
333
|
+
if (errors.length > 0)
|
|
334
|
+
return { ok: false, tasks: [], errors };
|
|
335
|
+
const byId = new Map(input.tasks.map((task) => [task.taskId, task]));
|
|
336
|
+
const running = input.tasks.filter((task) => task.status === "running");
|
|
337
|
+
if (running.length > input.parentContract.maxConcurrency) {
|
|
338
|
+
return {
|
|
339
|
+
ok: false,
|
|
340
|
+
tasks: [],
|
|
341
|
+
errors: [{
|
|
342
|
+
code: "PARENT_CONCURRENCY_EXCEEDED",
|
|
343
|
+
message: "Active work already exceeds the parent concurrency cap."
|
|
344
|
+
}]
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
const effectiveConcurrency = Math.min(input.maxConcurrency, input.parentContract.maxConcurrency);
|
|
348
|
+
const availableSlots = Math.max(effectiveConcurrency - running.length, 0);
|
|
349
|
+
if (availableSlots === 0)
|
|
350
|
+
return { ok: true, tasks: [], errors: [] };
|
|
351
|
+
const ready = input.tasks
|
|
352
|
+
.filter((task) => task.status === "queued" || task.status === "ready")
|
|
353
|
+
.filter((task) => task.dependsOn.every((dependencyId) => {
|
|
354
|
+
if (waiverValidation.evidence.some((waiver) => waiver.taskId === task.taskId
|
|
355
|
+
&& waiver.dependencyTaskId === dependencyId
|
|
356
|
+
&& waiver.parentPolicyVersion === input.parentContract.policyVersion))
|
|
357
|
+
return true;
|
|
358
|
+
const dependency = byId.get(dependencyId);
|
|
359
|
+
return dependency?.status === "accepted";
|
|
360
|
+
}))
|
|
361
|
+
.sort((left, right) => left.taskId.localeCompare(right.taskId));
|
|
362
|
+
const admitted = [];
|
|
363
|
+
const activeMutations = running.filter(isMutatingTask);
|
|
364
|
+
for (const candidate of ready) {
|
|
365
|
+
if (admitted.length >= availableSlots)
|
|
366
|
+
break;
|
|
367
|
+
if (isMutatingTask(candidate)
|
|
368
|
+
&& [...activeMutations, ...admitted.filter(isMutatingTask)].some((active) => tasksHaveScopeCollision(candidate, active))) {
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
admitted.push(candidate);
|
|
372
|
+
}
|
|
373
|
+
return { ok: true, tasks: admitted, errors: [] };
|
|
374
|
+
}
|
|
375
|
+
export function tasksHaveScopeCollision(left, right) {
|
|
376
|
+
if (!isMutatingTask(left) || !isMutatingTask(right))
|
|
377
|
+
return false;
|
|
378
|
+
return left.writeScope.some((leftPath) => right.writeScope.some((rightPath) => swarmPathPatternsOverlap(leftPath, rightPath)));
|
|
379
|
+
}
|
|
380
|
+
function isMutatingTask(task) {
|
|
381
|
+
return task.mutationMode === "write";
|
|
382
|
+
}
|
|
383
|
+
function activeReservations(ledger) {
|
|
384
|
+
return ledger.leases.reduce((total, lease) => lease.status === "reserved"
|
|
385
|
+
? { usd: total.usd + lease.reservedUsd, tokens: total.tokens + lease.reservedTokens }
|
|
386
|
+
: total, { usd: 0, tokens: 0 });
|
|
387
|
+
}
|
|
388
|
+
function budgetError(ledger, code, message) {
|
|
389
|
+
return {
|
|
390
|
+
ok: false,
|
|
391
|
+
ledger: cloneLedger(ledger),
|
|
392
|
+
errors: [{ code, message }]
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
function cloneLedger(ledger) {
|
|
396
|
+
return { ...ledger, leases: ledger.leases.map(cloneLease) };
|
|
397
|
+
}
|
|
398
|
+
function cloneLease(lease) {
|
|
399
|
+
return {
|
|
400
|
+
...lease,
|
|
401
|
+
...(lease.actualUsage ? { actualUsage: { ...lease.actualUsage } } : {})
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
function isNonnegativeFinite(value) {
|
|
405
|
+
return Number.isFinite(value) && value >= 0;
|
|
406
|
+
}
|
|
407
|
+
function validateBudgetLedger(ledger) {
|
|
408
|
+
if (!isNonnegativeFinite(ledger.capUsd)
|
|
409
|
+
|| (ledger.capTokens !== undefined && !isNonnegativeInteger(ledger.capTokens))
|
|
410
|
+
|| !isNonnegativeFinite(ledger.settledUsd)
|
|
411
|
+
|| !isNonnegativeInteger(ledger.settledTokens)) {
|
|
412
|
+
return false;
|
|
413
|
+
}
|
|
414
|
+
const ids = new Set();
|
|
415
|
+
let settledUsd = 0;
|
|
416
|
+
let settledTokens = 0;
|
|
417
|
+
let reservedUsd = 0;
|
|
418
|
+
let reservedTokens = 0;
|
|
419
|
+
let hasOverspentLease = false;
|
|
420
|
+
for (const lease of ledger.leases) {
|
|
421
|
+
if (!hasText(lease.leaseId) || ids.has(lease.leaseId) || !hasText(lease.agentId) || !hasText(lease.taskId)) {
|
|
422
|
+
return false;
|
|
423
|
+
}
|
|
424
|
+
ids.add(lease.leaseId);
|
|
425
|
+
if (!["reserved", "settled", "overspent", "released"].includes(lease.status))
|
|
426
|
+
return false;
|
|
427
|
+
if (!isNonnegativeFinite(lease.reservedUsd) || !isNonnegativeInteger(lease.reservedTokens))
|
|
428
|
+
return false;
|
|
429
|
+
if (lease.status === "settled" || lease.status === "overspent") {
|
|
430
|
+
if (!lease.actualUsage
|
|
431
|
+
|| !isNonnegativeFinite(lease.actualUsage.usd)
|
|
432
|
+
|| !isNonnegativeInteger(lease.actualUsage.tokens)) {
|
|
433
|
+
return false;
|
|
434
|
+
}
|
|
435
|
+
const exceededReservation = lease.actualUsage.usd > lease.reservedUsd
|
|
436
|
+
|| lease.actualUsage.tokens > lease.reservedTokens;
|
|
437
|
+
if ((lease.status === "settled" && exceededReservation)
|
|
438
|
+
|| (lease.status === "overspent" && !exceededReservation)) {
|
|
439
|
+
return false;
|
|
440
|
+
}
|
|
441
|
+
settledUsd += lease.actualUsage.usd;
|
|
442
|
+
settledTokens += lease.actualUsage.tokens;
|
|
443
|
+
if (lease.status === "overspent")
|
|
444
|
+
hasOverspentLease = true;
|
|
445
|
+
}
|
|
446
|
+
else if (lease.actualUsage !== undefined) {
|
|
447
|
+
return false;
|
|
448
|
+
}
|
|
449
|
+
if (lease.status === "reserved") {
|
|
450
|
+
reservedUsd += lease.reservedUsd;
|
|
451
|
+
reservedTokens += lease.reservedTokens;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
return nearlyEqual(ledger.settledUsd, settledUsd)
|
|
455
|
+
&& ledger.settledTokens === settledTokens
|
|
456
|
+
// Once a provider reports an overage, the spend is irreversible. Keep the
|
|
457
|
+
// ledger structurally valid so every already-running lease can settle its
|
|
458
|
+
// actual usage and terminal evidence can seal. Admission remains closed:
|
|
459
|
+
// reserveSwarmBudgetLease still rejects any further reservation whose
|
|
460
|
+
// settled + reserved total exceeds the hard cap.
|
|
461
|
+
&& (hasOverspentLease || settledUsd + reservedUsd <= ledger.capUsd)
|
|
462
|
+
&& (ledger.capTokens === undefined
|
|
463
|
+
|| hasOverspentLease
|
|
464
|
+
|| settledTokens + reservedTokens <= ledger.capTokens);
|
|
465
|
+
}
|
|
466
|
+
function isNonnegativeInteger(value) {
|
|
467
|
+
return Number.isInteger(value) && value >= 0;
|
|
468
|
+
}
|
|
469
|
+
function nearlyEqual(left, right) {
|
|
470
|
+
return Math.abs(left - right) <= Number.EPSILON * Math.max(1, Math.abs(left), Math.abs(right));
|
|
471
|
+
}
|
|
472
|
+
function hasText(value) {
|
|
473
|
+
return value.trim().length > 0;
|
|
474
|
+
}
|
|
475
|
+
function validateDependencyWaiverCapabilities(activeSwarmId, tasks, parent, registry, capabilities) {
|
|
476
|
+
if (capabilities.length === 0)
|
|
477
|
+
return { errors: [], evidence: [] };
|
|
478
|
+
if (!parent.recoveryPolicy.dependencyWaiversAllowed) {
|
|
479
|
+
return {
|
|
480
|
+
errors: [{
|
|
481
|
+
code: "DEPENDENCY_WAIVERS_DISABLED",
|
|
482
|
+
message: "Parent recovery policy does not allow dependency waivers."
|
|
483
|
+
}],
|
|
484
|
+
evidence: []
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
const registryState = registry ? parentWaiverRegistries.get(registry) : undefined;
|
|
488
|
+
if (!registry || !registryState) {
|
|
489
|
+
return {
|
|
490
|
+
errors: [{
|
|
491
|
+
code: "UNISSUED_DEPENDENCY_WAIVER",
|
|
492
|
+
message: "Dependency waiver capabilities require an internally issued parent registry."
|
|
493
|
+
}],
|
|
494
|
+
evidence: []
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
if (registryState.swarmId !== activeSwarmId) {
|
|
498
|
+
return {
|
|
499
|
+
errors: [{
|
|
500
|
+
code: "WAIVER_SWARM_MISMATCH",
|
|
501
|
+
message: "Parent waiver registry is not bound to the active swarm."
|
|
502
|
+
}],
|
|
503
|
+
evidence: []
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
if (registryState.parentPolicyVersion !== parent.policyVersion) {
|
|
507
|
+
return {
|
|
508
|
+
errors: [{
|
|
509
|
+
code: "WAIVER_POLICY_VERSION_MISMATCH",
|
|
510
|
+
message: "Parent waiver registry is not bound to the active policy version."
|
|
511
|
+
}],
|
|
512
|
+
evidence: []
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
const byId = new Map(tasks.map((task) => [task.taskId, task]));
|
|
516
|
+
const errors = [];
|
|
517
|
+
const evidence = [];
|
|
518
|
+
for (const capability of capabilities) {
|
|
519
|
+
const issued = isObject(capability) ? issuedWaiverCapabilities.get(capability) : undefined;
|
|
520
|
+
if (!issued || issued.registry !== registry) {
|
|
521
|
+
errors.push({
|
|
522
|
+
code: "UNISSUED_DEPENDENCY_WAIVER",
|
|
523
|
+
message: "A structurally valid object is not an internally issued waiver capability."
|
|
524
|
+
});
|
|
525
|
+
continue;
|
|
526
|
+
}
|
|
527
|
+
const waiver = issued.evidence;
|
|
528
|
+
if (waiver.parentPolicyVersion !== parent.policyVersion) {
|
|
529
|
+
errors.push({
|
|
530
|
+
code: "WAIVER_POLICY_VERSION_MISMATCH",
|
|
531
|
+
taskId: waiver.taskId,
|
|
532
|
+
message: "Dependency waiver is not bound to the active parent policy version."
|
|
533
|
+
});
|
|
534
|
+
continue;
|
|
535
|
+
}
|
|
536
|
+
const task = byId.get(waiver.taskId);
|
|
537
|
+
if (!task
|
|
538
|
+
|| !task.dependsOn.includes(waiver.dependencyTaskId)
|
|
539
|
+
|| !hasText(waiver.approvedBy)
|
|
540
|
+
|| !hasText(waiver.approvedAt)
|
|
541
|
+
|| Number.isNaN(Date.parse(waiver.approvedAt))) {
|
|
542
|
+
errors.push({
|
|
543
|
+
code: "INVALID_DEPENDENCY_WAIVER",
|
|
544
|
+
taskId: waiver.taskId,
|
|
545
|
+
message: "Dependency waiver must bind an existing dependency and include approval identity and time."
|
|
546
|
+
});
|
|
547
|
+
continue;
|
|
548
|
+
}
|
|
549
|
+
evidence.push({ ...waiver });
|
|
550
|
+
}
|
|
551
|
+
return { errors, evidence: errors.length === 0 ? evidence : [] };
|
|
552
|
+
}
|
|
553
|
+
function isObject(value) {
|
|
554
|
+
return (typeof value === "object" && value !== null) || typeof value === "function";
|
|
555
|
+
}
|