@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.
Files changed (30) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/delivery/agentic-executor.d.ts.map +1 -1
  3. package/dist/delivery/agentic-executor.js +52 -0
  4. package/dist/delivery/agentic-executor.js.map +1 -1
  5. package/dist/delivery/applier.d.ts.map +1 -1
  6. package/dist/delivery/applier.js +53 -0
  7. package/dist/delivery/applier.js.map +1 -1
  8. package/dist/delivery/contract-extractor.d.ts +16 -0
  9. package/dist/delivery/contract-extractor.d.ts.map +1 -1
  10. package/dist/delivery/contract-extractor.js +136 -3
  11. package/dist/delivery/contract-extractor.js.map +1 -1
  12. package/dist/delivery/mission-acceptance.d.ts.map +1 -1
  13. package/dist/delivery/mission-acceptance.js +48 -3
  14. package/dist/delivery/mission-acceptance.js.map +1 -1
  15. package/dist/delivery/stub-detector.d.ts +150 -0
  16. package/dist/delivery/stub-detector.d.ts.map +1 -0
  17. package/dist/delivery/stub-detector.js +474 -0
  18. package/dist/delivery/stub-detector.js.map +1 -0
  19. package/dist/delivery/visual-gate.d.ts +58 -0
  20. package/dist/delivery/visual-gate.d.ts.map +1 -1
  21. package/dist/delivery/visual-gate.js +87 -3
  22. package/dist/delivery/visual-gate.js.map +1 -1
  23. package/package.json +1 -1
  24. package/src/policies/enforcers/__pycache__/_common.cpython-312.pyc +0 -0
  25. package/src/policies/enforcers/enforcement_infra_protect.py +26 -1
  26. package/src/policies/enforcers/enforcement_self_protect.py +8 -0
  27. package/src/policies/enforcers/expert_review_required.py +22 -9
  28. package/templates/hooks/__pycache__/deliver_autoroute.cpython-312.pyc +0 -0
  29. package/tools/agents/scripts/__pycache__/toolcall_path_normalizer.cpython-312.pyc +0 -0
  30. 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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"}