@cleocode/contracts 2026.6.19 → 2026.7.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cleocode/contracts",
3
- "version": "2026.6.19",
3
+ "version": "2026.7.1",
4
4
  "description": "Domain types, interfaces, and contracts for the CLEO ecosystem",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -194,7 +194,7 @@
194
194
  "dependencies": {
195
195
  "zod": "^4.3.6",
196
196
  "zod-to-json-schema": "^3.25.2",
197
- "@cleocode/lafs": "2026.6.19"
197
+ "@cleocode/lafs": "2026.7.1"
198
198
  },
199
199
  "scripts": {
200
200
  "build": "tsc -b --force && node scripts/copy-assets.mjs && node scripts/emit-schemas.mjs",
package/src/index.ts CHANGED
@@ -777,6 +777,11 @@ export type {
777
777
  QueuePriorityClass,
778
778
  RateCheckRequest,
779
779
  RateResultResponse,
780
+ ResourceAdmitDisposition,
781
+ ResourceAdmitRequest,
782
+ ResourceAdmitResultResponse,
783
+ ResourceReleaseRequest,
784
+ ResourceReleaseResultResponse,
780
785
  ToolGrantedResponse,
781
786
  ToolGrantRequest,
782
787
  } from './lease-ipc/index.js';
@@ -808,6 +813,11 @@ export {
808
813
  QueuePriorityClassSchema,
809
814
  RateCheckRequestSchema,
810
815
  RateResultResponseSchema,
816
+ ResourceAdmitDispositionSchema,
817
+ ResourceAdmitRequestSchema,
818
+ ResourceAdmitResultResponseSchema,
819
+ ResourceReleaseRequestSchema,
820
+ ResourceReleaseResultResponseSchema,
811
821
  ToolGrantedResponseSchema,
812
822
  ToolGrantRequestSchema,
813
823
  } from './lease-ipc/index.js';
@@ -2057,6 +2067,21 @@ export type {
2057
2067
  } from './release/version-bump.js';
2058
2068
  // === Human Render Contract (Epic T10114, ADR-077) ===
2059
2069
  export * from './render/index.js';
2070
+ export type {
2071
+ AdmissionResult,
2072
+ GovernorMode,
2073
+ ResourceClass,
2074
+ ResourceDeferral,
2075
+ ResourceGrant,
2076
+ } from './resource-governor.js';
2077
+ // === Resource Governor — Never-OOM admission layer (T11999 / Epic T11992) ===
2078
+ export {
2079
+ DEFAULT_RESOURCE_RETRY_AFTER_MS,
2080
+ isResourceGrant,
2081
+ RESOURCE_BACKPRESSURE_CODE,
2082
+ RESOURCE_CLASSES,
2083
+ RESOURCE_DEFERRED_CODE,
2084
+ } from './resource-governor.js';
2060
2085
  // === Result Types (Dashboard, Stats, Log, Context, Sequence, Analysis, Deps) ===
2061
2086
  export type {
2062
2087
  BottleneckTask,
@@ -26,14 +26,14 @@ import {
26
26
 
27
27
  describe('lease-ipc v1.1 freeze guard (T11627)', () => {
28
28
  it('pins the parallel protocol version', () => {
29
- expect(LEASE_IPC_PROTOCOL_VERSION).toBe('1.1.0');
29
+ expect(LEASE_IPC_PROTOCOL_VERSION).toBe('1.2.0');
30
30
  });
31
31
 
32
32
  it('the version differs from the frozen supervisor-ipc v1.0 wire version', () => {
33
33
  // The two contracts are distinguished on the wire purely by this string;
34
34
  // it MUST NOT collide with '1.0.0'.
35
35
  expect(LEASE_IPC_PROTOCOL_VERSION).not.toBe('1.0.0');
36
- expect(isFrozenLeaseIpcVersion('1.1.0')).toBe(true);
36
+ expect(isFrozenLeaseIpcVersion('1.2.0')).toBe(true);
37
37
  expect(isFrozenLeaseIpcVersion('1.0.0')).toBe(false);
38
38
  });
39
39
 
@@ -46,6 +46,8 @@ describe('lease-ipc v1.1 freeze guard (T11627)', () => {
46
46
  'tool_grant',
47
47
  'queue_admit',
48
48
  'worker_heartbeat',
49
+ 'resource_admit',
50
+ 'resource_release',
49
51
  ]);
50
52
  });
51
53
 
@@ -60,6 +62,8 @@ describe('lease-ipc v1.1 freeze guard (T11627)', () => {
60
62
  'child_killed_unresponsive',
61
63
  'queue_admit_result',
62
64
  'heartbeat_ack',
65
+ 'resource_admit_result',
66
+ 'resource_release_result',
63
67
  'error',
64
68
  ]);
65
69
  });
@@ -87,6 +91,8 @@ describe('lease-ipc v1.1 freeze guard (T11627)', () => {
87
91
  'tool_grant',
88
92
  'queue_admit',
89
93
  'worker_heartbeat',
94
+ 'resource_admit',
95
+ 'resource_release',
90
96
  'lease_granted',
91
97
  'lease_queued',
92
98
  'lease_denied',
@@ -96,6 +102,8 @@ describe('lease-ipc v1.1 freeze guard (T11627)', () => {
96
102
  'child_killed_unresponsive',
97
103
  'queue_admit_result',
98
104
  'heartbeat_ack',
105
+ 'resource_admit_result',
106
+ 'resource_release_result',
99
107
  'error',
100
108
  ]);
101
109
  });
@@ -106,7 +114,7 @@ describe('lease-ipc v1.1 wire-shape parity with Rust serde (T11627)', () => {
106
114
  // Literal JSON shape produced by
107
115
  // cleo_supervisor::lease_ipc::LeaseEnvelope::request(...).to_ndjson().
108
116
  const wire = {
109
- protocol_version: '1.1.0',
117
+ protocol_version: '1.2.0',
110
118
  id: 'abc',
111
119
  direction: 'request',
112
120
  request: {
@@ -128,7 +136,7 @@ describe('lease-ipc v1.1 wire-shape parity with Rust serde (T11627)', () => {
128
136
 
129
137
  it('parses the exact JSON a Rust lease_granted response envelope serializes', () => {
130
138
  const wire = {
131
- protocol_version: '1.1.0',
139
+ protocol_version: '1.2.0',
132
140
  id: 'abc',
133
141
  direction: 'response',
134
142
  response: {
@@ -146,7 +154,7 @@ describe('lease-ipc v1.1 wire-shape parity with Rust serde (T11627)', () => {
146
154
 
147
155
  it('parses a Rust child_killed_unresponsive event envelope', () => {
148
156
  const wire = {
149
- protocol_version: '1.1.0',
157
+ protocol_version: '1.2.0',
150
158
  id: 'evt-1',
151
159
  direction: 'response',
152
160
  response: {
@@ -162,7 +170,7 @@ describe('lease-ipc v1.1 wire-shape parity with Rust serde (T11627)', () => {
162
170
 
163
171
  it('parses a reused v1.0 error response shape ({ kind, code, message })', () => {
164
172
  const wire = {
165
- protocol_version: '1.1.0',
173
+ protocol_version: '1.2.0',
166
174
  id: 'e',
167
175
  direction: 'response',
168
176
  response: { kind: 'error', code: 'E_LEASE_UNIMPLEMENTED', message: 'deferred handler' },
@@ -172,7 +180,7 @@ describe('lease-ipc v1.1 wire-shape parity with Rust serde (T11627)', () => {
172
180
 
173
181
  it('parses the exact JSON a Rust queue_admit request envelope serializes (T11630)', () => {
174
182
  const wire = {
175
- protocol_version: '1.1.0',
183
+ protocol_version: '1.2.0',
176
184
  id: 'qa-1',
177
185
  direction: 'request',
178
186
  request: {
@@ -192,7 +200,7 @@ describe('lease-ipc v1.1 wire-shape parity with Rust serde (T11627)', () => {
192
200
 
193
201
  it('parses the exact JSON a Rust queue_admit_result response envelope serializes (T11630)', () => {
194
202
  const wire = {
195
- protocol_version: '1.1.0',
203
+ protocol_version: '1.2.0',
196
204
  id: 'qa-1',
197
205
  direction: 'response',
198
206
  response: {
@@ -208,7 +216,7 @@ describe('lease-ipc v1.1 wire-shape parity with Rust serde (T11627)', () => {
208
216
 
209
217
  it('parses the exact JSON a Rust worker_heartbeat request envelope serializes (T11628)', () => {
210
218
  const wire = {
211
- protocol_version: '1.1.0',
219
+ protocol_version: '1.2.0',
212
220
  id: 'hb-1',
213
221
  direction: 'request',
214
222
  request: {
@@ -226,7 +234,7 @@ describe('lease-ipc v1.1 wire-shape parity with Rust serde (T11627)', () => {
226
234
 
227
235
  it('parses the exact JSON a Rust heartbeat_ack response envelope serializes (T11628)', () => {
228
236
  const wire = {
229
- protocol_version: '1.1.0',
237
+ protocol_version: '1.2.0',
230
238
  id: 'hb-1',
231
239
  direction: 'response',
232
240
  response: { kind: 'heartbeat_ack' },
@@ -238,9 +246,61 @@ describe('lease-ipc v1.1 wire-shape parity with Rust serde (T11627)', () => {
238
246
  }
239
247
  });
240
248
 
249
+ it('parses the exact JSON a Rust resource_admit request envelope serializes (T12001)', () => {
250
+ const wire = {
251
+ protocol_version: '1.2.0',
252
+ id: 'ra-1',
253
+ direction: 'request',
254
+ request: {
255
+ kind: 'resource_admit',
256
+ class: 'db-heavy',
257
+ holder_id: 'pid-42:db-heavy',
258
+ budget: 1,
259
+ },
260
+ };
261
+ const parsed = LeaseIpcEnvelopeSchema.safeParse(wire);
262
+ expect(parsed.success).toBe(true);
263
+ if (parsed.success && parsed.data.direction === 'request') {
264
+ expect(parsed.data.request.kind).toBe('resource_admit');
265
+ }
266
+ });
267
+
268
+ it('parses the exact JSON a Rust resource_admit_result response envelope serializes (T12001)', () => {
269
+ const wire = {
270
+ protocol_version: '1.2.0',
271
+ id: 'ra-1',
272
+ direction: 'response',
273
+ response: {
274
+ kind: 'resource_admit_result',
275
+ disposition: 'deferred',
276
+ retry_after_ms: 2000,
277
+ slots_remaining: 0,
278
+ },
279
+ };
280
+ const parsed = LeaseIpcEnvelopeSchema.safeParse(wire);
281
+ expect(parsed.success).toBe(true);
282
+ if (parsed.success && parsed.data.direction === 'response') {
283
+ expect(parsed.data.response.kind).toBe('resource_admit_result');
284
+ }
285
+ });
286
+
287
+ it('parses the exact JSON a Rust resource_release_result response envelope serializes (T12001)', () => {
288
+ const wire = {
289
+ protocol_version: '1.2.0',
290
+ id: 'rr-1',
291
+ direction: 'response',
292
+ response: { kind: 'resource_release_result', released: true, slots_remaining: 1 },
293
+ };
294
+ const parsed = LeaseIpcEnvelopeSchema.safeParse(wire);
295
+ expect(parsed.success).toBe(true);
296
+ if (parsed.success && parsed.data.direction === 'response') {
297
+ expect(parsed.data.response.kind).toBe('resource_release_result');
298
+ }
299
+ });
300
+
241
301
  it('rejects an unknown message kind (proves the union is closed)', () => {
242
302
  const wire = {
243
- protocol_version: '1.1.0',
303
+ protocol_version: '1.2.0',
244
304
  id: 'abc',
245
305
  direction: 'request',
246
306
  request: { kind: 'lease_teleport', scope: 'project', lane: 'tasks' },
@@ -250,7 +310,7 @@ describe('lease-ipc v1.1 wire-shape parity with Rust serde (T11627)', () => {
250
310
 
251
311
  it('rejects an unknown scope/lane enum value (snake_case enums are closed)', () => {
252
312
  const wire = {
253
- protocol_version: '1.1.0',
313
+ protocol_version: '1.2.0',
254
314
  id: 'abc',
255
315
  direction: 'request',
256
316
  request: {
@@ -298,6 +358,10 @@ function sampleRequestFor(kind: (typeof LEASE_IPC_REQUEST_KINDS)[number]): unkno
298
358
  };
299
359
  case 'worker_heartbeat':
300
360
  return { kind, child_id: 'worker-1', in_flight_llm: true };
361
+ case 'resource_admit':
362
+ return { kind, class: 'db-heavy', holder_id: 'pid-42:db-heavy', budget: 1 };
363
+ case 'resource_release':
364
+ return { kind, class: 'db-heavy', holder_id: 'pid-42:db-heavy' };
301
365
  }
302
366
  }
303
367
 
@@ -336,6 +400,10 @@ function sampleResponseFor(kind: (typeof LEASE_IPC_RESPONSE_KINDS)[number]): unk
336
400
  };
337
401
  case 'heartbeat_ack':
338
402
  return { kind };
403
+ case 'resource_admit_result':
404
+ return { kind, disposition: 'deferred', retry_after_ms: 2000, slots_remaining: 0 };
405
+ case 'resource_release_result':
406
+ return { kind, released: true, slots_remaining: 1 };
339
407
  case 'error':
340
408
  return { kind, code: 'E_X', message: 'x' };
341
409
  }
@@ -36,6 +36,11 @@ export type {
36
36
  QueuePriorityClass,
37
37
  RateCheckRequest,
38
38
  RateResultResponse,
39
+ ResourceAdmitDisposition,
40
+ ResourceAdmitRequest,
41
+ ResourceAdmitResultResponse,
42
+ ResourceReleaseRequest,
43
+ ResourceReleaseResultResponse,
39
44
  ToolGrantedResponse,
40
45
  ToolGrantRequest,
41
46
  WorkerHeartbeatRequest,
@@ -68,6 +73,11 @@ export {
68
73
  QueuePriorityClassSchema,
69
74
  RateCheckRequestSchema,
70
75
  RateResultResponseSchema,
76
+ ResourceAdmitDispositionSchema,
77
+ ResourceAdmitRequestSchema,
78
+ ResourceAdmitResultResponseSchema,
79
+ ResourceReleaseRequestSchema,
80
+ ResourceReleaseResultResponseSchema,
71
81
  ToolGrantedResponseSchema,
72
82
  ToolGrantRequestSchema,
73
83
  WorkerHeartbeatRequestSchema,
@@ -12,12 +12,12 @@
12
12
  *
13
13
  * ```jsonc
14
14
  * // request
15
- * { "protocol_version": "1.1.0", "id": "abc", "direction": "request",
15
+ * { "protocol_version": "1.2.0", "id": "abc", "direction": "request",
16
16
  * "request": { "kind": "lease_acquire", "scope": "project", "lane": "tasks",
17
17
  * "holder_id": "pid-42:tasks", "priority": 0, "ttl_ms": 30000,
18
18
  * "reentrant": true } }
19
19
  * // response
20
- * { "protocol_version": "1.1.0", "id": "abc", "direction": "response",
20
+ * { "protocol_version": "1.2.0", "id": "abc", "direction": "response",
21
21
  * "response": { "kind": "lease_granted", "scope": "project", "lane": "tasks",
22
22
  * "holder_id": "pid-42:tasks", "epoch": 7, "ttl_ms": 30000,
23
23
  * "expires_at_ms": 1000030000 } }
@@ -229,6 +229,50 @@ export const WorkerHeartbeatRequestSchema = z
229
229
  /** Worker-heartbeat request payload (inferred from {@link WorkerHeartbeatRequestSchema}). */
230
230
  export type WorkerHeartbeatRequest = z.infer<typeof WorkerHeartbeatRequestSchema>;
231
231
 
232
+ /**
233
+ * Admit (or defer) a heavy resource-intensive operation through the central
234
+ * per-class concurrency arbiter (T12001 · Epic T11992). Mirrors
235
+ * `ResourceAdmitReq`. `[wired]`
236
+ *
237
+ * The client computes `budget` from its local memory-pressure sample; the
238
+ * supervisor enforces the per-class in-flight COUNT centrally across processes,
239
+ * so heavy ops (builds, tests, exodus) can never co-schedule past the budget.
240
+ */
241
+ export const ResourceAdmitRequestSchema = z
242
+ .object({
243
+ /** Tag discriminating this request variant. */
244
+ kind: z.literal('resource_admit'),
245
+ /** The resource class (e.g. `"db-heavy"`, `"full-build"`, `"test-run"`). */
246
+ class: z.string().min(1),
247
+ /** The caller holding the slot — process/worktree-unique; the release key. */
248
+ holder_id: z.string().min(1),
249
+ /** The client-computed budget: the max concurrent holders for this class. */
250
+ budget: z.number().int().nonnegative(),
251
+ })
252
+ .strict();
253
+
254
+ /** Resource-admit request payload (inferred from {@link ResourceAdmitRequestSchema}). */
255
+ export type ResourceAdmitRequest = z.infer<typeof ResourceAdmitRequestSchema>;
256
+
257
+ /**
258
+ * Release a previously-admitted resource slot (T12001). Mirrors
259
+ * `ResourceReleaseReq`. Idempotent — releasing an unknown holder is a no-op.
260
+ * `[wired]`
261
+ */
262
+ export const ResourceReleaseRequestSchema = z
263
+ .object({
264
+ /** Tag discriminating this request variant. */
265
+ kind: z.literal('resource_release'),
266
+ /** The resource class the slot belongs to. */
267
+ class: z.string().min(1),
268
+ /** The holder that was admitted (matches the admit `holder_id`). */
269
+ holder_id: z.string().min(1),
270
+ })
271
+ .strict();
272
+
273
+ /** Resource-release request payload (inferred from {@link ResourceReleaseRequestSchema}). */
274
+ export type ResourceReleaseRequest = z.infer<typeof ResourceReleaseRequestSchema>;
275
+
232
276
  /**
233
277
  * The discriminated union of all client → arbiter lease requests. The `kind`
234
278
  * field selects the variant, matching the serde `#[serde(tag = "kind")]`
@@ -242,6 +286,8 @@ export const LeaseIpcRequestSchema = z.discriminatedUnion('kind', [
242
286
  ToolGrantRequestSchema,
243
287
  QueueAdmitRequestSchema,
244
288
  WorkerHeartbeatRequestSchema,
289
+ ResourceAdmitRequestSchema,
290
+ ResourceReleaseRequestSchema,
245
291
  ]);
246
292
 
247
293
  /** Any lease IPC request (inferred from {@link LeaseIpcRequestSchema}). */
@@ -445,6 +491,54 @@ export const HeartbeatAckResponseSchema = z
445
491
  /** Heartbeat-ack response payload (inferred from {@link HeartbeatAckResponseSchema}). */
446
492
  export type HeartbeatAckResponse = z.infer<typeof HeartbeatAckResponseSchema>;
447
493
 
494
+ /**
495
+ * The disposition of a `resource_admit` request (T12001) — `admitted` (run the
496
+ * heavy op now) or `deferred` (back off `retry_after_ms` and re-request; never a
497
+ * silent drop). Mirrors `ResourceAdmitDisposition`.
498
+ */
499
+ export const ResourceAdmitDispositionSchema = z.enum(['admitted', 'deferred']);
500
+
501
+ /** A `resource_admit` disposition (inferred from {@link ResourceAdmitDispositionSchema}). */
502
+ export type ResourceAdmitDisposition = z.infer<typeof ResourceAdmitDispositionSchema>;
503
+
504
+ /**
505
+ * Reply to a `resource_admit`: the heavy op was admitted or deferred (T12001).
506
+ * Mirrors `ResourceAdmitResult`.
507
+ */
508
+ export const ResourceAdmitResultResponseSchema = z
509
+ .object({
510
+ /** Tag discriminating this response variant. */
511
+ kind: z.literal('resource_admit_result'),
512
+ /** Whether the heavy op was admitted or deferred. */
513
+ disposition: ResourceAdmitDispositionSchema,
514
+ /** Back-off in ms before re-requesting (0 when admitted). */
515
+ retry_after_ms: z.number().int().nonnegative(),
516
+ /** Remaining slots for the class after this decision. */
517
+ slots_remaining: z.number().int().nonnegative(),
518
+ })
519
+ .strict();
520
+
521
+ /** Resource-admit result payload (inferred from {@link ResourceAdmitResultResponseSchema}). */
522
+ export type ResourceAdmitResultResponse = z.infer<typeof ResourceAdmitResultResponseSchema>;
523
+
524
+ /**
525
+ * Reply to a `resource_release`: the slot was released (T12001). Mirrors
526
+ * `ResourceReleaseResult`.
527
+ */
528
+ export const ResourceReleaseResultResponseSchema = z
529
+ .object({
530
+ /** Tag discriminating this response variant. */
531
+ kind: z.literal('resource_release_result'),
532
+ /** Whether a held slot was actually released (false = no such holder). */
533
+ released: z.boolean(),
534
+ /** Remaining in-flight holders for the class after the release. */
535
+ slots_remaining: z.number().int().nonnegative(),
536
+ })
537
+ .strict();
538
+
539
+ /** Resource-release result payload (inferred from {@link ResourceReleaseResultResponseSchema}). */
540
+ export type ResourceReleaseResultResponse = z.infer<typeof ResourceReleaseResultResponseSchema>;
541
+
448
542
  /**
449
543
  * An error response correlated to a request. Mirrors the v1.0
450
544
  * `crate::ipc::ErrorResult` shape reused by the Rust `LeaseResponse::Error`
@@ -479,6 +573,8 @@ export const LeaseIpcResponseSchema = z.discriminatedUnion('kind', [
479
573
  ChildKilledUnresponsiveResponseSchema,
480
574
  QueueAdmitResultResponseSchema,
481
575
  HeartbeatAckResponseSchema,
576
+ ResourceAdmitResultResponseSchema,
577
+ ResourceReleaseResultResponseSchema,
482
578
  LeaseErrorResponseSchema,
483
579
  ]);
484
580
 
@@ -559,10 +655,12 @@ export const LEASE_IPC_REQUEST_KINDS = [
559
655
  'tool_grant',
560
656
  'queue_admit',
561
657
  'worker_heartbeat',
658
+ 'resource_admit',
659
+ 'resource_release',
562
660
  ] as const;
563
661
 
564
662
  /**
565
- * The v1.1 response `kind` values, in declaration order. The schema-drift guard
663
+ * The v1.2 response `kind` values, in declaration order. The schema-drift guard
566
664
  * test pins this tuple.
567
665
  */
568
666
  export const LEASE_IPC_RESPONSE_KINDS = [
@@ -575,6 +673,8 @@ export const LEASE_IPC_RESPONSE_KINDS = [
575
673
  'child_killed_unresponsive',
576
674
  'queue_admit_result',
577
675
  'heartbeat_ack',
676
+ 'resource_admit_result',
677
+ 'resource_release_result',
578
678
  'error',
579
679
  ] as const;
580
680
 
@@ -5,7 +5,7 @@
5
5
  * TypeScript contract MUST agree on this value. It runs **in parallel** to the
6
6
  * byte-frozen `supervisor-ipc` v1.0 contract: the two are distinguished on the
7
7
  * wire purely by this version string, so a single accept loop can route
8
- * `'1.0.0'` → the v1.0 union and `'1.1.0'` → the lease union. Bump only via a
8
+ * `'1.0.0'` → the v1.0 union and `'1.2.0'` → the lease union. Bump only via a
9
9
  * coordinated dual (Rust + TS) edit, never edit this value in place.
10
10
  *
11
11
  * @see {@link LEASE_IPC_MESSAGE_KINDS} for the v1.1 message set.
@@ -14,13 +14,16 @@
14
14
  */
15
15
 
16
16
  /**
17
- * The `lease-ipc` v1.1 protocol version string.
17
+ * The `lease-ipc` v1.2 protocol version string.
18
18
  *
19
19
  * Mirrors `cleo_supervisor::lease_ipc::LEASE_IPC_PROTOCOL_VERSION` on the Rust
20
20
  * side. Both the Rust schema-drift guard (`lease_ipc::tests`) and the TS drift
21
21
  * test (`__tests__/freeze.test.ts`) pin this exact value.
22
+ *
23
+ * v1.2 (T12001 · Epic T11992) added the `resource_admit` / `resource_release`
24
+ * heavy-op admission verbs; v1.1 added `queue_admit` / `worker_heartbeat`.
22
25
  */
23
- export const LEASE_IPC_PROTOCOL_VERSION = '1.1.0' as const;
26
+ export const LEASE_IPC_PROTOCOL_VERSION = '1.2.0' as const;
24
27
 
25
28
  /**
26
29
  * Whether the given version string is the frozen `lease-ipc` v1.1 wire version.
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Resource Governor — shared types for the Never-OOM admission layer.
3
+ *
4
+ * The {@link ResourceGovernor} (in `@cleocode/core`) admits resource-intensive
5
+ * work through priority classes whose budgets are computed from host memory and
6
+ * memory-pressure (PSI). A denial returns a structured, retryable
7
+ * {@link ResourceDeferral} (code {@link RESOURCE_DEFERRED_CODE}) rather than a
8
+ * silent drop or a crash — orchestrators treat it like a lifecycle gate: wait
9
+ * and retry, never fail the task.
10
+ *
11
+ * Mirrors the llm-queue deferral contract (`retry_after_ms`, degrade-to-direct)
12
+ * so the two admission surfaces share one shape.
13
+ *
14
+ * @task T11999
15
+ * @epic T11992
16
+ * @adr resource-governor-never-oom-architecture §3.4
17
+ */
18
+
19
+ /**
20
+ * Governor arbitration mode. Mirrors the writer-lease mode shape
21
+ * (`writer-lease.ts` {@link LeaseMode}).
22
+ *
23
+ * - `supervisor` — defer to the Rust `cleo-supervisor` `resource_admit` verb
24
+ * (continuous PSI, priority wakeups). Demotes to `local` when the supervisor
25
+ * IPC client is not wired/reachable (never deadlocks).
26
+ * - `local` — DEFAULT. Daemon-off arbitration through shared per-class slot
27
+ * directories (proper-lockfile crash-stale auto-release) + a point-sample of
28
+ * {@link ResourceSample} taken inside `acquire`. Genuinely cross-process
29
+ * without a daemon, mirroring the tool-semaphore engine.
30
+ * - `off` — pure pass-through; every acquire is granted immediately.
31
+ */
32
+ export type GovernorMode = 'supervisor' | 'local' | 'off';
33
+
34
+ /**
35
+ * Priority classes (highest priority first). `interactive-cli` is NEVER gated;
36
+ * `full-build` is pinned to one machine-wide slot regardless of pressure.
37
+ *
38
+ * @adr resource-governor-never-oom-architecture §3.4 (classes)
39
+ */
40
+ export type ResourceClass =
41
+ | 'interactive-cli'
42
+ | 'agent-session'
43
+ | 'llm-call'
44
+ | 'test-run'
45
+ | 'scoped-build'
46
+ | 'full-build'
47
+ | 'db-heavy'
48
+ | 'background-autonomous';
49
+
50
+ /**
51
+ * All resource classes in descending priority order. Single source of truth for
52
+ * iteration + validation.
53
+ */
54
+ export const RESOURCE_CLASSES: readonly ResourceClass[] = Object.freeze([
55
+ 'interactive-cli',
56
+ 'agent-session',
57
+ 'llm-call',
58
+ 'test-run',
59
+ 'scoped-build',
60
+ 'full-build',
61
+ 'db-heavy',
62
+ 'background-autonomous',
63
+ ]);
64
+
65
+ /** Error code emitted when an acquire is denied because no slot is available. */
66
+ export const RESOURCE_DEFERRED_CODE = 'E_RESOURCE_DEFERRED' as const;
67
+
68
+ /**
69
+ * Soft-signal code raised on the next admit when the host enters `backoff` —
70
+ * asks granted work to checkpoint. Existing grants are NEVER revoked.
71
+ */
72
+ export const RESOURCE_BACKPRESSURE_CODE = 'E_RESOURCE_BACKPRESSURE' as const;
73
+
74
+ /**
75
+ * Structured, retryable deferral returned when admission is denied. Never a
76
+ * silent drop; callers back off `retryAfterMs` and re-request, or annotate the
77
+ * unit as deferred and let a pull-based retry pick it up.
78
+ */
79
+ export interface ResourceDeferral {
80
+ /** Discriminant for narrowing against the success grant. */
81
+ readonly deferred: true;
82
+ /** The class whose budget was exhausted. */
83
+ readonly class: ResourceClass;
84
+ /** Suggested back-off before re-requesting, in milliseconds. */
85
+ readonly retryAfterMs: number;
86
+ /** Human-readable reason (pressure state, budget, held count). */
87
+ readonly reason: string;
88
+ }
89
+
90
+ /**
91
+ * A granted admission slot. Hold it for the lifetime of the work, then
92
+ * {@link ResourceGrant.release}. Releasing reaps the slot so the next acquirer
93
+ * can proceed; releasing twice is a no-op.
94
+ */
95
+ export interface ResourceGrant {
96
+ /** Discriminant for narrowing against {@link ResourceDeferral}. */
97
+ readonly deferred: false;
98
+ /** The class this grant belongs to. */
99
+ readonly class: ResourceClass;
100
+ /**
101
+ * Slot index held (local mode), or `-1` for an ungated pass-through grant
102
+ * (`interactive-cli`, `off` mode, or an unbounded budget).
103
+ */
104
+ readonly slot: number;
105
+ /** Monotonic timestamp (ms) when the grant was acquired. */
106
+ readonly acquiredAtMs: number;
107
+ /** Release the slot. Idempotent. */
108
+ release(): Promise<void>;
109
+ }
110
+
111
+ /** Discriminated union returned by a non-blocking `tryAcquire`. */
112
+ export type AdmissionResult = ResourceGrant | ResourceDeferral;
113
+
114
+ /**
115
+ * Default back-off hint when a class is saturated, in milliseconds. Aligned
116
+ * with the llm-queue `DEFAULT_ADMIT_DEADLINE_MS` so the two surfaces agree.
117
+ */
118
+ export const DEFAULT_RESOURCE_RETRY_AFTER_MS = 2_000;
119
+
120
+ /** Type guard: did an admission attempt produce a usable grant? */
121
+ export function isResourceGrant(r: AdmissionResult): r is ResourceGrant {
122
+ return r.deferred === false;
123
+ }