@cleocode/core 2026.10.25 → 2026.10.26
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/build-provenance.json +2 -2
- package/dist/dispatch/contracts/output-contracts.d.ts +13 -0
- package/dist/dispatch/contracts/output-contracts.d.ts.map +1 -1
- package/dist/dispatch/contracts/output-contracts.js +29 -0
- package/dist/dispatch/contracts/output-contracts.js.map +1 -1
- package/dist/docs/docs-ops.d.ts +8 -0
- package/dist/docs/docs-ops.d.ts.map +1 -1
- package/dist/docs/docs-ops.js +24 -1
- package/dist/docs/docs-ops.js.map +1 -1
- package/dist/docs/docs-update.d.ts +8 -5
- package/dist/docs/docs-update.d.ts.map +1 -1
- package/dist/docs/docs-update.js +16 -5
- package/dist/docs/docs-update.js.map +1 -1
- package/dist/gateway-client/generated/types.gen.d.ts +27 -0
- package/dist/gateway-client/generated/types.gen.d.ts.map +1 -1
- package/dist/hooks/handlers/watchdog-hooks.d.ts.map +1 -1
- package/dist/hooks/handlers/watchdog-hooks.js +5 -0
- package/dist/hooks/handlers/watchdog-hooks.js.map +1 -1
- package/dist/internal.d.ts +3 -2
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +5 -2
- package/dist/internal.js.map +1 -1
- package/dist/lifecycle/verification/satisfies-validator.d.ts.map +1 -1
- package/dist/lifecycle/verification/satisfies-validator.js +22 -10
- package/dist/lifecycle/verification/satisfies-validator.js.map +1 -1
- package/dist/memory/brain-writer-thread.d.ts +30 -0
- package/dist/memory/brain-writer-thread.d.ts.map +1 -1
- package/dist/memory/brain-writer-thread.js +46 -1
- package/dist/memory/brain-writer-thread.js.map +1 -1
- package/dist/memory/brain-writer-worker.js +13 -2
- package/dist/memory/brain-writer-worker.js.map +1 -1
- package/dist/memory/embedding-backfill.d.ts +12 -0
- package/dist/memory/embedding-backfill.d.ts.map +1 -1
- package/dist/memory/embedding-backfill.js +51 -31
- package/dist/memory/embedding-backfill.js.map +1 -1
- package/dist/orchestrate/include-deps.d.ts +58 -0
- package/dist/orchestrate/include-deps.d.ts.map +1 -0
- package/dist/orchestrate/include-deps.js +138 -0
- package/dist/orchestrate/include-deps.js.map +1 -0
- package/dist/orchestrate/spawn-ops.d.ts +14 -33
- package/dist/orchestrate/spawn-ops.d.ts.map +1 -1
- package/dist/orchestrate/spawn-ops.js +49 -61
- package/dist/orchestrate/spawn-ops.js.map +1 -1
- package/dist/orchestrate/worktree-complete.d.ts +15 -1
- package/dist/orchestrate/worktree-complete.d.ts.map +1 -1
- package/dist/orchestrate/worktree-complete.js +36 -3
- package/dist/orchestrate/worktree-complete.js.map +1 -1
- package/dist/release/ci-evidence.d.ts +142 -4
- package/dist/release/ci-evidence.d.ts.map +1 -1
- package/dist/release/ci-evidence.js +298 -48
- package/dist/release/ci-evidence.js.map +1 -1
- package/dist/release/open.d.ts +25 -1
- package/dist/release/open.d.ts.map +1 -1
- package/dist/release/open.js +63 -11
- package/dist/release/open.js.map +1 -1
- package/dist/resources/admission-ledger.d.ts +83 -11
- package/dist/resources/admission-ledger.d.ts.map +1 -1
- package/dist/resources/admission-ledger.js +138 -39
- package/dist/resources/admission-ledger.js.map +1 -1
- package/dist/resources/governor.js +1 -1
- package/dist/resources/governor.js.map +1 -1
- package/dist/resources/hook-footprint.d.ts +24 -0
- package/dist/resources/hook-footprint.d.ts.map +1 -1
- package/dist/resources/hook-footprint.js +38 -1
- package/dist/resources/hook-footprint.js.map +1 -1
- package/dist/resources/run-class.d.ts +38 -5
- package/dist/resources/run-class.d.ts.map +1 -1
- package/dist/resources/run-class.js +60 -11
- package/dist/resources/run-class.js.map +1 -1
- package/dist/resources/run-governed.d.ts +13 -3
- package/dist/resources/run-governed.d.ts.map +1 -1
- package/dist/resources/run-governed.js +42 -9
- package/dist/resources/run-governed.js.map +1 -1
- package/dist/resources/slot-holder.d.ts +3 -3
- package/dist/resources/slot-holder.js +3 -3
- package/dist/resources/tool-groups.d.ts +11 -7
- package/dist/resources/tool-groups.d.ts.map +1 -1
- package/dist/resources/tool-groups.js +24 -13
- package/dist/resources/tool-groups.js.map +1 -1
- package/dist/spawn/branch-integration.d.ts +281 -0
- package/dist/spawn/branch-integration.d.ts.map +1 -0
- package/dist/spawn/branch-integration.js +715 -0
- package/dist/spawn/branch-integration.js.map +1 -0
- package/dist/spawn/branch-lock.d.ts +32 -270
- package/dist/spawn/branch-lock.d.ts.map +1 -1
- package/dist/spawn/branch-lock.js +59 -770
- package/dist/spawn/branch-lock.js.map +1 -1
- package/dist/spawn/index.d.ts +3 -1
- package/dist/spawn/index.d.ts.map +1 -1
- package/dist/spawn/index.js +5 -1
- package/dist/spawn/index.js.map +1 -1
- package/dist/store/exodus/recovery.d.ts.map +1 -1
- package/dist/store/exodus/recovery.js +9 -4
- package/dist/store/exodus/recovery.js.map +1 -1
- package/dist/system/health.d.ts.map +1 -1
- package/dist/system/health.js +3 -1
- package/dist/system/health.js.map +1 -1
- package/dist/tasks/ac-coverage-gate.d.ts +18 -2
- package/dist/tasks/ac-coverage-gate.d.ts.map +1 -1
- package/dist/tasks/ac-coverage-gate.js +40 -6
- package/dist/tasks/ac-coverage-gate.js.map +1 -1
- package/dist/tasks/ac-table.d.ts.map +1 -1
- package/dist/tasks/ac-table.js +3 -10
- package/dist/tasks/ac-table.js.map +1 -1
- package/dist/tasks/affected-packages.d.ts.map +1 -1
- package/dist/tasks/affected-packages.js +54 -10
- package/dist/tasks/affected-packages.js.map +1 -1
- package/dist/tasks/affected-scope.d.ts +10 -1
- package/dist/tasks/affected-scope.d.ts.map +1 -1
- package/dist/tasks/affected-scope.js +13 -3
- package/dist/tasks/affected-scope.js.map +1 -1
- package/dist/tasks/change-set.d.ts +9 -6
- package/dist/tasks/change-set.d.ts.map +1 -1
- package/dist/tasks/change-set.js +75 -97
- package/dist/tasks/change-set.js.map +1 -1
- package/dist/tasks/command-line.d.ts.map +1 -1
- package/dist/tasks/command-line.js +6 -2
- package/dist/tasks/command-line.js.map +1 -1
- package/dist/tasks/complete.d.ts.map +1 -1
- package/dist/tasks/complete.js +7 -2
- package/dist/tasks/complete.js.map +1 -1
- package/dist/tasks/done-plan.d.ts.map +1 -1
- package/dist/tasks/done-plan.js +111 -29
- package/dist/tasks/done-plan.js.map +1 -1
- package/dist/tasks/done-record.d.ts +14 -0
- package/dist/tasks/done-record.d.ts.map +1 -1
- package/dist/tasks/done-record.js +17 -10
- package/dist/tasks/done-record.js.map +1 -1
- package/dist/tasks/evidence-tree.d.ts +33 -0
- package/dist/tasks/evidence-tree.d.ts.map +1 -0
- package/dist/tasks/evidence-tree.js +132 -0
- package/dist/tasks/evidence-tree.js.map +1 -0
- package/dist/tasks/evidence.d.ts +42 -6
- package/dist/tasks/evidence.d.ts.map +1 -1
- package/dist/tasks/evidence.js +175 -79
- package/dist/tasks/evidence.js.map +1 -1
- package/dist/tasks/gate-preview.d.ts.map +1 -1
- package/dist/tasks/gate-preview.js +3 -2
- package/dist/tasks/gate-preview.js.map +1 -1
- package/dist/tasks/gate-runner.d.ts +11 -3
- package/dist/tasks/gate-runner.d.ts.map +1 -1
- package/dist/tasks/gate-runner.js +156 -37
- package/dist/tasks/gate-runner.js.map +1 -1
- package/dist/tasks/gate-staleness.d.ts +58 -0
- package/dist/tasks/gate-staleness.d.ts.map +1 -0
- package/dist/tasks/gate-staleness.js +78 -0
- package/dist/tasks/gate-staleness.js.map +1 -0
- package/dist/tasks/heavy-tool-env.d.ts +26 -1
- package/dist/tasks/heavy-tool-env.d.ts.map +1 -1
- package/dist/tasks/heavy-tool-env.js +92 -5
- package/dist/tasks/heavy-tool-env.js.map +1 -1
- package/dist/tasks/heavy-tool-limit.d.ts +24 -0
- package/dist/tasks/heavy-tool-limit.d.ts.map +1 -1
- package/dist/tasks/heavy-tool-limit.js +44 -1
- package/dist/tasks/heavy-tool-limit.js.map +1 -1
- package/dist/tasks/show.d.ts.map +1 -1
- package/dist/tasks/show.js +43 -0
- package/dist/tasks/show.js.map +1 -1
- package/dist/tasks/test-run-binding.d.ts +48 -3
- package/dist/tasks/test-run-binding.d.ts.map +1 -1
- package/dist/tasks/test-run-binding.js +111 -3
- package/dist/tasks/test-run-binding.js.map +1 -1
- package/dist/tasks/tool-cache.d.ts +58 -7
- package/dist/tasks/tool-cache.d.ts.map +1 -1
- package/dist/tasks/tool-cache.js +216 -16
- package/dist/tasks/tool-cache.js.map +1 -1
- package/dist/tasks/tool-resolver.d.ts +13 -0
- package/dist/tasks/tool-resolver.d.ts.map +1 -1
- package/dist/tasks/tool-resolver.js +34 -0
- package/dist/tasks/tool-resolver.js.map +1 -1
- package/dist/tasks/tool-runner-guard.d.ts +6 -0
- package/dist/tasks/tool-runner-guard.d.ts.map +1 -1
- package/dist/tasks/tool-runner-guard.js.map +1 -1
- package/dist/tasks/tool-semaphore.js +1 -1
- package/dist/tasks/tool-semaphore.js.map +1 -1
- package/dist/tasks/verification-policy.d.ts +26 -3
- package/dist/tasks/verification-policy.d.ts.map +1 -1
- package/dist/tasks/verification-policy.js +76 -11
- package/dist/tasks/verification-policy.js.map +1 -1
- package/dist/validation/doctor/checks.d.ts +12 -0
- package/dist/validation/doctor/checks.d.ts.map +1 -1
- package/dist/validation/doctor/checks.js +33 -0
- package/dist/validation/doctor/checks.js.map +1 -1
- package/dist/validation/engine-ops.d.ts +6 -0
- package/dist/validation/engine-ops.d.ts.map +1 -1
- package/dist/validation/engine-ops.js +51 -1
- package/dist/validation/engine-ops.js.map +1 -1
- package/dist/worktree/force-unlock.js +2 -2
- package/dist/worktree/force-unlock.js.map +1 -1
- package/dist/worktree/list.d.ts +7 -7
- package/dist/worktree/list.d.ts.map +1 -1
- package/dist/worktree/list.js +11 -56
- package/dist/worktree/list.js.map +1 -1
- package/dist/worktree/prune.d.ts +2 -2
- package/dist/worktree/prune.d.ts.map +1 -1
- package/dist/worktree/prune.js +4 -4
- package/dist/worktree/prune.js.map +1 -1
- package/package.json +13 -13
|
@@ -74,12 +74,12 @@ import { join } from 'node:path';
|
|
|
74
74
|
import lockfile from 'proper-lockfile';
|
|
75
75
|
import { getLogger } from '../logger.js';
|
|
76
76
|
import { getCleoHome } from '../paths.js';
|
|
77
|
-
import { admissionCapacityBytes, admissionReserveBytes, defaultSingleProcessHeapMb, GIB_PER_WORKER, HEAVY_TOOL_HEAP_MB, heavyToolWorkers, isHeavyTool, isMemoryBoundTool, planHeavyToolEnv, vitestWorkerCeiling, } from '../tasks/heavy-tool-env.js';
|
|
78
|
-
import { gitHookPath, HOOK_PEAK_MARGIN, hookFootprintKey, readHookPeak } from './hook-footprint.js';
|
|
77
|
+
import { admissionCapacityBytes, admissionReserveBytes, defaultSingleProcessHeapMb, GIB_PER_WORKER, HEAVY_TOOL_HEAP_MB, heavyToolWorkers, isHeavyTool, isMemoryBoundTool, planHeavyToolEnv, projectSettingOverrides, vitestWorkerCeiling, } from '../tasks/heavy-tool-env.js';
|
|
78
|
+
import { gitHookPath, HOOK_MIN_RUNS, HOOK_PEAK_MARGIN, hookFootprintKey, readHookPeak, scriptFootprint, } from './hook-footprint.js';
|
|
79
79
|
import { pressureScore, ResourceMonitor } from './monitor.js';
|
|
80
80
|
import { checkMemoryGate, MEMORY_GATE_RETRY_AFTER_MS, } from './pressure-gate.js';
|
|
81
81
|
import { processAncestors, processGroupOf, processGroupsOf } from './run-admission.js';
|
|
82
|
-
import { canonicalForClass, gitHookFor, lightCommandReason, namedTestFileCount, runFootprint, } from './run-class.js';
|
|
82
|
+
import { canonicalForClass, gitHookFor, heavyScriptReason, lightCommandReason, namedTestFileCount, nodeScriptOf, runFootprint, } from './run-class.js';
|
|
83
83
|
import { ownProcessStartedAt, systemPidProbe } from './slot-holder.js';
|
|
84
84
|
import { followToolGroups, isProbeableId } from './tool-groups.js';
|
|
85
85
|
import { memoryPressureCause, TOP_MEMORY_COUNT, TOP_MEMORY_FAILED_REUSE_MS, TOP_MEMORY_REUSE_MS, topMemoryConsumers, } from './top-memory.js';
|
|
@@ -244,7 +244,10 @@ function scopedBuildWorkers(totalRamGib, env) {
|
|
|
244
244
|
* A formatter on named files is charged {@link LIGHT_FOOTPRINT_BYTES} with no
|
|
245
245
|
* heap plan. A single Node linter on files, or a one-project `tsc -p`, is
|
|
246
246
|
* charged one process. A scoped build of a command that runs no heavy tool
|
|
247
|
-
* (`node x.mjs`, a hook-free `git push`) is charged light (T13452)
|
|
247
|
+
* (`node x.mjs`, a hook-free `git push`) is charged light (T13452), unless
|
|
248
|
+
* the script hosts a heavy tool: its path names a heavy job or it loads a
|
|
249
|
+
* compiler or test runner (T14050). Such a script, like a hook, is charged its
|
|
250
|
+
* measured peak once measured, and the class plan until then. Anything
|
|
248
251
|
* else gets the class's heavy-run plan, with a named-file test run capped at
|
|
249
252
|
* one worker per file (T13132) and a scoped build at half a heavy run's
|
|
250
253
|
* workers (T13759). A test run is also capped at the project's own vitest
|
|
@@ -259,6 +262,8 @@ function scopedBuildWorkers(totalRamGib, env) {
|
|
|
259
262
|
* @param hookPeak - a hook's measured peak. @defaultValue reads the hook footprint store
|
|
260
263
|
* @param cwd - the directory the command runs in, read for the project's vitest
|
|
261
264
|
* limit (T14033). @defaultValue process.cwd()
|
|
265
|
+
* @param scriptPeak - a `node` script's key, loaded heavy tool and measured
|
|
266
|
+
* peak (T14050). @defaultValue reads the script and the footprint store
|
|
262
267
|
*
|
|
263
268
|
* @example
|
|
264
269
|
* ```ts
|
|
@@ -269,18 +274,37 @@ export function planRunFootprint(cls, argv, env = process.env, totalRamGib = tot
|
|
|
269
274
|
hookPeak = (hook, globals) => {
|
|
270
275
|
const key = hookFootprintKey(process.cwd(), hook, globals, env); // CWD-OK: as above
|
|
271
276
|
return key === null ? null : { key, peakBytes: readHookPeak(key, admissionDir()) };
|
|
272
|
-
}, cwd = process.cwd()
|
|
277
|
+
}, cwd = process.cwd(), // CWD-OK: cleo run runs the command in its own cwd
|
|
278
|
+
scriptPeak = (script) => {
|
|
279
|
+
const found = scriptFootprint(cwd, script);
|
|
280
|
+
return found === null ? null : { ...found, peakBytes: readHookPeak(found.key, admissionDir()) };
|
|
281
|
+
}) {
|
|
273
282
|
let scope = cls === 'full-build' ? null : runFootprint(argv);
|
|
283
|
+
const sized = cls === 'scoped-build' && scope?.size === 'class';
|
|
284
|
+
// T14050: a node script that hosts a heavy tool (its path names one, or it
|
|
285
|
+
// loads a compiler or test runner in-process) is never light.
|
|
286
|
+
const script = sized ? nodeScriptOf(argv) : null;
|
|
287
|
+
const scripted = script !== null ? scriptPeak(script) : null;
|
|
288
|
+
const host = script === null
|
|
289
|
+
? null
|
|
290
|
+
: (heavyScriptReason(script) ??
|
|
291
|
+
(scripted?.heavyTool ? `it loads ${scripted.heavyTool}` : null));
|
|
274
292
|
// T13452: a scoped build of a command that runs no heavy tool (`node x.mjs`,
|
|
275
293
|
// `ls`) is charged light. An explicit test run keeps its class plan.
|
|
276
|
-
const light =
|
|
294
|
+
const light = sized && host === null ? lightCommandReason(argv) : null;
|
|
277
295
|
if (light !== null)
|
|
278
296
|
scope = { size: 'light', reason: light };
|
|
297
|
+
else if (host !== null)
|
|
298
|
+
scope = { size: 'class', reason: `node ${script} hosts a heavy tool (${host})` };
|
|
279
299
|
// T13452: a hooked git push/commit keeps the class plan, because whatever
|
|
280
300
|
// the hook runs (tests, a build) rides this admission.
|
|
281
301
|
const probe = scope?.size === 'light' ? gitHookFor(argv) : null;
|
|
282
302
|
const hook = probe?.hooks.find((h) => hookInstalled(h, probe.globals));
|
|
283
|
-
const measured = hook !== undefined && probe !== null
|
|
303
|
+
const measured = hook !== undefined && probe !== null
|
|
304
|
+
? hookPeak(hook, probe.globals)
|
|
305
|
+
: host !== null
|
|
306
|
+
? scripted
|
|
307
|
+
: null;
|
|
284
308
|
if (hook !== undefined) {
|
|
285
309
|
scope = { size: 'class', reason: `runs the ${hook} hook` };
|
|
286
310
|
}
|
|
@@ -297,7 +321,7 @@ hookPeak = (hook, globals) => {
|
|
|
297
321
|
const { overlay, resources } = planHeavyToolEnv('typecheck', env, totalRamGib);
|
|
298
322
|
return {
|
|
299
323
|
overlay,
|
|
300
|
-
resources,
|
|
324
|
+
resources: withProjectOverrides(resources, overlay, cwd),
|
|
301
325
|
...(resources !== null ? { footprintBytes: planFootprintBytes(resources) } : {}),
|
|
302
326
|
footprintReason: scope.reason,
|
|
303
327
|
namedFiles: null,
|
|
@@ -326,15 +350,39 @@ hookPeak = (hook, globals) => {
|
|
|
326
350
|
: planned;
|
|
327
351
|
return {
|
|
328
352
|
overlay,
|
|
329
|
-
resources,
|
|
353
|
+
resources: withProjectOverrides(resources, overlay, cwd),
|
|
330
354
|
...(charged !== undefined ? { footprintBytes: charged } : {}),
|
|
331
355
|
footprintReason: peak !== null && charged !== planned
|
|
332
356
|
? `${reason}, measured peak ${gib(peak)} + ${Math.round((HOOK_PEAK_MARGIN - 1) * 100)}%`
|
|
333
|
-
:
|
|
357
|
+
: measured !== null && peak === null && host !== null
|
|
358
|
+
? `${reason}; charged the plan until ${HOOK_MIN_RUNS} runs are measured`
|
|
359
|
+
: reason,
|
|
334
360
|
namedFiles,
|
|
335
361
|
...(measured !== null ? { hookKey: measured.key } : {}),
|
|
336
362
|
};
|
|
337
363
|
}
|
|
364
|
+
/**
|
|
365
|
+
* The charge `cleo run` prints before admission (T14050): the bytes asked of
|
|
366
|
+
* the ledger and how they were derived.
|
|
367
|
+
*
|
|
368
|
+
* @param cls - the resolved class.
|
|
369
|
+
* @param plan - from {@link planRunFootprint}.
|
|
370
|
+
*
|
|
371
|
+
* @example
|
|
372
|
+
* ```ts
|
|
373
|
+
* describeCharge('scoped-build', plan); // '1.0 GiB: biome check on 2 named paths'
|
|
374
|
+
* ```
|
|
375
|
+
*/
|
|
376
|
+
export function describeCharge(cls, plan) {
|
|
377
|
+
return `${gib(plan.footprintBytes ?? footprintForClass(cls))}: ${plan.footprintReason}`;
|
|
378
|
+
}
|
|
379
|
+
/** A plan with the project-file settings its overlay overrides (T14038). */
|
|
380
|
+
function withProjectOverrides(resources, overlay, cwd) {
|
|
381
|
+
if (resources === null)
|
|
382
|
+
return null;
|
|
383
|
+
const projectOverrides = projectSettingOverrides(cwd, overlay);
|
|
384
|
+
return projectOverrides.length > 0 ? { ...resources, projectOverrides } : resources;
|
|
385
|
+
}
|
|
338
386
|
/** Governor classes whose admission is the ledger's. */
|
|
339
387
|
export const LEDGER_CLASSES = new Set([
|
|
340
388
|
'test-run',
|
|
@@ -643,49 +691,80 @@ async function withLedger(dir, fn, signal, strictRead = false) {
|
|
|
643
691
|
}
|
|
644
692
|
}
|
|
645
693
|
}
|
|
646
|
-
// ---------------------------------------------------------------------------
|
|
647
|
-
// Liveness
|
|
648
|
-
// ---------------------------------------------------------------------------
|
|
649
694
|
/**
|
|
650
|
-
* Whether an entry's holder is still there.
|
|
695
|
+
* Whether an entry's holder is still there, and why.
|
|
651
696
|
*
|
|
652
697
|
* - A pid that is provably gone (or recycled: alive with another start time,
|
|
653
|
-
* once the heartbeat is stale) is dead as soon as
|
|
654
|
-
* started
|
|
698
|
+
* once the heartbeat is stale) is dead as soon as its recorded child and
|
|
699
|
+
* every tool group it started are gone too; a detached tool outlives a
|
|
700
|
+
* SIGKILLed cleo, and so does a foreground child (T13046).
|
|
655
701
|
* - A fresh heartbeat, or a live pid whose start time matches the recorded
|
|
656
702
|
* one, is alive.
|
|
657
703
|
* - A holder that cannot be identified (the probe fails, or no start time was
|
|
658
704
|
* recorded or can be read) is alive until its heartbeat is
|
|
659
|
-
* {@link LEDGER_ORPHAN_MS} old, and so is a gone holder whose
|
|
660
|
-
* still runs (a group id carries no start time, so it may be
|
|
661
|
-
* Past that bound both are dead: nothing holds the budget forever.
|
|
705
|
+
* {@link LEDGER_ORPHAN_MS} old, and so is a gone holder whose child or tool
|
|
706
|
+
* group id still runs (a group id carries no start time, so it may be
|
|
707
|
+
* recycled). Past that bound both are dead: nothing holds the budget forever.
|
|
662
708
|
* - An entry of another host is judged by its heartbeat alone.
|
|
663
709
|
*
|
|
664
710
|
* @param entry - the ledger entry.
|
|
665
711
|
* @param nowMs - the clock.
|
|
666
712
|
* @param probe - pid probe (tests inject one).
|
|
667
713
|
*/
|
|
668
|
-
export function
|
|
714
|
+
export function entryVerdict(entry, nowMs, probe = systemPidProbe) {
|
|
715
|
+
const alive = (reason) => ({ state: 'alive', reason });
|
|
716
|
+
const dead = (reason) => ({ state: 'dead', reason });
|
|
669
717
|
const age = nowMs - entry.heartbeatAtMs;
|
|
670
|
-
if (entry.host !== hostname())
|
|
671
|
-
return age < LEDGER_HEARTBEAT_STALE_MS
|
|
718
|
+
if (entry.host !== hostname()) {
|
|
719
|
+
return age < LEDGER_HEARTBEAT_STALE_MS
|
|
720
|
+
? alive(`other-host:${entry.host}`)
|
|
721
|
+
: dead(`other-host-stale:${entry.host}`);
|
|
722
|
+
}
|
|
672
723
|
const orphaned = age >= LEDGER_ORPHAN_MS;
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
724
|
+
/** What the holder left running: its child, then its tool groups. */
|
|
725
|
+
const survivor = () => {
|
|
726
|
+
const child = entry.child;
|
|
727
|
+
if (child !== undefined && isProbeableId(child.pid) && probe.liveness(child.pid) !== 'gone') {
|
|
728
|
+
const started = child.startedAt === null ? null : probe.startedAt(child.pid);
|
|
729
|
+
if (started === null || started === child.startedAt)
|
|
730
|
+
return `child-alive:${child.pid}`;
|
|
731
|
+
}
|
|
732
|
+
for (const g of entry.toolGroups) {
|
|
733
|
+
const state = isProbeableId(g) ? probe.groupLiveness(g) : 'unknown';
|
|
734
|
+
if (state !== 'gone')
|
|
735
|
+
return `group-${state}:${g}`;
|
|
736
|
+
}
|
|
737
|
+
return null;
|
|
738
|
+
};
|
|
739
|
+
const goneUnlessSurvivor = (reason) => {
|
|
740
|
+
if (orphaned)
|
|
741
|
+
return dead('orphaned');
|
|
742
|
+
const left = survivor();
|
|
743
|
+
return left === null ? dead(reason) : alive(left);
|
|
744
|
+
};
|
|
676
745
|
const pid = probe.liveness(entry.pid);
|
|
677
746
|
if (pid === 'gone')
|
|
678
|
-
return
|
|
747
|
+
return goneUnlessSurvivor('pid-gone');
|
|
679
748
|
if (age < LEDGER_HEARTBEAT_STALE_MS)
|
|
680
|
-
return 'alive';
|
|
749
|
+
return alive(pid === 'alive' ? 'pid-alive' : 'heartbeat-fresh');
|
|
681
750
|
if (pid === 'alive' && entry.startedAt !== null) {
|
|
682
751
|
const startedAt = probe.startedAt(entry.pid);
|
|
683
752
|
if (startedAt === entry.startedAt)
|
|
684
|
-
return 'alive';
|
|
753
|
+
return alive('pid-alive');
|
|
685
754
|
if (startedAt !== null)
|
|
686
|
-
return
|
|
755
|
+
return goneUnlessSurvivor('pid-recycled');
|
|
687
756
|
}
|
|
688
|
-
return orphaned ?
|
|
757
|
+
return orphaned ? dead('orphaned') : alive('unidentifiable');
|
|
758
|
+
}
|
|
759
|
+
/**
|
|
760
|
+
* Whether an entry's holder is still there: {@link entryVerdict}'s state.
|
|
761
|
+
*
|
|
762
|
+
* @param entry - the ledger entry.
|
|
763
|
+
* @param nowMs - the clock.
|
|
764
|
+
* @param probe - pid probe (tests inject one).
|
|
765
|
+
*/
|
|
766
|
+
export function entryLiveness(entry, nowMs, probe = systemPidProbe) {
|
|
767
|
+
return entryVerdict(entry, nowMs, probe).state;
|
|
689
768
|
}
|
|
690
769
|
/**
|
|
691
770
|
* The budget an entry of an unknown format is charged: its numeric
|
|
@@ -1208,15 +1287,26 @@ export function describeScope(e) {
|
|
|
1208
1287
|
return parts.length > 0 ? ` [${parts.join(', ')}]` : '';
|
|
1209
1288
|
}
|
|
1210
1289
|
/**
|
|
1211
|
-
* One line per admitted run: label, scope and task, pid, command,
|
|
1290
|
+
* One line per admitted run: label, scope and task, pid and host, command,
|
|
1291
|
+
* directory, charge and age, then its heartbeat age, child, tool groups and
|
|
1292
|
+
* liveness verdict with the reason (T14066), so a waiter can tell a stale
|
|
1293
|
+
* holder from a real one.
|
|
1212
1294
|
*
|
|
1213
1295
|
* @param entries - the ledger.
|
|
1214
1296
|
* @param nowMs - the clock.
|
|
1297
|
+
* @param probe - pid probe for the verdict (tests inject one).
|
|
1215
1298
|
*/
|
|
1216
|
-
export function describeHolders(entries, nowMs) {
|
|
1299
|
+
export function describeHolders(entries, nowMs, probe = systemPidProbe) {
|
|
1217
1300
|
return entries
|
|
1218
1301
|
.filter((e) => e.state === 'admitted')
|
|
1219
|
-
.map((e) =>
|
|
1302
|
+
.map((e) => {
|
|
1303
|
+
const verdict = entryVerdict(e, nowMs, probe);
|
|
1304
|
+
const child = e.child !== undefined ? `, child ${e.child.pid}` : '';
|
|
1305
|
+
const groups = e.toolGroups.length > 0 ? `, groups ${e.toolGroups.join(',')}` : '';
|
|
1306
|
+
return (`${e.label}${describeScope(e)} pid ${e.pid} on ${e.host} (${e.command})${e.cwd ? ` in ${e.cwd}` : ''}, ` +
|
|
1307
|
+
`${gib(e.footprintBytes)}${e.footprintReason ? ` (${e.footprintReason})` : ''}, for ${age(nowMs - (e.admittedAtMs ?? e.enqueuedAtMs))}; ` +
|
|
1308
|
+
`heartbeat ${age(nowMs - e.heartbeatAtMs)} ago${child}${groups}, ${verdict.state} (${verdict.reason})`);
|
|
1309
|
+
});
|
|
1220
1310
|
}
|
|
1221
1311
|
/** Commands too generic to name a lock-taking wrapper. */
|
|
1222
1312
|
const GENERIC_PROGRAMS = new Set([
|
|
@@ -1632,6 +1722,12 @@ async function admitInner(req, opts) {
|
|
|
1632
1722
|
const { share, lightShare, reading, sampled } = await sampleShare(opts, before.pressure, nowMs, before.entries);
|
|
1633
1723
|
lastReading = reading;
|
|
1634
1724
|
checkQueue();
|
|
1725
|
+
try {
|
|
1726
|
+
opts.onPass?.();
|
|
1727
|
+
}
|
|
1728
|
+
catch {
|
|
1729
|
+
// Best effort: the orphan bound still frees what this could not.
|
|
1730
|
+
}
|
|
1635
1731
|
const dead = findDead(before, now(), probe);
|
|
1636
1732
|
return withLedger(dir, (doc) => {
|
|
1637
1733
|
checkQueue();
|
|
@@ -1679,7 +1775,7 @@ async function admitInner(req, opts) {
|
|
|
1679
1775
|
retryAfterMs: reading !== null ? MEMORY_GATE_RETRY_AFTER_MS : 2_000,
|
|
1680
1776
|
memoryPressure: reading,
|
|
1681
1777
|
ahead,
|
|
1682
|
-
holders: describeLoad(doc, nowMs),
|
|
1778
|
+
holders: describeLoad(doc, nowMs, probe),
|
|
1683
1779
|
};
|
|
1684
1780
|
};
|
|
1685
1781
|
const attempt = async () => {
|
|
@@ -1712,7 +1808,7 @@ async function admitInner(req, opts) {
|
|
|
1712
1808
|
}
|
|
1713
1809
|
if (now() - lastReportAt >= LEDGER_HOLDER_REPORT_MS && opts.notice) {
|
|
1714
1810
|
lastReportAt = now();
|
|
1715
|
-
opts.notice(holderReport(first.doc, entry, pid, now() - t0, capacityBytes, now(), timeoutMs));
|
|
1811
|
+
opts.notice(holderReport(first.doc, entry, pid, now() - t0, capacityBytes, now(), timeoutMs, probe));
|
|
1716
1812
|
}
|
|
1717
1813
|
const remaining = timeoutMs - (now() - t0);
|
|
1718
1814
|
if (remaining <= 0)
|
|
@@ -1791,8 +1887,8 @@ function budgetLoad(doc, capacityBytes) {
|
|
|
1791
1887
|
};
|
|
1792
1888
|
}
|
|
1793
1889
|
/** {@link describeHolders}, plus a line for entries of another CLEO version. */
|
|
1794
|
-
function describeLoad(doc, nowMs) {
|
|
1795
|
-
const lines = describeHolders(doc.entries, nowMs);
|
|
1890
|
+
function describeLoad(doc, nowMs, probe) {
|
|
1891
|
+
const lines = describeHolders(doc.entries, nowMs, probe);
|
|
1796
1892
|
if (doc.foreign.length > 0) {
|
|
1797
1893
|
lines.push(`${doc.foreign.length} entr${doc.foreign.length === 1 ? 'y' : 'ies'} of another CLEO version`);
|
|
1798
1894
|
}
|
|
@@ -1803,13 +1899,13 @@ function describeLoad(doc, nowMs) {
|
|
|
1803
1899
|
* budget, it names the cause (the host's largest memory users, CLEO or not)
|
|
1804
1900
|
* and when the wait gives up (T13914).
|
|
1805
1901
|
*/
|
|
1806
|
-
function holderReport(doc, mine, pid, waitedMs, capacityBytes, nowMs, timeoutMs) {
|
|
1902
|
+
function holderReport(doc, mine, pid, waitedMs, capacityBytes, nowMs, timeoutMs, probe) {
|
|
1807
1903
|
const holders = doc.entries.filter((e) => e.state === 'admitted');
|
|
1808
1904
|
const used = budgetLoad(doc, capacityBytes).bytes;
|
|
1809
1905
|
const ahead = waitingAhead(doc.entries, mine, nowMs);
|
|
1810
1906
|
const lines = [
|
|
1811
1907
|
`still waiting after ${age(waitedMs)} for the machine budget (${gib(used)} of ${budgetPhrase(mine, ledgerBudget(doc.pressure, capacityBytes))} in use, ` +
|
|
1812
|
-
`queue position ${ahead + 1}${mine.priority === 'urgent' ? ' (urgent)' : ''}, ${ahead} waiting ahead). Holders: ${describeLoad(doc, nowMs).join('
|
|
1908
|
+
`queue position ${ahead + 1}${mine.priority === 'urgent' ? ' (urgent)' : ''}, ${ahead} waiting ahead). Holders: ${describeLoad(doc, nowMs, probe).join(' | ') || 'none'}.`,
|
|
1813
1909
|
];
|
|
1814
1910
|
const reading = doc.pressure?.reading ?? null;
|
|
1815
1911
|
if (reading !== null) {
|
|
@@ -1906,6 +2002,9 @@ function holdGrant(entry, dir, waitedMs, opts) {
|
|
|
1906
2002
|
ungoverned: null,
|
|
1907
2003
|
waitedMs,
|
|
1908
2004
|
footprintBytes: entry.footprintBytes,
|
|
2005
|
+
recordChild: (child) => {
|
|
2006
|
+
void update((e) => ({ ...e, child }));
|
|
2007
|
+
},
|
|
1909
2008
|
release: async () => {
|
|
1910
2009
|
if (released)
|
|
1911
2010
|
return;
|