@treeseed/sdk 0.13.0-rc.119 → 0.13.0-rc.120
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/.treeseed-build-complete.json +1 -1
- package/dist/agent-capacity/allocation.d.ts +0 -3
- package/dist/agent-capacity/allocation.js +0 -221
- package/dist/agent-capacity/contracts/capacity/assignments/agent-execution.d.ts +280 -84
- package/dist/agent-capacity/contracts/capacity/assignments/agent-execution.js +22 -1
- package/dist/agent-capacity/contracts/capacity/assignments/assignment-records.d.ts +5 -0
- package/dist/agent-capacity/contracts/capacity/workdays/assignment-allocation.d.ts +82 -0
- package/dist/agent-capacity/contracts/capacity/workdays/assignment-allocation.js +115 -0
- package/dist/agent-capacity/contracts/capacity/workdays/capability-accounting.d.ts +142 -0
- package/dist/agent-capacity/contracts/capacity/workdays/capability-accounting.js +51 -0
- package/dist/agent-capacity/contracts/capacity/workdays/workday-allocation.d.ts +167 -35
- package/dist/agent-capacity/contracts/capacity/workdays/workday-allocation.js +59 -20
- package/dist/agent-capacity/contracts/capacity/workdays/workday-records.d.ts +2 -21
- package/dist/agent-capacity/contracts/support/execution-graph.d.ts +18 -18
- package/dist/agent-capacity/contracts/support/time-capacity.d.ts +0 -14
- package/dist/agent-capacity/contracts/support/time-capacity.js +1 -8
- package/dist/agent-capacity/validation/agent-definition-schema.d.ts +338 -338
- package/dist/capacity/agents/agent-capacity.d.ts +2 -0
- package/dist/capacity/agents/agent-capacity.js +2 -0
- package/dist/capacity-provider/client.d.ts +5 -3
- package/dist/capacity-provider/contracts/capacity-facts.d.ts +14 -0
- package/dist/capacity-provider/contracts/governance.d.ts +6 -0
- package/dist/capacity-provider/sandbox-contracts.d.ts +55 -1
- package/dist/capacity-provider/sandbox-contracts.js +4 -0
- package/dist/capacity-provider/source-workspace.d.ts +69 -37
- package/dist/capacity-provider/source-workspace.js +36 -5
- package/dist/content/validation/auxiliary-content-schemas.d.ts +10 -10
- package/dist/content/validation/content-model-schemas.d.ts +613 -613
- package/dist/content/validation/content-model-schemas.js +14 -2
- package/dist/content/validation/portable-content-data.d.ts +22 -22
- package/dist/deployment/postgres/requirements.d.ts +3 -2
- package/dist/deployment/postgres/requirements.js +3 -1
- package/dist/operator-contracts/canonical-command-tree.js +20 -6
- package/dist/operator-contracts/catalog/communication-operations.d.ts +2 -0
- package/dist/operator-contracts/catalog/workdays/profile-commands.js +4 -4
- package/dist/operator-contracts/catalog/workdays/profile-operations.d.ts +28 -3
- package/dist/operator-contracts/catalog/workdays/profile-operations.js +10 -8
- package/dist/operator-contracts/catalog/workdays/selection-commands.js +13 -0
- package/dist/operator-contracts/command-tree.d.ts +1 -1
- package/dist/operator-contracts/communication/contracts.d.ts +6 -0
- package/dist/operator-contracts/communication/contracts.js +2 -0
- package/dist/operator-contracts/control-plane-operations.d.ts +34 -3
- package/dist/operator-contracts/control-plane-operations.js +1 -0
- package/dist/operator-contracts/index.d.ts +0 -1
- package/dist/operator-contracts/index.js +0 -1
- package/dist/operator-contracts/workday-lifecycle.d.ts +8 -20
- package/dist/operator-contracts/workday-lifecycle.js +15 -5
- package/dist/secrets-capability/shared-resources.d.ts +6 -6
- package/package.json +1 -1
- package/dist/agent-capacity/policy/capacity/allocations/allocation-hierarchy.d.ts +0 -29
- package/dist/agent-capacity/policy/capacity/allocations/allocation-hierarchy.js +0 -110
- package/dist/operator-contracts/workday-profile.d.ts +0 -72
- package/dist/operator-contracts/workday-profile.js +0 -106
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { type WorkdayBorrowingEvidence } from './workday-profile.js';
|
|
2
1
|
import type { WorkdayAgentSelection } from '../agent-capacity/workday.js';
|
|
3
2
|
export { normalizeWorkdayAgentSelection } from '../agent-capacity/workday.js';
|
|
4
3
|
export type WorkdayDemandMode = 'planning' | 'acting';
|
|
@@ -7,14 +6,22 @@ export interface WorkdayIntent {
|
|
|
7
6
|
teamId: string;
|
|
8
7
|
profileId: string;
|
|
9
8
|
projects: 'all' | string[];
|
|
9
|
+
/** Selects the existing execution custody mode; omission defaults safely to simulation. */
|
|
10
|
+
executionMode?: 'simulation' | 'production';
|
|
10
11
|
startsAt: string;
|
|
11
12
|
endsAt?: string;
|
|
12
13
|
durationSeconds?: number;
|
|
13
14
|
objectiveFilters?: string[];
|
|
15
|
+
/** Runs cooperative planning profiles without admitting accepted acting work. */
|
|
16
|
+
planningOnly?: boolean;
|
|
17
|
+
/** Selects exact governed proposals for cooperative planning and estimating. */
|
|
18
|
+
proposalIds?: string[];
|
|
14
19
|
/** Selects accepted decisions for API-derived acting work; it does not grant acting authority. */
|
|
15
20
|
decisionIds?: string[];
|
|
16
21
|
/** Limits cooperative planning; acting still requires accepted decision/estimate authority. */
|
|
17
22
|
agentSelection?: Partial<WorkdayAgentSelection>;
|
|
23
|
+
/** High-level overrides of the canonical workday allocation policy. */
|
|
24
|
+
allocation?: Partial<Pick<import('../agent-capacity/contracts/capacity/workdays/workday-allocation.js').WorkdayPolicy, 'planningPercent' | 'allocationWeight' | 'planningTurnMaximumSeconds' | 'projectPercentages' | 'agentClassPercentages'>>;
|
|
18
25
|
operatorConstraints?: {
|
|
19
26
|
providerIds?: string[];
|
|
20
27
|
maxConcurrency?: number;
|
|
@@ -42,8 +49,6 @@ export interface WorkdaySelectedDemand {
|
|
|
42
49
|
export interface WorkdayClassAccounting {
|
|
43
50
|
classSlug: string;
|
|
44
51
|
allocatedSeconds: number;
|
|
45
|
-
borrowedSeconds: number;
|
|
46
|
-
lentSeconds: number;
|
|
47
52
|
idleSeconds: number;
|
|
48
53
|
reservedSeconds: number;
|
|
49
54
|
activeSeconds: number;
|
|
@@ -65,11 +70,9 @@ export interface WorkdayPreflightReceipt {
|
|
|
65
70
|
reservationDigest: string;
|
|
66
71
|
selectedDemands: WorkdaySelectedDemand[];
|
|
67
72
|
classAccounting: WorkdayClassAccounting[];
|
|
68
|
-
borrowing: WorkdayBorrowingEvidence[];
|
|
69
73
|
startsAt: string;
|
|
70
74
|
endsAt: string;
|
|
71
75
|
maxConcurrency: number;
|
|
72
|
-
reserveSeconds: number;
|
|
73
76
|
preflightDigest: string;
|
|
74
77
|
expiresAt: string;
|
|
75
78
|
}
|
|
@@ -98,21 +101,6 @@ export interface WorkdayStartReceipt {
|
|
|
98
101
|
providerReceiptRefs: string[];
|
|
99
102
|
transactionReceiptId: string;
|
|
100
103
|
}
|
|
101
|
-
export interface WorkdaySchedule {
|
|
102
|
-
schemaVersion: 'treeseed.workday-schedule/v1';
|
|
103
|
-
id: string;
|
|
104
|
-
teamId: string;
|
|
105
|
-
profileId: string;
|
|
106
|
-
profileVersion: string;
|
|
107
|
-
projectScope: 'all' | string[];
|
|
108
|
-
cadence: {
|
|
109
|
-
timezone: string;
|
|
110
|
-
expression: string;
|
|
111
|
-
};
|
|
112
|
-
durationSeconds: number;
|
|
113
|
-
status: 'active' | 'paused' | 'retired';
|
|
114
|
-
nextStartAt: string | null;
|
|
115
|
-
}
|
|
116
104
|
export interface WorkdaySettlement {
|
|
117
105
|
schemaVersion: 'treeseed.workday-settlement/v1';
|
|
118
106
|
workdayId: string;
|
|
@@ -1,19 +1,32 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { workdayAllocationOverridesSchema } from "../agent-capacity/contracts/capacity/workdays/workday-allocation.js";
|
|
2
2
|
import { normalizeWorkdayAgentSelection } from "../agent-capacity/workday.js";
|
|
3
3
|
function validateWorkdayIntent(intent) {
|
|
4
4
|
const diagnostics = [];
|
|
5
5
|
if (intent.schemaVersion !== "treeseed.workday-intent/v1") diagnostics.push({ code: "schema_version_invalid", path: "schemaVersion", message: "Unsupported workday intent schema." });
|
|
6
6
|
if (!intent.teamId.trim()) diagnostics.push({ code: "team_required", path: "teamId", message: "Team identity is required." });
|
|
7
7
|
if (!intent.profileId.trim()) diagnostics.push({ code: "profile_required", path: "profileId", message: "Allocation profile identity is required." });
|
|
8
|
-
if (intent.
|
|
8
|
+
if (intent.executionMode !== void 0 && !["simulation", "production"].includes(intent.executionMode)) diagnostics.push({ code: "execution_mode_invalid", path: "executionMode", message: "Select simulation or production custody." });
|
|
9
|
+
if (intent.endsAt !== void 0 && intent.durationSeconds !== void 0) diagnostics.push({ code: "time_range_ambiguous", path: "endsAt", message: "Specify endsAt or durationSeconds, not both; omission uses the team policy duration." });
|
|
9
10
|
const start = Date.parse(intent.startsAt);
|
|
10
11
|
if (!Number.isFinite(start)) diagnostics.push({ code: "start_invalid", path: "startsAt", message: "startsAt must be an ISO timestamp." });
|
|
11
12
|
if (intent.endsAt !== void 0 && (!Number.isFinite(Date.parse(intent.endsAt)) || Date.parse(intent.endsAt) <= start)) diagnostics.push({ code: "end_invalid", path: "endsAt", message: "endsAt must be a valid timestamp after startsAt." });
|
|
12
13
|
if (intent.durationSeconds !== void 0 && (!Number.isInteger(intent.durationSeconds) || intent.durationSeconds <= 0)) diagnostics.push({ code: "duration_invalid", path: "durationSeconds", message: "durationSeconds must be a positive integer." });
|
|
14
|
+
if (intent.planningOnly !== void 0 && typeof intent.planningOnly !== "boolean") diagnostics.push({ code: "planning_only_invalid", path: "planningOnly", message: "planningOnly must be boolean." });
|
|
15
|
+
if (intent.proposalIds !== void 0 && (!Array.isArray(intent.proposalIds) || intent.proposalIds.length === 0 || intent.proposalIds.length > 64 || intent.proposalIds.some((entry) => typeof entry !== "string" || !entry.trim() || entry.length > 128))) {
|
|
16
|
+
diagnostics.push({ code: "proposal_selection_invalid", path: "proposalIds", message: "Proposal selection must be a bounded nonempty array of proposal identities." });
|
|
17
|
+
}
|
|
13
18
|
if (intent.decisionIds !== void 0 && (!Array.isArray(intent.decisionIds) || intent.decisionIds.length === 0 || intent.decisionIds.length > 64 || intent.decisionIds.some((entry) => typeof entry !== "string" || !entry.trim() || entry.length > 128))) {
|
|
14
19
|
diagnostics.push({ code: "decision_selection_invalid", path: "decisionIds", message: "Decision selection must be a bounded nonempty array of decision identities." });
|
|
15
20
|
}
|
|
16
21
|
if (intent.agentSelection !== void 0) diagnostics.push(...validateWorkdayIntentSelection(intent.agentSelection));
|
|
22
|
+
if (intent.allocation !== void 0) {
|
|
23
|
+
const result = workdayAllocationOverridesSchema.safeParse(intent.allocation);
|
|
24
|
+
if (!result.success) diagnostics.push(...result.error.issues.map((issue) => ({
|
|
25
|
+
code: "allocation_invalid",
|
|
26
|
+
path: `allocation.${issue.path.join(".")}`,
|
|
27
|
+
message: issue.message
|
|
28
|
+
})));
|
|
29
|
+
}
|
|
17
30
|
return diagnostics;
|
|
18
31
|
}
|
|
19
32
|
function validateWorkdayIntentSelection(value) {
|
|
@@ -57,8 +70,6 @@ function validateWorkdayPreflight(receipt, now = /* @__PURE__ */ new Date()) {
|
|
|
57
70
|
else if (expiry <= now.getTime()) diagnostics.push({ code: "preflight_expired", path: "expiresAt", message: "Workday preflight has expired and must be regenerated." });
|
|
58
71
|
if (!Number.isFinite(Date.parse(receipt.startsAt)) || !Number.isFinite(Date.parse(receipt.endsAt)) || Date.parse(receipt.endsAt) <= Date.parse(receipt.startsAt)) diagnostics.push({ code: "preflight_time_range_invalid", path: "endsAt", message: "Preflight must bind a valid time range." });
|
|
59
72
|
if (!Number.isInteger(receipt.maxConcurrency) || receipt.maxConcurrency <= 0) diagnostics.push({ code: "preflight_concurrency_invalid", path: "maxConcurrency", message: "Preflight concurrency must be a positive integer." });
|
|
60
|
-
if (!Number.isFinite(receipt.reserveSeconds) || receipt.reserveSeconds < 0) diagnostics.push({ code: "preflight_reserve_invalid", path: "reserveSeconds", message: "Preflight reserve seconds must be finite and non-negative." });
|
|
61
|
-
for (const [index, evidence] of receipt.borrowing.entries()) diagnostics.push(...validateWorkdayBorrowingEvidence(evidence).map((diagnostic) => ({ ...diagnostic, path: `borrowing.${index}.${diagnostic.path}` })));
|
|
62
73
|
if (new Set(receipt.selectedDemands.map((demand) => demand.id)).size !== receipt.selectedDemands.length) diagnostics.push({ code: "preflight_demand_duplicate", path: "selectedDemands", message: "Preflight demand identities must be unique." });
|
|
63
74
|
return diagnostics;
|
|
64
75
|
}
|
|
@@ -76,7 +87,6 @@ function validateWorkdaySettlement(settlement) {
|
|
|
76
87
|
for (const [field, value] of Object.entries(accounting).filter(([field2]) => field2 !== "classSlug")) {
|
|
77
88
|
if (!Number.isFinite(value) || value < 0) diagnostics.push({ code: "settlement_accounting_invalid", path: `classAccounting.${index}.${field}`, message: "Settlement seconds must be finite and non-negative." });
|
|
78
89
|
}
|
|
79
|
-
if (accounting.lentSeconds > accounting.allocatedSeconds + accounting.borrowedSeconds) diagnostics.push({ code: "settlement_lending_invalid", path: `classAccounting.${index}.lentSeconds`, message: "A class cannot lend more capacity than it held." });
|
|
80
90
|
}
|
|
81
91
|
if (!settlement.preflightDigest.trim() || !settlement.settlementDigest.trim()) diagnostics.push({ code: "settlement_digest_required", path: "settlementDigest", message: "Settlement must bind its preflight and final accounting digests." });
|
|
82
92
|
if (!Number.isFinite(Date.parse(settlement.startedAt)) || !Number.isFinite(Date.parse(settlement.completedAt)) || Date.parse(settlement.completedAt) < Date.parse(settlement.startedAt)) diagnostics.push({ code: "settlement_time_range_invalid", path: "completedAt", message: "Settlement must bind a valid completion time at or after workday start." });
|
|
@@ -151,13 +151,13 @@ export declare const sharedResourceGrantSchema: z.ZodEffects<z.ZodObject<{
|
|
|
151
151
|
assignmentIds: z.ZodArray<z.ZodString, "many">;
|
|
152
152
|
}, "strict", z.ZodTypeAny, {
|
|
153
153
|
environment: "production" | "staging" | "shared";
|
|
154
|
-
projectIds: string[];
|
|
155
154
|
assignmentIds: string[];
|
|
155
|
+
projectIds: string[];
|
|
156
156
|
resourceIds: string[];
|
|
157
157
|
}, {
|
|
158
158
|
environment: "production" | "staging" | "shared";
|
|
159
|
-
projectIds: string[];
|
|
160
159
|
assignmentIds: string[];
|
|
160
|
+
projectIds: string[];
|
|
161
161
|
resourceIds: string[];
|
|
162
162
|
}>;
|
|
163
163
|
limits: z.ZodObject<{
|
|
@@ -192,8 +192,8 @@ export declare const sharedResourceGrantSchema: z.ZodEffects<z.ZodObject<{
|
|
|
192
192
|
expiresAt: string | null;
|
|
193
193
|
scope: {
|
|
194
194
|
environment: "production" | "staging" | "shared";
|
|
195
|
-
projectIds: string[];
|
|
196
195
|
assignmentIds: string[];
|
|
196
|
+
projectIds: string[];
|
|
197
197
|
resourceIds: string[];
|
|
198
198
|
};
|
|
199
199
|
origin: "explicit" | "default-policy";
|
|
@@ -228,8 +228,8 @@ export declare const sharedResourceGrantSchema: z.ZodEffects<z.ZodObject<{
|
|
|
228
228
|
expiresAt: string | null;
|
|
229
229
|
scope: {
|
|
230
230
|
environment: "production" | "staging" | "shared";
|
|
231
|
-
projectIds: string[];
|
|
232
231
|
assignmentIds: string[];
|
|
232
|
+
projectIds: string[];
|
|
233
233
|
resourceIds: string[];
|
|
234
234
|
};
|
|
235
235
|
origin: "explicit" | "default-policy";
|
|
@@ -264,8 +264,8 @@ export declare const sharedResourceGrantSchema: z.ZodEffects<z.ZodObject<{
|
|
|
264
264
|
expiresAt: string | null;
|
|
265
265
|
scope: {
|
|
266
266
|
environment: "production" | "staging" | "shared";
|
|
267
|
-
projectIds: string[];
|
|
268
267
|
assignmentIds: string[];
|
|
268
|
+
projectIds: string[];
|
|
269
269
|
resourceIds: string[];
|
|
270
270
|
};
|
|
271
271
|
origin: "explicit" | "default-policy";
|
|
@@ -300,8 +300,8 @@ export declare const sharedResourceGrantSchema: z.ZodEffects<z.ZodObject<{
|
|
|
300
300
|
expiresAt: string | null;
|
|
301
301
|
scope: {
|
|
302
302
|
environment: "production" | "staging" | "shared";
|
|
303
|
-
projectIds: string[];
|
|
304
303
|
assignmentIds: string[];
|
|
304
|
+
projectIds: string[];
|
|
305
305
|
resourceIds: string[];
|
|
306
306
|
};
|
|
307
307
|
origin: "explicit" | "default-policy";
|
package/package.json
CHANGED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import type { CapacityAdmissionCounterClaim, CapacityAdmissionReasonCode, CapacityAllocationSetV2 } from '../../../allocation.js';
|
|
2
|
-
interface AllocationHierarchyInput {
|
|
3
|
-
allocation: CapacityAllocationSetV2;
|
|
4
|
-
workdayId: string;
|
|
5
|
-
totalSeconds: number;
|
|
6
|
-
requestedSeconds: number;
|
|
7
|
-
selectedSliceIds: string[];
|
|
8
|
-
committedBySlice: Record<string, number>;
|
|
9
|
-
committedBorrowedByRule?: Record<string, number>;
|
|
10
|
-
reserveCommittedSeconds?: number;
|
|
11
|
-
approvedBorrowingRuleIds?: string[];
|
|
12
|
-
}
|
|
13
|
-
export interface AllocationHierarchyDecision {
|
|
14
|
-
reasons: CapacityAdmissionReasonCode[];
|
|
15
|
-
explanation: Array<{
|
|
16
|
-
gate: string;
|
|
17
|
-
allowed: boolean;
|
|
18
|
-
remaining?: number;
|
|
19
|
-
detail?: string;
|
|
20
|
-
}>;
|
|
21
|
-
counterClaims: CapacityAdmissionCounterClaim[];
|
|
22
|
-
maxReservableSeconds: number;
|
|
23
|
-
requiresApproval: boolean;
|
|
24
|
-
calculations: Record<string, unknown>;
|
|
25
|
-
priorityBand: 'minimum' | 'target' | 'normal' | 'overflow';
|
|
26
|
-
priorityScore: number;
|
|
27
|
-
}
|
|
28
|
-
export declare function evaluateAllocationHierarchy(input: AllocationHierarchyInput): AllocationHierarchyDecision;
|
|
29
|
-
export {};
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
function sliceLimits(allocation, totalSeconds, slice, cache = /* @__PURE__ */ new Map()) {
|
|
2
|
-
const cached = cache.get(slice.id);
|
|
3
|
-
if (cached) return cached;
|
|
4
|
-
const parent = slice.parentSliceId ? allocation.slices.find((candidate) => candidate.id === slice.parentSliceId) : null;
|
|
5
|
-
const basis = parent ? sliceLimits(allocation, totalSeconds, parent, cache).target : totalSeconds;
|
|
6
|
-
const limits = {
|
|
7
|
-
min: basis * slice.policy.minPercent / 100,
|
|
8
|
-
target: basis * slice.policy.targetPercent / 100,
|
|
9
|
-
max: basis * slice.policy.maxPercent / 100,
|
|
10
|
-
hard: basis * slice.policy.hardCapPercent / 100
|
|
11
|
-
};
|
|
12
|
-
cache.set(slice.id, limits);
|
|
13
|
-
return limits;
|
|
14
|
-
}
|
|
15
|
-
function claim(id, scope, scopeId, periodKey, hardLimit, amount) {
|
|
16
|
-
return amount > 0 ? { id, scope, scopeId, periodKey, hardLimit, amount, release: "settlement-difference" } : null;
|
|
17
|
-
}
|
|
18
|
-
function evaluateAllocationHierarchy(input) {
|
|
19
|
-
const reasons = [];
|
|
20
|
-
const explanation = [];
|
|
21
|
-
const counterClaims = [];
|
|
22
|
-
const calculations = {};
|
|
23
|
-
const approved = new Set(input.approvedBorrowingRuleIds ?? []);
|
|
24
|
-
const cache = /* @__PURE__ */ new Map();
|
|
25
|
-
let remaining = Number.POSITIVE_INFINITY;
|
|
26
|
-
let requiresApproval = false;
|
|
27
|
-
let priorityScore = 4;
|
|
28
|
-
for (const sliceId of input.selectedSliceIds) {
|
|
29
|
-
const slice = input.allocation.slices.find((candidate) => candidate.id === sliceId);
|
|
30
|
-
if (!slice) {
|
|
31
|
-
reasons.push("allocation_slice_missing");
|
|
32
|
-
explanation.push({ gate: `allocation:${sliceId}`, allowed: false, detail: "selected slice does not exist" });
|
|
33
|
-
continue;
|
|
34
|
-
}
|
|
35
|
-
const limits = sliceLimits(input.allocation, input.totalSeconds, slice, cache);
|
|
36
|
-
const committed = input.committedBySlice[sliceId] ?? 0;
|
|
37
|
-
const normalAvailable = Math.max(0, limits.max - committed);
|
|
38
|
-
const overflowAvailable = Math.max(0, limits.hard - Math.max(committed, limits.max));
|
|
39
|
-
let available = normalAvailable;
|
|
40
|
-
let overflowSource = "none";
|
|
41
|
-
let overflowRuleId = null;
|
|
42
|
-
let overflowSourceAvailable = 0;
|
|
43
|
-
if (overflowAvailable > 0) {
|
|
44
|
-
const rule = input.allocation.borrowingRules.find((candidate) => candidate.toSliceId === sliceId);
|
|
45
|
-
if (rule) {
|
|
46
|
-
const donor = input.allocation.slices.find((candidate) => candidate.id === rule.fromSliceId);
|
|
47
|
-
if (donor) {
|
|
48
|
-
const donorLimits = sliceLimits(input.allocation, input.totalSeconds, donor, cache);
|
|
49
|
-
const donorCommitted = input.committedBySlice[donor.id] ?? 0;
|
|
50
|
-
const donorAvailable = Math.max(0, donorLimits.target - Math.max(donorCommitted, donorLimits.min));
|
|
51
|
-
const ruleLimit = donorLimits.target * rule.maxPercent / 100;
|
|
52
|
-
const ruleAvailable = Math.max(0, ruleLimit - (input.committedBorrowedByRule?.[rule.id] ?? 0));
|
|
53
|
-
overflowSource = "borrowing-rule";
|
|
54
|
-
overflowRuleId = rule.id;
|
|
55
|
-
overflowSourceAvailable = Math.min(donorAvailable, ruleAvailable);
|
|
56
|
-
}
|
|
57
|
-
} else if (!slice.parentSliceId) {
|
|
58
|
-
overflowSource = "reserve";
|
|
59
|
-
overflowSourceAvailable = Math.max(0, input.totalSeconds * input.allocation.reservePolicy.percent / 100 - (input.reserveCommittedSeconds ?? 0));
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
const usableOverflow = Math.min(overflowAvailable, overflowSourceAvailable);
|
|
63
|
-
if (input.allocation.reservePolicy.overflow === "borrow") available += usableOverflow;
|
|
64
|
-
else if (input.requestedSeconds > normalAvailable && input.allocation.reservePolicy.overflow === "approval-required") {
|
|
65
|
-
requiresApproval = true;
|
|
66
|
-
reasons.push("allocation_borrowing_approval_required");
|
|
67
|
-
}
|
|
68
|
-
if (overflowRuleId) {
|
|
69
|
-
const rule = input.allocation.borrowingRules.find((candidate) => candidate.id === overflowRuleId);
|
|
70
|
-
if (rule.requiresApproval && !approved.has(rule.id) && input.requestedSeconds > normalAvailable) {
|
|
71
|
-
requiresApproval = true;
|
|
72
|
-
reasons.push("allocation_borrowing_approval_required");
|
|
73
|
-
available = normalAvailable;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
if (input.requestedSeconds > available && !reasons.includes("allocation_borrowing_approval_required")) {
|
|
77
|
-
reasons.push(input.allocation.reservePolicy.overflow === "deny" ? "allocation_borrowing_denied" : "allocation_hard_cap_exhausted");
|
|
78
|
-
}
|
|
79
|
-
remaining = Math.min(remaining, available);
|
|
80
|
-
const ownAmount = Math.min(input.requestedSeconds, normalAvailable);
|
|
81
|
-
const overflowAmount = Math.max(0, input.requestedSeconds - ownAmount);
|
|
82
|
-
const ownClaim = claim(`allocation-slice:${input.allocation.id}:${slice.id}:${input.workdayId}`, "allocation-slice", `${input.allocation.id}:${slice.id}`, input.workdayId, limits.max, ownAmount);
|
|
83
|
-
if (ownClaim) counterClaims.push(ownClaim);
|
|
84
|
-
if (overflowAmount > 0 && overflowAmount <= usableOverflow && input.allocation.reservePolicy.overflow === "borrow") {
|
|
85
|
-
const overflowClaim = claim(`allocation-overflow:${input.allocation.id}:${slice.id}:${input.workdayId}`, "allocation-overflow", `${input.allocation.id}:${slice.id}`, input.workdayId, Math.max(0, limits.hard - limits.max), overflowAmount);
|
|
86
|
-
if (overflowClaim) counterClaims.push(overflowClaim);
|
|
87
|
-
if (overflowSource === "reserve") {
|
|
88
|
-
const reserveClaim = claim(`allocation-reserve:${input.allocation.id}:${input.workdayId}`, "allocation-reserve", input.allocation.id, input.workdayId, input.totalSeconds * input.allocation.reservePolicy.percent / 100, overflowAmount);
|
|
89
|
-
if (reserveClaim) counterClaims.push(reserveClaim);
|
|
90
|
-
} else if (overflowRuleId) {
|
|
91
|
-
const rule = input.allocation.borrowingRules.find((candidate) => candidate.id === overflowRuleId);
|
|
92
|
-
const donor = input.allocation.slices.find((candidate) => candidate.id === rule.fromSliceId);
|
|
93
|
-
const donorLimits = sliceLimits(input.allocation, input.totalSeconds, donor, cache);
|
|
94
|
-
const donorClaim = claim(`allocation-slice:${input.allocation.id}:${donor.id}:${input.workdayId}`, "allocation-slice", `${input.allocation.id}:${donor.id}`, input.workdayId, donorLimits.target, overflowAmount);
|
|
95
|
-
const ruleClaim = claim(`allocation-borrow:${input.allocation.id}:${rule.id}:${input.workdayId}`, "allocation-borrow", `${input.allocation.id}:${rule.id}`, input.workdayId, donorLimits.target * rule.maxPercent / 100, overflowAmount);
|
|
96
|
-
if (donorClaim) counterClaims.push(donorClaim);
|
|
97
|
-
if (ruleClaim) counterClaims.push(ruleClaim);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
const position = committed < limits.min ? "below-minimum" : committed < limits.target ? "below-target" : committed < limits.max ? "above-target" : "overflow";
|
|
101
|
-
priorityScore = Math.min(priorityScore, position === "below-minimum" ? 4 : position === "below-target" ? 3 : position === "above-target" ? 2 : 1);
|
|
102
|
-
calculations[sliceId] = { committed, ...limits, normalAvailable, overflowAvailable, overflowSource, overflowSourceAvailable, usableOverflow, position };
|
|
103
|
-
explanation.push({ gate: `allocation:${sliceId}`, allowed: input.requestedSeconds <= available, remaining: available, detail: JSON.stringify(calculations[sliceId]) });
|
|
104
|
-
}
|
|
105
|
-
const priorityBand = priorityScore === 4 ? "minimum" : priorityScore === 3 ? "target" : priorityScore === 2 ? "normal" : "overflow";
|
|
106
|
-
return { reasons: [...new Set(reasons)], explanation, counterClaims, maxReservableSeconds: Number.isFinite(remaining) ? Math.max(0, remaining) : 0, requiresApproval, calculations, priorityBand, priorityScore };
|
|
107
|
-
}
|
|
108
|
-
export {
|
|
109
|
-
evaluateAllocationHierarchy
|
|
110
|
-
};
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
export type WorkdayDemandSourcePolicy = 'approved-decisions' | 'planning-inputs' | 'questions' | 'proposals' | 'knowledge-gaps' | 'reliability-campaigns';
|
|
2
|
-
export interface WorkdayClassAllocation {
|
|
3
|
-
classSlug: string;
|
|
4
|
-
minimumPercent: number;
|
|
5
|
-
targetPercent: number;
|
|
6
|
-
maximumPercent: number;
|
|
7
|
-
mayLend: boolean;
|
|
8
|
-
mayBorrow: boolean;
|
|
9
|
-
}
|
|
10
|
-
export interface WorkdayAllocationProfile {
|
|
11
|
-
schemaVersion: 'treeseed.workday-allocation-profile/v1';
|
|
12
|
-
id: string;
|
|
13
|
-
version: string;
|
|
14
|
-
projects: 'all' | string[];
|
|
15
|
-
excludedClassSlugs?: Record<string, string[]>;
|
|
16
|
-
classes: WorkdayClassAllocation[];
|
|
17
|
-
demandSources: WorkdayDemandSourcePolicy[];
|
|
18
|
-
actingDecisionRequired: true;
|
|
19
|
-
defaultDurationSeconds: number;
|
|
20
|
-
maxConcurrency: number;
|
|
21
|
-
reservePercent: number;
|
|
22
|
-
prioritization: {
|
|
23
|
-
strategy: 'priority-then-age' | 'age-then-priority' | 'weighted-fair';
|
|
24
|
-
starvationLimitSeconds: number;
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
export interface RepositoryWorkdayProfileBundle {
|
|
28
|
-
schemaVersion: 'treeseed.workday-allocation-profile-bundle/v1';
|
|
29
|
-
profiles: WorkdayAllocationProfile[];
|
|
30
|
-
}
|
|
31
|
-
export interface ProjectAgentClassMembership {
|
|
32
|
-
projectId: string;
|
|
33
|
-
agentId: string;
|
|
34
|
-
classSlug: string;
|
|
35
|
-
status: 'active' | 'paused' | 'archived';
|
|
36
|
-
}
|
|
37
|
-
export interface ProjectAgentIdentity {
|
|
38
|
-
projectId: string;
|
|
39
|
-
agentId: string;
|
|
40
|
-
}
|
|
41
|
-
export interface ProjectClassCatalog {
|
|
42
|
-
projectId: string;
|
|
43
|
-
classSlugs: string[];
|
|
44
|
-
}
|
|
45
|
-
export interface WorkdayProfileDiagnostic {
|
|
46
|
-
code: string;
|
|
47
|
-
path: string;
|
|
48
|
-
message: string;
|
|
49
|
-
}
|
|
50
|
-
export interface WorkdayBorrowingEvidence {
|
|
51
|
-
workdayId: string;
|
|
52
|
-
borrowerClassSlug: string;
|
|
53
|
-
lenderClassSlug: string;
|
|
54
|
-
borrowedSeconds: number;
|
|
55
|
-
lendingPermitted: boolean;
|
|
56
|
-
borrowingPermitted: boolean;
|
|
57
|
-
lenderAllocatedSecondsBefore: number;
|
|
58
|
-
lenderAllocatedSecondsAfter: number;
|
|
59
|
-
lenderMinimumSeconds: number;
|
|
60
|
-
borrowerAllocatedSecondsBefore: number;
|
|
61
|
-
borrowerAllocatedSecondsAfter: number;
|
|
62
|
-
borrowerMaximumSeconds: number;
|
|
63
|
-
profileId: string;
|
|
64
|
-
profileVersion: string;
|
|
65
|
-
profileDigest: string;
|
|
66
|
-
}
|
|
67
|
-
export declare function validateOneClassPerProjectAgent(memberships: ProjectAgentClassMembership[], expectedAgents?: ProjectAgentIdentity[]): WorkdayProfileDiagnostic[];
|
|
68
|
-
export declare function validateWorkdayAllocationProfile(profile: WorkdayAllocationProfile, classCatalogs?: ProjectClassCatalog[]): WorkdayProfileDiagnostic[];
|
|
69
|
-
export declare function normalizeWorkdayAllocationProfile(profile: WorkdayAllocationProfile): WorkdayAllocationProfile;
|
|
70
|
-
export declare function validateRepositoryWorkdayProfileBundle(bundle: RepositoryWorkdayProfileBundle, classCatalogs?: ProjectClassCatalog[]): WorkdayProfileDiagnostic[];
|
|
71
|
-
export declare function normalizeRepositoryWorkdayProfileBundle(bundle: RepositoryWorkdayProfileBundle): RepositoryWorkdayProfileBundle;
|
|
72
|
-
export declare function validateWorkdayBorrowingEvidence(evidence: WorkdayBorrowingEvidence): WorkdayProfileDiagnostic[];
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
const PERCENT_TOLERANCE = 1e-6;
|
|
2
|
-
const PROFILE_VERSION = /^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
3
|
-
function validateOneClassPerProjectAgent(memberships, expectedAgents = []) {
|
|
4
|
-
const diagnostics = [];
|
|
5
|
-
const byAgent = /* @__PURE__ */ new Map();
|
|
6
|
-
for (const membership of memberships) {
|
|
7
|
-
const key = `${membership.projectId}:${membership.agentId}`;
|
|
8
|
-
byAgent.set(key, [...byAgent.get(key) ?? [], membership]);
|
|
9
|
-
}
|
|
10
|
-
for (const [key, values] of byAgent) {
|
|
11
|
-
if (values.length !== 1) diagnostics.push({ code: "agent_class_membership_multiple", path: `memberships.${key}`, message: "Each project agent must have exactly one allocation-class membership." });
|
|
12
|
-
if (values.some((value) => value.status !== "active")) diagnostics.push({ code: "agent_class_membership_inactive", path: `memberships.${key}`, message: "The single allocation-class membership must be active." });
|
|
13
|
-
}
|
|
14
|
-
for (const agent of expectedAgents) {
|
|
15
|
-
const key = `${agent.projectId}:${agent.agentId}`;
|
|
16
|
-
if (!byAgent.has(key)) diagnostics.push({ code: "agent_class_membership_missing", path: `memberships.${key}`, message: "Each project agent must have exactly one allocation-class membership." });
|
|
17
|
-
}
|
|
18
|
-
return diagnostics;
|
|
19
|
-
}
|
|
20
|
-
function validateWorkdayAllocationProfile(profile, classCatalogs) {
|
|
21
|
-
const diagnostics = [];
|
|
22
|
-
if (profile.schemaVersion !== "treeseed.workday-allocation-profile/v1") diagnostics.push({ code: "schema_version_invalid", path: "schemaVersion", message: "Unsupported workday allocation profile schema." });
|
|
23
|
-
if (!profile.id.trim()) diagnostics.push({ code: "profile_id_required", path: "id", message: "Profile id is required." });
|
|
24
|
-
if (!PROFILE_VERSION.test(profile.version)) diagnostics.push({ code: "profile_version_invalid", path: "version", message: "Profile version must be a semantic version." });
|
|
25
|
-
if (profile.actingDecisionRequired !== true) diagnostics.push({ code: "acting_decision_requirement_invalid", path: "actingDecisionRequired", message: "Acting work must require an approved decision." });
|
|
26
|
-
if (profile.projects !== "all" && profile.projects.length === 0) diagnostics.push({ code: "project_scope_empty", path: "projects", message: "Profile project scope must select at least one project or all." });
|
|
27
|
-
if (profile.demandSources.length === 0) diagnostics.push({ code: "demand_sources_empty", path: "demandSources", message: "At least one governed demand source is required." });
|
|
28
|
-
if (new Set(profile.demandSources).size !== profile.demandSources.length) diagnostics.push({ code: "demand_source_duplicate", path: "demandSources", message: "Demand sources must be unique." });
|
|
29
|
-
if (!Number.isInteger(profile.defaultDurationSeconds) || profile.defaultDurationSeconds <= 0) diagnostics.push({ code: "duration_invalid", path: "defaultDurationSeconds", message: "Default duration must be a positive integer number of seconds." });
|
|
30
|
-
if (!Number.isInteger(profile.maxConcurrency) || profile.maxConcurrency <= 0) diagnostics.push({ code: "concurrency_invalid", path: "maxConcurrency", message: "Maximum concurrency must be a positive integer." });
|
|
31
|
-
if (!Number.isFinite(profile.reservePercent) || profile.reservePercent < 0 || profile.reservePercent > 100) diagnostics.push({ code: "reserve_invalid", path: "reservePercent", message: "Reserve percent must be between 0 and 100." });
|
|
32
|
-
if (!Number.isFinite(profile.prioritization.starvationLimitSeconds) || profile.prioritization.starvationLimitSeconds <= 0) diagnostics.push({ code: "starvation_limit_invalid", path: "prioritization.starvationLimitSeconds", message: "Starvation limit must be positive." });
|
|
33
|
-
const classSlugs = /* @__PURE__ */ new Set();
|
|
34
|
-
let minimumTotal = 0;
|
|
35
|
-
let targetTotal = 0;
|
|
36
|
-
let maximumTotal = 0;
|
|
37
|
-
for (const [index, allocation] of profile.classes.entries()) {
|
|
38
|
-
const path = `classes.${index}`;
|
|
39
|
-
if (!allocation.classSlug.trim()) diagnostics.push({ code: "class_slug_required", path: `${path}.classSlug`, message: "Class slug is required." });
|
|
40
|
-
if (classSlugs.has(allocation.classSlug)) diagnostics.push({ code: "class_slug_duplicate", path: `${path}.classSlug`, message: `Class ${allocation.classSlug} is declared more than once.` });
|
|
41
|
-
classSlugs.add(allocation.classSlug);
|
|
42
|
-
const values = [allocation.minimumPercent, allocation.targetPercent, allocation.maximumPercent];
|
|
43
|
-
if (values.some((value) => !Number.isFinite(value) || value < 0 || value > 100) || allocation.minimumPercent > allocation.targetPercent || allocation.targetPercent > allocation.maximumPercent) diagnostics.push({ code: "class_allocation_invalid", path, message: "Class allocation must satisfy 0 <= minimum <= target <= maximum <= 100." });
|
|
44
|
-
minimumTotal += allocation.minimumPercent;
|
|
45
|
-
targetTotal += allocation.targetPercent;
|
|
46
|
-
maximumTotal += allocation.maximumPercent;
|
|
47
|
-
}
|
|
48
|
-
if (minimumTotal > 100 + PERCENT_TOLERANCE) diagnostics.push({ code: "minimum_total_invalid", path: "classes", message: "Class minimum percentages cannot total more than 100." });
|
|
49
|
-
if (Math.abs(targetTotal - 100) > PERCENT_TOLERANCE) diagnostics.push({ code: "target_total_invalid", path: "classes", message: "Class target percentages must total exactly 100." });
|
|
50
|
-
if (maximumTotal < 100 - PERCENT_TOLERANCE) diagnostics.push({ code: "maximum_total_invalid", path: "classes", message: "Class maximum percentages must total at least 100." });
|
|
51
|
-
const selectedProjects = classCatalogs === void 0 ? [] : profile.projects === "all" ? classCatalogs.map((catalog) => catalog.projectId) : profile.projects;
|
|
52
|
-
for (const projectId of selectedProjects) {
|
|
53
|
-
const catalog = classCatalogs?.find((candidate) => candidate.projectId === projectId);
|
|
54
|
-
if (!catalog) {
|
|
55
|
-
diagnostics.push({ code: "project_class_catalog_missing", path: `projects.${projectId}`, message: `No class catalog exists for selected project ${projectId}.` });
|
|
56
|
-
continue;
|
|
57
|
-
}
|
|
58
|
-
const exclusions = new Set(profile.excludedClassSlugs?.[projectId] ?? []);
|
|
59
|
-
for (const classSlug of classSlugs) {
|
|
60
|
-
if (!catalog.classSlugs.includes(classSlug) && !exclusions.has(classSlug)) diagnostics.push({ code: "class_missing_from_project", path: `projects.${projectId}.${classSlug}`, message: `Class ${classSlug} is missing from project ${projectId} and is not explicitly excluded.` });
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
return diagnostics;
|
|
64
|
-
}
|
|
65
|
-
function normalizeWorkdayAllocationProfile(profile) {
|
|
66
|
-
return {
|
|
67
|
-
...profile,
|
|
68
|
-
projects: profile.projects === "all" ? "all" : [...new Set(profile.projects)].sort(),
|
|
69
|
-
excludedClassSlugs: profile.excludedClassSlugs ? Object.fromEntries(Object.entries(profile.excludedClassSlugs).sort(([left], [right]) => left.localeCompare(right)).map(([projectId, slugs]) => [projectId, [...new Set(slugs)].sort()])) : void 0,
|
|
70
|
-
classes: [...profile.classes].sort((left, right) => left.classSlug.localeCompare(right.classSlug)),
|
|
71
|
-
demandSources: [...new Set(profile.demandSources)].sort()
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
function validateRepositoryWorkdayProfileBundle(bundle, classCatalogs) {
|
|
75
|
-
const diagnostics = [];
|
|
76
|
-
if (bundle.schemaVersion !== "treeseed.workday-allocation-profile-bundle/v1") diagnostics.push({ code: "bundle_schema_version_invalid", path: "schemaVersion", message: "Unsupported repository workday profile bundle schema." });
|
|
77
|
-
if (!Array.isArray(bundle.profiles) || bundle.profiles.length === 0) diagnostics.push({ code: "bundle_profiles_empty", path: "profiles", message: "A repository workday profile bundle must contain at least one profile." });
|
|
78
|
-
const profileIds = /* @__PURE__ */ new Set();
|
|
79
|
-
for (const [index, profile] of (Array.isArray(bundle.profiles) ? bundle.profiles : []).entries()) {
|
|
80
|
-
if (profileIds.has(profile.id)) diagnostics.push({ code: "bundle_profile_id_duplicate", path: `profiles.${index}.id`, message: `Stable profile id ${profile.id} is declared more than once; a repository bundle contains exactly one current generation per profile.` });
|
|
81
|
-
profileIds.add(profile.id);
|
|
82
|
-
diagnostics.push(...validateWorkdayAllocationProfile(profile, classCatalogs).map((diagnostic) => ({ ...diagnostic, path: `profiles.${index}.${diagnostic.path}` })));
|
|
83
|
-
}
|
|
84
|
-
return diagnostics;
|
|
85
|
-
}
|
|
86
|
-
function normalizeRepositoryWorkdayProfileBundle(bundle) {
|
|
87
|
-
return { schemaVersion: "treeseed.workday-allocation-profile-bundle/v1", profiles: [...bundle.profiles].map(normalizeWorkdayAllocationProfile).sort((left, right) => left.id.localeCompare(right.id) || left.version.localeCompare(right.version)) };
|
|
88
|
-
}
|
|
89
|
-
function validateWorkdayBorrowingEvidence(evidence) {
|
|
90
|
-
const diagnostics = [];
|
|
91
|
-
if (!evidence.lendingPermitted) diagnostics.push({ code: "lending_not_permitted", path: "lendingPermitted", message: "The profile does not permit the lender to lend capacity." });
|
|
92
|
-
if (!evidence.borrowingPermitted) diagnostics.push({ code: "borrowing_not_permitted", path: "borrowingPermitted", message: "The profile does not permit the borrower to borrow capacity." });
|
|
93
|
-
if (evidence.borrowedSeconds <= 0) diagnostics.push({ code: "borrowed_seconds_invalid", path: "borrowedSeconds", message: "Borrowed seconds must be positive." });
|
|
94
|
-
if (evidence.lenderAllocatedSecondsAfter < evidence.lenderMinimumSeconds) diagnostics.push({ code: "lender_minimum_violated", path: "lenderAllocatedSecondsAfter", message: "Borrowing cannot reduce the lender below its minimum." });
|
|
95
|
-
if (evidence.borrowerAllocatedSecondsAfter > evidence.borrowerMaximumSeconds) diagnostics.push({ code: "borrower_maximum_violated", path: "borrowerAllocatedSecondsAfter", message: "Borrowing cannot raise the borrower above its maximum." });
|
|
96
|
-
if (evidence.lenderAllocatedSecondsBefore - evidence.lenderAllocatedSecondsAfter !== evidence.borrowedSeconds || evidence.borrowerAllocatedSecondsAfter - evidence.borrowerAllocatedSecondsBefore !== evidence.borrowedSeconds) diagnostics.push({ code: "borrowing_accounting_mismatch", path: "borrowedSeconds", message: "Lender and borrower deltas must exactly equal the borrowed seconds." });
|
|
97
|
-
return diagnostics;
|
|
98
|
-
}
|
|
99
|
-
export {
|
|
100
|
-
normalizeRepositoryWorkdayProfileBundle,
|
|
101
|
-
normalizeWorkdayAllocationProfile,
|
|
102
|
-
validateOneClassPerProjectAgent,
|
|
103
|
-
validateRepositoryWorkdayProfileBundle,
|
|
104
|
-
validateWorkdayAllocationProfile,
|
|
105
|
-
validateWorkdayBorrowingEvidence
|
|
106
|
-
};
|