@kernloop/workflows 0.1.0
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/LICENSE +21 -0
- package/dist/budget.d.ts +111 -0
- package/dist/budget.d.ts.map +1 -0
- package/dist/budget.js +146 -0
- package/dist/budget.js.map +1 -0
- package/dist/checkpoints.d.ts +40 -0
- package/dist/checkpoints.d.ts.map +1 -0
- package/dist/checkpoints.js +92 -0
- package/dist/checkpoints.js.map +1 -0
- package/dist/child-iterate-fixtures.d.ts +36 -0
- package/dist/child-iterate-fixtures.d.ts.map +1 -0
- package/dist/child-iterate-fixtures.js +86 -0
- package/dist/child-iterate-fixtures.js.map +1 -0
- package/dist/child-iterate.d.ts +71 -0
- package/dist/child-iterate.d.ts.map +1 -0
- package/dist/child-iterate.js +61 -0
- package/dist/child-iterate.js.map +1 -0
- package/dist/child-spend.d.ts +41 -0
- package/dist/child-spend.d.ts.map +1 -0
- package/dist/child-spend.js +76 -0
- package/dist/child-spend.js.map +1 -0
- package/dist/config.d.ts +43 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +90 -0
- package/dist/config.js.map +1 -0
- package/dist/engine-errors.d.ts +11 -0
- package/dist/engine-errors.d.ts.map +1 -0
- package/dist/engine-errors.js +23 -0
- package/dist/engine-errors.js.map +1 -0
- package/dist/engine-testkit.d.ts +44 -0
- package/dist/engine-testkit.d.ts.map +1 -0
- package/dist/engine-testkit.js +93 -0
- package/dist/engine-testkit.js.map +1 -0
- package/dist/engine-types.d.ts +78 -0
- package/dist/engine-types.d.ts.map +1 -0
- package/dist/engine-types.js +2 -0
- package/dist/engine-types.js.map +1 -0
- package/dist/engine.d.ts +12 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +262 -0
- package/dist/engine.js.map +1 -0
- package/dist/graph.d.ts +87 -0
- package/dist/graph.d.ts.map +1 -0
- package/dist/graph.js +72 -0
- package/dist/graph.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/manifest.d.ts +19 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +41 -0
- package/dist/manifest.js.map +1 -0
- package/dist/state.d.ts +1078 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/state.js +151 -0
- package/dist/state.js.map +1 -0
- package/dist/steps.d.ts +77 -0
- package/dist/steps.d.ts.map +1 -0
- package/dist/steps.js +270 -0
- package/dist/steps.js.map +1 -0
- package/dist/verdict-disposition.d.ts +23 -0
- package/dist/verdict-disposition.d.ts.map +1 -0
- package/dist/verdict-disposition.js +28 -0
- package/dist/verdict-disposition.js.map +1 -0
- package/package.json +38 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 William Zujkowski and Kernloop contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/budget.d.ts
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime budget enforcement for the canonical loop (spec §8) [CLM-0077].
|
|
3
|
+
* The kernel meters per-adapter spend; the engine reads a metered snapshot
|
|
4
|
+
* through an injected `spent()` seam (workflows imports no kernel — it talks
|
|
5
|
+
* a plain function) and decides whether a bounded run may continue.
|
|
6
|
+
*
|
|
7
|
+
* Two modes, one tracking discipline: `enforce` HALTS the run (escalates,
|
|
8
|
+
* resumable) the moment metered spend exceeds the parent TaskContract budget;
|
|
9
|
+
* `unlimited` NEVER halts on budget — the restriction is lifted, the tracking
|
|
10
|
+
* is not. Usage/cost is metered and reported identically in both modes; an
|
|
11
|
+
* `unlimited` run is recorded honestly so a report never implies a cap was
|
|
12
|
+
* honored when it wasn't. Kc still bounds child iteration in unlimited mode
|
|
13
|
+
* (unlimited budget is not unlimited iterations — raising Kc allows more).
|
|
14
|
+
*/
|
|
15
|
+
import { z } from 'zod';
|
|
16
|
+
import type { Finding } from '@kernloop/contracts';
|
|
17
|
+
import type { RunState } from './state.js';
|
|
18
|
+
/** Enforcement mode for a run's budget (spec §8) [CLM-0077]. */
|
|
19
|
+
export declare const BudgetModeSchema: z.ZodEnum<{
|
|
20
|
+
enforce: "enforce";
|
|
21
|
+
unlimited: "unlimited";
|
|
22
|
+
}>;
|
|
23
|
+
export type BudgetMode = z.infer<typeof BudgetModeSchema>;
|
|
24
|
+
/** Metered spend snapshot the engine reads (tokens + usd; wall-clock is the run's). */
|
|
25
|
+
export interface BudgetSpend {
|
|
26
|
+
readonly tokens: number;
|
|
27
|
+
readonly usd: number;
|
|
28
|
+
}
|
|
29
|
+
/** The parent budget a bounded run may not exceed (the TaskContract.budget dims). */
|
|
30
|
+
export interface BudgetLimit {
|
|
31
|
+
readonly tokens: number;
|
|
32
|
+
readonly usd: number;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* The injected budget guard. `mode` is the enforcement mode; `limit` is the
|
|
36
|
+
* parent budget; `spent()` returns the live metered snapshot (the CLI's
|
|
37
|
+
* `totals`). Absent → no runtime enforcement (the engine's tests and any
|
|
38
|
+
* composition root that does not meter run with no budget halt; Kc still
|
|
39
|
+
* bounds child iteration). Present with `enforce` → the run halts when spend
|
|
40
|
+
* exceeds the limit on any tracked dimension.
|
|
41
|
+
*/
|
|
42
|
+
export interface BudgetGuard {
|
|
43
|
+
readonly mode: BudgetMode;
|
|
44
|
+
readonly limit: BudgetLimit;
|
|
45
|
+
readonly spent: () => BudgetSpend;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* True when a bounded run has overspent its parent budget on any tracked
|
|
49
|
+
* dimension. `unlimited` mode always returns false (never halts); an absent
|
|
50
|
+
* guard never halts. Comparison is strict `>`: spending exactly the budget is
|
|
51
|
+
* within it, exceeding it is the halt.
|
|
52
|
+
*/
|
|
53
|
+
export declare function overBudget(guard: BudgetGuard | undefined): boolean;
|
|
54
|
+
/** A per-child sliced budget the fan-out attributes spend against (#56). */
|
|
55
|
+
export interface ChildBudget {
|
|
56
|
+
readonly tokens: number;
|
|
57
|
+
readonly usd: number;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* True when a child has overspent ITS OWN sliced budget (#56) — gauged by the
|
|
61
|
+
* spend ATTRIBUTED to its sub-chain, never the run total, so one child's
|
|
62
|
+
* overspend is independent of its siblings'. A zero-slice child (a specialist
|
|
63
|
+
* adds WORK, not budget — spec §6) carries nothing to overspend and is never
|
|
64
|
+
* gated here; Kc and the run budget still bound it. Strict `>`, mirroring
|
|
65
|
+
* {@link overBudget}.
|
|
66
|
+
*/
|
|
67
|
+
export declare function childOverOwnBudget(budget: ChildBudget, spend: BudgetSpend): boolean;
|
|
68
|
+
/** The structured finding recorded when an enforce-mode run halts on budget [CLM-0077]. */
|
|
69
|
+
export declare function overspendFinding(guard: BudgetGuard): Finding;
|
|
70
|
+
/**
|
|
71
|
+
* Runtime budget enforcement [CLM-0077]: in `enforce` mode a run that has now
|
|
72
|
+
* overspent its parent budget HALTS as escalated (resumable). `unlimited` never
|
|
73
|
+
* halts here; a finished run is not retro-halted — its cost is still reported in
|
|
74
|
+
* full by the always-on metering. Mutates `state` in place.
|
|
75
|
+
*/
|
|
76
|
+
export declare function enforceBudget(state: RunState, guard: BudgetGuard | undefined): void;
|
|
77
|
+
/**
|
|
78
|
+
* The reserve a PRE-node guard keeps free so dispatching the next node cannot
|
|
79
|
+
* push spend past the limit (#342) — per dimension, the larger of a configurable
|
|
80
|
+
* headroom floor (`headroomFraction × limit`) and the largest single-node spend
|
|
81
|
+
* observed so far. The floor covers COLD START (observedMax is 0 until a node
|
|
82
|
+
* runs, so the first/most-expensive node is otherwise unprotected); observed-max
|
|
83
|
+
* adapts the reserve to the run's actual worst node.
|
|
84
|
+
*/
|
|
85
|
+
export declare function nodeReserve(limit: BudgetLimit, observedMax: BudgetSpend, headroomFraction: number): BudgetSpend;
|
|
86
|
+
/**
|
|
87
|
+
* True when dispatching the next node could overshoot the budget (#342): in
|
|
88
|
+
* `enforce` mode, the remaining budget on some dimension is below the reserve.
|
|
89
|
+
* Halting BEFORE the node turns the cap from a soft post-hoc trip into a
|
|
90
|
+
* near-ceiling. `unlimited`/absent never pre-halts.
|
|
91
|
+
*/
|
|
92
|
+
export declare function preNodeOvershoot(guard: BudgetGuard | undefined, observedMax: BudgetSpend, headroomFraction: number): boolean;
|
|
93
|
+
/** The finding recorded when a run halts BEFORE a node on the pre-node guard
|
|
94
|
+
* (#342) — names remaining + reserve + the largest node so the early halt is
|
|
95
|
+
* legible, never opaque. */
|
|
96
|
+
export declare function preNodeHaltFinding(guard: BudgetGuard, observedMax: BudgetSpend, headroomFraction: number): Finding;
|
|
97
|
+
/**
|
|
98
|
+
* Pre-node budget guard (#342): in `enforce` mode, if dispatching the next node
|
|
99
|
+
* could overshoot, HALT as escalated BEFORE it runs — keeping the post-node
|
|
100
|
+
* {@link enforceBudget} as the backstop for the first/novel node the reserve
|
|
101
|
+
* cannot yet anticipate. Mutates `state` in place.
|
|
102
|
+
*/
|
|
103
|
+
export declare function enforceBudgetPreNode(state: RunState, guard: BudgetGuard | undefined, observedMax: BudgetSpend, headroomFraction: number): boolean;
|
|
104
|
+
/** Update the run's largest-single-node spend (#342) from the metered delta
|
|
105
|
+
* across one node's execution — per dimension max. A negative delta (a
|
|
106
|
+
* per-process meter reset on resume) contributes nothing. Mutates `state`. */
|
|
107
|
+
export declare function trackNodeSpend(state: RunState, before: BudgetSpend, after: BudgetSpend): void;
|
|
108
|
+
/** Fold a completed node's metered spend into the run's observed-max (#342),
|
|
109
|
+
* skipping unmetered runs (no guard or no pre-node snapshot). Mutates `state`. */
|
|
110
|
+
export declare function foldNodeSpend(state: RunState, guard: BudgetGuard | undefined, before: BudgetSpend | undefined): void;
|
|
111
|
+
//# sourceMappingURL=budget.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"budget.d.ts","sourceRoot":"","sources":["../src/budget.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,gEAAgE;AAChE,eAAO,MAAM,gBAAgB;;;EAAmC,CAAC;AACjE,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE1D,uFAAuF;AACvF,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED,qFAAqF;AACrF,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,WAAW,CAAC;CACnC;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,WAAW,GAAG,SAAS,GAAG,OAAO,CAIlE;AAED,4EAA4E;AAC5E,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,CAGnF;AAED,2FAA2F;AAC3F,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAS5D;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,WAAW,GAAG,SAAS,GAAG,IAAI,CAMnF;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,WAAW,EAClB,WAAW,EAAE,WAAW,EACxB,gBAAgB,EAAE,MAAM,GACvB,WAAW,CAKb;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,WAAW,GAAG,SAAS,EAC9B,WAAW,EAAE,WAAW,EACxB,gBAAgB,EAAE,MAAM,GACvB,OAAO,CAWT;AAED;;4BAE4B;AAC5B,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,WAAW,EAClB,WAAW,EAAE,WAAW,EACxB,gBAAgB,EAAE,MAAM,GACvB,OAAO,CAYT;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,QAAQ,EACf,KAAK,EAAE,WAAW,GAAG,SAAS,EAC9B,WAAW,EAAE,WAAW,EACxB,gBAAgB,EAAE,MAAM,GACvB,OAAO,CAOT;AAED;;8EAE8E;AAC9E,wBAAgB,cAAc,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,GAAG,IAAI,CAK7F;AAED;kFACkF;AAClF,wBAAgB,aAAa,CAC3B,KAAK,EAAE,QAAQ,EACf,KAAK,EAAE,WAAW,GAAG,SAAS,EAC9B,MAAM,EAAE,WAAW,GAAG,SAAS,GAC9B,IAAI,CAEN"}
|
package/dist/budget.js
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime budget enforcement for the canonical loop (spec §8) [CLM-0077].
|
|
3
|
+
* The kernel meters per-adapter spend; the engine reads a metered snapshot
|
|
4
|
+
* through an injected `spent()` seam (workflows imports no kernel — it talks
|
|
5
|
+
* a plain function) and decides whether a bounded run may continue.
|
|
6
|
+
*
|
|
7
|
+
* Two modes, one tracking discipline: `enforce` HALTS the run (escalates,
|
|
8
|
+
* resumable) the moment metered spend exceeds the parent TaskContract budget;
|
|
9
|
+
* `unlimited` NEVER halts on budget — the restriction is lifted, the tracking
|
|
10
|
+
* is not. Usage/cost is metered and reported identically in both modes; an
|
|
11
|
+
* `unlimited` run is recorded honestly so a report never implies a cap was
|
|
12
|
+
* honored when it wasn't. Kc still bounds child iteration in unlimited mode
|
|
13
|
+
* (unlimited budget is not unlimited iterations — raising Kc allows more).
|
|
14
|
+
*/
|
|
15
|
+
import { z } from 'zod';
|
|
16
|
+
/** Enforcement mode for a run's budget (spec §8) [CLM-0077]. */
|
|
17
|
+
export const BudgetModeSchema = z.enum(['enforce', 'unlimited']);
|
|
18
|
+
/**
|
|
19
|
+
* True when a bounded run has overspent its parent budget on any tracked
|
|
20
|
+
* dimension. `unlimited` mode always returns false (never halts); an absent
|
|
21
|
+
* guard never halts. Comparison is strict `>`: spending exactly the budget is
|
|
22
|
+
* within it, exceeding it is the halt.
|
|
23
|
+
*/
|
|
24
|
+
export function overBudget(guard) {
|
|
25
|
+
if (guard === undefined || guard.mode === 'unlimited')
|
|
26
|
+
return false;
|
|
27
|
+
const spent = guard.spent();
|
|
28
|
+
return spent.tokens > guard.limit.tokens || spent.usd > guard.limit.usd;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* True when a child has overspent ITS OWN sliced budget (#56) — gauged by the
|
|
32
|
+
* spend ATTRIBUTED to its sub-chain, never the run total, so one child's
|
|
33
|
+
* overspend is independent of its siblings'. A zero-slice child (a specialist
|
|
34
|
+
* adds WORK, not budget — spec §6) carries nothing to overspend and is never
|
|
35
|
+
* gated here; Kc and the run budget still bound it. Strict `>`, mirroring
|
|
36
|
+
* {@link overBudget}.
|
|
37
|
+
*/
|
|
38
|
+
export function childOverOwnBudget(budget, spend) {
|
|
39
|
+
if (budget.tokens <= 0 && budget.usd <= 0)
|
|
40
|
+
return false;
|
|
41
|
+
return spend.tokens > budget.tokens || spend.usd > budget.usd;
|
|
42
|
+
}
|
|
43
|
+
/** The structured finding recorded when an enforce-mode run halts on budget [CLM-0077]. */
|
|
44
|
+
export function overspendFinding(guard) {
|
|
45
|
+
const spent = guard.spent();
|
|
46
|
+
return {
|
|
47
|
+
severity: 'error',
|
|
48
|
+
message: `run exceeded its budget (spent ${String(spent.tokens)} tokens / $${String(spent.usd)}; ` +
|
|
49
|
+
`limit ${String(guard.limit.tokens)} tokens / $${String(guard.limit.usd)}) — ` +
|
|
50
|
+
'halted in enforce mode; raise the budget or re-run unlimited, then resume',
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Runtime budget enforcement [CLM-0077]: in `enforce` mode a run that has now
|
|
55
|
+
* overspent its parent budget HALTS as escalated (resumable). `unlimited` never
|
|
56
|
+
* halts here; a finished run is not retro-halted — its cost is still reported in
|
|
57
|
+
* full by the always-on metering. Mutates `state` in place.
|
|
58
|
+
*/
|
|
59
|
+
export function enforceBudget(state, guard) {
|
|
60
|
+
if (state.status !== 'running' || state.cursor.phase === 'done')
|
|
61
|
+
return;
|
|
62
|
+
if (guard === undefined || !overBudget(guard))
|
|
63
|
+
return;
|
|
64
|
+
state.status = 'escalated';
|
|
65
|
+
state.haltReason = 'budget';
|
|
66
|
+
state.findings.push(overspendFinding(guard));
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* The reserve a PRE-node guard keeps free so dispatching the next node cannot
|
|
70
|
+
* push spend past the limit (#342) — per dimension, the larger of a configurable
|
|
71
|
+
* headroom floor (`headroomFraction × limit`) and the largest single-node spend
|
|
72
|
+
* observed so far. The floor covers COLD START (observedMax is 0 until a node
|
|
73
|
+
* runs, so the first/most-expensive node is otherwise unprotected); observed-max
|
|
74
|
+
* adapts the reserve to the run's actual worst node.
|
|
75
|
+
*/
|
|
76
|
+
export function nodeReserve(limit, observedMax, headroomFraction) {
|
|
77
|
+
return {
|
|
78
|
+
tokens: Math.max(headroomFraction * limit.tokens, observedMax.tokens),
|
|
79
|
+
usd: Math.max(headroomFraction * limit.usd, observedMax.usd),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* True when dispatching the next node could overshoot the budget (#342): in
|
|
84
|
+
* `enforce` mode, the remaining budget on some dimension is below the reserve.
|
|
85
|
+
* Halting BEFORE the node turns the cap from a soft post-hoc trip into a
|
|
86
|
+
* near-ceiling. `unlimited`/absent never pre-halts.
|
|
87
|
+
*/
|
|
88
|
+
export function preNodeOvershoot(guard, observedMax, headroomFraction) {
|
|
89
|
+
if (guard === undefined || guard.mode !== 'enforce')
|
|
90
|
+
return false;
|
|
91
|
+
// Already over → the post-node {@link enforceBudget} owns this with its clearer
|
|
92
|
+
// "exceeded its budget" halt; the pre-node guard only PREVENTS overshoot (still
|
|
93
|
+
// within budget, but the next node's reserve isn't covered).
|
|
94
|
+
if (overBudget(guard))
|
|
95
|
+
return false;
|
|
96
|
+
const spent = guard.spent();
|
|
97
|
+
const reserve = nodeReserve(guard.limit, observedMax, headroomFraction);
|
|
98
|
+
return (guard.limit.tokens - spent.tokens < reserve.tokens || guard.limit.usd - spent.usd < reserve.usd);
|
|
99
|
+
}
|
|
100
|
+
/** The finding recorded when a run halts BEFORE a node on the pre-node guard
|
|
101
|
+
* (#342) — names remaining + reserve + the largest node so the early halt is
|
|
102
|
+
* legible, never opaque. */
|
|
103
|
+
export function preNodeHaltFinding(guard, observedMax, headroomFraction) {
|
|
104
|
+
const spent = guard.spent();
|
|
105
|
+
const reserve = nodeReserve(guard.limit, observedMax, headroomFraction);
|
|
106
|
+
return {
|
|
107
|
+
severity: 'error',
|
|
108
|
+
message: `run halted BEFORE the next node to avoid budget overshoot (#342): remaining ` +
|
|
109
|
+
`${String(guard.limit.tokens - spent.tokens)} tokens / $${String(guard.limit.usd - spent.usd)} ` +
|
|
110
|
+
`< reserve ${String(reserve.tokens)} tokens / $${String(reserve.usd)} ` +
|
|
111
|
+
`(largest node so far ${String(observedMax.tokens)} tokens / $${String(observedMax.usd)}) — ` +
|
|
112
|
+
`raise the budget or re-run unlimited, then resume`,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Pre-node budget guard (#342): in `enforce` mode, if dispatching the next node
|
|
117
|
+
* could overshoot, HALT as escalated BEFORE it runs — keeping the post-node
|
|
118
|
+
* {@link enforceBudget} as the backstop for the first/novel node the reserve
|
|
119
|
+
* cannot yet anticipate. Mutates `state` in place.
|
|
120
|
+
*/
|
|
121
|
+
export function enforceBudgetPreNode(state, guard, observedMax, headroomFraction) {
|
|
122
|
+
if (state.status !== 'running' || state.cursor.phase === 'done')
|
|
123
|
+
return false;
|
|
124
|
+
if (guard === undefined || !preNodeOvershoot(guard, observedMax, headroomFraction))
|
|
125
|
+
return false;
|
|
126
|
+
state.status = 'escalated';
|
|
127
|
+
state.haltReason = 'budget';
|
|
128
|
+
state.findings.push(preNodeHaltFinding(guard, observedMax, headroomFraction));
|
|
129
|
+
return true; // halted before the node — the caller skips dispatch
|
|
130
|
+
}
|
|
131
|
+
/** Update the run's largest-single-node spend (#342) from the metered delta
|
|
132
|
+
* across one node's execution — per dimension max. A negative delta (a
|
|
133
|
+
* per-process meter reset on resume) contributes nothing. Mutates `state`. */
|
|
134
|
+
export function trackNodeSpend(state, before, after) {
|
|
135
|
+
state.observedMaxNodeSpend = {
|
|
136
|
+
tokens: Math.max(state.observedMaxNodeSpend.tokens, after.tokens - before.tokens),
|
|
137
|
+
usd: Math.max(state.observedMaxNodeSpend.usd, after.usd - before.usd),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
/** Fold a completed node's metered spend into the run's observed-max (#342),
|
|
141
|
+
* skipping unmetered runs (no guard or no pre-node snapshot). Mutates `state`. */
|
|
142
|
+
export function foldNodeSpend(state, guard, before) {
|
|
143
|
+
if (guard !== undefined && before !== undefined)
|
|
144
|
+
trackNodeSpend(state, before, guard.spent());
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=budget.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"budget.js","sourceRoot":"","sources":["../src/budget.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type CheckpointRecord } from './state.js';
|
|
2
|
+
/**
|
|
3
|
+
* What the engine needs from storage [CLM-0044]. `save` MUST be durable
|
|
4
|
+
* when it resolves — the engine treats a rejected save as a run failure
|
|
5
|
+
* (a checkpoint that silently failed to persist would let `resume` lie).
|
|
6
|
+
*/
|
|
7
|
+
export interface CheckpointStore {
|
|
8
|
+
/** Append one checkpoint. Records for a run arrive in increasing `seq`. */
|
|
9
|
+
save(record: CheckpointRecord): Promise<void>;
|
|
10
|
+
/** The highest-`seq` checkpoint for a run, or undefined if none. */
|
|
11
|
+
latest(runId: string): Promise<CheckpointRecord | undefined>;
|
|
12
|
+
/** All checkpoints for a run, in increasing `seq`. */
|
|
13
|
+
list(runId: string): Promise<readonly CheckpointRecord[]>;
|
|
14
|
+
}
|
|
15
|
+
/** In-memory store: honest, bounded to the process lifetime. */
|
|
16
|
+
export declare class InMemoryCheckpointStore implements CheckpointStore {
|
|
17
|
+
private readonly byRun;
|
|
18
|
+
save(record: CheckpointRecord): Promise<void>;
|
|
19
|
+
latest(runId: string): Promise<CheckpointRecord | undefined>;
|
|
20
|
+
list(runId: string): Promise<readonly CheckpointRecord[]>;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Append-only JSONL file store: one checkpoint per line. Reads tolerate
|
|
24
|
+
* corrupt lines (unparseable JSON or schema-invalid records) by skipping
|
|
25
|
+
* them — a torn final line is exactly what a kill mid-write leaves behind,
|
|
26
|
+
* and the last COMPLETE checkpoint is the resume point [CLM-0044]. Skipped
|
|
27
|
+
* lines are counted on the instance so callers can surface the damage; they
|
|
28
|
+
* are never silently repaired.
|
|
29
|
+
*/
|
|
30
|
+
export declare class JsonlCheckpointStore implements CheckpointStore {
|
|
31
|
+
private readonly file;
|
|
32
|
+
/** Corrupt lines encountered by reads since construction. */
|
|
33
|
+
corruptLines: number;
|
|
34
|
+
constructor(file: string);
|
|
35
|
+
save(record: CheckpointRecord): Promise<void>;
|
|
36
|
+
latest(runId: string): Promise<CheckpointRecord | undefined>;
|
|
37
|
+
list(runId: string): Promise<readonly CheckpointRecord[]>;
|
|
38
|
+
private parseLine;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=checkpoints.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checkpoints.d.ts","sourceRoot":"","sources":["../src/checkpoints.ts"],"names":[],"mappings":"AAWA,OAAO,EAA0B,KAAK,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE3E;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,2EAA2E;IAC3E,IAAI,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9C,oEAAoE;IACpE,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC,CAAC;IAC7D,sDAAsD;IACtD,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,gBAAgB,EAAE,CAAC,CAAC;CAC3D;AAED,gEAAgE;AAChE,qBAAa,uBAAwB,YAAW,eAAe;IAC7D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAyC;IAE/D,IAAI,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAO7C,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC;IAK5D,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,gBAAgB,EAAE,CAAC;CAG1D;AAED;;;;;;;GAOG;AACH,qBAAa,oBAAqB,YAAW,eAAe;IAC1D,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,6DAA6D;IAC7D,YAAY,SAAK;gBAEL,IAAI,EAAE,MAAM;IAIlB,IAAI,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAK7C,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC;IAK5D,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,gBAAgB,EAAE,CAAC;IAkB/D,OAAO,CAAC,SAAS;CAelB"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checkpoint persistence — an INJECTED interface (p2 design notes, open
|
|
3
|
+
* question 2): the composition root decides where checkpoints live; the
|
|
4
|
+
* engine only knows this contract. Two real implementations ship here, both
|
|
5
|
+
* wiring-complete: an in-memory store (tests, ephemeral runs) and an
|
|
6
|
+
* append-only JSONL file store (a durable one the composition root can bind
|
|
7
|
+
* today). Ported from v1's ICheckpointStore/InMemoryCheckpointStore
|
|
8
|
+
* (nexus-agents orchestration/graph) — see PORT-NOTES.md for the deltas.
|
|
9
|
+
*/
|
|
10
|
+
import { appendFile, mkdir, readFile } from 'node:fs/promises';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import { CheckpointRecordSchema } from './state.js';
|
|
13
|
+
/** In-memory store: honest, bounded to the process lifetime. */
|
|
14
|
+
export class InMemoryCheckpointStore {
|
|
15
|
+
byRun = new Map();
|
|
16
|
+
save(record) {
|
|
17
|
+
const records = this.byRun.get(record.runId) ?? [];
|
|
18
|
+
records.push(record);
|
|
19
|
+
this.byRun.set(record.runId, records);
|
|
20
|
+
return Promise.resolve();
|
|
21
|
+
}
|
|
22
|
+
latest(runId) {
|
|
23
|
+
const records = this.byRun.get(runId);
|
|
24
|
+
return Promise.resolve(records?.[records.length - 1]);
|
|
25
|
+
}
|
|
26
|
+
list(runId) {
|
|
27
|
+
return Promise.resolve(this.byRun.get(runId) ?? []);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Append-only JSONL file store: one checkpoint per line. Reads tolerate
|
|
32
|
+
* corrupt lines (unparseable JSON or schema-invalid records) by skipping
|
|
33
|
+
* them — a torn final line is exactly what a kill mid-write leaves behind,
|
|
34
|
+
* and the last COMPLETE checkpoint is the resume point [CLM-0044]. Skipped
|
|
35
|
+
* lines are counted on the instance so callers can surface the damage; they
|
|
36
|
+
* are never silently repaired.
|
|
37
|
+
*/
|
|
38
|
+
export class JsonlCheckpointStore {
|
|
39
|
+
file;
|
|
40
|
+
/** Corrupt lines encountered by reads since construction. */
|
|
41
|
+
corruptLines = 0;
|
|
42
|
+
constructor(file) {
|
|
43
|
+
this.file = path.resolve(file);
|
|
44
|
+
}
|
|
45
|
+
async save(record) {
|
|
46
|
+
await mkdir(path.dirname(this.file), { recursive: true });
|
|
47
|
+
await appendFile(this.file, `${JSON.stringify(record)}\n`, 'utf8');
|
|
48
|
+
}
|
|
49
|
+
async latest(runId) {
|
|
50
|
+
const records = await this.list(runId);
|
|
51
|
+
return records[records.length - 1];
|
|
52
|
+
}
|
|
53
|
+
async list(runId) {
|
|
54
|
+
let raw;
|
|
55
|
+
try {
|
|
56
|
+
raw = await readFile(this.file, 'utf8');
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
if (error.code === 'ENOENT')
|
|
60
|
+
return [];
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
const records = [];
|
|
64
|
+
for (const line of raw.split('\n')) {
|
|
65
|
+
if (line.trim() === '')
|
|
66
|
+
continue;
|
|
67
|
+
const record = this.parseLine(line);
|
|
68
|
+
if (record === undefined)
|
|
69
|
+
continue;
|
|
70
|
+
if (record.runId === runId)
|
|
71
|
+
records.push(record);
|
|
72
|
+
}
|
|
73
|
+
return records.sort((a, b) => a.seq - b.seq);
|
|
74
|
+
}
|
|
75
|
+
parseLine(line) {
|
|
76
|
+
let parsed;
|
|
77
|
+
try {
|
|
78
|
+
parsed = JSON.parse(line);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
this.corruptLines += 1;
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
const result = CheckpointRecordSchema.safeParse(parsed);
|
|
85
|
+
if (!result.success) {
|
|
86
|
+
this.corruptLines += 1;
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
return result.data;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=checkpoints.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"checkpoints.js","sourceRoot":"","sources":["../src/checkpoints.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared scripted fixtures for the child-iteration + budget-mode tests
|
|
3
|
+
* [CLM-0043, CLM-0077]. Kept apart so neither test file outgrows the 400-line
|
|
4
|
+
* budget; not a runtime export (test-only, imported by `*.test.ts`).
|
|
5
|
+
*/
|
|
6
|
+
import type { Brief, Finding, Outcome, TaskContract, Verdict } from '@kernloop/contracts';
|
|
7
|
+
import type { NodeContext, NodeExecutor } from './engine.js';
|
|
8
|
+
export declare const task: TaskContract;
|
|
9
|
+
export declare const brief: (taskId: string) => Brief;
|
|
10
|
+
export declare const verdict: (taskId: string, gate: string, result: Verdict["result"]) => Verdict;
|
|
11
|
+
export declare const outcome: (taskId: string) => Outcome;
|
|
12
|
+
export declare const names: (trace: readonly {
|
|
13
|
+
node: string;
|
|
14
|
+
childId?: string;
|
|
15
|
+
}[]) => string[];
|
|
16
|
+
/**
|
|
17
|
+
* A scripted executor set. `qualityByChild` maps a child id to its sequence of
|
|
18
|
+
* quality results (last entry repeats). Records each implement's NodeContext so
|
|
19
|
+
* tests can assert the folded child findings + childIteration.
|
|
20
|
+
*/
|
|
21
|
+
export declare function scripted(qualityByChild?: Record<string, Array<Verdict['result']>>): {
|
|
22
|
+
executors: Record<string, NodeExecutor>;
|
|
23
|
+
qualityCalls: Record<string, number>;
|
|
24
|
+
implementCtx: {
|
|
25
|
+
childId: string;
|
|
26
|
+
iteration: number;
|
|
27
|
+
findings: readonly Finding[];
|
|
28
|
+
}[];
|
|
29
|
+
};
|
|
30
|
+
export declare function counted(executors: Record<string, NodeExecutor>): {
|
|
31
|
+
executors: {
|
|
32
|
+
[k: string]: (input: unknown, ctx: NodeContext) => Promise<unknown>;
|
|
33
|
+
};
|
|
34
|
+
calls: Record<string, number>;
|
|
35
|
+
};
|
|
36
|
+
//# sourceMappingURL=child-iterate-fixtures.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"child-iterate-fixtures.d.ts","sourceRoot":"","sources":["../src/child-iterate-fixtures.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAC1F,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE7D,eAAO,MAAM,IAAI,EAAE,YASlB,CAAC;AAEF,eAAO,MAAM,KAAK,GAAI,QAAQ,MAAM,KAAG,KAKrC,CAAC;AAEH,eAAO,MAAM,OAAO,GAAI,QAAQ,MAAM,EAAE,MAAM,MAAM,EAAE,QAAQ,OAAO,CAAC,QAAQ,CAAC,KAAG,OAUhF,CAAC;AAEH,eAAO,MAAM,OAAO,GAAI,QAAQ,MAAM,KAAG,OAOvC,CAAC;AAEH,eAAO,MAAM,KAAK,GAAI,OAAO,SAAS;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,aACO,CAAC;AAElF;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,cAAc,GAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAM;;;;iBAE/C,MAAM;mBAAa,MAAM;kBAAY,SAAS,OAAO,EAAE;;EAiC7F;AAED,wBAAgB,OAAO,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC;;6BAKjD,OAAO,OAAO,WAAW;;;EAOtC"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
export const task = {
|
|
2
|
+
id: 'task-1',
|
|
3
|
+
goal: 'ship the feature',
|
|
4
|
+
constraints: [],
|
|
5
|
+
budget: { tokens: 1000, usd: 1, wallClockMin: 10 },
|
|
6
|
+
evidence: [],
|
|
7
|
+
definitionOfDone: [],
|
|
8
|
+
authorityCeiling: 'suggest',
|
|
9
|
+
overlay: 'repo',
|
|
10
|
+
};
|
|
11
|
+
export const brief = (taskId) => ({
|
|
12
|
+
taskId,
|
|
13
|
+
sections: [],
|
|
14
|
+
budget: { allotted: 10, used: 0 },
|
|
15
|
+
compilerVersion: 'scripted-1',
|
|
16
|
+
});
|
|
17
|
+
export const verdict = (taskId, gate, result) => ({
|
|
18
|
+
taskId,
|
|
19
|
+
gate,
|
|
20
|
+
result,
|
|
21
|
+
confidence: 1,
|
|
22
|
+
findings: result === 'approve' || result === 'pass'
|
|
23
|
+
? []
|
|
24
|
+
: [{ severity: 'error', message: `${gate} wants ${taskId} fixed` }],
|
|
25
|
+
cost: { tokens: 0, usd: 0 },
|
|
26
|
+
});
|
|
27
|
+
export const outcome = (taskId) => ({
|
|
28
|
+
taskId,
|
|
29
|
+
status: 'success',
|
|
30
|
+
signals: [],
|
|
31
|
+
cost: { tokens: 0, usd: 0 },
|
|
32
|
+
traceRef: `trace-${taskId}`,
|
|
33
|
+
distillCandidates: [],
|
|
34
|
+
});
|
|
35
|
+
export const names = (trace) => trace.map((t) => (t.childId === undefined ? t.node : `${t.node}:${t.childId}`));
|
|
36
|
+
/**
|
|
37
|
+
* A scripted executor set. `qualityByChild` maps a child id to its sequence of
|
|
38
|
+
* quality results (last entry repeats). Records each implement's NodeContext so
|
|
39
|
+
* tests can assert the folded child findings + childIteration.
|
|
40
|
+
*/
|
|
41
|
+
export function scripted(qualityByChild = {}) {
|
|
42
|
+
const qualityCalls = {};
|
|
43
|
+
const implementCtx = [];
|
|
44
|
+
const executors = {
|
|
45
|
+
frame: () => Promise.resolve(task),
|
|
46
|
+
research: () => Promise.resolve(brief(task.id)),
|
|
47
|
+
plan: () => Promise.resolve(brief(task.id)),
|
|
48
|
+
vote: (_i, ctx) => Promise.resolve(verdict(ctx.taskId, 'vote', 'approve')),
|
|
49
|
+
decompose: () => Promise.resolve([
|
|
50
|
+
{ ...task, id: `${task.id}.c1`, parent: task.id },
|
|
51
|
+
{ ...task, id: `${task.id}.c2`, parent: task.id },
|
|
52
|
+
]),
|
|
53
|
+
implement: (input, ctx) => {
|
|
54
|
+
const c = input;
|
|
55
|
+
implementCtx.push({
|
|
56
|
+
childId: c.id,
|
|
57
|
+
iteration: ctx.childIteration ?? -1,
|
|
58
|
+
findings: ctx.findings,
|
|
59
|
+
});
|
|
60
|
+
return Promise.resolve(outcome(c.id));
|
|
61
|
+
},
|
|
62
|
+
quality: (_i, ctx) => {
|
|
63
|
+
const id = ctx.child?.id ?? ctx.taskId;
|
|
64
|
+
const seq = qualityByChild[id] ?? ['pass'];
|
|
65
|
+
const n = qualityCalls[id] ?? 0;
|
|
66
|
+
qualityCalls[id] = n + 1;
|
|
67
|
+
return Promise.resolve(verdict(id, 'quality', seq[Math.min(n, seq.length - 1)] ?? 'pass'));
|
|
68
|
+
},
|
|
69
|
+
review: (_i, ctx) => Promise.resolve(verdict(ctx.child?.id ?? ctx.taskId, 'review', 'approve')),
|
|
70
|
+
integrate: () => Promise.resolve(outcome(task.id)),
|
|
71
|
+
retrospect: (input) => Promise.resolve(input),
|
|
72
|
+
};
|
|
73
|
+
return { executors, qualityCalls, implementCtx };
|
|
74
|
+
}
|
|
75
|
+
export function counted(executors) {
|
|
76
|
+
const calls = {};
|
|
77
|
+
const wrapped = Object.fromEntries(Object.entries(executors).map(([key, fn]) => [
|
|
78
|
+
key,
|
|
79
|
+
(input, ctx) => {
|
|
80
|
+
calls[key] = (calls[key] ?? 0) + 1;
|
|
81
|
+
return fn(input, ctx);
|
|
82
|
+
},
|
|
83
|
+
]));
|
|
84
|
+
return { executors: wrapped, calls };
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=child-iterate-fixtures.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"child-iterate-fixtures.js","sourceRoot":"","sources":["../src/child-iterate-fixtures.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The review-driven child iteration back-edge [CLM-0043] — the actor-critic
|
|
3
|
+
* inner loop. It MIRRORS the proven vote→plan machinery (steps.ts
|
|
4
|
+
* `advanceVote`): the branching lives in `advanceChild`, NOT in the graph,
|
|
5
|
+
* which stays frozen and acyclic. When a child's quality gate rejects and the
|
|
6
|
+
* child is still within its Kc bound (and the run is within budget), the
|
|
7
|
+
* cursor resets to the child's `implement` sub-node, the gate's findings fold
|
|
8
|
+
* into `child.findings` (read by the coder's next attempt), and the child's
|
|
9
|
+
* `iteration` increments — the same shape as a rejected vote re-entering plan.
|
|
10
|
+
*
|
|
11
|
+
* At the bound (Kc exhausted OR budget exceeded) the child is marked
|
|
12
|
+
* `escalated` with its findings recorded, and the fan-out advances to the next
|
|
13
|
+
* child: one stuck child must not sink the sprint. On a passing quality gate
|
|
14
|
+
* the chain advances through review to the next child (today's behavior).
|
|
15
|
+
*
|
|
16
|
+
* HONESTY GUARD: the review gate is advisory (CLM-0064) and does NOT drive
|
|
17
|
+
* iteration by default. Quality drives the loop; review findings ride along as
|
|
18
|
+
* non-gating hints folded into the next attempt. Review-driven iteration is
|
|
19
|
+
* gated behind `reviewDrivesIteration` (default off) — enabled only when the
|
|
20
|
+
* review gate is promoted to enforce; we never claim review enforces.
|
|
21
|
+
*/
|
|
22
|
+
import type { Finding, Verdict } from '@kernloop/contracts';
|
|
23
|
+
import type { LoopNode } from './graph.js';
|
|
24
|
+
import type { ChildResult, RunState } from './state.js';
|
|
25
|
+
/** How a child sub-gate's verdict steers the fan-out cursor. */
|
|
26
|
+
export type ChildBranch = 'pass' | 'reiterate' | 'escalate';
|
|
27
|
+
/** An audit event fired on each child re-iteration (the CLI wires it to the chain). */
|
|
28
|
+
export interface ChildIterateEvent {
|
|
29
|
+
readonly childId: string;
|
|
30
|
+
readonly iteration: number;
|
|
31
|
+
readonly gate: string;
|
|
32
|
+
readonly findingCount: number;
|
|
33
|
+
}
|
|
34
|
+
/** Which gates drive child iteration this run — flags the engine resolves from
|
|
35
|
+
* config (review at enforce; parsimony at intensity full/ultra). */
|
|
36
|
+
export interface IterationDrivers {
|
|
37
|
+
readonly reviewDrives: boolean;
|
|
38
|
+
readonly parsimonyDrives: boolean;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Whether THIS gate sub-node drives child iteration. Quality always drives.
|
|
42
|
+
* Review drives only when `reviewDrives` is on (the review gate is at enforce)
|
|
43
|
+
* — the honesty guard. Parsimony drives only when `parsimonyDrives` is on (the
|
|
44
|
+
* overlay's `gates.parsimony.intensity` is full/ultra, #9/#415) — at lite/off it
|
|
45
|
+
* is advisory/disabled. Non-driving gates never trigger a re-implement; their
|
|
46
|
+
* findings still fold in as hints (see {@link foldHints}).
|
|
47
|
+
*/
|
|
48
|
+
export declare function gateDrivesIteration(node: LoopNode, drivers: IterationDrivers): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Decide the branch after a driving gate ran for a child. `pass` advances the
|
|
51
|
+
* sub-chain; `reiterate` re-runs implement (within Kc and budget); `escalate`
|
|
52
|
+
* stops the child at the bound. `withinBudget` is false when a re-entry would
|
|
53
|
+
* exceed the run budget (Part B) — that forces `escalate` before Kc.
|
|
54
|
+
*
|
|
55
|
+
* An `escalate` VERDICT (#192) — the gate ruling "a human must decide" — forces
|
|
56
|
+
* `escalate` IMMEDIATELY, regardless of Kc or budget: a human ruling is not a
|
|
57
|
+
* re-attempt. Routing goes through {@link verdictDisposition} so a future
|
|
58
|
+
* `VerdictResult` value is a compile error here, never a silent mis-branch.
|
|
59
|
+
*/
|
|
60
|
+
export declare function childBranch(verdict: Verdict, result: ChildResult, kc: number, withinBudget: boolean): ChildBranch;
|
|
61
|
+
/** Fold a non-driving gate's findings into the child as hints (review → next attempt). */
|
|
62
|
+
export declare function foldHints(result: ChildResult, findings: readonly Finding[]): void;
|
|
63
|
+
/**
|
|
64
|
+
* Re-enter the child's implement sub-node: fold the driving gate's findings,
|
|
65
|
+
* bump the child iteration, reset the sub-cursor to 0 (implement). Mirrors a
|
|
66
|
+
* rejected vote pushing findings and re-entering plan.
|
|
67
|
+
*/
|
|
68
|
+
export declare function reiterateChild(state: RunState, result: ChildResult, findings: readonly Finding[]): void;
|
|
69
|
+
/** Mark a child escalated at the bound: record the findings, never re-attempt. */
|
|
70
|
+
export declare function escalateChild(result: ChildResult, findings: readonly Finding[]): void;
|
|
71
|
+
//# sourceMappingURL=child-iterate.d.ts.map
|