@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.
Files changed (53) hide show
  1. package/dist/.treeseed-build-complete.json +1 -1
  2. package/dist/agent-capacity/allocation.d.ts +0 -3
  3. package/dist/agent-capacity/allocation.js +0 -221
  4. package/dist/agent-capacity/contracts/capacity/assignments/agent-execution.d.ts +280 -84
  5. package/dist/agent-capacity/contracts/capacity/assignments/agent-execution.js +22 -1
  6. package/dist/agent-capacity/contracts/capacity/assignments/assignment-records.d.ts +5 -0
  7. package/dist/agent-capacity/contracts/capacity/workdays/assignment-allocation.d.ts +82 -0
  8. package/dist/agent-capacity/contracts/capacity/workdays/assignment-allocation.js +115 -0
  9. package/dist/agent-capacity/contracts/capacity/workdays/capability-accounting.d.ts +142 -0
  10. package/dist/agent-capacity/contracts/capacity/workdays/capability-accounting.js +51 -0
  11. package/dist/agent-capacity/contracts/capacity/workdays/workday-allocation.d.ts +167 -35
  12. package/dist/agent-capacity/contracts/capacity/workdays/workday-allocation.js +59 -20
  13. package/dist/agent-capacity/contracts/capacity/workdays/workday-records.d.ts +2 -21
  14. package/dist/agent-capacity/contracts/support/execution-graph.d.ts +18 -18
  15. package/dist/agent-capacity/contracts/support/time-capacity.d.ts +0 -14
  16. package/dist/agent-capacity/contracts/support/time-capacity.js +1 -8
  17. package/dist/agent-capacity/validation/agent-definition-schema.d.ts +338 -338
  18. package/dist/capacity/agents/agent-capacity.d.ts +2 -0
  19. package/dist/capacity/agents/agent-capacity.js +2 -0
  20. package/dist/capacity-provider/client.d.ts +5 -3
  21. package/dist/capacity-provider/contracts/capacity-facts.d.ts +14 -0
  22. package/dist/capacity-provider/contracts/governance.d.ts +6 -0
  23. package/dist/capacity-provider/sandbox-contracts.d.ts +55 -1
  24. package/dist/capacity-provider/sandbox-contracts.js +4 -0
  25. package/dist/capacity-provider/source-workspace.d.ts +69 -37
  26. package/dist/capacity-provider/source-workspace.js +36 -5
  27. package/dist/content/validation/auxiliary-content-schemas.d.ts +10 -10
  28. package/dist/content/validation/content-model-schemas.d.ts +613 -613
  29. package/dist/content/validation/content-model-schemas.js +14 -2
  30. package/dist/content/validation/portable-content-data.d.ts +22 -22
  31. package/dist/deployment/postgres/requirements.d.ts +3 -2
  32. package/dist/deployment/postgres/requirements.js +3 -1
  33. package/dist/operator-contracts/canonical-command-tree.js +20 -6
  34. package/dist/operator-contracts/catalog/communication-operations.d.ts +2 -0
  35. package/dist/operator-contracts/catalog/workdays/profile-commands.js +4 -4
  36. package/dist/operator-contracts/catalog/workdays/profile-operations.d.ts +28 -3
  37. package/dist/operator-contracts/catalog/workdays/profile-operations.js +10 -8
  38. package/dist/operator-contracts/catalog/workdays/selection-commands.js +13 -0
  39. package/dist/operator-contracts/command-tree.d.ts +1 -1
  40. package/dist/operator-contracts/communication/contracts.d.ts +6 -0
  41. package/dist/operator-contracts/communication/contracts.js +2 -0
  42. package/dist/operator-contracts/control-plane-operations.d.ts +34 -3
  43. package/dist/operator-contracts/control-plane-operations.js +1 -0
  44. package/dist/operator-contracts/index.d.ts +0 -1
  45. package/dist/operator-contracts/index.js +0 -1
  46. package/dist/operator-contracts/workday-lifecycle.d.ts +8 -20
  47. package/dist/operator-contracts/workday-lifecycle.js +15 -5
  48. package/dist/secrets-capability/shared-resources.d.ts +6 -6
  49. package/package.json +1 -1
  50. package/dist/agent-capacity/policy/capacity/allocations/allocation-hierarchy.d.ts +0 -29
  51. package/dist/agent-capacity/policy/capacity/allocations/allocation-hierarchy.js +0 -110
  52. package/dist/operator-contracts/workday-profile.d.ts +0 -72
  53. 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 { validateWorkdayBorrowingEvidence } from "./workday-profile.js";
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.endsAt === void 0 === (intent.durationSeconds === void 0)) diagnostics.push({ code: "time_range_ambiguous", path: "endsAt", message: "Specify exactly one of endsAt or durationSeconds." });
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@treeseed/sdk",
3
- "version": "0.13.0-rc.119",
3
+ "version": "0.13.0-rc.120",
4
4
  "description": "Portable TreeSeed contracts, standards, and typed remote control-plane clients.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -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
- };