@miller-tech/uap 1.172.14 → 1.173.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/delivery/agentic-executor.d.ts.map +1 -1
- package/dist/delivery/agentic-executor.js +52 -0
- package/dist/delivery/agentic-executor.js.map +1 -1
- package/dist/delivery/applier.d.ts.map +1 -1
- package/dist/delivery/applier.js +53 -0
- package/dist/delivery/applier.js.map +1 -1
- package/dist/delivery/contract-extractor.d.ts +16 -0
- package/dist/delivery/contract-extractor.d.ts.map +1 -1
- package/dist/delivery/contract-extractor.js +136 -3
- package/dist/delivery/contract-extractor.js.map +1 -1
- package/dist/delivery/mission-acceptance.d.ts.map +1 -1
- package/dist/delivery/mission-acceptance.js +48 -3
- package/dist/delivery/mission-acceptance.js.map +1 -1
- package/dist/delivery/stub-detector.d.ts +150 -0
- package/dist/delivery/stub-detector.d.ts.map +1 -0
- package/dist/delivery/stub-detector.js +474 -0
- package/dist/delivery/stub-detector.js.map +1 -0
- package/dist/delivery/visual-gate.d.ts +58 -0
- package/dist/delivery/visual-gate.d.ts.map +1 -1
- package/dist/delivery/visual-gate.js +87 -3
- package/dist/delivery/visual-gate.js.map +1 -1
- package/package.json +1 -1
- package/src/policies/enforcers/__pycache__/_common.cpython-312.pyc +0 -0
- package/src/policies/enforcers/enforcement_infra_protect.py +26 -1
- package/src/policies/enforcers/enforcement_self_protect.py +8 -0
- package/src/policies/enforcers/expert_review_required.py +22 -9
- 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/tests/test_enforcer_escape_hatches.py +123 -0
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Refuse a write whose content is a STUB rather than an implementation.
|
|
3
|
+
*
|
|
4
|
+
* The existing P3 anti-gutting guard (agentic-executor) compares BYTE SIZES and
|
|
5
|
+
* only fires when the target already exists. That leaves the failure actually
|
|
6
|
+
* observed wide open: a weak model writing brand-new files as stubs. From a real
|
|
7
|
+
* run (octopus_invaders_v3, 2026-07-30) — SEVEN modules written straight to disk
|
|
8
|
+
* as skeletons, then re-read by the model and used as the house style for more of
|
|
9
|
+
* them. A first write has nothing to shrink from, and a stub→stub rewrite is no
|
|
10
|
+
* shrink, so neither tripped the size guard. Nor do the run gates catch it: a
|
|
11
|
+
* stub LOADS cleanly, which is exactly why execution-gate.ts documents the
|
|
12
|
+
* "frozen game" class it could not detect.
|
|
13
|
+
*
|
|
14
|
+
* WHY EMPTY BODIES, NOT THE WORD "STUB"
|
|
15
|
+
* The header said "— Stub" in every one of those files, which is trivially
|
|
16
|
+
* greppable — and equally trivially omitted by the next model. The durable
|
|
17
|
+
* signal is semantic: a module that declares a full API surface and implements
|
|
18
|
+
* none of it.
|
|
19
|
+
*
|
|
20
|
+
* WHAT COUNTS AS A CALLABLE (and why this is not one regex)
|
|
21
|
+
* The ratio is only meaningful if the denominator is right, and the first cut of
|
|
22
|
+
* this file got it wrong in three ways that review caught, each of which biased
|
|
23
|
+
* toward refusing REAL code:
|
|
24
|
+
*
|
|
25
|
+
* - `if (…) {`, `while (…) {`, `catch (e) {` all match "identifier, parens,
|
|
26
|
+
* brace", so control flow was counted as API surface. A defensive module of
|
|
27
|
+
* three real functions wrapped in three ignore-catches scored 50% empty.
|
|
28
|
+
* - a `}` inside a comment or string terminated the brace scan early, so real
|
|
29
|
+
* bodies were measured as empty ("// clamp to } of the arena" emptied its
|
|
30
|
+
* function).
|
|
31
|
+
* - `export const init = () => {}` matched NOTHING, because every alternative
|
|
32
|
+
* required a word character or `)` before the paren. The dominant modern stub
|
|
33
|
+
* idiom scored zero callables and sailed through — and it is the first shape
|
|
34
|
+
* a model reaches for after one refusal.
|
|
35
|
+
*
|
|
36
|
+
* So: strip comments and string literals first (length-preserving, to keep
|
|
37
|
+
* indices valid), then find bodies by scanning BACKWARD from each `{` to decide
|
|
38
|
+
* whether it opens a callable. That handles return-type annotations, generics,
|
|
39
|
+
* and nested parens in parameter lists, none of which a `[^()]*` regex can.
|
|
40
|
+
*
|
|
41
|
+
* MEASURED POPULATIONS (all figures produced BY this implementation, not recalled
|
|
42
|
+
* from an earlier one — the first draft of this header cited numbers from a
|
|
43
|
+
* scanner that counted `if` blocks as callables, and they no longer reproduced)
|
|
44
|
+
*
|
|
45
|
+
* the 7 live stubs 4–10 callables, 75–90% empty, ~300–390 bytes each
|
|
46
|
+
* config.js (real, same directory, same run) 10 callables, 0% empty, 7.0 KB
|
|
47
|
+
* this repo, every .ts under src/ 324 files, 0 flagged
|
|
48
|
+
* worst real file anywhere here execution-gate.ts, 50% empty / 72 callables
|
|
49
|
+
*
|
|
50
|
+
* The gap between "worst real file" and the unlabelled bar is what the thresholds
|
|
51
|
+
* actually rest on, so a negative-control test re-derives it on every run instead
|
|
52
|
+
* of trusting this comment.
|
|
53
|
+
*/
|
|
54
|
+
/** Files whose shape legitimately has empty or absent bodies. */
|
|
55
|
+
const EXEMPT_EXTENSIONS = new Set([
|
|
56
|
+
'.d.ts', // ambient declarations are ALL signature and no body by definition
|
|
57
|
+
'.d.mts',
|
|
58
|
+
'.d.cts',
|
|
59
|
+
'.json',
|
|
60
|
+
'.md',
|
|
61
|
+
'.txt',
|
|
62
|
+
'.yml',
|
|
63
|
+
'.yaml',
|
|
64
|
+
'.toml',
|
|
65
|
+
'.lock',
|
|
66
|
+
'.css',
|
|
67
|
+
'.scss',
|
|
68
|
+
'.svg',
|
|
69
|
+
// NOT exempt: .html. A single-file page with all the logic in an inline
|
|
70
|
+
// <script> is the dominant artifact shape in the runs this guard exists for,
|
|
71
|
+
// and exempting the extension let that whole shape through. An ordinary HTML
|
|
72
|
+
// file has no callables at all, so it never reaches the thresholds anyway.
|
|
73
|
+
]);
|
|
74
|
+
/**
|
|
75
|
+
* Keywords that take `(...)` and a block but are NOT callables. Counting these
|
|
76
|
+
* as API surface is what let a real module of three functions plus three
|
|
77
|
+
* ignore-catches read as 50% empty.
|
|
78
|
+
*/
|
|
79
|
+
const CONTROL_KEYWORDS = new Set([
|
|
80
|
+
'if',
|
|
81
|
+
'else',
|
|
82
|
+
'for',
|
|
83
|
+
'while',
|
|
84
|
+
'do',
|
|
85
|
+
'switch',
|
|
86
|
+
'catch',
|
|
87
|
+
'with',
|
|
88
|
+
'return',
|
|
89
|
+
'typeof',
|
|
90
|
+
'instanceof',
|
|
91
|
+
'delete',
|
|
92
|
+
'void',
|
|
93
|
+
'in',
|
|
94
|
+
'of',
|
|
95
|
+
'new',
|
|
96
|
+
'await',
|
|
97
|
+
'yield',
|
|
98
|
+
]);
|
|
99
|
+
/**
|
|
100
|
+
* Explicit self-labelling. Weak on its own — a model can drop the word — but a
|
|
101
|
+
* strong confirmation when present, so it lowers BOTH bars: the callable count
|
|
102
|
+
* and the empty-ratio required (6→3 and 0.60→0.45).
|
|
103
|
+
* Anchored near the top of the file: "stub" inside a deep code comment is
|
|
104
|
+
* usually discussing the concept, not announcing one.
|
|
105
|
+
*/
|
|
106
|
+
const MARKER_RE = /(?:^|\n)\s*(?:\/\/|\/\*|\*|#)?\s*.{0,60}\b(?:stub|placeholder|not\s+implemented|unimplemented|to\s*-?\s*do\s*:?\s*implement)\b/i;
|
|
107
|
+
const MARKER_SCAN_LINES = 12;
|
|
108
|
+
/** Also cap the marker scan by characters: MARKER_RE has two `\s*` around an
|
|
109
|
+
* optional group, which backtracks quadratically on one very long line. */
|
|
110
|
+
const MARKER_SCAN_CHARS = 2000;
|
|
111
|
+
/** Above this many characters, skip detection entirely (bundled output). */
|
|
112
|
+
const MAX_SCAN_CHARS = 200_000;
|
|
113
|
+
/** Cap on retained bodies — bounds the O(input × depth) slice allocation. */
|
|
114
|
+
const MAX_BODIES = 2000;
|
|
115
|
+
/**
|
|
116
|
+
* Blank out comments and string/template literals, replacing their contents with
|
|
117
|
+
* spaces so every index in the result still lines up with the input.
|
|
118
|
+
*
|
|
119
|
+
* Needed because the brace scan and the signature scan both count raw
|
|
120
|
+
* punctuation, and a `}` or `(` inside a comment or a string is not code. The
|
|
121
|
+
* length-preserving choice matters: bodies are sliced by index afterwards.
|
|
122
|
+
*/
|
|
123
|
+
export function stripNonCode(src) {
|
|
124
|
+
const out = src.split('');
|
|
125
|
+
const blank = (from, to) => {
|
|
126
|
+
for (let i = from; i < to && i < out.length; i++) {
|
|
127
|
+
if (out[i] !== '\n')
|
|
128
|
+
out[i] = ' ';
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
let i = 0;
|
|
132
|
+
while (i < src.length) {
|
|
133
|
+
const c = src[i];
|
|
134
|
+
const next = src[i + 1];
|
|
135
|
+
if (c === '/' && next === '/') {
|
|
136
|
+
let j = i + 2;
|
|
137
|
+
while (j < src.length && src[j] !== '\n')
|
|
138
|
+
j++;
|
|
139
|
+
blank(i, j);
|
|
140
|
+
i = j;
|
|
141
|
+
}
|
|
142
|
+
else if (c === '/' && next === '*') {
|
|
143
|
+
let j = i + 2;
|
|
144
|
+
while (j < src.length && !(src[j] === '*' && src[j + 1] === '/'))
|
|
145
|
+
j++;
|
|
146
|
+
blank(i, Math.min(j + 2, src.length));
|
|
147
|
+
i = j + 2;
|
|
148
|
+
}
|
|
149
|
+
else if (c === '"' || c === "'" || c === '`') {
|
|
150
|
+
let j = i + 1;
|
|
151
|
+
while (j < src.length) {
|
|
152
|
+
if (src[j] === '\\') {
|
|
153
|
+
j += 2;
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
if (src[j] === c)
|
|
157
|
+
break;
|
|
158
|
+
// An unterminated single/double quote must not eat the rest of the file.
|
|
159
|
+
if (c !== '`' && src[j] === '\n')
|
|
160
|
+
break;
|
|
161
|
+
j++;
|
|
162
|
+
}
|
|
163
|
+
blank(i + 1, j);
|
|
164
|
+
i = j + 1;
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
i++;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return out.join('');
|
|
171
|
+
}
|
|
172
|
+
/** Walk back over whitespace from `i` (exclusive), returning the new index. */
|
|
173
|
+
function skipWsBack(s, i) {
|
|
174
|
+
let j = i;
|
|
175
|
+
while (j > 0 && /\s/.test(s[j - 1]))
|
|
176
|
+
j--;
|
|
177
|
+
return j;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Map every `)` to its matching `(` (and every `{` to its `}`), in ONE pass.
|
|
181
|
+
*
|
|
182
|
+
* Both directions were previously resolved by rescanning per candidate, which is
|
|
183
|
+
* quadratic on unbalanced input: an unclosed brace made each candidate scan to
|
|
184
|
+
* EOF, and an unmatched paren made each `){` scan back to index 0. At the 200 KB
|
|
185
|
+
* scan cap that is still ~10^10 character steps — a synchronous stall inside a
|
|
186
|
+
* write handler, which is indistinguishable from a wedge and stops the deliver
|
|
187
|
+
* heartbeat advancing, so the lock's wedge-reclaim can reclaim a run that is
|
|
188
|
+
* merely spinning in this function.
|
|
189
|
+
*/
|
|
190
|
+
function bracketMaps(s) {
|
|
191
|
+
const closeOf = new Map();
|
|
192
|
+
const openOf = new Map();
|
|
193
|
+
const braces = [];
|
|
194
|
+
const parens = [];
|
|
195
|
+
for (let i = 0; i < s.length; i++) {
|
|
196
|
+
const c = s[i];
|
|
197
|
+
if (c === '{')
|
|
198
|
+
braces.push(i);
|
|
199
|
+
else if (c === '}') {
|
|
200
|
+
const open = braces.pop();
|
|
201
|
+
if (open !== undefined)
|
|
202
|
+
closeOf.set(open, i);
|
|
203
|
+
}
|
|
204
|
+
else if (c === '(')
|
|
205
|
+
parens.push(i);
|
|
206
|
+
else if (c === ')') {
|
|
207
|
+
const open = parens.pop();
|
|
208
|
+
if (open !== undefined)
|
|
209
|
+
openOf.set(i, open);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return { closeOf, openOf };
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Does the `{` at `open` begin a function body?
|
|
216
|
+
*
|
|
217
|
+
* Decided by walking backward, which is the only way to accept a return-type
|
|
218
|
+
* annotation, a generic parameter list, or nested parens in the parameters
|
|
219
|
+
* without hand-rolling a full parser:
|
|
220
|
+
*
|
|
221
|
+
* `=> {` → arrow body
|
|
222
|
+
* `) {` → declaration/method, unless the name before the
|
|
223
|
+
* paren is control flow
|
|
224
|
+
* `): SomeType<A, B> {` → same, after skipping the annotation
|
|
225
|
+
*/
|
|
226
|
+
function opensCallable(s, open, openOf) {
|
|
227
|
+
let i = skipWsBack(s, open);
|
|
228
|
+
if (i >= 2 && s[i - 1] === '>' && s[i - 2] === '=')
|
|
229
|
+
return true; // => {
|
|
230
|
+
// Skip a return-type annotation: `: Foo<Bar> | null` back to its `:`.
|
|
231
|
+
if (i > 0 && s[i - 1] !== ')') {
|
|
232
|
+
let j = i;
|
|
233
|
+
while (j > 0 && /[\w$.<>[\]|&,\s'"]/.test(s[j - 1]))
|
|
234
|
+
j--;
|
|
235
|
+
if (j > 0 && s[j - 1] === ':') {
|
|
236
|
+
i = skipWsBack(s, j - 1);
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
if (i === 0 || s[i - 1] !== ')')
|
|
243
|
+
return false;
|
|
244
|
+
const openParen = openOf.get(i - 1);
|
|
245
|
+
if (openParen === undefined)
|
|
246
|
+
return false;
|
|
247
|
+
let k = skipWsBack(s, openParen);
|
|
248
|
+
// Skip a generic parameter list: `foo<T extends X>(…)`.
|
|
249
|
+
if (k > 0 && s[k - 1] === '>') {
|
|
250
|
+
let depth = 0;
|
|
251
|
+
let j = k - 1;
|
|
252
|
+
for (; j >= 0; j--) {
|
|
253
|
+
if (s[j] === '>')
|
|
254
|
+
depth++;
|
|
255
|
+
else if (s[j] === '<') {
|
|
256
|
+
depth--;
|
|
257
|
+
if (depth === 0)
|
|
258
|
+
break;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
if (j < 0)
|
|
262
|
+
return false;
|
|
263
|
+
k = skipWsBack(s, j);
|
|
264
|
+
}
|
|
265
|
+
// The identifier immediately before the parameter list.
|
|
266
|
+
let n = k;
|
|
267
|
+
while (n > 0 && /[\w$]/.test(s[n - 1]))
|
|
268
|
+
n--;
|
|
269
|
+
const name = s.slice(n, k);
|
|
270
|
+
if (name === '') {
|
|
271
|
+
// `(…) {` with no name: an IIFE header or a bare block. Only treat it as a
|
|
272
|
+
// callable when `function` precedes it (`function (a) {`).
|
|
273
|
+
const before = s.slice(Math.max(0, skipWsBack(s, n) - 8), skipWsBack(s, n));
|
|
274
|
+
return /\bfunction$/.test(before);
|
|
275
|
+
}
|
|
276
|
+
if (CONTROL_KEYWORDS.has(name))
|
|
277
|
+
return false;
|
|
278
|
+
return true;
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Bodies of function-like constructs, brace-matched over stripped source.
|
|
282
|
+
*
|
|
283
|
+
* A regex cannot do this: `\{[^{}]*\}` only ever matches innermost braces, so a
|
|
284
|
+
* real file full of nested logic reports a handful of callables and the ratio
|
|
285
|
+
* becomes meaningless (measured during design — the naive version found 1–2
|
|
286
|
+
* callables in 1000-line modules).
|
|
287
|
+
*/
|
|
288
|
+
export function extractFunctionBodies(src) {
|
|
289
|
+
const s = stripNonCode(src);
|
|
290
|
+
const { closeOf, openOf } = bracketMaps(s);
|
|
291
|
+
const bodies = [];
|
|
292
|
+
for (let open = 0; open < s.length; open++) {
|
|
293
|
+
if (s[open] !== '{')
|
|
294
|
+
continue;
|
|
295
|
+
const end = closeOf.get(open);
|
|
296
|
+
if (end === undefined)
|
|
297
|
+
continue; // never closed — skip rather than guess
|
|
298
|
+
if (!opensCallable(s, open, openOf))
|
|
299
|
+
continue;
|
|
300
|
+
bodies.push(s.slice(open + 1, end));
|
|
301
|
+
// Nested bodies mean the retained text is O(input × nesting depth), so a
|
|
302
|
+
// pathological file of `f(){` repeated can allocate orders of magnitude more
|
|
303
|
+
// than it contains. Past this many callables the ratio has long since told us
|
|
304
|
+
// whatever it is going to.
|
|
305
|
+
if (bodies.length >= MAX_BODIES)
|
|
306
|
+
break;
|
|
307
|
+
}
|
|
308
|
+
return bodies;
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Is a body empty of executable content? Comments do not count as substance.
|
|
312
|
+
*
|
|
313
|
+
* `return true` / `return 0` were originally counted as empty too. They are not:
|
|
314
|
+
* `canShoot() { return true; }` is an ordinary predicate, and treating it as a
|
|
315
|
+
* stub put a real module three points from a false refusal — and, because
|
|
316
|
+
* contract-extractor reuses this predicate, annotated real exports as "declared
|
|
317
|
+
* but NOT implemented" in the text handed to dependent epics. Only genuinely
|
|
318
|
+
* vacuous returns count.
|
|
319
|
+
*/
|
|
320
|
+
export function isEmptyBody(body) {
|
|
321
|
+
const stripped = stripNonCode(body)
|
|
322
|
+
.replace(/[ \t]+/g, ' ')
|
|
323
|
+
.trim();
|
|
324
|
+
if (stripped === '')
|
|
325
|
+
return true;
|
|
326
|
+
return /^return(\s*(?:null|undefined|\{\s*\}|\[\s*\]))?\s*;?$/.test(stripped);
|
|
327
|
+
}
|
|
328
|
+
function pctOf(ratio) {
|
|
329
|
+
return Math.round(ratio * 100);
|
|
330
|
+
}
|
|
331
|
+
function extensionOf(path) {
|
|
332
|
+
const base = path.split(/[\\/]/).pop() ?? '';
|
|
333
|
+
const lower = base.toLowerCase();
|
|
334
|
+
for (const ext of ['.d.ts', '.d.mts', '.d.cts']) {
|
|
335
|
+
if (lower.endsWith(ext))
|
|
336
|
+
return ext;
|
|
337
|
+
}
|
|
338
|
+
const dot = lower.lastIndexOf('.');
|
|
339
|
+
return dot > 0 ? lower.slice(dot) : '';
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Decide whether `content` is a stub for `path`.
|
|
343
|
+
*
|
|
344
|
+
* Thresholds are set from the measured populations, biased toward letting things
|
|
345
|
+
* through: a false refusal blocks legitimate work and trains the operator to set
|
|
346
|
+
* the override, which costs more than an occasional miss.
|
|
347
|
+
*
|
|
348
|
+
* - ≥6 callables and ≥60% empty → stub, no label needed.
|
|
349
|
+
* - ≥3 callables and ≥45% empty AND a self-label → stub.
|
|
350
|
+
*
|
|
351
|
+
* The two bars differ because a self-label is strong evidence and should lower
|
|
352
|
+
* the ratio required, not merely the surface size. Replaying the real corpus
|
|
353
|
+
* caught this: background.js is unambiguously a stub ("Background Module —
|
|
354
|
+
* Stub") but sits at 50% empty, so a single 60% bar let it through — 6 of 7
|
|
355
|
+
* blocked, a miss on a file the guard exists for.
|
|
356
|
+
*
|
|
357
|
+
* A file with one or two empty bodies is never flagged, on either bar: no-op
|
|
358
|
+
* defaults and intentional no-op handlers are ordinary, and there is no evidence
|
|
359
|
+
* to separate them from a stub at that size. That promise is what the labelled
|
|
360
|
+
* bar's `empties >= 3` floor keeps — with only `ratio >= 0.45`, a 3-body file
|
|
361
|
+
* with 2 empties and a "TODO: implement" header would have been refused.
|
|
362
|
+
*
|
|
363
|
+
* NOT flagged, by design: `throw new Error("TODO")` / `todo!()` /
|
|
364
|
+
* `raise NotImplementedError` bodies. Those are what a SCAFFOLD epic is
|
|
365
|
+
* explicitly instructed to produce (epic-mission.ts), and they are honest —
|
|
366
|
+
* they announce themselves at runtime instead of silently succeeding, which is
|
|
367
|
+
* the whole problem with `{}`.
|
|
368
|
+
*/
|
|
369
|
+
export function detectStub(path, content, prior) {
|
|
370
|
+
const none = { isStub: false, reason: '', callables: 0, emptyRatio: 0, marker: false };
|
|
371
|
+
if (EXEMPT_EXTENSIONS.has(extensionOf(path)))
|
|
372
|
+
return none;
|
|
373
|
+
// Above this, skip rather than scan. Minified/vendored bundles are the case —
|
|
374
|
+
// and the harness actively steers models into writing them, because the visual
|
|
375
|
+
// gate's advice for a failed CDN request is "vendor the dependency locally".
|
|
376
|
+
// The shape argument does not apply to generated output anyway.
|
|
377
|
+
if (content.length > MAX_SCAN_CHARS)
|
|
378
|
+
return none;
|
|
379
|
+
const head = content.split('\n').slice(0, MARKER_SCAN_LINES).join('\n').slice(0, MARKER_SCAN_CHARS);
|
|
380
|
+
const marker = MARKER_RE.test(head);
|
|
381
|
+
const bodies = extractFunctionBodies(content);
|
|
382
|
+
if (bodies.length === 0)
|
|
383
|
+
return { ...none, marker };
|
|
384
|
+
const empties = bodies.filter(isEmptyBody).length;
|
|
385
|
+
const ratio = empties / bodies.length;
|
|
386
|
+
const base = { callables: bodies.length, emptyRatio: ratio, marker };
|
|
387
|
+
const bigSurface = bodies.length >= 6 && ratio >= 0.6;
|
|
388
|
+
// The labelled bar also needs an ABSOLUTE floor of empty bodies. Without it a
|
|
389
|
+
// 3-callable file at 45% (two empties) could be refused on the strength of the
|
|
390
|
+
// word "stub" appearing in its header — and MARKER_RE matches prose that merely
|
|
391
|
+
// discusses stubs ("this module used to be a stub"), including this very file's
|
|
392
|
+
// own header. Three empty bodies is real evidence; two is a coincidence.
|
|
393
|
+
// The labelled bar is also bounded ABOVE. Measured: the worst genuinely-real
|
|
394
|
+
// file in this repo (src/delivery/execution-gate.ts) sits at 50% empty across
|
|
395
|
+
// 72 callables — under the 60% bar with room, but over the labelled 45% one. It
|
|
396
|
+
// is unflagged today only because its header happens not to say "stub"; adding
|
|
397
|
+
// that word to a comment would have refused it. A self-label is evidence about
|
|
398
|
+
// a small module that declares an API and implements none of it, which is what
|
|
399
|
+
// background.js (4 callables) is. It says nothing useful about a 72-callable
|
|
400
|
+
// implementation file, so the lowered bar does not apply there.
|
|
401
|
+
const labelled = bodies.length >= 3 && bodies.length <= 12 && empties >= 3 && ratio >= 0.45 && marker;
|
|
402
|
+
if (!bigSurface && !labelled)
|
|
403
|
+
return { ...base, isStub: false, reason: '' };
|
|
404
|
+
// MONOTONE PROGRESS. A FILL epic reads an 8-function skeleton and re-emits it
|
|
405
|
+
// with three functions implemented: still 62% empty, so the snapshot test
|
|
406
|
+
// refuses it — and refuses every subsequent partial improvement, ratcheting the
|
|
407
|
+
// file permanently shut. That is the same unsatisfiable-gate class this guard's
|
|
408
|
+
// own epic exemption exists to avoid, reached from the other direction. If the
|
|
409
|
+
// file on disk is already a stub and the new content implements strictly more
|
|
410
|
+
// of it, the write is progress and is allowed.
|
|
411
|
+
if (prior !== undefined && prior !== '') {
|
|
412
|
+
const before = detectStub(path, prior);
|
|
413
|
+
if (before.isStub) {
|
|
414
|
+
const beforeEmpties = Math.round(before.emptyRatio * before.callables);
|
|
415
|
+
if (empties < beforeEmpties || ratio < before.emptyRatio) {
|
|
416
|
+
return {
|
|
417
|
+
...base,
|
|
418
|
+
isStub: false,
|
|
419
|
+
reason: `still ${pctOf(ratio)}% empty, but fewer empty bodies than before — progress`,
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
const pct = pctOf(ratio);
|
|
425
|
+
return {
|
|
426
|
+
...base,
|
|
427
|
+
isStub: true,
|
|
428
|
+
reason: `${bodies.length} function bodies declared and ${empties} of them (${pct}%) are empty` +
|
|
429
|
+
(marker ? ', and the file labels itself a stub/placeholder' : '') +
|
|
430
|
+
'. This declares an API surface without implementing it',
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* The refusal text handed back to the model.
|
|
435
|
+
*
|
|
436
|
+
* Says what to do instead, because a bare refusal on the agentic path produces a
|
|
437
|
+
* retry of the identical content — the same reason the P3 guard spells out
|
|
438
|
+
* edit_file. Names the override so a human reading the transcript can unblock a
|
|
439
|
+
* legitimate case without reading this source.
|
|
440
|
+
*/
|
|
441
|
+
export function stubRefusal(path, verdict) {
|
|
442
|
+
return (`ERROR: refusing to write ${path} — ${verdict.reason}. ` +
|
|
443
|
+
`Write the REAL implementation: each function must contain the logic that makes it work. ` +
|
|
444
|
+
`Re-sending this same skeleton will be refused again. ` +
|
|
445
|
+
`If this file is deliberately a skeleton (a scaffold phase), give each body an explicit ` +
|
|
446
|
+
`throw new Error('TODO: <what>') instead of an empty block — that is honest at runtime, ` +
|
|
447
|
+
`and it is accepted. ` +
|
|
448
|
+
`If a genuinely empty-bodied file is intended (an interface, an abstract base), ` +
|
|
449
|
+
`set UAP_DELIVER_ALLOW_STUBS=1.`);
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Operator opt-out, matching UAP_DELIVER_ALLOW_GUTTING's shape. ENV ONLY.
|
|
453
|
+
*
|
|
454
|
+
* There was briefly a second disjunct here, `UAP_EPIC_STUBS_OK`, set by the epic
|
|
455
|
+
* controller for CONTRACTS and SCAFFOLD epics — the two phases whose prompts say
|
|
456
|
+
* stub bodies are the DELIVERABLE. It is gone, because it was SELF-GRANTABLE:
|
|
457
|
+
* `epic.scaffold` and `epic.contracts` come out of the PLANNER MODEL's JSON
|
|
458
|
+
* (decompose.ts), and the planner and the executor are the same model. Labelling
|
|
459
|
+
* a phase `"scaffold": true` would have switched the guard off for it. An
|
|
460
|
+
* override the constrained party can set is not an override, it is an off switch
|
|
461
|
+
* — the same reasoning that made expert-review's inline waiver indefensible.
|
|
462
|
+
*
|
|
463
|
+
* Nothing is lost by removing it. A COMPLIANT scaffold already passes: the prompt
|
|
464
|
+
* asks for `throw new Error("TODO")` / `todo!()` / `raise NotImplementedError`
|
|
465
|
+
* bodies, and those are not empty. The exemption would only ever have permitted
|
|
466
|
+
* the one shape the prompt does not ask for, bare `{}` — which is also the only
|
|
467
|
+
* shape that fails silently at runtime instead of announcing itself. So the
|
|
468
|
+
* refusal names the compliant form instead of waiving the check, which keeps the
|
|
469
|
+
* phase satisfiable without handing the model a switch.
|
|
470
|
+
*/
|
|
471
|
+
export function stubGuardDisabled() {
|
|
472
|
+
return process.env.UAP_DELIVER_ALLOW_STUBS === '1';
|
|
473
|
+
}
|
|
474
|
+
//# sourceMappingURL=stub-detector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stub-detector.js","sourceRoot":"","sources":["../../src/delivery/stub-detector.ts"],"names":[],"mappings":"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"}
|
|
@@ -77,6 +77,14 @@ export interface PageVisualReport {
|
|
|
77
77
|
export interface VisualVerdict {
|
|
78
78
|
/** False when any page has objective visual/behavioral problems. */
|
|
79
79
|
passed: boolean;
|
|
80
|
+
/**
|
|
81
|
+
* True when any page has a STRUCTURAL problem (see structuralProblems): the
|
|
82
|
+
* page did not load, threw an uncaught error, lost a dependency, or is missing
|
|
83
|
+
* the canvas its own source demands. Callers use this to block in modes where
|
|
84
|
+
* the graded richness floors are only advisory. An unpainted canvas is NOT
|
|
85
|
+
* structural — see the note in structuralProblems for why.
|
|
86
|
+
*/
|
|
87
|
+
structural: boolean;
|
|
80
88
|
/** True when the gate could not run (no browser) — advisory pass. */
|
|
81
89
|
skipped: boolean;
|
|
82
90
|
feedback: string;
|
|
@@ -175,6 +183,56 @@ export declare function judgePage(report: Omit<PageVisualReport, 'problems'>, ta
|
|
|
175
183
|
maxDominantRatio?: number;
|
|
176
184
|
minMotionRatio?: number;
|
|
177
185
|
}): string[];
|
|
186
|
+
/**
|
|
187
|
+
* The subset of a page's problems that are STRUCTURAL rather than graded.
|
|
188
|
+
*
|
|
189
|
+
* The distinction is not severity-by-taste, it is whether the finding is a
|
|
190
|
+
* BINARY FACT about the page or a position against a tunable floor:
|
|
191
|
+
*
|
|
192
|
+
* structural — the page did not load, or it threw an uncaught error.
|
|
193
|
+
* graded — rendered fewer colors than the floor asks for (including none
|
|
194
|
+
* at all); animated less than the motion floor asks for; a
|
|
195
|
+
* dependency failed to download; the canvas its source implies
|
|
196
|
+
* is absent.
|
|
197
|
+
*
|
|
198
|
+
* Structural findings block in modes where the graded floors are advisory. That
|
|
199
|
+
* set is deliberately SMALL, and three candidates were cut after review traced
|
|
200
|
+
* what each would do mid-build:
|
|
201
|
+
*
|
|
202
|
+
* - a canvas that painted nothing. It reads like a binary fact, and was
|
|
203
|
+
* classified as one first. But an unpainted canvas is the shape of a
|
|
204
|
+
* legitimate scaffold epic — run J (live, 2026-07-17) had scaffolds split
|
|
205
|
+
* twice on "1 distinct color < 3 required" with every other gate green.
|
|
206
|
+
* - a failed external request. Mid-build a 404 is the NORMAL state: an early
|
|
207
|
+
* epic writes index.html referencing js/game.js that a later epic creates
|
|
208
|
+
* (which is why missingMissionFiles exists). Blocking on it would fail every
|
|
209
|
+
* non-final epic, and this gate's feedback for the case assumes a CDN and
|
|
210
|
+
* tells the model to vendor a dependency — actively wrong advice for a module
|
|
211
|
+
* that simply is not written yet.
|
|
212
|
+
* - "source expects a canvas animation but no canvas exists". `hasCanvas` comes
|
|
213
|
+
* from an in-page probe that reports false whenever `evaluate` throws or its
|
|
214
|
+
* JSON fails to parse, so one flaky evaluation under load would hard-block a
|
|
215
|
+
* page that renders fine. The interaction gate was kept out of this branch for
|
|
216
|
+
* exactly that reason; a newly-blocking predicate deserves the same standard.
|
|
217
|
+
*
|
|
218
|
+
* All three remain in `problems`, so they still block in primary mode and on the
|
|
219
|
+
* final epic. They are simply not grounds to block a mode that has real gates.
|
|
220
|
+
*
|
|
221
|
+
* Pure over the report, so it is recomputed rather than threaded through
|
|
222
|
+
* judgePage. A test asserts structural ⊆ problems, because nothing else enforces
|
|
223
|
+
* that invariant and a verdict reading "renders clean" while blocking would be
|
|
224
|
+
* incoherent.
|
|
225
|
+
*/
|
|
226
|
+
export declare function structuralProblems(report: PageVisualReport): string[];
|
|
227
|
+
/**
|
|
228
|
+
* Blocking text for a STRUCTURAL failure, naming only the structural findings.
|
|
229
|
+
*
|
|
230
|
+
* The full `feedback` lists every problem including the graded richness floors.
|
|
231
|
+
* Handing that to the model in a mode that declares those floors advisory points
|
|
232
|
+
* it at the color floor when the real blocker is one TypeError — the same
|
|
233
|
+
* misdirection this gate already recorded for failed requests.
|
|
234
|
+
*/
|
|
235
|
+
export declare function structuralFeedback(verdict: VisualVerdict): string;
|
|
178
236
|
/**
|
|
179
237
|
* Run the visual gate over the project's entry pages. Fail-open (skipped=true)
|
|
180
238
|
* only when no browser can launch.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"visual-gate.d.ts","sourceRoot":"","sources":["../../src/delivery/visual-gate.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"visual-gate.d.ts","sourceRoot":"","sources":["../../src/delivery/visual-gate.ts"],"names":[],"mappings":"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"}
|