@miller-tech/uap 1.213.0 → 1.215.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/dist/.tsbuildinfo +1 -1
- package/dist/bin/cli.js +2 -0
- package/dist/bin/cli.js.map +1 -1
- package/dist/cli/deliver.d.ts +66 -0
- package/dist/cli/deliver.d.ts.map +1 -1
- package/dist/cli/deliver.js +227 -7
- package/dist/cli/deliver.js.map +1 -1
- package/dist/cli/verify.d.ts +5 -0
- package/dist/cli/verify.d.ts.map +1 -1
- package/dist/cli/verify.js +50 -1
- package/dist/cli/verify.js.map +1 -1
- package/dist/cli/wizard-config.d.ts.map +1 -1
- package/dist/cli/wizard-config.js +26 -1
- package/dist/cli/wizard-config.js.map +1 -1
- package/dist/delivery/await-run.d.ts +47 -0
- package/dist/delivery/await-run.d.ts.map +1 -1
- package/dist/delivery/await-run.js +392 -2
- package/dist/delivery/await-run.js.map +1 -1
- package/dist/delivery/capability-profile.d.ts +124 -0
- package/dist/delivery/capability-profile.d.ts.map +1 -0
- package/dist/delivery/capability-profile.js +392 -0
- package/dist/delivery/capability-profile.js.map +1 -0
- package/dist/delivery/convergence-loop.d.ts +38 -0
- package/dist/delivery/convergence-loop.d.ts.map +1 -1
- package/dist/delivery/convergence-loop.js +98 -1
- package/dist/delivery/convergence-loop.js.map +1 -1
- package/dist/delivery/epic-controller.d.ts +9 -0
- package/dist/delivery/epic-controller.d.ts.map +1 -1
- package/dist/delivery/epic-controller.js +1 -0
- package/dist/delivery/epic-controller.js.map +1 -1
- package/dist/delivery/epic-mission.d.ts +10 -2
- package/dist/delivery/epic-mission.d.ts.map +1 -1
- package/dist/delivery/epic-mission.js +1 -1
- package/dist/delivery/epic-mission.js.map +1 -1
- package/dist/delivery/escalation.d.ts +8 -0
- package/dist/delivery/escalation.d.ts.map +1 -1
- package/dist/delivery/escalation.js +8 -1
- package/dist/delivery/escalation.js.map +1 -1
- package/dist/delivery/execution-gate.d.ts +29 -0
- package/dist/delivery/execution-gate.d.ts.map +1 -1
- package/dist/delivery/execution-gate.js +237 -9
- package/dist/delivery/execution-gate.js.map +1 -1
- package/dist/delivery/mission-acceptance.d.ts +4 -0
- package/dist/delivery/mission-acceptance.d.ts.map +1 -1
- package/dist/delivery/mission-acceptance.js +35 -1
- package/dist/delivery/mission-acceptance.js.map +1 -1
- package/dist/delivery/run-state.d.ts +31 -0
- package/dist/delivery/run-state.d.ts.map +1 -1
- package/dist/delivery/run-state.js +49 -0
- package/dist/delivery/run-state.js.map +1 -1
- package/dist/mcp-router/tools/deliver.d.ts.map +1 -1
- package/dist/mcp-router/tools/deliver.js +5 -2
- package/dist/mcp-router/tools/deliver.js.map +1 -1
- package/package.json +1 -1
- package/scripts/lib/llama-upstream.sh +64 -16
- package/src/policies/enforcers/__pycache__/_common.cpython-312.pyc +0 -0
- package/templates/hooks/__pycache__/deliver_autoroute.cpython-312.pyc +0 -0
- package/tools/agents/scripts/__pycache__/toolcall_path_normalizer.cpython-312.pyc +0 -0
- package/tools/agents/scripts/confidence_escalation.py +57 -0
- package/tools/agents/tests/test_confidence_escalation.py +140 -2
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capability profile — a RUNTIME measurement of how much the deliverable
|
|
3
|
+
* actually does, persisted like a visual baseline and compared across a run.
|
|
4
|
+
*
|
|
5
|
+
* WHY THIS EXISTS. Every other rung asks "does it run cleanly?". None asks
|
|
6
|
+
* "does it still do as much?", and those are different questions with opposite
|
|
7
|
+
* failure modes: a build can buy a clean verdict by doing LESS. Measured on
|
|
8
|
+
* octopus_invaders_v4 (2026-08-19) — one run took the gate score 0% -> 100%
|
|
9
|
+
* while the deliverable lost most of its function:
|
|
10
|
+
*
|
|
11
|
+
* draw calls 12 -> 3 rAF loops 6 -> 2
|
|
12
|
+
* listeners 8 -> 3 frames/7s 419 -> 25
|
|
13
|
+
* bundle 65.8KB -> 25.8KB
|
|
14
|
+
*
|
|
15
|
+
* The acceptance judge scored that 8/9. The execution gate passed it: it DOES
|
|
16
|
+
* render, it DOES tick, no console errors. `--keep-best`/no-regress
|
|
17
|
+
* (src/delivery/snapshot.ts) could not catch it either, because that rolls back
|
|
18
|
+
* on the GATE SCORE and the gate score went UP.
|
|
19
|
+
*
|
|
20
|
+
* WHAT THIS FILE IS AND IS NOT. It is the PURE half: the profile shape, the
|
|
21
|
+
* comparison, the feedback, and the on-disk baseline. The MEASUREMENT lives in
|
|
22
|
+
* execution-gate.ts, inside the browser session that rung already opens — see
|
|
23
|
+
* the note on ordering below. Splitting it that way is deliberate and was the
|
|
24
|
+
* main finding of architecture review:
|
|
25
|
+
*
|
|
26
|
+
* - Ordering becomes STRUCTURAL rather than a convention. A profile only
|
|
27
|
+
* exists as a byproduct of a page that loaded and ticked, so it is
|
|
28
|
+
* impossible to grade the capability of a build that did not run. The old
|
|
29
|
+
* arrangement enforced that by putting a call in the right place, which is
|
|
30
|
+
* the kind of rule that survives exactly until someone moves the call.
|
|
31
|
+
* - It stops duplicating a browser launch, a 1.5s settle, and a verbatim copy
|
|
32
|
+
* of the start-click selector block. That copy had ALREADY drifted on
|
|
33
|
+
* introduction (it gained an ArrowLeft the execution gate does not send), and
|
|
34
|
+
* a drifted start-click means both sides measure the menu, agree, and the
|
|
35
|
+
* rung goes silently inert. This repo has shipped inert probes twice.
|
|
36
|
+
* - It makes the reading reachable from `uap verify`, not just from deliver. A
|
|
37
|
+
* deliver-only bar re-creates the Generator!=Evaluator divergence this
|
|
38
|
+
* codebase already records: the sessions that edit files directly never go
|
|
39
|
+
* through the convergence loop at all, and a direct-edit session is exactly
|
|
40
|
+
* what produced the recorded regression.
|
|
41
|
+
*
|
|
42
|
+
* FAILS OPEN, always. An unmeasurable page, an absent browser, a baseline that
|
|
43
|
+
* never rendered — all return "no regression". You cannot regress from broken,
|
|
44
|
+
* and a capability check that guesses is worse than none: it blocks working
|
|
45
|
+
* deliveries on browser noise. Every threshold is deliberately loose; this
|
|
46
|
+
* catches gutting, not drift.
|
|
47
|
+
*
|
|
48
|
+
* WHAT THIS IS NOT: a defence against a page that is deliberately lying.
|
|
49
|
+
*
|
|
50
|
+
* Say it plainly, because the numbers look like evidence and will be trusted as
|
|
51
|
+
* evidence. The counters live INSIDE the page's own realm and count API
|
|
52
|
+
* INVOCATIONS, not rendered output — so a page that wants a rich profile can
|
|
53
|
+
* simply call the instrumented APIs. Measured against this exact code: a page
|
|
54
|
+
* drawing a single 40x40 rect, by adding 64 no-op listeners, calling
|
|
55
|
+
* `useProgram` 64 times on a null context, and issuing 5000 zero-area
|
|
56
|
+
* `fillRect`s per frame, reported 5001 draws/frame, 64 shader programs, 64 event
|
|
57
|
+
* types and 60fps. Nothing here detects that, and sealing more surfaces does not
|
|
58
|
+
* converge: you cannot instrument a realm the adversary controls from inside it.
|
|
59
|
+
* Closing that needs an out-of-realm oracle — screenshot pixels captured
|
|
60
|
+
* host-side over CDP, which the visual gate already collects.
|
|
61
|
+
*
|
|
62
|
+
* So the honest claim is narrower than the motivating incident makes it sound.
|
|
63
|
+
* The octopus regression was a model SIMPLIFYING under gate pressure, not a
|
|
64
|
+
* model gaming a metric it knew about, and that is the case this catches: an
|
|
65
|
+
* unintentional loss of function that every other rung reads as success. Treat a
|
|
66
|
+
* clean capability verdict as "nothing obviously fell off", never as "the
|
|
67
|
+
* deliverable was not gutted".
|
|
68
|
+
*
|
|
69
|
+
* What the hardening here DOES buy is that the gate cannot be turned off by
|
|
70
|
+
* accident or by one line: the hooks are sealed under a per-run random key, the
|
|
71
|
+
* pristine `querySelector`/`performance.now` are captured at install time (a
|
|
72
|
+
* one-line override of either used to disable the whole reading), the baseline
|
|
73
|
+
* is in the integrity capture, and no value from the page or from disk reaches a
|
|
74
|
+
* threshold without coercion.
|
|
75
|
+
*/
|
|
76
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
77
|
+
import { dirname, join } from 'node:path';
|
|
78
|
+
export function unmeasured(reason, driver) {
|
|
79
|
+
return {
|
|
80
|
+
measured: false,
|
|
81
|
+
reason,
|
|
82
|
+
elapsedMs: 0,
|
|
83
|
+
frames: 0,
|
|
84
|
+
drawCalls: 0,
|
|
85
|
+
glDrawCalls: 0,
|
|
86
|
+
ctx2dDrawCalls: 0,
|
|
87
|
+
programsUsed: 0,
|
|
88
|
+
listenerTypes: 0,
|
|
89
|
+
listeners: 0,
|
|
90
|
+
canvas: false,
|
|
91
|
+
...(driver ? { driver } : {}),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
/** Frames per second, from the page's own measured interval. */
|
|
95
|
+
export const fpsOf = (p) => p.elapsedMs > 0 ? (p.frames * 1000) / p.elapsedMs : 0;
|
|
96
|
+
/** Draw calls per frame — a property of the SCENE, invariant to frame rate. */
|
|
97
|
+
export const drawsPerFrame = (p) => p.frames > 0 ? p.drawCalls / p.frames : 0;
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
// Thresholds
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
/**
|
|
102
|
+
* Scene-content ratios. Loose on purpose: the target is a build that lost most
|
|
103
|
+
* of its function, not one that lost a little. A tight threshold fails ordinary
|
|
104
|
+
* refactors (batching draws, merging two shader programs) and teaches the loop
|
|
105
|
+
* to pad its numbers back up, which is worse than not checking.
|
|
106
|
+
*/
|
|
107
|
+
const DRAW_RATIO = 0.6;
|
|
108
|
+
const PROGRAM_RATIO = 0.6;
|
|
109
|
+
const LISTENER_RATIO = 0.6;
|
|
110
|
+
/**
|
|
111
|
+
* Frame rate is judged as an ABSOLUTE LIVENESS FLOOR, not a ratio — this is the
|
|
112
|
+
* single most important calibration decision in the file.
|
|
113
|
+
*
|
|
114
|
+
* A ratio cannot work here. The baseline is taken before the run's first model
|
|
115
|
+
* call; the candidate is taken minutes later on a box that is simultaneously
|
|
116
|
+
* decoding tokens on the same GPU, under software WebGL, possibly alongside
|
|
117
|
+
* other concurrent deliver tasks. Contention alone plausibly moves frame rate
|
|
118
|
+
* by 2-4x. The ratio this file originally used (0.4) trips at 2.5x — inside the
|
|
119
|
+
* noise, so it WOULD have misfired, blocking healthy turns and then telling the
|
|
120
|
+
* model to "restore the missing behaviour" that was never missing.
|
|
121
|
+
*
|
|
122
|
+
* A floor is load-invariant in the way that matters. On the measured octopus
|
|
123
|
+
* numbers — 59.9 fps healthy, 3.6 fps gutted — this fires with 2.8x of margin,
|
|
124
|
+
* while a healthy page would have to fall below 10 fps (6x contention) to
|
|
125
|
+
* false-fire. Same catch, 2.4x more headroom.
|
|
126
|
+
*/
|
|
127
|
+
const DEAD_FPS = 10;
|
|
128
|
+
const ALIVE_FPS = 30;
|
|
129
|
+
/** Below these, a metric is too small for a ratio to mean anything. */
|
|
130
|
+
const MIN_DRAWS_PER_FRAME = 0.5;
|
|
131
|
+
const MIN_PROGRAMS = 2;
|
|
132
|
+
const MIN_LISTENER_TYPES = 3;
|
|
133
|
+
/**
|
|
134
|
+
* How many metrics must drop before the turn is BLOCKED.
|
|
135
|
+
*
|
|
136
|
+
* One is too few. Each metric has a legitimate way to fall on its own: merging
|
|
137
|
+
* five shaders into an uber-shader takes programs 5->1; switching a per-entity
|
|
138
|
+
* draw path to an instanced one legitimately collapses draws-per-frame; moving
|
|
139
|
+
* to delegated event handling collapses listener types. Blocking on any single
|
|
140
|
+
* one of those, with feedback that says "restore the missing behaviour", asks
|
|
141
|
+
* the model to undo a correct optimisation.
|
|
142
|
+
*
|
|
143
|
+
* Two is enough to catch the thing this exists for: the recorded regression
|
|
144
|
+
* dropped ALL FOUR at once. A lone drop is still reported — as a note the judge
|
|
145
|
+
* sees, not a block.
|
|
146
|
+
*/
|
|
147
|
+
const BLOCK_AT_FINDINGS = 2;
|
|
148
|
+
/**
|
|
149
|
+
* Compare a post-run profile against the pre-run baseline. PURE — unit-tested
|
|
150
|
+
* without a browser.
|
|
151
|
+
*/
|
|
152
|
+
export function compareCapability(baseline, current) {
|
|
153
|
+
const none = (skipped) => ({ regressed: false, skipped, findings: [], table: [] });
|
|
154
|
+
if (!capabilityEnabled())
|
|
155
|
+
return none('capability check disabled (UAP_CAPABILITY=0)');
|
|
156
|
+
if (!baseline?.measured)
|
|
157
|
+
return none('no capability baseline to compare against');
|
|
158
|
+
// A vanished entry page is the MOST complete regression possible, and the
|
|
159
|
+
// naive "either side unmeasured -> skip" rule forgave it outright: delete
|
|
160
|
+
// index.html and the reading becomes 'no-entry', which used to read as
|
|
161
|
+
// "cannot measure". Everything else that cannot be measured is an
|
|
162
|
+
// ENVIRONMENT failure and must still fail open.
|
|
163
|
+
if (!current?.measured) {
|
|
164
|
+
// Corroborated: the baseline must itself name the page that has vanished.
|
|
165
|
+
// Otherwise a single string written to current.json asserts the existence of
|
|
166
|
+
// something that was never measured, and that assertion bypasses the
|
|
167
|
+
// two-finding rule to block every turn.
|
|
168
|
+
if (current?.reason === 'no-entry' && baseline.entry) {
|
|
169
|
+
return {
|
|
170
|
+
regressed: true,
|
|
171
|
+
findings: ['the web entry page is GONE — there is no longer a page to run'],
|
|
172
|
+
table: [`entry: ${baseline.entry ?? '(a page)'} -> (none)`],
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
return none(`capability not measurable now (${current?.reason ?? 'unknown'})`);
|
|
176
|
+
}
|
|
177
|
+
// Two drivers have different frame-rate regimes and the fallback is silent.
|
|
178
|
+
if (baseline.driver && current.driver && baseline.driver !== current.driver) {
|
|
179
|
+
return none(`different browser drivers (${baseline.driver} then ${current.driver}) — not comparable`);
|
|
180
|
+
}
|
|
181
|
+
// You cannot regress from broken. A baseline that never rendered and never
|
|
182
|
+
// ticked gives every candidate an infinite improvement ratio.
|
|
183
|
+
if (baseline.frames === 0 && baseline.drawCalls === 0) {
|
|
184
|
+
return none('baseline never rendered — nothing to regress from');
|
|
185
|
+
}
|
|
186
|
+
const bFps = fpsOf(baseline);
|
|
187
|
+
const cFps = fpsOf(current);
|
|
188
|
+
const bDpf = drawsPerFrame(baseline);
|
|
189
|
+
const cDpf = drawsPerFrame(current);
|
|
190
|
+
const table = [
|
|
191
|
+
`frame rate: ${bFps.toFixed(1)} -> ${cFps.toFixed(1)} fps (${baseline.frames} frames in ${baseline.elapsedMs}ms -> ${current.frames} in ${current.elapsedMs}ms)`,
|
|
192
|
+
`draw calls per frame: ${bDpf.toFixed(2)} -> ${cDpf.toFixed(2)} (${baseline.drawCalls} -> ${current.drawCalls} total)`,
|
|
193
|
+
`shader programs used: ${baseline.programsUsed} -> ${current.programsUsed}`,
|
|
194
|
+
`event types handled: ${baseline.listenerTypes} -> ${current.listenerTypes}`,
|
|
195
|
+
];
|
|
196
|
+
const findings = [];
|
|
197
|
+
const fmt = (n) => (Number.isInteger(n) ? String(n) : n.toFixed(2));
|
|
198
|
+
const ratioCheck = (label, base, cur, floor, ratio, unit) => {
|
|
199
|
+
if (!Number.isFinite(base) || !Number.isFinite(cur))
|
|
200
|
+
return;
|
|
201
|
+
if (base < floor)
|
|
202
|
+
return; // too small to judge
|
|
203
|
+
if (cur >= base * ratio)
|
|
204
|
+
return;
|
|
205
|
+
const pct = Math.round((1 - cur / base) * 100);
|
|
206
|
+
findings.push(`${label} dropped ${pct}%: ${fmt(base)} -> ${fmt(cur)} ${unit}`);
|
|
207
|
+
};
|
|
208
|
+
// Liveness: an absolute floor, not a ratio (see DEAD_FPS).
|
|
209
|
+
if (Number.isFinite(bFps) && Number.isFinite(cFps) && bFps >= ALIVE_FPS && cFps < DEAD_FPS) {
|
|
210
|
+
findings.push(`the render loop is no longer running at a usable rate: ${bFps.toFixed(1)} -> ${cFps.toFixed(1)} fps`);
|
|
211
|
+
}
|
|
212
|
+
// Draws-per-frame is only meaningful when BOTH sides actually drove rAF. A
|
|
213
|
+
// page whose loop is setInterval never touches requestAnimationFrame, so it
|
|
214
|
+
// reads 0 frames while still drawing — and dividing by a floored 1 turned
|
|
215
|
+
// draws-per-frame into draws-per-window, a number ~300x too large. Measured:
|
|
216
|
+
// a setInterval -> rAF refactor (an IMPROVEMENT) reported "draw calls per
|
|
217
|
+
// frame dropped 100%: 6300 -> 21".
|
|
218
|
+
if (baseline.frames > 0 && current.frames > 0) {
|
|
219
|
+
ratioCheck('draw calls per frame', bDpf, cDpf, MIN_DRAWS_PER_FRAME, DRAW_RATIO, 'per frame');
|
|
220
|
+
}
|
|
221
|
+
ratioCheck('distinct shader programs', baseline.programsUsed, current.programsUsed, MIN_PROGRAMS, PROGRAM_RATIO, 'programs');
|
|
222
|
+
ratioCheck('distinct event types handled', baseline.listenerTypes, current.listenerTypes, MIN_LISTENER_TYPES, LISTENER_RATIO, 'types');
|
|
223
|
+
return { regressed: findings.length >= BLOCK_AT_FINDINGS, findings, table };
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* The sentence the model is given. Verbatim numbers, no score: a percentage is
|
|
227
|
+
* what stalled the run this whole class of gate came from — the model needs to
|
|
228
|
+
* know WHAT it deleted, not how badly it is doing.
|
|
229
|
+
*/
|
|
230
|
+
export function capabilityFeedback(cmp) {
|
|
231
|
+
if (!cmp.regressed)
|
|
232
|
+
return '';
|
|
233
|
+
return [
|
|
234
|
+
'CAPABILITY REGRESSION — the app still runs, but it now DOES LESS than the build you started from.',
|
|
235
|
+
'',
|
|
236
|
+
...cmp.findings.map((f) => `- ${f}`),
|
|
237
|
+
'',
|
|
238
|
+
'Measured before your changes vs now:',
|
|
239
|
+
...cmp.table.map((t) => ` ${t}`),
|
|
240
|
+
'',
|
|
241
|
+
'This is not a rendering-cleanliness problem: a smaller, emptier app renders perfectly cleanly.',
|
|
242
|
+
'Something that used to be drawn, animated, or responded to is no longer there. Restore the',
|
|
243
|
+
'missing behaviour — the features, entities, effects and input handling from the original build —',
|
|
244
|
+
'without reintroducing errors. Do NOT simplify the app further to make gates pass.',
|
|
245
|
+
].join('\n');
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* What to say when the check did NOT block: a single-metric drop, or the reason
|
|
249
|
+
* it declined to judge at all.
|
|
250
|
+
*
|
|
251
|
+
* The skip half is the important half. `skipped` used to be returned and read by
|
|
252
|
+
* nobody — not the acceptance gate, not the keep-best veto, not verify — so a
|
|
253
|
+
* check that had silently disarmed itself (a driver swap mid-run, a vanished
|
|
254
|
+
* baseline, an unmeasurable page) was indistinguishable from one that ran and
|
|
255
|
+
* found nothing. That is exactly the inert-rung failure this module's header
|
|
256
|
+
* warns about, reproduced in the module's own output.
|
|
257
|
+
*/
|
|
258
|
+
export function capabilityNote(cmp) {
|
|
259
|
+
if (cmp.skipped)
|
|
260
|
+
return `Capability check did not run: ${cmp.skipped}.`;
|
|
261
|
+
if (cmp.regressed || cmp.findings.length === 0)
|
|
262
|
+
return '';
|
|
263
|
+
return `Capability note (not blocking): ${cmp.findings.join('; ')}.`;
|
|
264
|
+
}
|
|
265
|
+
/** True when the check produced no opinion — callers should SAY so, not assume health. */
|
|
266
|
+
export const capabilityInactive = (cmp) => Boolean(cmp.skipped);
|
|
267
|
+
// ---------------------------------------------------------------------------
|
|
268
|
+
// Persistence
|
|
269
|
+
// ---------------------------------------------------------------------------
|
|
270
|
+
/**
|
|
271
|
+
* On-disk layout, mirroring `.uap/visual/` (see visual-baseline.ts):
|
|
272
|
+
* .uap/capability/current.json written by the execution gate every run
|
|
273
|
+
* .uap/capability/baseline.json pinned pre-run, or by `uap verify --approve`
|
|
274
|
+
*
|
|
275
|
+
* Disk is what makes this reachable from BOTH sides. The execution gate often
|
|
276
|
+
* runs in a spawned runner (synthesizeExecutionRung), so a return value does
|
|
277
|
+
* not cross that boundary — the visual gate has the same constraint and solves
|
|
278
|
+
* it the same way, with files. It also survives `--resume`, where an in-memory
|
|
279
|
+
* baseline would be re-taken from the already-modified tree.
|
|
280
|
+
*/
|
|
281
|
+
/** Opt-out, matching every other gate in this subsystem. Off means the profile
|
|
282
|
+
* is never written and every comparison declines. */
|
|
283
|
+
export const capabilityEnabled = () => !['0', 'false', 'off', 'no'].includes((process.env.UAP_CAPABILITY ?? '').trim().toLowerCase());
|
|
284
|
+
export const CAPABILITY_DIR = join('.uap', 'capability');
|
|
285
|
+
export const currentPath = (root) => join(root, CAPABILITY_DIR, 'current.json');
|
|
286
|
+
export const baselinePath = (root) => join(root, CAPABILITY_DIR, 'baseline.json');
|
|
287
|
+
function readProfile(path) {
|
|
288
|
+
try {
|
|
289
|
+
if (!existsSync(path))
|
|
290
|
+
return null;
|
|
291
|
+
const raw = JSON.parse(readFileSync(path, 'utf-8'));
|
|
292
|
+
return sanitizeProfile(raw);
|
|
293
|
+
}
|
|
294
|
+
catch {
|
|
295
|
+
return null;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
function writeProfile(path, profile) {
|
|
299
|
+
try {
|
|
300
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
301
|
+
writeFileSync(path, `${JSON.stringify(profile, null, 2)}\n`);
|
|
302
|
+
return true;
|
|
303
|
+
}
|
|
304
|
+
catch {
|
|
305
|
+
return false;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Drop any previous run's reading. Called at the START of every execution-gate
|
|
310
|
+
* run so `current.json` exists only when THIS run measured something — see the
|
|
311
|
+
* comment at that call site for what staleness cost.
|
|
312
|
+
*/
|
|
313
|
+
export function invalidateCapabilityCurrent(root) {
|
|
314
|
+
try {
|
|
315
|
+
if (existsSync(currentPath(root)))
|
|
316
|
+
rmSync(currentPath(root), { force: true });
|
|
317
|
+
}
|
|
318
|
+
catch {
|
|
319
|
+
/* fail-soft: a stale reading is better than a crashed gate */
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Drop a baseline this run could not re-pin.
|
|
324
|
+
*
|
|
325
|
+
* A baseline is only meaningful for the tree it was taken from. Left in place
|
|
326
|
+
* across runs it becomes a reading of a DIFFERENT app: a run that pins 14
|
|
327
|
+
* draws/frame on a rich game, followed later by an unrelated run on a
|
|
328
|
+
* deliberately simpler project, blocks every turn against a stale number — and
|
|
329
|
+
* the operator has to know about an --approve flag to escape. Better to have no
|
|
330
|
+
* opinion than a confidently wrong one.
|
|
331
|
+
*/
|
|
332
|
+
export function invalidateCapabilityBaseline(root) {
|
|
333
|
+
try {
|
|
334
|
+
if (existsSync(baselinePath(root)))
|
|
335
|
+
rmSync(baselinePath(root), { force: true });
|
|
336
|
+
}
|
|
337
|
+
catch {
|
|
338
|
+
/* fail-soft */
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
export const readCapabilityBaseline = (root) => readProfile(baselinePath(root));
|
|
342
|
+
export const readCapabilityCurrent = (root) => readProfile(currentPath(root));
|
|
343
|
+
export const writeCapabilityCurrent = (root, p) => writeProfile(currentPath(root), p);
|
|
344
|
+
export const writeCapabilityBaseline = (root, p) => writeProfile(baselinePath(root), p);
|
|
345
|
+
/**
|
|
346
|
+
* Coerce an untrusted reading into a profile, or null.
|
|
347
|
+
*
|
|
348
|
+
* EVERY number here is attacker-controllable: the page under test is code a
|
|
349
|
+
* model just wrote, and it can replace the read-back hook. `typeof x ===
|
|
350
|
+
* 'number'` is not validation — `typeof NaN` and `typeof Infinity` are both
|
|
351
|
+
* 'number', and a NaN baseline propagates through every threshold comparison
|
|
352
|
+
* (`NaN < floor` is false, `NaN >= base*ratio` is false) to produce
|
|
353
|
+
* "dropped NaN%" findings on EVERY turn: a permanent, unactionable block.
|
|
354
|
+
* Measured before this guard existed.
|
|
355
|
+
*/
|
|
356
|
+
const REASONS = ['no-entry', 'no-browser', 'not-installed', 'no-reading', 'error'];
|
|
357
|
+
export function sanitizeProfile(raw) {
|
|
358
|
+
if (!raw || typeof raw !== 'object')
|
|
359
|
+
return null;
|
|
360
|
+
const o = raw;
|
|
361
|
+
// A page cannot be trusted to report a plausible magnitude either; the cap is
|
|
362
|
+
// far above any real reading (60fps for a minute is 3600 frames).
|
|
363
|
+
const CAP = 10_000_000;
|
|
364
|
+
const num = (k) => {
|
|
365
|
+
const v = o[k];
|
|
366
|
+
return typeof v === 'number' && Number.isFinite(v) && v >= 0 ? Math.min(v, CAP) : 0;
|
|
367
|
+
};
|
|
368
|
+
if (typeof o.measured !== 'boolean')
|
|
369
|
+
return null;
|
|
370
|
+
const gl = num('glDrawCalls');
|
|
371
|
+
const ctx = num('ctx2dDrawCalls');
|
|
372
|
+
return {
|
|
373
|
+
measured: o.measured,
|
|
374
|
+
// Validated against the union, not cast into it. This is the only string
|
|
375
|
+
// read from disk that steers control flow (see the no-entry branch in
|
|
376
|
+
// compareCapability), so an unrecognised value must become "no reason"
|
|
377
|
+
// rather than a verdict.
|
|
378
|
+
...(REASONS.includes(o.reason) ? { reason: o.reason } : {}),
|
|
379
|
+
elapsedMs: num('elapsedMs'),
|
|
380
|
+
frames: num('frames'),
|
|
381
|
+
drawCalls: gl + ctx,
|
|
382
|
+
glDrawCalls: gl,
|
|
383
|
+
ctx2dDrawCalls: ctx,
|
|
384
|
+
programsUsed: num('programsUsed'),
|
|
385
|
+
listenerTypes: num('listenerTypes'),
|
|
386
|
+
listeners: num('listeners'),
|
|
387
|
+
canvas: o.canvas === true,
|
|
388
|
+
...(typeof o.driver === 'string' ? { driver: o.driver } : {}),
|
|
389
|
+
...(typeof o.entry === 'string' ? { entry: o.entry } : {}),
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
//# sourceMappingURL=capability-profile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capability-profile.js","sourceRoot":"","sources":["../../src/delivery/capability-profile.ts"],"names":[],"mappings":"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"}
|
|
@@ -345,6 +345,44 @@ export interface ConvergenceConfig {
|
|
|
345
345
|
* Bounded by the ceiling and the stagnation guard so it cannot loop forever.
|
|
346
346
|
*/
|
|
347
347
|
untilDelivered?: boolean;
|
|
348
|
+
/**
|
|
349
|
+
* End this run after N consecutive turns that moved NEITHER the gate score
|
|
350
|
+
* nor the acceptance score. Off (undefined) unless a caller sets it.
|
|
351
|
+
*
|
|
352
|
+
* WHY THIS IS OPT-IN, AND WHY IT IS NOT A SECOND STAGNATION RAIL
|
|
353
|
+
* A previous attempt at plateau detection lived here unconditionally and was
|
|
354
|
+
* removed: `untilDelivered` already stops EXTENDING at STAGNATION_LIMIT, and
|
|
355
|
+
* an always-on rail was measured unreachable (its streak can never exceed
|
|
356
|
+
* `stagnantTurns`) or redundant. Nothing about that changed.
|
|
357
|
+
*
|
|
358
|
+
* What changed is the CALLER. An epic attempt has somewhere better to spend
|
|
359
|
+
* the time — a fresh attempt on the same epic — and the epic controller is
|
|
360
|
+
* the only layer that knows whether one is left. So this is set only when a
|
|
361
|
+
* retry remains, and the decision it encodes is "yield to a fresh attempt",
|
|
362
|
+
* not "give up on the mission". On the LAST attempt it stays off and the run
|
|
363
|
+
* plays out exactly as before.
|
|
364
|
+
*
|
|
365
|
+
* MEASURED (2026-08-20, rust-pg-ext, qwen3.8-27b), four flat attempts:
|
|
366
|
+
* fix-bioband-derive a1: 5 turns flat @60% -> a2 PASSED in 2 turns
|
|
367
|
+
* fix-slope-arithmetic a1: 5 turns flat @40%, a2: 5 turns flat @40%
|
|
368
|
+
* At the shipped threshold those three attempts give back 21.2 minutes of
|
|
369
|
+
* wall clock. What that buys is narrower than it first looks, and the first
|
|
370
|
+
* draft of this comment overstated it twice:
|
|
371
|
+
*
|
|
372
|
+
* - No turn budget is TRANSFERRED. Every attempt gets its own `maxTurns`
|
|
373
|
+
* regardless, so a fresh attempt is not lengthened by an earlier one
|
|
374
|
+
* ending sooner; the epic's total exploration shrinks. Wall clock is the
|
|
375
|
+
* saving, and the only one.
|
|
376
|
+
* - That run did not die on its clock one epic short. All seven epics were
|
|
377
|
+
* attempted; `fix-slope-arithmetic` failed on merit after three attempts
|
|
378
|
+
* and a re-plan ("epic controller incomplete — failed epic(s): …").
|
|
379
|
+
* Reclaiming its minutes would not have changed that outcome.
|
|
380
|
+
*
|
|
381
|
+
* The real case is still good: time spent on turns whose score is provably
|
|
382
|
+
* not moving is time no longer available to the other epics, to the re-plan,
|
|
383
|
+
* or to the run's wall-clock budget.
|
|
384
|
+
*/
|
|
385
|
+
abortOnFlatTurns?: number;
|
|
348
386
|
/**
|
|
349
387
|
* Hard upper bound on turns when untilDelivered is set. Library default 50;
|
|
350
388
|
* the `uap deliver --until-delivered` CLI defaults it to 30 (override with
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"convergence-loop.d.ts","sourceRoot":"","sources":["../../src/delivery/convergence-loop.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAkBH,OAAO,KAAK,EAAE,QAAQ,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"convergence-loop.d.ts","sourceRoot":"","sources":["../../src/delivery/convergence-loop.ts"],"names":[],"mappings":"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"}
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
import { join } from 'node:path';
|
|
18
18
|
import { execSync } from 'child_process';
|
|
19
|
-
import { statSync } from 'fs';
|
|
19
|
+
import { existsSync, statSync } from 'fs';
|
|
20
20
|
import { ENDPOINT_UNREACHABLE } from '../models/long-fetch.js';
|
|
21
21
|
/**
|
|
22
22
|
* Consecutive unreachable-endpoint turns before the run gives up. Two, so a
|
|
@@ -25,6 +25,7 @@ import { ENDPOINT_UNREACHABLE } from '../models/long-fetch.js';
|
|
|
25
25
|
*/
|
|
26
26
|
const UNREACHABLE_ABORT_LIMIT = 2;
|
|
27
27
|
import { bindFrozenFragments, resolveBoundVariant, } from '../self-tuning/prompt-variants.js';
|
|
28
|
+
import { baselinePath } from './capability-profile.js';
|
|
28
29
|
import { mergeRedetectedRungs, detectRungs, runLadder } from './verifier-ladder.js';
|
|
29
30
|
import { applyFileBlocks, listGateConfigFiles } from './applier.js';
|
|
30
31
|
import { snapshotProtection } from './spec-imports.js';
|
|
@@ -606,6 +607,26 @@ export class ConvergenceLoop {
|
|
|
606
607
|
// run must not read as turn-1 movement.
|
|
607
608
|
let lastTurnFingerprint = null;
|
|
608
609
|
let bestScoreSeen = -1;
|
|
610
|
+
// Yield-rail state (opt-in; see abortOnFlatTurns). Sanitised once here so
|
|
611
|
+
// a caller-supplied value cannot be NaN/fractional/negative downstream.
|
|
612
|
+
const rawFlatLimit = this.config.abortOnFlatTurns;
|
|
613
|
+
const flatTurnLimit = typeof rawFlatLimit === 'number' && Number.isFinite(rawFlatLimit) && rawFlatLimit >= 1
|
|
614
|
+
// SCALED to the turn budget, never below what the caller asked for.
|
|
615
|
+
//
|
|
616
|
+
// A fixed count is wrong across budgets. Replaying this rail over 157
|
|
617
|
+
// deliver logs (51 attempts where it would arm), every breakthrough it
|
|
618
|
+
// still destroys at the default is a LONG attempt — 6 to 8 turns, from
|
|
619
|
+
// runs whose maxTurns is 20, where three flat turns is a much weaker
|
|
620
|
+
// signal than it is out of five. Quartering the budget tracks that:
|
|
621
|
+
// maxTurns 5 -> 3 (the floor), maxTurns 20 -> 5.
|
|
622
|
+
? Math.max(Math.floor(rawFlatLimit), Math.ceil(maxTurns / 4))
|
|
623
|
+
: undefined;
|
|
624
|
+
let flatTurnStreak = 0;
|
|
625
|
+
// Acceptance high-water for the yield rail only — NOT bestAcceptance, which
|
|
626
|
+
// untilDelivered advances in a block ABOVE this one (comparing against it
|
|
627
|
+
// here is always false) and never advances at all without untilDelivered
|
|
628
|
+
// (making every judged turn look like progress). Both readings were wrong.
|
|
629
|
+
let bestAcceptanceSeen = -1;
|
|
609
630
|
let stallReason;
|
|
610
631
|
let executor = this.executor;
|
|
611
632
|
let explorerSettings = this.config.explorer;
|
|
@@ -686,6 +707,25 @@ export class ConvergenceLoop {
|
|
|
686
707
|
/* fail-soft: capture stays as it was */
|
|
687
708
|
}
|
|
688
709
|
}
|
|
710
|
+
// The capability BASELINE joins the capture for the same reason as the gate
|
|
711
|
+
// script and the runner configs: it is a trust anchor that run_bash can
|
|
712
|
+
// reach. It lived in `.uap/`, which the applier protects but the bash sweep
|
|
713
|
+
// SKIPS by design (the harness writes there constantly) and this capture did
|
|
714
|
+
// not cover — so `echo '{}' > .uap/capability/baseline.json`, or simply
|
|
715
|
+
// deleting it, silently disarmed both the acceptance check AND the keep-best
|
|
716
|
+
// veto that stops a gutted tree becoming the run's best snapshot.
|
|
717
|
+
//
|
|
718
|
+
// `current.json` is deliberately NOT protected: the gate rewrites it every
|
|
719
|
+
// run, so it is an output, not an anchor.
|
|
720
|
+
try {
|
|
721
|
+
const capBaseline = baselinePath(this.config.projectRoot);
|
|
722
|
+
if (existsSync(capBaseline)) {
|
|
723
|
+
protectedList = [...new Set([...(protectedList ?? []), capBaseline])];
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
catch {
|
|
727
|
+
/* fail-soft */
|
|
728
|
+
}
|
|
689
729
|
const applyOptions = protectTests
|
|
690
730
|
? { protectedFiles, protectGateConfigs: true }
|
|
691
731
|
: undefined;
|
|
@@ -1127,6 +1167,63 @@ export class ConvergenceLoop {
|
|
|
1127
1167
|
`reproduce this result.`;
|
|
1128
1168
|
break;
|
|
1129
1169
|
}
|
|
1170
|
+
// The YIELD rail (opt-in — see abortOnFlatTurns). Deliberately unlike the
|
|
1171
|
+
// rail above: it does NOT require provablyIdle, because the attempts it
|
|
1172
|
+
// exists for wrote real edits on every turn and were invisible to that
|
|
1173
|
+
// check. Acceptance movement counts as progress for the same reason
|
|
1174
|
+
// untilDelivered counts it — an objective-green run pins the gate score
|
|
1175
|
+
// at 1.0 while a multi-criterion spec completes criterion by criterion.
|
|
1176
|
+
//
|
|
1177
|
+
// Escalation does NOT reset it, and that is the one exemption this rail
|
|
1178
|
+
// drops. In all FOUR flat attempts measured, `↑ turn 3: enable critic`
|
|
1179
|
+
// fired and the score did not move once afterwards. Yielding to a fresh
|
|
1180
|
+
// attempt is a bigger change of approach than enabling a critic inside
|
|
1181
|
+
// the attempt that is already failing — and the fresh attempt gets the
|
|
1182
|
+
// escalation ladder too.
|
|
1183
|
+
if (flatTurnLimit !== undefined) {
|
|
1184
|
+
const acceptanceMoved = acceptanceMet !== undefined && acceptanceMet > bestAcceptanceSeen;
|
|
1185
|
+
if (acceptanceMoved)
|
|
1186
|
+
bestAcceptanceSeen = acceptanceMet;
|
|
1187
|
+
// A directive that REPLACES THE EXECUTOR is exempt — and only that.
|
|
1188
|
+
//
|
|
1189
|
+
// The first version of this rail exempted no escalation at all, on the
|
|
1190
|
+
// evidence that `↑ turn 3: enable critic` fired in four measured flat
|
|
1191
|
+
// attempts and moved the score in none. That evidence is about the
|
|
1192
|
+
// CRITIC tier and does not reach the tier above it: with a stronger
|
|
1193
|
+
// model configured, `defaultEscalationLadder` appends a `switchExecutor`
|
|
1194
|
+
// tier at turn 5, and `createRepairEscalation` returns one whenever
|
|
1195
|
+
// compile errors are RISING — i.e. on exactly the flat turn this rail
|
|
1196
|
+
// fires. Measured against the real ladder: unexempted, an attempt that
|
|
1197
|
+
// master delivers on turn 6 with the stronger model was abandoned on
|
|
1198
|
+
// turn 3, and the one-shot repair pass never ran once.
|
|
1199
|
+
//
|
|
1200
|
+
// A directive is decided on THIS turn and consumed by the NEXT one, so
|
|
1201
|
+
// breaking here discards it. Give a new executor the single turn it
|
|
1202
|
+
// needs to prove itself; the critic and reseed tiers stay unexempted,
|
|
1203
|
+
// because those are the ones the measurement found worthless.
|
|
1204
|
+
const executorChanged = Boolean(directive.switchExecutor);
|
|
1205
|
+
// `inconclusive` stays exempt: a turn that never reached a model is
|
|
1206
|
+
// missing data, not a flat result, and flaky turns must not burn an
|
|
1207
|
+
// attempt.
|
|
1208
|
+
if (inconclusive || scoreMoved || acceptanceMoved || executorChanged)
|
|
1209
|
+
flatTurnStreak = 0;
|
|
1210
|
+
else
|
|
1211
|
+
flatTurnStreak++;
|
|
1212
|
+
if (flatTurnStreak >= flatTurnLimit) {
|
|
1213
|
+
// SHORT by design. This is prepended to finalFeedback, and
|
|
1214
|
+
// epic-mission slices that to 400 chars to build the next attempt's
|
|
1215
|
+
// `lastFailure` — so every character here evicts a character of real
|
|
1216
|
+
// gate feedback from the retry's steering. The 339-char first draft
|
|
1217
|
+
// left ~58 chars of gate output and cut the actionable line, which
|
|
1218
|
+
// defeated the recovery the rail depends on. Rationale lives above,
|
|
1219
|
+
// not on the wire. Deliberately does NOT claim the tree changed: this
|
|
1220
|
+
// rail never consults `provablyIdle` or `filesApplied`.
|
|
1221
|
+
stallReason =
|
|
1222
|
+
`plateau: ${flatTurnStreak} turns with no gate (best ` +
|
|
1223
|
+
`${(bestScoreSeen * 100).toFixed(0)}%) or acceptance progress — yielding to a fresh attempt.`;
|
|
1224
|
+
break;
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1130
1227
|
// Phase 3: structured critique of the failed turn (fail-soft). Skipped
|
|
1131
1228
|
// on the last turn — prevContext is never consumed, so it would waste
|
|
1132
1229
|
// a model call.
|