@treeseed/sdk 0.8.11 → 0.8.13

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/capacity.js CHANGED
@@ -1,6 +1,23 @@
1
+ const DEFAULT_EXECUTION_PROFILE_ID = "standard-code-model";
1
2
  function finiteNumber(value) {
2
3
  return typeof value === "number" && Number.isFinite(value) ? value : null;
3
4
  }
5
+ function finiteOrParsedNumber(value) {
6
+ if (typeof value === "number" && Number.isFinite(value)) return value;
7
+ if (typeof value === "string" && value.trim()) {
8
+ const parsed = Number.parseFloat(value);
9
+ return Number.isFinite(parsed) ? parsed : null;
10
+ }
11
+ return null;
12
+ }
13
+ function positiveNumber(value, fallback) {
14
+ const parsed = finiteNumber(value);
15
+ return parsed !== null && parsed > 0 ? parsed : fallback;
16
+ }
17
+ function nonNegativeNumber(value, fallback) {
18
+ const parsed = finiteNumber(value);
19
+ return parsed !== null && parsed >= 0 ? parsed : fallback;
20
+ }
4
21
  function scarcityPenalty(level) {
5
22
  if (level === "high") return 35;
6
23
  if (level === "medium") return 15;
@@ -19,6 +36,601 @@ function booleanValue(value) {
19
36
  function numberValue(value) {
20
37
  return typeof value === "number" && Number.isFinite(value) ? value : null;
21
38
  }
39
+ function profileIdFrom(input) {
40
+ if (!input) return DEFAULT_EXECUTION_PROFILE_ID;
41
+ if (typeof input === "string") return input.trim() || DEFAULT_EXECUTION_PROFILE_ID;
42
+ return input.id?.trim() || DEFAULT_EXECUTION_PROFILE_ID;
43
+ }
44
+ function metadataFlag(metadata, key) {
45
+ return metadata?.[key] === true || metadata?.[key] === "true";
46
+ }
47
+ function numericActuals(values) {
48
+ return values.map((value) => finiteOrParsedNumber(value)).filter((value) => value !== null && value >= 0).sort((left, right) => left - right);
49
+ }
50
+ function estimateLearningPercentile(values, percentile) {
51
+ const sorted = numericActuals(values);
52
+ if (sorted.length === 0) return null;
53
+ const bounded = Math.min(100, Math.max(0, percentile));
54
+ const index = Math.ceil(bounded / 100 * sorted.length) - 1;
55
+ return sorted[Math.min(sorted.length - 1, Math.max(0, index))] ?? null;
56
+ }
57
+ function estimateLearningVariance(values) {
58
+ const samples = numericActuals(values);
59
+ if (samples.length <= 1) return 0;
60
+ const mean = samples.reduce((total, value) => total + value, 0) / samples.length;
61
+ return samples.reduce((total, value) => total + (value - mean) ** 2, 0) / samples.length;
62
+ }
63
+ function isInterruptedUsageActual(actual) {
64
+ const metadata = actual.metadata ?? {};
65
+ return metadataFlag(metadata, "interrupted") || metadataFlag(metadata, "partial");
66
+ }
67
+ function estimateProfileConfidenceScore(input) {
68
+ const sampleCount = Math.max(0, Math.floor(finiteOrParsedNumber(input.sampleCount) ?? 0));
69
+ const sampleScore = Math.min(1, sampleCount / 20);
70
+ const p50 = Math.max(1, finiteOrParsedNumber(input.creditsP50) ?? 1);
71
+ const variance = Math.max(0, finiteOrParsedNumber(input.creditsVariance) ?? 0);
72
+ const varianceScore = 1 / (1 + Math.sqrt(variance) / p50);
73
+ let ageScore = 1;
74
+ if (input.lastSampleAt) {
75
+ const now = input.now instanceof Date ? input.now : new Date(input.now ?? Date.now());
76
+ const last = new Date(input.lastSampleAt);
77
+ if (Number.isFinite(last.valueOf()) && Number.isFinite(now.valueOf())) {
78
+ const days = Math.max(0, (now.valueOf() - last.valueOf()) / 864e5);
79
+ ageScore = days > 90 ? 0.35 : days > 30 ? 0.7 : 1;
80
+ }
81
+ }
82
+ return Math.max(0, Math.min(1, sampleScore * varianceScore * ageScore));
83
+ }
84
+ function estimateConfidenceFromProfile(profile, now) {
85
+ if (!profile) return "medium";
86
+ const confidenceScore = finiteOrParsedNumber(profile.confidenceScore) ?? estimateProfileConfidenceScore({
87
+ sampleCount: profile.completedSampleCount ?? profile.sampleCount,
88
+ creditsVariance: profile.creditsVariance,
89
+ creditsP50: profile.creditsP50,
90
+ lastSampleAt: profile.lastSampleAt ?? profile.updatedAt,
91
+ now
92
+ });
93
+ if (confidenceScore >= 0.75) return "high";
94
+ if (confidenceScore >= 0.35) return "medium";
95
+ return "low";
96
+ }
97
+ function selectTaskEstimateProfile(input) {
98
+ const taskSignature = input.taskSignature?.trim();
99
+ if (!taskSignature) return null;
100
+ const executionProfileId = input.executionProfileId?.trim() || profileIdFrom(input.executionProfile);
101
+ const profiles = input.profiles ?? [];
102
+ return profiles.find(
103
+ (profile) => profile.taskSignature === taskSignature && (profile.executionProfileId || DEFAULT_EXECUTION_PROFILE_ID) === executionProfileId
104
+ ) ?? profiles.find(
105
+ (profile) => profile.taskSignature === taskSignature && (profile.executionProfileId || DEFAULT_EXECUTION_PROFILE_ID) === DEFAULT_EXECUTION_PROFILE_ID
106
+ ) ?? null;
107
+ }
108
+ function buildTaskEstimateProfileFromActuals(input) {
109
+ const taskSignature = input.taskSignature;
110
+ const executionProfileId = input.executionProfileId?.trim() || DEFAULT_EXECUTION_PROFILE_ID;
111
+ const matching = input.actuals.filter(
112
+ (actual) => actual.taskSignature === taskSignature && (actual.executionProfileId || DEFAULT_EXECUTION_PROFILE_ID) === executionProfileId
113
+ );
114
+ const completed = matching.filter((actual) => !isInterruptedUsageActual(actual));
115
+ const interrupted = matching.filter((actual) => isInterruptedUsageActual(actual));
116
+ const credits = completed.map((actual) => actual.actualCredits);
117
+ const creditsP50 = estimateLearningPercentile(credits, 50);
118
+ const creditsP90 = estimateLearningPercentile(credits, 90);
119
+ const creditsVariance = estimateLearningVariance(credits);
120
+ const outlierLimit = creditsP90 === null ? null : Math.max(creditsP90 * 1.5, (creditsP50 ?? creditsP90) + Math.sqrt(creditsVariance));
121
+ const lastCreatedAt = matching.map((actual) => actual.createdAt).filter((value) => typeof value === "string" && value.length > 0).sort();
122
+ const partialCredits = interrupted.reduce((total, actual) => total + Math.max(0, finiteOrParsedNumber(actual.actualCredits) ?? 0), 0);
123
+ const updatedAt = input.now instanceof Date ? input.now.toISOString() : typeof input.now === "string" ? input.now : (/* @__PURE__ */ new Date()).toISOString();
124
+ return {
125
+ taskSignature,
126
+ executionProfileId,
127
+ sampleCount: matching.length,
128
+ completedSampleCount: completed.length,
129
+ interruptedSampleCount: interrupted.length,
130
+ inputTokensP50: estimateLearningPercentile(completed.map((actual) => actual.inputTokens), 50),
131
+ inputTokensP90: estimateLearningPercentile(completed.map((actual) => actual.inputTokens), 90),
132
+ outputTokensP50: estimateLearningPercentile(completed.map((actual) => actual.outputTokens), 50),
133
+ outputTokensP90: estimateLearningPercentile(completed.map((actual) => actual.outputTokens), 90),
134
+ quotaMinutesP50: estimateLearningPercentile(completed.map((actual) => actual.quotaMinutes), 50),
135
+ quotaMinutesP90: estimateLearningPercentile(completed.map((actual) => actual.quotaMinutes), 90),
136
+ filesChangedP50: estimateLearningPercentile(completed.map((actual) => actual.filesChanged), 50),
137
+ filesChangedP90: estimateLearningPercentile(completed.map((actual) => actual.filesChanged), 90),
138
+ creditsP50,
139
+ creditsP90,
140
+ creditsVariance,
141
+ confidenceScore: estimateProfileConfidenceScore({
142
+ sampleCount: completed.length,
143
+ creditsVariance,
144
+ creditsP50,
145
+ lastSampleAt: lastCreatedAt.at(-1) ?? null,
146
+ now: input.now
147
+ }),
148
+ outlierCount: outlierLimit === null ? 0 : credits.filter((value) => value > outlierLimit).length,
149
+ partialCredits,
150
+ firstSampleAt: lastCreatedAt[0] ?? null,
151
+ lastSampleAt: lastCreatedAt.at(-1) ?? null,
152
+ updatedAt
153
+ };
154
+ }
155
+ const DEFAULT_EXECUTION_PROFILES = {
156
+ "local-runner": {
157
+ id: "local-runner",
158
+ modelClass: "local",
159
+ qualityWeight: 1,
160
+ costMultiplier: 1,
161
+ latencyClass: "low",
162
+ concurrencyClass: "read_only",
163
+ quotaBehavior: "compute_bound"
164
+ },
165
+ "local-fast-model": {
166
+ id: "local-fast-model",
167
+ modelClass: "local",
168
+ qualityWeight: 0.65,
169
+ costMultiplier: 0.35,
170
+ latencyClass: "low",
171
+ concurrencyClass: "read_only",
172
+ quotaBehavior: "compute_bound"
173
+ },
174
+ "small-code-model": {
175
+ id: "small-code-model",
176
+ modelClass: "coding",
177
+ qualityWeight: 0.75,
178
+ costMultiplier: 0.5,
179
+ latencyClass: "low",
180
+ concurrencyClass: "repository_claim",
181
+ quotaBehavior: "api_metered"
182
+ },
183
+ "standard-code-model": {
184
+ id: "standard-code-model",
185
+ modelClass: "coding",
186
+ qualityWeight: 1,
187
+ costMultiplier: 1,
188
+ latencyClass: "medium",
189
+ concurrencyClass: "repository_claim",
190
+ quotaBehavior: "api_metered"
191
+ },
192
+ "large-reasoning-model": {
193
+ id: "large-reasoning-model",
194
+ modelClass: "reasoning",
195
+ qualityWeight: 1.5,
196
+ costMultiplier: 3,
197
+ latencyClass: "high",
198
+ concurrencyClass: "exclusive_project",
199
+ quotaBehavior: "api_metered"
200
+ },
201
+ "long-context-architect": {
202
+ id: "long-context-architect",
203
+ modelClass: "reasoning",
204
+ contextWindowTokens: 2e5,
205
+ qualityWeight: 1.75,
206
+ costMultiplier: 4,
207
+ latencyClass: "high",
208
+ concurrencyClass: "exclusive_project",
209
+ quotaBehavior: "api_metered"
210
+ },
211
+ "cheap-review-model": {
212
+ id: "cheap-review-model",
213
+ modelClass: "review",
214
+ qualityWeight: 0.8,
215
+ costMultiplier: 0.6,
216
+ latencyClass: "low",
217
+ concurrencyClass: "read_only",
218
+ quotaBehavior: "api_metered"
219
+ },
220
+ "human-review": {
221
+ id: "human-review",
222
+ modelClass: "human",
223
+ qualityWeight: 2,
224
+ costMultiplier: 10,
225
+ latencyClass: "high",
226
+ concurrencyClass: "human_attention",
227
+ quotaBehavior: "attention_bound"
228
+ }
229
+ };
230
+ const DEFAULT_TASK_ADMISSION_POLICY = {
231
+ planningThresholdCredits: 20,
232
+ approvalThresholdCredits: 50,
233
+ reserveBufferPercent: 15,
234
+ recoveryBudgetCredits: 0,
235
+ maxDownstreamTasks: 4,
236
+ maxPlanningDepth: 2,
237
+ maxAdmittedPlanTasksPerCycle: 4,
238
+ planningTaskSignature: "planner.dag_proposal",
239
+ allowBackfill: true,
240
+ maxAttentionLoad: null,
241
+ reserveAttentionPercent: 0,
242
+ maxContextTokens: null,
243
+ maxContextSaturationPercent: 100,
244
+ coordinationOverheadFactor: 1,
245
+ predictiveReservePolicy: null,
246
+ utilityPolicy: null
247
+ };
248
+ function normalizeExecutionProfile(input) {
249
+ if (!input) return DEFAULT_EXECUTION_PROFILES["standard-code-model"];
250
+ if (typeof input === "string") {
251
+ return DEFAULT_EXECUTION_PROFILES[input] ?? {
252
+ id: input,
253
+ qualityWeight: 1,
254
+ costMultiplier: 1,
255
+ latencyClass: "medium",
256
+ metadata: { source: "ad_hoc" }
257
+ };
258
+ }
259
+ return {
260
+ ...input,
261
+ qualityWeight: positiveNumber(input.qualityWeight, 1),
262
+ costMultiplier: positiveNumber(input.costMultiplier, 1),
263
+ latencyClass: input.latencyClass || "medium"
264
+ };
265
+ }
266
+ function normalizeTaskAdmissionPolicy(input = {}) {
267
+ const reserveBufferPercent = nonNegativeNumber(input?.reserveBufferPercent, DEFAULT_TASK_ADMISSION_POLICY.reserveBufferPercent);
268
+ return {
269
+ ...DEFAULT_TASK_ADMISSION_POLICY,
270
+ ...input ?? {},
271
+ planningThresholdCredits: positiveNumber(input?.planningThresholdCredits, DEFAULT_TASK_ADMISSION_POLICY.planningThresholdCredits),
272
+ approvalThresholdCredits: positiveNumber(input?.approvalThresholdCredits, DEFAULT_TASK_ADMISSION_POLICY.approvalThresholdCredits),
273
+ reserveBufferPercent: Math.min(100, reserveBufferPercent),
274
+ recoveryBudgetCredits: nonNegativeNumber(input?.recoveryBudgetCredits, DEFAULT_TASK_ADMISSION_POLICY.recoveryBudgetCredits),
275
+ maxDownstreamTasks: Math.max(0, Math.floor(nonNegativeNumber(input?.maxDownstreamTasks, DEFAULT_TASK_ADMISSION_POLICY.maxDownstreamTasks))),
276
+ maxPlanningDepth: Math.max(0, Math.floor(nonNegativeNumber(input?.maxPlanningDepth, DEFAULT_TASK_ADMISSION_POLICY.maxPlanningDepth))),
277
+ maxAdmittedPlanTasksPerCycle: Math.max(1, Math.floor(nonNegativeNumber(input?.maxAdmittedPlanTasksPerCycle, DEFAULT_TASK_ADMISSION_POLICY.maxAdmittedPlanTasksPerCycle))),
278
+ planningTaskSignature: typeof input?.planningTaskSignature === "string" && input.planningTaskSignature.trim() ? input.planningTaskSignature.trim() : DEFAULT_TASK_ADMISSION_POLICY.planningTaskSignature,
279
+ allowBackfill: input?.allowBackfill ?? DEFAULT_TASK_ADMISSION_POLICY.allowBackfill,
280
+ maxAttentionLoad: finiteOrParsedNumber(input?.maxAttentionLoad) ?? DEFAULT_TASK_ADMISSION_POLICY.maxAttentionLoad,
281
+ reserveAttentionPercent: Math.min(100, nonNegativeNumber(input?.reserveAttentionPercent, DEFAULT_TASK_ADMISSION_POLICY.reserveAttentionPercent ?? 0)),
282
+ maxContextTokens: finiteOrParsedNumber(input?.maxContextTokens) ?? DEFAULT_TASK_ADMISSION_POLICY.maxContextTokens,
283
+ maxContextSaturationPercent: Math.min(100, positiveNumber(input?.maxContextSaturationPercent, DEFAULT_TASK_ADMISSION_POLICY.maxContextSaturationPercent ?? 100)),
284
+ coordinationOverheadFactor: nonNegativeNumber(input?.coordinationOverheadFactor, DEFAULT_TASK_ADMISSION_POLICY.coordinationOverheadFactor ?? 1)
285
+ };
286
+ }
287
+ function normalizeAttentionPolicy(input = {}) {
288
+ return {
289
+ maxAttentionLoad: finiteOrParsedNumber(input?.maxAttentionLoad) ?? null,
290
+ reserveAttentionPercent: Math.min(100, nonNegativeNumber(input?.reserveAttentionPercent, 0)),
291
+ maxContextTokens: finiteOrParsedNumber(input?.maxContextTokens) ?? null,
292
+ maxContextSaturationPercent: Math.min(100, positiveNumber(input?.maxContextSaturationPercent, 100)),
293
+ coordinationOverheadFactor: nonNegativeNumber(input?.coordinationOverheadFactor, 1)
294
+ };
295
+ }
296
+ function normalizeUtilityPolicy(input = {}) {
297
+ return {
298
+ minimumUtilityScore: finiteOrParsedNumber(input?.minimumUtilityScore) ?? null,
299
+ minimumUtilityPerCredit: finiteOrParsedNumber(input?.minimumUtilityPerCredit) ?? null,
300
+ riskPenaltyFactor: nonNegativeNumber(input?.riskPenaltyFactor, 1),
301
+ deadlineWindowHours: positiveNumber(input?.deadlineWindowHours, 72),
302
+ maintenanceWeight: nonNegativeNumber(input?.maintenanceWeight, 1),
303
+ priorityWeight: nonNegativeNumber(input?.priorityWeight, 1)
304
+ };
305
+ }
306
+ function normalizePredictiveReservePolicy(input = {}) {
307
+ const raw = readRecord(input);
308
+ return {
309
+ enabled: raw.enabled === true || raw.enabled === "true",
310
+ baseReservePercent: Math.min(100, nonNegativeNumber(input?.baseReservePercent, 0)),
311
+ maxReservePercent: Math.min(100, positiveNumber(input?.maxReservePercent, 50)),
312
+ incidentReservePercent: Math.min(100, nonNegativeNumber(input?.incidentReservePercent, 15)),
313
+ triggerBurstReservePercent: Math.min(100, nonNegativeNumber(input?.triggerBurstReservePercent, 10)),
314
+ deploymentWindowReservePercent: Math.min(100, nonNegativeNumber(input?.deploymentWindowReservePercent, 10)),
315
+ providerDegradationReservePercent: Math.min(100, nonNegativeNumber(input?.providerDegradationReservePercent, 10)),
316
+ quotaPressureReservePercent: Math.min(100, nonNegativeNumber(input?.quotaPressureReservePercent, 10))
317
+ };
318
+ }
319
+ function isoHoursUntil(value, now) {
320
+ if (typeof value !== "string" || !value.trim()) return null;
321
+ const target = new Date(value);
322
+ if (!Number.isFinite(target.valueOf())) return null;
323
+ return (target.valueOf() - now.valueOf()) / 36e5;
324
+ }
325
+ function riskPenalty(classification, policy) {
326
+ if (classification?.risk === "high") return 20 * policy.riskPenaltyFactor;
327
+ if (classification?.risk === "medium") return 8 * policy.riskPenaltyFactor;
328
+ return 0;
329
+ }
330
+ function qualityScoreFromProfile(profile, confidence) {
331
+ const confidenceWeight = confidence === "high" ? 1.1 : confidence === "low" ? 0.75 : 1;
332
+ return Math.max(0, profile.qualityWeight * confidenceWeight);
333
+ }
334
+ function estimateUtilityForTask(input) {
335
+ const policy = normalizeUtilityPolicy(input.utilityPolicy);
336
+ const profile = normalizeExecutionProfile(input.executionProfile);
337
+ const metadata = readRecord(input.metadata);
338
+ const priority = Math.max(0, finiteOrParsedNumber(input.priority) ?? finiteOrParsedNumber(metadata.priority) ?? 0);
339
+ const explicitUtility = finiteOrParsedNumber(input.utilityValue) ?? finiteOrParsedNumber(metadata.utilityValue);
340
+ const maintenanceValue = Math.max(0, finiteOrParsedNumber(input.maintenanceValue) ?? finiteOrParsedNumber(metadata.maintenanceValue) ?? 0);
341
+ const now = input.now instanceof Date ? input.now : new Date(input.now ?? Date.now());
342
+ const hoursUntilDeadline = isoHoursUntil(input.deadlineAt ?? metadata.deadlineAt, now);
343
+ const deadlinePressure = hoursUntilDeadline === null ? 0 : Math.max(0, Math.min(30, (policy.deadlineWindowHours - hoursUntilDeadline) / Math.max(1, policy.deadlineWindowHours) * 30));
344
+ const successProbability = Math.max(0, Math.min(1, finiteOrParsedNumber(input.successProbability) ?? finiteOrParsedNumber(metadata.successProbability) ?? 1));
345
+ const qualityScore = qualityScoreFromProfile(profile, input.classification?.confidence);
346
+ const baseUtility = explicitUtility ?? priority * policy.priorityWeight + maintenanceValue * policy.maintenanceWeight;
347
+ const risk = riskPenalty(input.classification, policy);
348
+ const utilityScore = Math.max(0, (baseUtility + deadlinePressure) * successProbability * Math.max(0.1, qualityScore) - risk);
349
+ const reservedCredits = Math.max(1, finiteOrParsedNumber(input.estimate?.reservedCredits) ?? 1);
350
+ return {
351
+ utilityValue: Math.max(0, baseUtility),
352
+ maintenanceValue,
353
+ deadlinePressure,
354
+ successProbability,
355
+ qualityScore,
356
+ riskPenalty: risk,
357
+ utilityScore,
358
+ utilityPerCredit: utilityScore / reservedCredits,
359
+ source: input.source ?? "capacity_utility_estimator",
360
+ metadata
361
+ };
362
+ }
363
+ function reserveSignalFlag(metadata, ...keys) {
364
+ return keys.some((key) => metadata[key] === true || metadata[key] === "true");
365
+ }
366
+ function predictReserveForCapacityPlan(input) {
367
+ const policy = normalizePredictiveReservePolicy(input.policy);
368
+ const metadata = readRecord(input.metadata);
369
+ const reasons = [];
370
+ const signals = {};
371
+ let reservePercent = policy.enabled ? policy.baseReservePercent : 0;
372
+ const providerDegraded = input.plan?.providers.some((provider) => provider.status === "degraded" || metadataStatus(provider.metadata) === "degraded") ?? false;
373
+ const quotaPressure = input.plan?.providers.some((provider) => {
374
+ const pressure = readRecord(provider.metadata?.pressure);
375
+ const quota = finiteOrParsedNumber(pressure.quotaRemainingPercent) ?? finiteOrParsedNumber(provider.metadata?.quotaRemainingPercent);
376
+ return quota !== null && quota < 20;
377
+ }) ?? false;
378
+ if (policy.enabled && reserveSignalFlag(metadata, "incidentLikely", "likelyIncident")) {
379
+ reservePercent += policy.incidentReservePercent;
380
+ reasons.push("incident_reserve");
381
+ signals.incidentLikely = true;
382
+ }
383
+ if (policy.enabled && reserveSignalFlag(metadata, "triggerBurstLikely", "expectedTriggerBurst")) {
384
+ reservePercent += policy.triggerBurstReservePercent;
385
+ reasons.push("trigger_burst_reserve");
386
+ signals.triggerBurstLikely = true;
387
+ }
388
+ if (policy.enabled && reserveSignalFlag(metadata, "deploymentWindow", "deploymentWindowActive")) {
389
+ reservePercent += policy.deploymentWindowReservePercent;
390
+ reasons.push("deployment_window_reserve");
391
+ signals.deploymentWindow = true;
392
+ }
393
+ if (policy.enabled && providerDegraded) {
394
+ reservePercent += policy.providerDegradationReservePercent;
395
+ reasons.push("provider_degradation_reserve");
396
+ signals.providerDegraded = true;
397
+ }
398
+ if (policy.enabled && quotaPressure) {
399
+ reservePercent += policy.quotaPressureReservePercent;
400
+ reasons.push("quota_pressure_reserve");
401
+ signals.quotaPressure = true;
402
+ }
403
+ const boundedPercent = Math.min(policy.maxReservePercent, Math.max(0, reservePercent));
404
+ const budget = Math.max(0, finiteOrParsedNumber(input.dailyCreditBudget) ?? finiteOrParsedNumber(input.remainingCredits) ?? input.plan?.remaining.dailyCredits ?? 0);
405
+ const reserveCredits = Math.ceil(budget * boundedPercent / 100);
406
+ const remaining = Math.max(0, finiteOrParsedNumber(input.remainingCredits) ?? input.plan?.remaining.dailyCredits ?? budget);
407
+ return {
408
+ reservePercent: boundedPercent,
409
+ reserveCredits,
410
+ activelyAllocatableCredits: Math.max(0, remaining - reserveCredits),
411
+ reasons,
412
+ signals
413
+ };
414
+ }
415
+ function normalizeHybridExecutionPlan(input) {
416
+ const record = readRecord(input);
417
+ const rawPhases = Array.isArray(record.phases) ? record.phases : [];
418
+ const phases = rawPhases.map((phase, index) => {
419
+ const entry = readRecord(phase);
420
+ const kind = typeof entry.kind === "string" && entry.kind.trim() ? entry.kind.trim() : `phase_${index + 1}`;
421
+ const executionProfileId = typeof entry.executionProfileId === "string" && entry.executionProfileId.trim() ? entry.executionProfileId.trim() : typeof entry.executionProfile === "string" && entry.executionProfile.trim() ? entry.executionProfile.trim() : DEFAULT_EXECUTION_PROFILE_ID;
422
+ const mutationAllowed = entry.mutationAllowed === true || kind === "implementation" && entry.mutationAllowed !== false;
423
+ return {
424
+ id: typeof entry.id === "string" && entry.id.trim() ? entry.id.trim() : kind,
425
+ kind,
426
+ executionProfileId,
427
+ taskSignature: typeof entry.taskSignature === "string" && entry.taskSignature.trim() ? entry.taskSignature.trim() : null,
428
+ required: entry.required !== false,
429
+ admissionRequired: entry.admissionRequired !== false,
430
+ mutationAllowed,
431
+ metadata: readRecord(entry.metadata)
432
+ };
433
+ });
434
+ if (phases.length === 0) return null;
435
+ return {
436
+ schemaVersion: 1,
437
+ planId: typeof record.planId === "string" && record.planId.trim() ? record.planId.trim() : "hybrid-execution-plan",
438
+ phases,
439
+ escalationPolicy: readRecord(record.escalationPolicy),
440
+ metadata: readRecord(record.metadata)
441
+ };
442
+ }
443
+ function normalizePlanningPolicy(input = {}) {
444
+ const admissionPolicy = normalizeTaskAdmissionPolicy(input);
445
+ return {
446
+ maxDownstreamTasks: admissionPolicy.maxDownstreamTasks,
447
+ maxPlanningDepth: admissionPolicy.maxPlanningDepth,
448
+ maxAdmittedPlanTasksPerCycle: admissionPolicy.maxAdmittedPlanTasksPerCycle,
449
+ planningTaskSignature: admissionPolicy.planningTaskSignature
450
+ };
451
+ }
452
+ function stablePlanId(input) {
453
+ const source = typeof input.sourceTaskId === "string" && input.sourceTaskId.trim() ? input.sourceTaskId.trim() : typeof input.parentTaskId === "string" && input.parentTaskId.trim() ? input.parentTaskId.trim() : "plan";
454
+ return `${source}:proposal`;
455
+ }
456
+ function readRecord(value) {
457
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
458
+ }
459
+ function readPlanString(value) {
460
+ return typeof value === "string" && value.trim() ? value.trim() : "";
461
+ }
462
+ function normalizePlannedTaskNode(value, index) {
463
+ const input = readRecord(value);
464
+ const payload = readRecord(input.payload);
465
+ const type = readPlanString(input.type) || readPlanString(payload.type);
466
+ if (!type) return null;
467
+ const id = readPlanString(input.id) || `node-${index + 1}`;
468
+ const p50 = finiteNumber(input.estimatedCreditsP50) ?? finiteNumber(payload.estimatedCreditsP50) ?? finiteNumber(input.estimatedCredits) ?? finiteNumber(payload.estimatedCredits) ?? null;
469
+ const p90 = finiteNumber(input.estimatedCreditsP90) ?? finiteNumber(payload.estimatedCreditsP90) ?? p50;
470
+ return {
471
+ id,
472
+ type,
473
+ agentId: readPlanString(input.agentId) || readPlanString(payload.agentId) || null,
474
+ title: readPlanString(input.title) || null,
475
+ priority: finiteNumber(input.priority) ?? finiteNumber(payload.priority),
476
+ taskSignature: readPlanString(input.taskSignature) || readPlanString(payload.taskSignature) || null,
477
+ payload,
478
+ estimatedCreditsP50: p50,
479
+ estimatedCreditsP90: p90,
480
+ risk: input.risk === "low" || input.risk === "medium" || input.risk === "high" ? input.risk : null,
481
+ mutationScope: input.mutationScope === "none" || input.mutationScope === "repository_read" || input.mutationScope === "repository_write" || input.mutationScope === "production" ? input.mutationScope : null,
482
+ confidence: input.confidence === "low" || input.confidence === "medium" || input.confidence === "high" ? input.confidence : null,
483
+ expectedFanout: finiteNumber(input.expectedFanout) ?? finiteNumber(payload.expectedFanout),
484
+ requiresApproval: typeof input.requiresApproval === "boolean" ? input.requiresApproval : null,
485
+ requiresPlanning: typeof input.requiresPlanning === "boolean" ? input.requiresPlanning : null,
486
+ dependsOn: Array.isArray(input.dependsOn) ? input.dependsOn.filter((entry) => typeof entry === "string" && entry.trim().length > 0) : [],
487
+ metadata: readRecord(input.metadata)
488
+ };
489
+ }
490
+ function synthesizePlanEstimate(tasks) {
491
+ const totalEstimatedCreditsP50 = tasks.reduce((total, task) => total + Math.max(0, Math.ceil(finiteNumber(task.estimatedCreditsP50) ?? finiteNumber(task.estimatedCreditsP90) ?? 1)), 0);
492
+ const totalEstimatedCreditsP90 = tasks.reduce((total, task) => {
493
+ const p50 = Math.max(1, Math.ceil(finiteNumber(task.estimatedCreditsP50) ?? 1));
494
+ return total + Math.max(p50, Math.ceil(finiteNumber(task.estimatedCreditsP90) ?? p50));
495
+ }, 0);
496
+ return {
497
+ totalEstimatedCreditsP50,
498
+ totalEstimatedCreditsP90
499
+ };
500
+ }
501
+ function rankPlannedTaskNodes(tasks) {
502
+ const boundedness = (task) => {
503
+ const fanout = Math.max(0, Math.floor(finiteNumber(task.expectedFanout) ?? 0));
504
+ const riskPenalty2 = task.risk === "high" ? 3 : task.risk === "medium" ? 1 : 0;
505
+ const mutationPenalty = task.mutationScope === "production" ? 4 : task.mutationScope === "repository_write" ? 2 : 0;
506
+ return fanout + riskPenalty2 + mutationPenalty;
507
+ };
508
+ return [...tasks].sort((left, right) => {
509
+ const priorityDelta = (finiteNumber(right.priority) ?? 0) - (finiteNumber(left.priority) ?? 0);
510
+ if (priorityDelta !== 0) return priorityDelta;
511
+ const boundedDelta = boundedness(left) - boundedness(right);
512
+ if (boundedDelta !== 0) return boundedDelta;
513
+ return String(left.id ?? left.type).localeCompare(String(right.id ?? right.type));
514
+ });
515
+ }
516
+ function normalizeTaskPlanProposal(input, policyInput) {
517
+ const policy = normalizePlanningPolicy(policyInput);
518
+ const record = readRecord(input);
519
+ const tasks = (Array.isArray(record.tasks) ? record.tasks : []).map((entry, index) => normalizePlannedTaskNode(entry, index)).filter((entry) => Boolean(entry));
520
+ const estimate = synthesizePlanEstimate(tasks);
521
+ const planningDepth = Math.max(0, Math.floor(finiteNumber(record.planningDepth) ?? 0));
522
+ return {
523
+ schemaVersion: 1,
524
+ planId: readPlanString(record.planId) || stablePlanId(record),
525
+ sourceTaskId: readPlanString(record.sourceTaskId) || null,
526
+ parentTaskId: readPlanString(record.parentTaskId) || null,
527
+ planningDepth,
528
+ tasks,
529
+ totalEstimatedCreditsP50: Math.max(0, Math.ceil(finiteNumber(record.totalEstimatedCreditsP50) ?? estimate.totalEstimatedCreditsP50)),
530
+ totalEstimatedCreditsP90: Math.max(0, Math.ceil(finiteNumber(record.totalEstimatedCreditsP90) ?? estimate.totalEstimatedCreditsP90)),
531
+ createdAt: readPlanString(record.createdAt) || null,
532
+ metadata: readRecord(record.metadata)
533
+ };
534
+ }
535
+ function validateTaskPlanProposal(input, policyInput) {
536
+ const policy = normalizePlanningPolicy(policyInput);
537
+ const reasons = [];
538
+ if (input.planningDepth > policy.maxPlanningDepth) {
539
+ reasons.push("planning_depth_exceeded");
540
+ }
541
+ if (input.tasks.length > policy.maxDownstreamTasks) {
542
+ reasons.push("fanout_limit_exceeded");
543
+ }
544
+ const rejected = input.tasks.map((node) => {
545
+ const nodeReasons = [];
546
+ if (!node.type) nodeReasons.push("missing_type");
547
+ if (Math.max(0, Math.floor(finiteNumber(node.expectedFanout) ?? 0)) > policy.maxDownstreamTasks) {
548
+ nodeReasons.push("node_fanout_limit_exceeded");
549
+ }
550
+ return nodeReasons.length > 0 ? { node, reasons: nodeReasons } : null;
551
+ }).filter((entry) => Boolean(entry));
552
+ return {
553
+ ok: reasons.length === 0 && rejected.length === 0,
554
+ reasons,
555
+ rejected
556
+ };
557
+ }
558
+ function progressivelyAdmitPlanProposal(input) {
559
+ const policy = normalizePlanningPolicy(input.policy);
560
+ const proposal = normalizeTaskPlanProposal(input.proposal, policy);
561
+ const validation = validateTaskPlanProposal(proposal, policy);
562
+ const reasons = [...validation.reasons];
563
+ const admitted = [];
564
+ const deferred = [];
565
+ const rejected = [...validation.rejected];
566
+ if (!validation.ok) {
567
+ return {
568
+ proposal,
569
+ admitted,
570
+ deferred: proposal.tasks.filter((node) => !rejected.some((entry) => entry.node.id === node.id)),
571
+ rejected,
572
+ totalEstimatedCreditsP50: proposal.totalEstimatedCreditsP50,
573
+ totalEstimatedCreditsP90: proposal.totalEstimatedCreditsP90,
574
+ admittedCreditsP90: 0,
575
+ reasons
576
+ };
577
+ }
578
+ let availableCredits = Math.max(0, Math.floor(nonNegativeNumber(input.availableCredits, Number.POSITIVE_INFINITY)));
579
+ let remainingQueuedCredits = Math.max(0, Math.floor(nonNegativeNumber(input.remainingQueuedCredits, Number.POSITIVE_INFINITY)));
580
+ let remainingQueuedSlots = Math.max(0, Math.floor(nonNegativeNumber(input.remainingQueuedSlots, policy.maxAdmittedPlanTasksPerCycle)));
581
+ let admittedCreditsP90 = 0;
582
+ for (const task of rankPlannedTaskNodes(proposal.tasks)) {
583
+ if (admitted.length >= policy.maxAdmittedPlanTasksPerCycle || remainingQueuedSlots <= 0) {
584
+ deferred.push(task);
585
+ reasons.push("plan_cycle_limit_reached");
586
+ continue;
587
+ }
588
+ const p50 = Math.max(1, Math.ceil(finiteNumber(task.estimatedCreditsP50) ?? 1));
589
+ const p90 = Math.max(p50, Math.ceil(finiteNumber(task.estimatedCreditsP90) ?? p50));
590
+ if (p90 > availableCredits || p90 > remainingQueuedCredits) {
591
+ deferred.push(task);
592
+ reasons.push("insufficient_plan_budget");
593
+ continue;
594
+ }
595
+ admitted.push(task);
596
+ admittedCreditsP90 += p90;
597
+ availableCredits -= p90;
598
+ remainingQueuedCredits -= p90;
599
+ remainingQueuedSlots -= 1;
600
+ }
601
+ return {
602
+ proposal,
603
+ admitted,
604
+ deferred,
605
+ rejected,
606
+ totalEstimatedCreditsP50: proposal.totalEstimatedCreditsP50,
607
+ totalEstimatedCreditsP90: proposal.totalEstimatedCreditsP90,
608
+ admittedCreditsP90,
609
+ reasons: [...new Set(reasons)]
610
+ };
611
+ }
612
+ function computeWorkdayBudgetEnvelope(input) {
613
+ const dailyCreditBudget = Math.max(0, Math.floor(nonNegativeNumber(input.dailyCreditBudget, 0)));
614
+ const usedCredits = Math.max(0, Math.ceil(nonNegativeNumber(input.usedCredits, 0)));
615
+ const queuedCredits = Math.max(0, Math.ceil(nonNegativeNumber(input.queuedCredits, 0)));
616
+ const reservePercent = Math.min(100, Math.max(0, nonNegativeNumber(input.reserveBufferPercent, DEFAULT_TASK_ADMISSION_POLICY.reserveBufferPercent)));
617
+ const reserveBufferCredits = Math.ceil(dailyCreditBudget * reservePercent / 100);
618
+ const recoveryBudgetCredits = Math.ceil(nonNegativeNumber(input.recoveryBudgetCredits, DEFAULT_TASK_ADMISSION_POLICY.recoveryBudgetCredits));
619
+ const remainingCredits = Math.max(0, dailyCreditBudget - usedCredits - queuedCredits);
620
+ const activelyAllocatableCredits = Math.max(0, dailyCreditBudget - usedCredits - queuedCredits - reserveBufferCredits - recoveryBudgetCredits);
621
+ return {
622
+ dailyCreditBudget,
623
+ usedCredits,
624
+ queuedCredits,
625
+ reserveBufferCredits,
626
+ recoveryBudgetCredits,
627
+ activelyAllocatableCredits,
628
+ remainingCredits
629
+ };
630
+ }
631
+ function mutationRequiresRepositoryClaim(scope) {
632
+ return scope === "repository_write" || scope === "production";
633
+ }
22
634
  function reservationDebit(reservation) {
23
635
  if (reservation.state === "released" || reservation.state === "expired" || reservation.state === "cancelled") {
24
636
  return 0;
@@ -98,22 +710,125 @@ function lanePolicyReasons(lane, input) {
98
710
  return reasons;
99
711
  }
100
712
  function reserveCreditsForEstimate(input) {
101
- const profileP50 = finiteNumber(input.profile?.creditsP50);
102
- const profileP90 = finiteNumber(input.profile?.creditsP90);
713
+ const executionProfile = input.executionProfile ? normalizeExecutionProfile(input.executionProfile) : input.executionProfileId ? normalizeExecutionProfile(input.executionProfileId) : null;
714
+ const selectedProfile = input.profile ?? selectTaskEstimateProfile({
715
+ profiles: input.profiles,
716
+ taskSignature: input.taskSignature ?? input.taskKind,
717
+ executionProfile,
718
+ executionProfileId: input.executionProfileId
719
+ });
720
+ const profileP50 = finiteNumber(selectedProfile?.creditsP50);
721
+ const profileP90 = finiteNumber(selectedProfile?.creditsP90);
722
+ const costMultiplier = positiveNumber(input.costMultiplier ?? executionProfile?.costMultiplier ?? 1, 1);
103
723
  const p50 = Math.max(1, Math.ceil(
104
724
  finiteNumber(input.estimatedCreditsP50) ?? profileP50 ?? finiteNumber(input.defaultCredits) ?? 1
105
725
  ));
106
726
  const p90 = Math.max(p50, Math.ceil(
107
727
  finiteNumber(input.estimatedCreditsP90) ?? profileP90 ?? p50 * 2
108
728
  ));
109
- const confidence = input.confidence ?? "medium";
110
- const reserved = confidence === "high" ? Math.max(p50, Math.ceil((p50 + p90) * 0.75)) : p90;
729
+ const profileConfidence = selectedProfile ? estimateConfidenceFromProfile(selectedProfile) : null;
730
+ const confidence = input.confidence ?? profileConfidence ?? "medium";
731
+ const baseReserved = confidence === "high" ? Math.max(p50, Math.ceil((p50 + p90) * 0.75)) : p90;
732
+ const reserved = Math.max(1, Math.ceil(baseReserved * costMultiplier));
111
733
  return {
112
734
  taskSignature: input.taskSignature ?? input.taskKind ?? "unknown",
113
735
  confidence,
114
736
  estimatedCreditsP50: p50,
115
737
  estimatedCreditsP90: p90,
116
- reservedCredits: reserved
738
+ reservedCredits: reserved,
739
+ baseReservedCredits: baseReserved,
740
+ executionProfileId: executionProfile?.id ?? input.executionProfileId ?? selectedProfile?.executionProfileId ?? null,
741
+ costMultiplier
742
+ };
743
+ }
744
+ function estimateForClassification(input) {
745
+ const classification = input.classification ?? null;
746
+ return reserveCreditsForEstimate({
747
+ ...input,
748
+ taskSignature: input.taskSignature ?? classification?.taskSignature,
749
+ confidence: input.confidence ?? classification?.confidence
750
+ });
751
+ }
752
+ function decideTaskAdmission(input) {
753
+ const policy = normalizeTaskAdmissionPolicy(input.policy);
754
+ const budget = computeWorkdayBudgetEnvelope({
755
+ ...input.budget,
756
+ reserveBufferPercent: policy.reserveBufferPercent,
757
+ recoveryBudgetCredits: policy.recoveryBudgetCredits
758
+ });
759
+ const reasons = [];
760
+ const reservedCredits = Math.max(1, Math.ceil(input.estimate.reservedCredits));
761
+ const fanout = Math.max(0, Math.floor(input.classification.expectedFanout ?? 0));
762
+ const highRisk = input.classification.risk === "high" || input.classification.mutationScope === "production";
763
+ const requiresPlanning = input.classification.requiresPlanning || fanout > policy.maxDownstreamTasks || reservedCredits >= policy.planningThresholdCredits && (input.classification.confidence === "low" || fanout > 1 || highRisk);
764
+ const requiresApproval = input.classification.requiresApproval || highRisk || reservedCredits >= policy.approvalThresholdCredits;
765
+ let outcome = "admitted";
766
+ if (!input.classification.taskSignature || input.classification.taskSignature === "unknown") {
767
+ outcome = "rejected";
768
+ reasons.push("unknown_task_signature");
769
+ }
770
+ if (fanout > policy.maxDownstreamTasks) {
771
+ reasons.push("fanout_limit_exceeded");
772
+ }
773
+ if (requiresPlanning) {
774
+ outcome = "planning_required";
775
+ reasons.push("planning_required");
776
+ }
777
+ if (requiresApproval) {
778
+ outcome = "approval_required";
779
+ reasons.push("approval_required");
780
+ }
781
+ if (outcome === "admitted" && reservedCredits > budget.activelyAllocatableCredits) {
782
+ outcome = "budget_blocked";
783
+ reasons.push("insufficient_allocatable_budget");
784
+ }
785
+ return {
786
+ outcome,
787
+ taskSignature: input.classification.taskSignature,
788
+ estimatedCreditsP50: input.estimate.estimatedCreditsP50,
789
+ estimatedCreditsP90: input.estimate.estimatedCreditsP90,
790
+ reservedCredits,
791
+ baseReservedCredits: input.estimate.baseReservedCredits,
792
+ executionProfileId: input.estimate.executionProfileId ?? null,
793
+ costMultiplier: input.estimate.costMultiplier ?? null,
794
+ reasons: [...new Set(reasons)],
795
+ requiresApproval,
796
+ requiresPlanning,
797
+ budget,
798
+ policySnapshot: policy,
799
+ metadata: {
800
+ ...input.metadata ?? {},
801
+ source: input.source ?? null,
802
+ classification: input.classification
803
+ }
804
+ };
805
+ }
806
+ function shouldInterruptForCapacity(input) {
807
+ const reservedCredits = nonNegativeNumber(input.reservedCredits, 0);
808
+ const consumedCredits = nonNegativeNumber(input.consumedCredits, 0);
809
+ const threshold = Math.min(100, Math.max(1, nonNegativeNumber(input.reservationUsedPercentThreshold, 80)));
810
+ const usedPercent = reservedCredits > 0 ? consumedCredits / reservedCredits * 100 : 0;
811
+ const estimatedRemainingP50 = nonNegativeNumber(input.estimatedRemainingCreditsP50, 0);
812
+ const estimatedRemainingP90 = nonNegativeNumber(input.estimatedRemainingCreditsP90, estimatedRemainingP50);
813
+ const recoveryBudgetRemaining = input.recoveryBudgetRemainingCredits === void 0 || input.recoveryBudgetRemainingCredits === null ? null : nonNegativeNumber(input.recoveryBudgetRemainingCredits, 0);
814
+ const recoveryMinimum = nonNegativeNumber(input.recoveryBudgetMinimumCredits, 3);
815
+ const reasons = [];
816
+ if (input.providerAvailable === false) {
817
+ reasons.push("provider_unavailable");
818
+ }
819
+ if (reservedCredits > 0 && usedPercent >= threshold && estimatedRemainingP50 > Math.max(0, reservedCredits - consumedCredits)) {
820
+ reasons.push("reservation_exhaustion_risk");
821
+ }
822
+ if (recoveryBudgetRemaining !== null && recoveryBudgetRemaining < recoveryMinimum) {
823
+ reasons.push("recovery_budget_low");
824
+ }
825
+ return {
826
+ interrupt: reasons.length > 0,
827
+ reasons,
828
+ usedPercent,
829
+ estimatedRemainingP50,
830
+ estimatedRemainingP90,
831
+ remainingReservationCredits: Math.max(0, reservedCredits - consumedCredits)
117
832
  };
118
833
  }
119
834
  function summarizeCapacityPlan(plan) {
@@ -290,10 +1005,304 @@ function settleCapacityActuals(input) {
290
1005
  overrunCredits
291
1006
  };
292
1007
  }
1008
+ function distinctProfiles(profiles) {
1009
+ const seen = /* @__PURE__ */ new Set();
1010
+ return profiles.filter((profile) => {
1011
+ if (seen.has(profile.id)) return false;
1012
+ seen.add(profile.id);
1013
+ return true;
1014
+ });
1015
+ }
1016
+ function executionProfilesForRoute(input) {
1017
+ const explicitProfiles = Array.isArray(input.executionProfiles) && input.executionProfiles.length > 0 ? input.executionProfiles : null;
1018
+ const preferred = stringArray(input.preferredExecutionProfiles);
1019
+ const disallowed = new Set(stringArray(input.disallowedExecutionProfiles));
1020
+ const rawProfiles = explicitProfiles ?? (preferred.length > 0 ? preferred : input.executionProfile ? [input.executionProfile] : input.estimate.executionProfileId ? [input.estimate.executionProfileId] : [DEFAULT_EXECUTION_PROFILE_ID]);
1021
+ const profiles = distinctProfiles(rawProfiles.map((profile) => normalizeExecutionProfile(profile))).filter((profile) => !disallowed.has(profile.id));
1022
+ return profiles.length > 0 ? profiles : [normalizeExecutionProfile(DEFAULT_EXECUTION_PROFILE_ID)];
1023
+ }
1024
+ function estimateForRouteProfile(input, profile) {
1025
+ const selectedProfile = selectTaskEstimateProfile({
1026
+ profiles: input.estimateProfiles ?? input.plan.estimateProfiles ?? null,
1027
+ taskSignature: input.estimate.taskSignature,
1028
+ executionProfile: profile,
1029
+ executionProfileId: profile.id
1030
+ });
1031
+ return reserveCreditsForEstimate({
1032
+ taskSignature: input.estimate.taskSignature,
1033
+ taskKind: input.taskKind ?? input.estimate.taskSignature,
1034
+ confidence: input.estimate.confidence ?? input.classification?.confidence ?? "medium",
1035
+ estimatedCreditsP50: selectedProfile ? void 0 : input.estimate.estimatedCreditsP50,
1036
+ estimatedCreditsP90: selectedProfile ? void 0 : input.estimate.estimatedCreditsP90,
1037
+ defaultCredits: input.estimate.estimatedCreditsP50,
1038
+ profiles: input.estimateProfiles ?? input.plan.estimateProfiles ?? null,
1039
+ profile: selectedProfile,
1040
+ executionProfile: profile,
1041
+ executionProfileId: profile.id
1042
+ });
1043
+ }
1044
+ function routeMinimumQuality(input) {
1045
+ const explicit = finiteOrParsedNumber(input.minimumQualityWeight);
1046
+ if (explicit !== null) return Math.max(0, explicit);
1047
+ if (input.production || input.classification?.mutationScope === "production") return 1.5;
1048
+ if (input.classification?.risk === "high" || input.classification?.requiresApproval) return 1.25;
1049
+ if (input.classification?.confidence === "low") return 1.1;
1050
+ return 0;
1051
+ }
1052
+ function routeRequiredContext(input) {
1053
+ return Math.max(0, finiteOrParsedNumber(input.requiredContextTokens) ?? 0);
1054
+ }
1055
+ function reservationMetadata(reservation) {
1056
+ return readRecord(reservation.metadata);
1057
+ }
1058
+ function attentionValueFromMetadata(metadata, ...keys) {
1059
+ const estimate = readRecord(metadata.attentionEstimate);
1060
+ for (const key of keys) {
1061
+ const value = finiteOrParsedNumber(metadata[key]) ?? finiteOrParsedNumber(estimate[key]);
1062
+ if (value !== null) return value;
1063
+ }
1064
+ return null;
1065
+ }
1066
+ function activeLaneReservations(plan, provider, lane) {
1067
+ return plan.activeReservations.filter(
1068
+ (reservation) => reservation.capacityProviderId === provider.id && (!lane || reservation.laneId === lane.id) && (reservation.state === "reserved" || reservation.state === "consuming")
1069
+ );
1070
+ }
1071
+ function attentionLimitNumber(lane, provider, key) {
1072
+ return finiteOrParsedNumber(lane.hardLimits?.[key]) ?? finiteOrParsedNumber(lane.routingPolicy?.[key]) ?? finiteOrParsedNumber(lane.metadata?.[key]) ?? finiteOrParsedNumber(readRecord(lane.metadata?.pressure)[key]) ?? finiteOrParsedNumber(provider.capacityModel?.[key]) ?? finiteOrParsedNumber(provider.metadata?.[key]) ?? finiteOrParsedNumber(readRecord(provider.metadata?.pressure)[key]);
1073
+ }
1074
+ function deriveAttentionWeight(classification, profile, contextTokens) {
1075
+ let weight = 1;
1076
+ const mutationScope = classification?.mutationScope ?? "repository_read";
1077
+ if (mutationScope === "repository_read") weight = 1.5;
1078
+ if (mutationScope === "repository_write") weight = 3;
1079
+ if (mutationScope === "production") weight = 6;
1080
+ if (classification?.risk === "medium") weight += 1;
1081
+ if (classification?.risk === "high") weight += 3;
1082
+ if (classification?.confidence === "low") weight += 1;
1083
+ weight += Math.max(0, classification?.expectedFanout ?? 0) * 0.5;
1084
+ if (profile.concurrencyClass === "human_attention" || profile.quotaBehavior === "attention_bound") weight += 5;
1085
+ if (profile.concurrencyClass === "exclusive_project") weight += 3;
1086
+ if (contextTokens >= 1e5) weight += 4;
1087
+ else if (contextTokens >= 32e3) weight += 2;
1088
+ return Math.max(0, weight);
1089
+ }
1090
+ function estimateAttentionForTask(input) {
1091
+ const profile = normalizeExecutionProfile(input.executionProfile);
1092
+ const policy = normalizeAttentionPolicy(input.attentionPolicy);
1093
+ const requiredContextTokens = Math.max(0, finiteOrParsedNumber(input.requiredContextTokens) ?? 0);
1094
+ const estimatedContextTokens = Math.max(requiredContextTokens, finiteOrParsedNumber(input.estimatedContextTokens) ?? requiredContextTokens);
1095
+ const baseAttention = finiteOrParsedNumber(input.attentionWeight) ?? deriveAttentionWeight(input.classification, profile, estimatedContextTokens);
1096
+ const coordinationWeight = finiteOrParsedNumber(input.coordinationWeight) ?? Math.max(0, input.classification?.expectedFanout ?? 0) * policy.coordinationOverheadFactor;
1097
+ return {
1098
+ attentionWeight: Math.max(0, Math.ceil(baseAttention * 100) / 100),
1099
+ coordinationWeight: Math.max(0, Math.ceil(coordinationWeight * 100) / 100),
1100
+ totalAttentionWeight: Math.max(0, Math.ceil((baseAttention + coordinationWeight) * 100) / 100),
1101
+ estimatedContextTokens,
1102
+ requiredContextTokens,
1103
+ source: input.source ?? "capacity_attention_estimator",
1104
+ metadata: input.metadata ?? {}
1105
+ };
1106
+ }
1107
+ function attentionEstimateForRoute(input, profile) {
1108
+ if (input.attentionEstimate) return input.attentionEstimate;
1109
+ return estimateAttentionForTask({
1110
+ classification: input.classification,
1111
+ executionProfile: profile,
1112
+ attentionPolicy: input.attentionPolicy,
1113
+ attentionWeight: input.attentionWeight,
1114
+ coordinationWeight: input.coordinationWeight,
1115
+ estimatedContextTokens: input.estimatedContextTokens,
1116
+ requiredContextTokens: input.requiredContextTokens,
1117
+ source: input.source ?? "capacity_router"
1118
+ });
1119
+ }
1120
+ function readPressureNumber(provider, lane, key) {
1121
+ const laneValue = finiteOrParsedNumber(lane.metadata?.[key]);
1122
+ if (laneValue !== null) return laneValue;
1123
+ const providerValue = finiteOrParsedNumber(provider.metadata?.[key]);
1124
+ if (providerValue !== null) return providerValue;
1125
+ const lanePressure = readRecord(lane.metadata?.pressure);
1126
+ const providerPressure = readRecord(provider.metadata?.pressure);
1127
+ return finiteOrParsedNumber(lanePressure[key]) ?? finiteOrParsedNumber(providerPressure[key]);
1128
+ }
1129
+ function readPressureBoolean(provider, lane, key) {
1130
+ const values = [
1131
+ lane.metadata?.[key],
1132
+ provider.metadata?.[key],
1133
+ readRecord(lane.metadata?.pressure)[key],
1134
+ readRecord(provider.metadata?.pressure)[key]
1135
+ ];
1136
+ return values.some((value) => value === true || value === "true");
1137
+ }
1138
+ function hardLimitNumber(lane, provider, ...keys) {
1139
+ for (const key of keys) {
1140
+ const value = finiteOrParsedNumber(lane.hardLimits?.[key]) ?? finiteOrParsedNumber(lane.routingPolicy?.[key]) ?? finiteOrParsedNumber(provider.capacityModel?.[key]) ?? finiteOrParsedNumber(provider.metadata?.[key]);
1141
+ if (value !== null && value >= 0) return value;
1142
+ }
1143
+ return null;
1144
+ }
1145
+ function capacityRoutePressure(plan, provider, lane) {
1146
+ const reservations = activeLaneReservations(plan, provider, lane);
1147
+ const activeReservations = reservations.length;
1148
+ const maxActiveReservations = hardLimitNumber(
1149
+ lane,
1150
+ provider,
1151
+ "maxActiveReservations",
1152
+ "maxConcurrentTasks",
1153
+ "maxConcurrentWorkers"
1154
+ ) ?? (provider.maxConcurrentWorkers > 0 ? provider.maxConcurrentWorkers : null);
1155
+ const congestionRatio = maxActiveReservations && maxActiveReservations > 0 ? activeReservations / maxActiveReservations : 0;
1156
+ const activeAttentionLoad = reservations.reduce((total, reservation) => {
1157
+ const metadata = reservationMetadata(reservation);
1158
+ return total + Math.max(0, attentionValueFromMetadata(metadata, "totalAttentionWeight", "attentionWeight") ?? 0);
1159
+ }, 0);
1160
+ const maxAttentionLoad = attentionLimitNumber(lane, provider, "maxAttentionLoad");
1161
+ const activeContextTokens = reservations.reduce((total, reservation) => {
1162
+ const metadata = reservationMetadata(reservation);
1163
+ return total + Math.max(0, attentionValueFromMetadata(metadata, "estimatedContextTokens", "contextTokens", "requiredContextTokens") ?? 0);
1164
+ }, 0);
1165
+ const maxContextTokens = attentionLimitNumber(lane, provider, "maxContextTokens");
1166
+ return {
1167
+ activeReservations,
1168
+ maxActiveReservations,
1169
+ congestionRatio,
1170
+ quotaRemainingPercent: readPressureNumber(provider, lane, "quotaRemainingPercent"),
1171
+ sessionRemainingMinutes: readPressureNumber(provider, lane, "sessionRemainingMinutes"),
1172
+ subscriptionSaturationPercent: readPressureNumber(provider, lane, "subscriptionSaturationPercent"),
1173
+ providerUnavailable: readPressureBoolean(provider, lane, "providerUnavailable"),
1174
+ activeAttentionLoad,
1175
+ maxAttentionLoad,
1176
+ attentionSaturationPercent: maxAttentionLoad && maxAttentionLoad > 0 ? activeAttentionLoad / maxAttentionLoad * 100 : null,
1177
+ activeContextTokens,
1178
+ maxContextTokens,
1179
+ contextSaturationPercent: maxContextTokens && maxContextTokens > 0 ? activeContextTokens / maxContextTokens * 100 : null
1180
+ };
1181
+ }
1182
+ function latencyPenalty(profile) {
1183
+ if (profile.latencyClass === "high") return 12;
1184
+ if (profile.latencyClass === "medium") return 5;
1185
+ return 0;
1186
+ }
1187
+ function quotaPressurePenalty(pressure) {
1188
+ let penalty = 0;
1189
+ if (pressure.quotaRemainingPercent !== null) {
1190
+ penalty += Math.max(0, 100 - pressure.quotaRemainingPercent) * 0.25;
1191
+ }
1192
+ if (pressure.subscriptionSaturationPercent !== null) {
1193
+ penalty += Math.max(0, pressure.subscriptionSaturationPercent) * 0.35;
1194
+ }
1195
+ if (pressure.sessionRemainingMinutes !== null && pressure.sessionRemainingMinutes < 20) {
1196
+ penalty += (20 - pressure.sessionRemainingMinutes) * 1.5;
1197
+ }
1198
+ return penalty;
1199
+ }
1200
+ function attentionPressurePenalty(pressure, estimate, policy) {
1201
+ if (!pressure.maxAttentionLoad || pressure.maxAttentionLoad <= 0) return 0;
1202
+ const allocatableLoad = Math.max(0, pressure.maxAttentionLoad * (1 - policy.reserveAttentionPercent / 100));
1203
+ const projected = pressure.activeAttentionLoad + estimate.totalAttentionWeight;
1204
+ const saturation = allocatableLoad > 0 ? projected / allocatableLoad : 1;
1205
+ return Math.max(0, saturation) * 35;
1206
+ }
1207
+ function contextPressurePenalty(pressure, estimate, policy) {
1208
+ const maxContextTokens = pressure.maxContextTokens ?? policy.maxContextTokens;
1209
+ if (!maxContextTokens || maxContextTokens <= 0) return 0;
1210
+ const projected = pressure.activeContextTokens + estimate.estimatedContextTokens;
1211
+ const saturationPercent = projected / maxContextTokens * 100;
1212
+ return Math.max(0, saturationPercent - 50) * 0.6;
1213
+ }
1214
+ function laneSupportsExecutionProfile(lane, profile) {
1215
+ const allowedProfiles = stringArray(lane.routingPolicy?.executionProfiles);
1216
+ if (allowedProfiles.length > 0 && !allowedProfiles.includes(profile.id)) return false;
1217
+ const allowedModelClasses = stringArray(lane.routingPolicy?.modelClasses);
1218
+ if (allowedModelClasses.length > 0 && profile.modelClass && !allowedModelClasses.includes(profile.modelClass)) return false;
1219
+ return true;
1220
+ }
1221
+ function routeTrustScore(provider, lane, profile) {
1222
+ const laneMetadata = readRecord(lane.metadata);
1223
+ const providerMetadata = readRecord(provider.metadata);
1224
+ const profileMetadata = readRecord(profile.metadata);
1225
+ const trust = finiteOrParsedNumber(laneMetadata.trustScore) ?? finiteOrParsedNumber(providerMetadata.trustScore) ?? finiteOrParsedNumber(profileMetadata.trustScore) ?? 1;
1226
+ const availability = finiteOrParsedNumber(laneMetadata.availabilityScore) ?? finiteOrParsedNumber(providerMetadata.availabilityScore) ?? 1;
1227
+ return Math.max(0, Math.min(1, trust)) * Math.max(0, Math.min(1, availability));
1228
+ }
1229
+ function routeSuccessProbability(input) {
1230
+ const laneMetadata = readRecord(input.lane.metadata);
1231
+ const providerMetadata = readRecord(input.provider.metadata);
1232
+ const profileMetadata = readRecord(input.profile.metadata);
1233
+ const success = finiteOrParsedNumber(input.explicit) ?? finiteOrParsedNumber(laneMetadata.successProbability) ?? finiteOrParsedNumber(providerMetadata.successProbability) ?? finiteOrParsedNumber(profileMetadata.successProbability) ?? input.utilityEstimate.successProbability ?? 1;
1234
+ return Math.max(0, Math.min(1, success));
1235
+ }
1236
+ function routePriceMultiplier(provider, lane, profile) {
1237
+ const laneMetadata = readRecord(lane.metadata);
1238
+ const providerMetadata = readRecord(provider.metadata);
1239
+ const profileMetadata = readRecord(profile.metadata);
1240
+ return Math.max(0.1, finiteOrParsedNumber(laneMetadata.priceMultiplier) ?? finiteOrParsedNumber(providerMetadata.priceMultiplier) ?? finiteOrParsedNumber(profileMetadata.priceMultiplier) ?? 1);
1241
+ }
1242
+ function routeScore(input) {
1243
+ const reasons = [...input.baseScore.reasons];
1244
+ const qualityFit = input.minimumQualityWeight > 0 ? input.profile.qualityWeight / input.minimumQualityWeight : input.profile.qualityWeight;
1245
+ const qualityBonus = Math.min(50, Math.max(0, input.profile.qualityWeight * 20));
1246
+ const costPenalty = Math.max(0, input.estimate.reservedCredits);
1247
+ const latency = latencyPenalty(input.profile);
1248
+ const congestion = input.pressure.congestionRatio * 45;
1249
+ const quota = quotaPressurePenalty(input.pressure);
1250
+ const attention = attentionPressurePenalty(input.pressure, input.attentionEstimate, input.attentionPolicy);
1251
+ const context = contextPressurePenalty(input.pressure, input.attentionEstimate, input.attentionPolicy);
1252
+ const priceMultiplier = routePriceMultiplier(input.provider, input.lane, input.profile);
1253
+ const utilityBonus = input.utilityEstimate.utilityScore > 0 ? Math.min(100, input.utilityEstimate.utilityPerCredit * 18 * Math.max(0.1, input.trustScore) * Math.max(0.1, input.successProbability)) : 0;
1254
+ const cooperativeBonus = input.cooperativeRouting ? input.trustScore * 20 + input.successProbability * 15 : 0;
1255
+ const predictedReserveImpact = input.reservePrediction?.reserveCredits ?? 0;
1256
+ const laneModelFit = input.profile.modelClass && input.lane.modelClass === input.profile.modelClass ? 15 : 0;
1257
+ const riskBonus = input.minimumQualityWeight >= 1.25 && input.profile.qualityWeight >= input.minimumQualityWeight ? 10 : 0;
1258
+ const score = input.baseScore.score + qualityBonus + laneModelFit + riskBonus + utilityBonus + cooperativeBonus - costPenalty - latency - congestion - quota - attention - context - Math.max(0, priceMultiplier - 1) * 8 - (input.reservePrediction && input.reservePrediction.reservePercent > 0 ? Math.min(20, predictedReserveImpact * 0.25) : 0);
1259
+ if (laneModelFit > 0) reasons.push("execution_profile_model_class_match");
1260
+ if (congestion > 0) reasons.push("lane_congestion_pressure");
1261
+ if (quota > 0) reasons.push("quota_pressure");
1262
+ if (attention > 0) reasons.push("attention_pressure");
1263
+ if (context > 0) reasons.push("context_pressure");
1264
+ if (utilityBonus > 0) reasons.push("utility_scored");
1265
+ if (cooperativeBonus > 0) reasons.push("cooperative_route_scored");
1266
+ if (predictedReserveImpact > 0) reasons.push("predictive_reserve_applied");
1267
+ return {
1268
+ ...input.baseScore,
1269
+ score,
1270
+ qualityFit,
1271
+ latencyPenalty: latency,
1272
+ quotaPressure: quota,
1273
+ congestionPenalty: congestion,
1274
+ attentionPenalty: attention,
1275
+ contextPenalty: context,
1276
+ utilityScore: input.utilityEstimate.utilityScore,
1277
+ utilityPerCredit: input.utilityEstimate.utilityPerCredit,
1278
+ predictedReserveImpact,
1279
+ trustScore: input.trustScore,
1280
+ successProbability: input.successProbability,
1281
+ costPenalty,
1282
+ executionProfileId: input.profile.id,
1283
+ reservedCredits: input.estimate.reservedCredits,
1284
+ attentionEstimate: input.attentionEstimate,
1285
+ reasons: [...new Set(reasons)]
1286
+ };
1287
+ }
1288
+ function routeCandidateKey(candidate) {
1289
+ return `${candidate.providerId}:${candidate.laneId}:${candidate.grantId}:${candidate.executionProfileId ?? DEFAULT_EXECUTION_PROFILE_ID}`;
1290
+ }
293
1291
  function routeAndReserveCapacity(input) {
294
1292
  const providers = input.plan.providers.filter((provider2) => providerIsEligible(provider2, input));
295
1293
  const grants = input.plan.grants.filter((grant2) => grantIsEligible(grant2, input));
296
1294
  const candidates = [];
1295
+ const executionProfiles = executionProfilesForRoute(input);
1296
+ const minimumQualityWeight = routeMinimumQuality(input);
1297
+ const requiredContextTokens = routeRequiredContext(input);
1298
+ const attentionPolicy = normalizeAttentionPolicy(input.attentionPolicy);
1299
+ const utilityPolicy = normalizeUtilityPolicy(input.utilityPolicy);
1300
+ const predictiveReservePolicy = normalizePredictiveReservePolicy(input.predictiveReservePolicy);
1301
+ const hybridExecutionPlan = normalizeHybridExecutionPlan(input.hybridExecutionPlan ?? readRecord(input.metadata).hybridExecutionPlan);
1302
+ const preferredProfiles = new Set(stringArray(input.preferredExecutionProfiles));
1303
+ const disallowedProfiles = new Set(stringArray(input.disallowedExecutionProfiles));
1304
+ const cooperativeRouting = input.cooperativeRouting === true || readRecord(input.metadata).cooperativeRouting === true;
1305
+ const trustRequirement = finiteOrParsedNumber(input.trustRequirement);
297
1306
  for (const grant2 of grants) {
298
1307
  const provider2 = providers.find((candidate) => candidate.id === grant2.capacityProviderId);
299
1308
  if (!provider2) continue;
@@ -301,31 +1310,135 @@ function routeAndReserveCapacity(input) {
301
1310
  (lane2) => lane2.capacityProviderId === provider2.id && (!grant2.laneId || grant2.laneId === lane2.id)
302
1311
  );
303
1312
  for (const lane2 of lanes) {
304
- const reasons = lanePolicyReasons(lane2, input);
305
1313
  const remainingCredits = grantRemainingCredits(input.plan, grant2);
306
- if (remainingCredits !== null && remainingCredits < input.estimate.reservedCredits && (grant2.overflowPolicy === "deny" || grant2.overflowPolicy === "hard_grant")) {
307
- reasons.push("insufficient_budget");
1314
+ const pressure = capacityRoutePressure(input.plan, provider2, lane2);
1315
+ for (const profile of executionProfiles) {
1316
+ const estimate = estimateForRouteProfile(input, profile);
1317
+ const attentionEstimate = attentionEstimateForRoute(input, profile);
1318
+ const utilityEstimate = input.utilityEstimate ?? estimateUtilityForTask({
1319
+ classification: input.classification,
1320
+ executionProfile: profile,
1321
+ estimate,
1322
+ utilityPolicy,
1323
+ utilityValue: input.utilityValue,
1324
+ maintenanceValue: input.maintenanceValue,
1325
+ priority: finiteOrParsedNumber(readRecord(input.metadata).priority),
1326
+ deadlineAt: input.deadlineAt ?? (typeof readRecord(input.metadata).deadlineAt === "string" ? readRecord(input.metadata).deadlineAt : null),
1327
+ successProbability: input.successProbability,
1328
+ metadata: input.metadata,
1329
+ source: input.source ?? "capacity_router"
1330
+ });
1331
+ const reservePrediction = predictReserveForCapacityPlan({
1332
+ plan: input.plan,
1333
+ policy: predictiveReservePolicy,
1334
+ remainingCredits,
1335
+ metadata: input.metadata
1336
+ });
1337
+ const trustScore = routeTrustScore(provider2, lane2, profile);
1338
+ const successProbability = routeSuccessProbability({
1339
+ provider: provider2,
1340
+ lane: lane2,
1341
+ profile,
1342
+ explicit: input.successProbability,
1343
+ utilityEstimate
1344
+ });
1345
+ const estimateInput = { ...input, estimate };
1346
+ const reasons = lanePolicyReasons(lane2, estimateInput);
1347
+ let spilloverReason = null;
1348
+ if (trustRequirement !== null && trustScore < trustRequirement) reasons.push("trust_below_requirement");
1349
+ if (utilityPolicy.minimumUtilityScore !== null && utilityEstimate.utilityScore < utilityPolicy.minimumUtilityScore) reasons.push("utility_below_minimum");
1350
+ if (utilityPolicy.minimumUtilityPerCredit !== null && utilityEstimate.utilityPerCredit < utilityPolicy.minimumUtilityPerCredit) reasons.push("utility_per_credit_below_minimum");
1351
+ if (predictiveReservePolicy.enabled && reservePrediction.reserveCredits > 0 && reservePrediction.activelyAllocatableCredits < estimate.reservedCredits && utilityEstimate.utilityScore < 50) {
1352
+ reasons.push("predictive_reserve_blocked");
1353
+ }
1354
+ const routeMaxAttentionLoad = attentionPolicy.maxAttentionLoad ?? pressure.maxAttentionLoad;
1355
+ if (routeMaxAttentionLoad !== null && routeMaxAttentionLoad > 0) {
1356
+ const allocatableAttention = Math.max(0, routeMaxAttentionLoad * (1 - attentionPolicy.reserveAttentionPercent / 100));
1357
+ const projectedAttention = pressure.activeAttentionLoad + attentionEstimate.totalAttentionWeight;
1358
+ if (projectedAttention > allocatableAttention) reasons.push("attention_load_exceeded");
1359
+ const availableAttention = Math.max(0, allocatableAttention - pressure.activeAttentionLoad);
1360
+ const minimumAttentionAvailable = finiteOrParsedNumber(input.minimumAttentionAvailable);
1361
+ if (minimumAttentionAvailable !== null && availableAttention < minimumAttentionAvailable) reasons.push("minimum_attention_unavailable");
1362
+ }
1363
+ const routeMaxContextTokens = attentionPolicy.maxContextTokens ?? pressure.maxContextTokens;
1364
+ if (routeMaxContextTokens !== null && routeMaxContextTokens > 0) {
1365
+ const projectedContextTokens = pressure.activeContextTokens + attentionEstimate.estimatedContextTokens;
1366
+ const projectedSaturation = projectedContextTokens / routeMaxContextTokens * 100;
1367
+ if (projectedContextTokens > routeMaxContextTokens || projectedSaturation > attentionPolicy.maxContextSaturationPercent) {
1368
+ reasons.push("context_saturation_exceeded");
1369
+ }
1370
+ }
1371
+ if (disallowedProfiles.has(profile.id)) reasons.push("execution_profile_disallowed");
1372
+ if (preferredProfiles.size > 0 && !preferredProfiles.has(profile.id)) reasons.push("execution_profile_not_preferred");
1373
+ if (!laneSupportsExecutionProfile(lane2, profile)) reasons.push("execution_profile_not_supported");
1374
+ if (minimumQualityWeight > 0 && profile.qualityWeight < minimumQualityWeight) reasons.push("quality_below_minimum");
1375
+ if (requiredContextTokens > 0 && profile.contextWindowTokens !== null && profile.contextWindowTokens !== void 0 && profile.contextWindowTokens < requiredContextTokens) {
1376
+ reasons.push("context_window_too_small");
1377
+ }
1378
+ if (pressure.providerUnavailable) reasons.push("provider_unavailable");
1379
+ if (pressure.maxActiveReservations !== null && pressure.congestionRatio >= 1) reasons.push("lane_congested");
1380
+ if (pressure.quotaRemainingPercent !== null && pressure.quotaRemainingPercent <= 0) reasons.push("quota_exhausted");
1381
+ if (pressure.sessionRemainingMinutes !== null && pressure.sessionRemainingMinutes <= 0) reasons.push("session_exhausted");
1382
+ if (remainingCredits !== null && remainingCredits < estimate.reservedCredits) {
1383
+ if (grant2.overflowPolicy === "approval_required") {
1384
+ reasons.push("approval_required");
1385
+ } else if (grant2.overflowPolicy === "fallback_lane") {
1386
+ spilloverReason = "fallback_lane";
1387
+ reasons.push("fallback_lane_exhausted");
1388
+ } else if (grant2.overflowPolicy === "deny" || grant2.overflowPolicy === "hard_grant") {
1389
+ reasons.push("insufficient_budget");
1390
+ } else {
1391
+ reasons.push("soft_budget_pressure");
1392
+ }
1393
+ }
1394
+ const baseScore = scoreCapacityLane({
1395
+ lane: lane2,
1396
+ grant: grant2,
1397
+ remainingCredits,
1398
+ taskKind: input.taskKind ?? estimate.taskSignature,
1399
+ requiredCapabilities: input.requiredCapabilities,
1400
+ modelClass: input.modelClass ?? profile.modelClass ?? null
1401
+ });
1402
+ const score = routeScore({
1403
+ provider: provider2,
1404
+ lane: lane2,
1405
+ grant: grant2,
1406
+ estimate,
1407
+ profile,
1408
+ remainingCredits,
1409
+ pressure,
1410
+ minimumQualityWeight,
1411
+ attentionEstimate,
1412
+ attentionPolicy,
1413
+ utilityEstimate,
1414
+ reservePrediction,
1415
+ trustScore,
1416
+ successProbability,
1417
+ cooperativeRouting,
1418
+ baseScore
1419
+ });
1420
+ candidates.push({
1421
+ providerId: provider2.id,
1422
+ laneId: lane2.id,
1423
+ grantId: grant2.id,
1424
+ executionProfileId: profile.id,
1425
+ remainingCredits,
1426
+ score,
1427
+ eligible: reasons.filter(
1428
+ (reason) => reason !== "soft_budget_pressure" && reason !== "execution_profile_not_preferred"
1429
+ ).length === 0,
1430
+ reasons: [.../* @__PURE__ */ new Set([...reasons, ...score.reasons])],
1431
+ estimate,
1432
+ pressure,
1433
+ qualityFit: score.qualityFit,
1434
+ attentionEstimate,
1435
+ utilityEstimate,
1436
+ reservePrediction,
1437
+ trustScore,
1438
+ successProbability,
1439
+ spilloverReason
1440
+ });
308
1441
  }
309
- if (remainingCredits !== null && remainingCredits < input.estimate.reservedCredits && grant2.overflowPolicy === "approval_required") {
310
- reasons.push("approval_required");
311
- }
312
- const score = scoreCapacityLane({
313
- lane: lane2,
314
- grant: grant2,
315
- remainingCredits,
316
- taskKind: input.taskKind ?? input.estimate.taskSignature,
317
- requiredCapabilities: input.requiredCapabilities,
318
- modelClass: input.modelClass ?? null
319
- });
320
- candidates.push({
321
- providerId: provider2.id,
322
- laneId: lane2.id,
323
- grantId: grant2.id,
324
- remainingCredits,
325
- score,
326
- eligible: reasons.length === 0,
327
- reasons
328
- });
329
1442
  }
330
1443
  }
331
1444
  if (input.plan.providers.length === 0 || providers.length === 0) {
@@ -346,7 +1459,9 @@ function routeAndReserveCapacity(input) {
346
1459
  candidates
347
1460
  };
348
1461
  }
349
- const eligible = candidates.filter((candidate) => candidate.eligible).sort((left, right) => right.score.score - left.score.score || left.laneId.localeCompare(right.laneId));
1462
+ const eligible = candidates.filter((candidate) => candidate.eligible).sort(
1463
+ (left, right) => right.score.score - left.score.score || (left.estimate?.reservedCredits ?? Number.MAX_SAFE_INTEGER) - (right.estimate?.reservedCredits ?? Number.MAX_SAFE_INTEGER) || routeCandidateKey(left).localeCompare(routeCandidateKey(right))
1464
+ );
350
1465
  const selected = eligible[0] ?? null;
351
1466
  if (!selected) {
352
1467
  const hasApprovalBlock = candidates.some((candidate) => candidate.reasons.includes("approval_required"));
@@ -362,6 +1477,7 @@ function routeAndReserveCapacity(input) {
362
1477
  const provider = providers.find((candidate) => candidate.id === selected.providerId);
363
1478
  const lane = input.plan.lanes.find((candidate) => candidate.id === selected.laneId);
364
1479
  const grant = grants.find((candidate) => candidate.id === selected.grantId);
1480
+ const selectedEstimate = selected.estimate ?? input.estimate;
365
1481
  if (!provider || !lane || !grant) {
366
1482
  return {
367
1483
  ok: false,
@@ -375,12 +1491,22 @@ function routeAndReserveCapacity(input) {
375
1491
  providerId: candidate.providerId,
376
1492
  laneId: candidate.laneId,
377
1493
  grantId: candidate.grantId,
1494
+ executionProfileId: candidate.executionProfileId ?? null,
378
1495
  remainingCredits: candidate.remainingCredits,
379
1496
  eligible: candidate.eligible,
380
1497
  reasons: candidate.reasons,
381
- score: candidate.score.score
1498
+ score: candidate.score.score,
1499
+ reservedCredits: candidate.estimate?.reservedCredits ?? null,
1500
+ qualityFit: candidate.qualityFit ?? null,
1501
+ attentionEstimate: candidate.attentionEstimate ?? null,
1502
+ utilityEstimate: candidate.utilityEstimate ?? null,
1503
+ reservePrediction: candidate.reservePrediction ?? null,
1504
+ trustScore: candidate.trustScore ?? null,
1505
+ successProbability: candidate.successProbability ?? null,
1506
+ pressure: candidate.pressure ?? null,
1507
+ spilloverReason: candidate.spilloverReason ?? null
382
1508
  }));
383
- const scorePayload = Object.fromEntries(candidates.map((candidate) => [candidate.laneId, candidate.score]));
1509
+ const scorePayload = Object.fromEntries(candidates.map((candidate) => [routeCandidateKey(candidate), candidate.score]));
384
1510
  const reservation = {
385
1511
  capacityProviderId: provider.id,
386
1512
  laneId: lane.id,
@@ -389,13 +1515,20 @@ function routeAndReserveCapacity(input) {
389
1515
  workDayId: input.workDayId ?? null,
390
1516
  taskId: input.taskId ?? null,
391
1517
  state: "reserved",
392
- reservedCredits: input.estimate.reservedCredits,
1518
+ reservedCredits: selectedEstimate.reservedCredits,
393
1519
  metadata: {
394
1520
  ...input.metadata ?? {},
395
1521
  grantId: grant.id,
396
- taskSignature: input.estimate.taskSignature,
397
- estimatedCreditsP50: input.estimate.estimatedCreditsP50,
398
- estimatedCreditsP90: input.estimate.estimatedCreditsP90
1522
+ executionProfileId: selected.executionProfileId ?? selectedEstimate.executionProfileId ?? null,
1523
+ taskSignature: selectedEstimate.taskSignature,
1524
+ estimatedCreditsP50: selectedEstimate.estimatedCreditsP50,
1525
+ estimatedCreditsP90: selectedEstimate.estimatedCreditsP90,
1526
+ routingScore: selected.score.score,
1527
+ attentionEstimate: selected.attentionEstimate ?? null,
1528
+ utilityEstimate: selected.utilityEstimate ?? null,
1529
+ reservePrediction: selected.reservePrediction ?? null,
1530
+ hybridExecutionPlan,
1531
+ routingCandidates: candidatePayload
399
1532
  }
400
1533
  };
401
1534
  const routingDecision = {
@@ -404,7 +1537,7 @@ function routeAndReserveCapacity(input) {
404
1537
  projectId: input.plan.projectId,
405
1538
  selectedProviderId: provider.id,
406
1539
  selectedLaneId: lane.id,
407
- selectedModel: input.selectedModel ?? null,
1540
+ selectedModel: input.selectedModel ?? selected.executionProfileId ?? null,
408
1541
  decision: "selected",
409
1542
  reason: selected.score.reasons.length > 0 ? selected.score.reasons.join(",") : "best_eligible_lane",
410
1543
  candidates: candidatePayload,
@@ -412,8 +1545,14 @@ function routeAndReserveCapacity(input) {
412
1545
  metadata: {
413
1546
  ...input.metadata ?? {},
414
1547
  grantId: grant.id,
1548
+ executionProfileId: selected.executionProfileId ?? selectedEstimate.executionProfileId ?? null,
415
1549
  remainingCreditsBefore: selected.remainingCredits,
416
- reservedCredits: input.estimate.reservedCredits
1550
+ reservedCredits: selectedEstimate.reservedCredits,
1551
+ attentionEstimate: selected.attentionEstimate ?? null,
1552
+ utilityEstimate: selected.utilityEstimate ?? null,
1553
+ reservePrediction: selected.reservePrediction ?? null,
1554
+ hybridExecutionPlan,
1555
+ escalationPath: candidatePayload.filter((candidate) => candidate.eligible).sort((left, right) => Number(left.reservedCredits ?? 0) - Number(right.reservedCredits ?? 0)).map((candidate) => candidate.executionProfileId).filter((value, index, array) => typeof value === "string" && value && array.indexOf(value) === index)
417
1556
  }
418
1557
  };
419
1558
  const ledgerEntry = {
@@ -424,12 +1563,17 @@ function routeAndReserveCapacity(input) {
424
1563
  workDayId: input.workDayId ?? null,
425
1564
  taskId: input.taskId ?? null,
426
1565
  phase: "reservation_created",
427
- credits: input.estimate.reservedCredits,
1566
+ credits: selectedEstimate.reservedCredits,
428
1567
  source: input.source ?? "capacity_coordinator",
429
1568
  metadata: {
430
1569
  ...input.metadata ?? {},
431
1570
  grantId: grant.id,
432
- taskSignature: input.estimate.taskSignature
1571
+ executionProfileId: selected.executionProfileId ?? selectedEstimate.executionProfileId ?? null,
1572
+ taskSignature: selectedEstimate.taskSignature,
1573
+ attentionEstimate: selected.attentionEstimate ?? null,
1574
+ utilityEstimate: selected.utilityEstimate ?? null,
1575
+ reservePrediction: selected.reservePrediction ?? null,
1576
+ hybridExecutionPlan
433
1577
  }
434
1578
  };
435
1579
  return {
@@ -437,7 +1581,7 @@ function routeAndReserveCapacity(input) {
437
1581
  provider,
438
1582
  lane,
439
1583
  grant,
440
- estimate: input.estimate,
1584
+ estimate: selectedEstimate,
441
1585
  remainingCreditsBefore: selected.remainingCredits,
442
1586
  reservation,
443
1587
  routingDecision,
@@ -448,22 +1592,61 @@ function routeAndReserveCapacity(input) {
448
1592
  grantId: grant.id,
449
1593
  reservationId: reservation.id ?? null,
450
1594
  routingDecisionId: routingDecision.id ?? null,
451
- estimatedCreditsP50: input.estimate.estimatedCreditsP50,
452
- estimatedCreditsP90: input.estimate.estimatedCreditsP90,
453
- reservedCredits: input.estimate.reservedCredits
1595
+ estimatedCreditsP50: selectedEstimate.estimatedCreditsP50,
1596
+ estimatedCreditsP90: selectedEstimate.estimatedCreditsP90,
1597
+ reservedCredits: selectedEstimate.reservedCredits,
1598
+ executionProfileId: selected.executionProfileId ?? selectedEstimate.executionProfileId ?? null,
1599
+ costMultiplier: selectedEstimate.costMultiplier ?? null,
1600
+ score: selected.score.score,
1601
+ attentionEstimate: selected.attentionEstimate ?? null,
1602
+ utilityEstimate: selected.utilityEstimate ?? null,
1603
+ reservePrediction: selected.reservePrediction ?? null,
1604
+ hybridExecutionPlan,
1605
+ candidates: candidatePayload
454
1606
  },
455
1607
  candidates
456
1608
  };
457
1609
  }
458
1610
  export {
1611
+ DEFAULT_EXECUTION_PROFILES,
1612
+ DEFAULT_EXECUTION_PROFILE_ID,
1613
+ DEFAULT_TASK_ADMISSION_POLICY,
1614
+ buildTaskEstimateProfileFromActuals,
1615
+ capacityRoutePressure,
1616
+ computeWorkdayBudgetEnvelope,
459
1617
  createReservationReleaseEntry,
1618
+ decideTaskAdmission,
1619
+ estimateAttentionForTask,
1620
+ estimateConfidenceFromProfile,
1621
+ estimateForClassification,
1622
+ estimateLearningPercentile,
1623
+ estimateLearningVariance,
1624
+ estimateProfileConfidenceScore,
1625
+ estimateUtilityForTask,
1626
+ isInterruptedUsageActual,
1627
+ mutationRequiresRepositoryClaim,
1628
+ normalizeAttentionPolicy,
1629
+ normalizeExecutionProfile,
1630
+ normalizeHybridExecutionPlan,
1631
+ normalizePlanningPolicy,
1632
+ normalizePredictiveReservePolicy,
1633
+ normalizeTaskAdmissionPolicy,
1634
+ normalizeTaskPlanProposal,
1635
+ normalizeUtilityPolicy,
1636
+ predictReserveForCapacityPlan,
1637
+ progressivelyAdmitPlanProposal,
1638
+ rankPlannedTaskNodes,
460
1639
  reservationHasCapacity,
461
1640
  reserveCreditsForEstimate,
462
1641
  routeAndReserveCapacity,
463
1642
  scoreCapacityLane,
464
1643
  selectBestCapacityLane,
1644
+ selectTaskEstimateProfile,
465
1645
  settleCapacityActuals,
1646
+ shouldInterruptForCapacity,
466
1647
  summarizeCapacityPlan,
467
1648
  summarizeProjectCapacityPlan,
468
- summarizeTeamCapacityPlan
1649
+ summarizeTeamCapacityPlan,
1650
+ synthesizePlanEstimate,
1651
+ validateTaskPlanProposal
469
1652
  };