@miller-tech/uap 1.163.13 → 1.165.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 (142) hide show
  1. package/README.md +1 -1
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/bin/cli.js +5 -4
  4. package/dist/bin/cli.js.map +1 -1
  5. package/dist/browser/web-browser.d.ts +7 -0
  6. package/dist/browser/web-browser.d.ts.map +1 -1
  7. package/dist/browser/web-browser.js +15 -0
  8. package/dist/browser/web-browser.js.map +1 -1
  9. package/dist/cli/deliver.d.ts +32 -3
  10. package/dist/cli/deliver.d.ts.map +1 -1
  11. package/dist/cli/deliver.js +198 -31
  12. package/dist/cli/deliver.js.map +1 -1
  13. package/dist/cli/guided-setup.d.ts.map +1 -1
  14. package/dist/cli/guided-setup.js +13 -1
  15. package/dist/cli/guided-setup.js.map +1 -1
  16. package/dist/cli/model.d.ts.map +1 -1
  17. package/dist/cli/model.js +9 -1
  18. package/dist/cli/model.js.map +1 -1
  19. package/dist/cli/proxy-lifecycle.d.ts +66 -13
  20. package/dist/cli/proxy-lifecycle.d.ts.map +1 -1
  21. package/dist/cli/proxy-lifecycle.js +116 -49
  22. package/dist/cli/proxy-lifecycle.js.map +1 -1
  23. package/dist/cli/proxy.d.ts +46 -0
  24. package/dist/cli/proxy.d.ts.map +1 -1
  25. package/dist/cli/proxy.js +353 -23
  26. package/dist/cli/proxy.js.map +1 -1
  27. package/dist/cli/verify.d.ts +6 -0
  28. package/dist/cli/verify.d.ts.map +1 -1
  29. package/dist/cli/verify.js +64 -0
  30. package/dist/cli/verify.js.map +1 -1
  31. package/dist/cli/wizard-config.d.ts +4 -0
  32. package/dist/cli/wizard-config.d.ts.map +1 -1
  33. package/dist/cli/wizard-config.js +14 -3
  34. package/dist/cli/wizard-config.js.map +1 -1
  35. package/dist/config/settings-registry.d.ts.map +1 -1
  36. package/dist/config/settings-registry.js +25 -0
  37. package/dist/config/settings-registry.js.map +1 -1
  38. package/dist/coordination/effort-profile.d.ts +39 -0
  39. package/dist/coordination/effort-profile.d.ts.map +1 -0
  40. package/dist/coordination/effort-profile.js +74 -0
  41. package/dist/coordination/effort-profile.js.map +1 -0
  42. package/dist/dashboard/server.d.ts.map +1 -1
  43. package/dist/dashboard/server.js +68 -10
  44. package/dist/dashboard/server.js.map +1 -1
  45. package/dist/delivery/agentic-executor.d.ts +15 -0
  46. package/dist/delivery/agentic-executor.d.ts.map +1 -1
  47. package/dist/delivery/agentic-executor.js +64 -3
  48. package/dist/delivery/agentic-executor.js.map +1 -1
  49. package/dist/delivery/convergence-loop.d.ts +76 -1
  50. package/dist/delivery/convergence-loop.d.ts.map +1 -1
  51. package/dist/delivery/convergence-loop.js +71 -6
  52. package/dist/delivery/convergence-loop.js.map +1 -1
  53. package/dist/delivery/critic.d.ts +7 -0
  54. package/dist/delivery/critic.d.ts.map +1 -1
  55. package/dist/delivery/critic.js.map +1 -1
  56. package/dist/delivery/decompose.d.ts +9 -0
  57. package/dist/delivery/decompose.d.ts.map +1 -1
  58. package/dist/delivery/decompose.js +11 -0
  59. package/dist/delivery/decompose.js.map +1 -1
  60. package/dist/delivery/execution-gate.d.ts +4 -0
  61. package/dist/delivery/execution-gate.d.ts.map +1 -1
  62. package/dist/delivery/execution-gate.js +194 -2
  63. package/dist/delivery/execution-gate.js.map +1 -1
  64. package/dist/delivery/graph-safety.d.ts +75 -0
  65. package/dist/delivery/graph-safety.d.ts.map +1 -0
  66. package/dist/delivery/graph-safety.js +98 -0
  67. package/dist/delivery/graph-safety.js.map +1 -0
  68. package/dist/delivery/mission-acceptance.d.ts.map +1 -1
  69. package/dist/delivery/mission-acceptance.js +69 -21
  70. package/dist/delivery/mission-acceptance.js.map +1 -1
  71. package/dist/delivery/per-phase-escalation-controller.d.ts +37 -0
  72. package/dist/delivery/per-phase-escalation-controller.d.ts.map +1 -0
  73. package/dist/delivery/per-phase-escalation-controller.js +68 -0
  74. package/dist/delivery/per-phase-escalation-controller.js.map +1 -0
  75. package/dist/delivery/per-phase-escalation.d.ts +53 -0
  76. package/dist/delivery/per-phase-escalation.d.ts.map +1 -0
  77. package/dist/delivery/per-phase-escalation.js +57 -0
  78. package/dist/delivery/per-phase-escalation.js.map +1 -0
  79. package/dist/delivery/reflect.d.ts +56 -0
  80. package/dist/delivery/reflect.d.ts.map +1 -0
  81. package/dist/delivery/reflect.js +79 -0
  82. package/dist/delivery/reflect.js.map +1 -0
  83. package/dist/delivery/user-paths.d.ts +42 -0
  84. package/dist/delivery/user-paths.d.ts.map +1 -1
  85. package/dist/delivery/user-paths.js +254 -6
  86. package/dist/delivery/user-paths.js.map +1 -1
  87. package/dist/delivery/user-validation.d.ts.map +1 -1
  88. package/dist/delivery/user-validation.js +21 -1
  89. package/dist/delivery/user-validation.js.map +1 -1
  90. package/dist/delivery/verification-provenance.d.ts +47 -0
  91. package/dist/delivery/verification-provenance.d.ts.map +1 -0
  92. package/dist/delivery/verification-provenance.js +48 -0
  93. package/dist/delivery/verification-provenance.js.map +1 -0
  94. package/dist/delivery/vision-judge.d.ts.map +1 -1
  95. package/dist/delivery/vision-judge.js +8 -1
  96. package/dist/delivery/vision-judge.js.map +1 -1
  97. package/dist/models/complexity.d.ts +36 -0
  98. package/dist/models/complexity.d.ts.map +1 -0
  99. package/dist/models/complexity.js +75 -0
  100. package/dist/models/complexity.js.map +1 -0
  101. package/dist/models/openai-compat-client.d.ts +26 -0
  102. package/dist/models/openai-compat-client.d.ts.map +1 -1
  103. package/dist/models/openai-compat-client.js +60 -8
  104. package/dist/models/openai-compat-client.js.map +1 -1
  105. package/dist/models/router.d.ts.map +1 -1
  106. package/dist/models/router.js +39 -10
  107. package/dist/models/router.js.map +1 -1
  108. package/dist/models/types.d.ts +60 -10
  109. package/dist/models/types.d.ts.map +1 -1
  110. package/dist/models/types.js +128 -11
  111. package/dist/models/types.js.map +1 -1
  112. package/dist/self-tuning/prompt-variants.d.ts +93 -0
  113. package/dist/self-tuning/prompt-variants.d.ts.map +1 -0
  114. package/dist/self-tuning/prompt-variants.js +143 -0
  115. package/dist/self-tuning/prompt-variants.js.map +1 -0
  116. package/dist/tasks/types.d.ts +2 -2
  117. package/dist/types/config.d.ts +8 -0
  118. package/dist/types/config.d.ts.map +1 -1
  119. package/docs/INDEX.md +1 -1
  120. package/docs/benchmarks/COMPREHENSIVE_BENCHMARKS.md +47 -53
  121. package/docs/benchmarks/VALIDATION_RESULTS.md +0 -2
  122. package/docs/guides/COORDINATION.md +1 -1
  123. package/docs/guides/DEPLOY_BATCHING.md +1 -1
  124. package/docs/guides/DROIDS_AND_SKILLS.md +3 -2
  125. package/docs/guides/LOCAL_MODELS.md +1 -1
  126. package/docs/guides/MCP_ROUTER.md +1 -1
  127. package/docs/guides/MEMORY.md +2 -2
  128. package/docs/guides/MULTI_MODEL.md +1 -1
  129. package/docs/guides/POLICIES.md +1 -1
  130. package/docs/guides/PROXY.md +47 -4
  131. package/docs/guides/SANDBOX.md +1 -1
  132. package/docs/guides/WORKTREE_WORKFLOW.md +1 -1
  133. package/docs/reference/CLI.md +16 -3
  134. package/docs/reference/CONFIGURATION.md +1 -1
  135. package/docs/reference/FEATURES.md +1 -1
  136. package/package.json +1 -1
  137. package/src/policies/enforcers/__pycache__/_common.cpython-312.pyc +0 -0
  138. package/src/policies/enforcers/iac_plan_destruction_check.py +32 -4
  139. package/templates/hooks/__pycache__/deliver_autoroute.cpython-312.pyc +0 -0
  140. package/templates/hooks/session-end.sh +9 -0
  141. package/templates/hooks/session-start.sh +10 -0
  142. package/tools/agents/scripts/__pycache__/toolcall_path_normalizer.cpython-312.pyc +0 -0
@@ -11,13 +11,15 @@ import { existsSync, mkdirSync, openSync, readFileSync, rmSync, writeSync, close
11
11
  import { join, resolve, relative } from 'path';
12
12
  import { ConvergenceLoop, composeIterationHooks } from '../delivery/convergence-loop.js';
13
13
  import { createModelJudge } from '../delivery/judge.js';
14
+ import { resolveJudgePlan, formatVerificationProvenance, } from '../delivery/verification-provenance.js';
14
15
  import { createModelCritic } from '../delivery/critic.js';
15
16
  import { MAX_CANDIDATES } from '../delivery/explorer.js';
16
17
  import { generateStrategySeeds, seedsFromIdeas } from '../delivery/ideation.js';
17
18
  import { DEFAULT_STRATEGY_SEEDS } from '../delivery/explorer.js';
18
19
  import { planAutoOptimization } from '../delivery/auto-optimizer.js';
19
- import { RoutingPresets, resolvePresetModel } from '../models/types.js';
20
- import { measureQueryComplexity } from '../utils/query-complexity.js';
20
+ import { RoutingPresets, resolvePresetModel, resolvePhaseChain } from '../models/types.js';
21
+ import { classifyComplexity, tierToRouting } from '../models/complexity.js';
22
+ import { promptSelectionFromConfig } from '../self-tuning/prompt-variants.js';
21
23
  import { authorAcceptanceGate } from '../delivery/self-gate.js';
22
24
  import { applyPendingIntents } from '../delivery/pending-intents.js';
23
25
  import { runAcceptanceGate } from '../delivery/acceptance-judge.js';
@@ -47,6 +49,36 @@ export function shouldSkipAcceptanceJudge(opts) {
47
49
  opts.complexity === 'simple' &&
48
50
  !opts.acceptancePrimary);
49
51
  }
52
+ /**
53
+ * When the model fails to author a self-gate, is that fatal?
54
+ *
55
+ * Only if the run would then have NO convergence target at all. Getting this
56
+ * wrong in the permissive direction re-opens the false-green door the
57
+ * anti-vacuous floor exists to close: with no self-gate rung and no acceptance
58
+ * judge, a repo whose gates were already green is "delivered" having written
59
+ * nothing.
60
+ *
61
+ * Two traps this predicate exists to avoid, both of which `!options.acceptance`
62
+ * alone walks straight into:
63
+ *
64
+ * - `--acceptance` is on but the judge is SKIPPED for a simple task
65
+ * (shouldSkipAcceptanceJudge), so no gate object is ever built.
66
+ * - max fidelity is treated as a standing blocker. It is not: the vision review
67
+ * lives INSIDE the acceptance gate, so if that gate was never built there is
68
+ * nothing to block, and visionAcceptanceFeedback additionally fails open on
69
+ * five separate conditions (no vision model, no screenshots, non-final epic,
70
+ * any thrown error, fidelity not max).
71
+ *
72
+ * So the only thing that makes a missing self-gate survivable is a judge that
73
+ * will actually run — plus at least one real rung to converge against.
74
+ */
75
+ export function selfGateFailureIsFatal(opts) {
76
+ // A judge that will actually run is the only substitute. Surviving rungs do not
77
+ // rescue this: needsSelfGate is raised precisely when the objective gates are
78
+ // all GREEN (no convergence pressure) or absent entirely (the loop throws), so
79
+ // in both cases a missing self-gate with no judge leaves nothing to satisfy.
80
+ return !(opts.acceptanceEnabled && !opts.judgeSkipped);
81
+ }
50
82
  /**
51
83
  * Generator≠Evaluator: resolve which model should AUTHOR + JUDGE the acceptance
52
84
  * gate. Returns the evaluator preset id when it must differ from the generator
@@ -105,7 +137,7 @@ import { initLedger, markItem } from '../delivery/completion-ledger.js';
105
137
  import { loadUapConfigRaw } from '../utils/config-loader.js';
106
138
  import { resolveEscalateModelId } from '../delivery/repair-escalation.js';
107
139
  import { createUserValidationRunner, resolveUserValidationMode, synthesizeUserValidationRung, } from '../delivery/user-validation.js';
108
- import { deriveUserPaths, fallbackWebManifest, loadUserPaths, mergeUserPaths, USER_PATHS_FILE } from '../delivery/user-paths.js';
140
+ import { deriveUserPaths, fallbackWebManifest, loadUserPaths, mergeUserPaths, renderAcceptanceContract, USER_PATHS_FILE } from '../delivery/user-paths.js';
109
141
  import { detectArtifactType } from '../delivery/execution-gate.js';
110
142
  /** Raw .uap.json reader usable before the main cfgRawEarly declaration. */
111
143
  function cfgRawEarlyForUvFactory(projectRoot) {
@@ -182,20 +214,14 @@ function fail(message) {
182
214
  }
183
215
  class ExitError extends Error {
184
216
  }
185
- /** Deliver's 3-level complexity (simple/moderate/complex) → the routing tier
186
- * scale (low/medium/high/critical). Deliver's classifier never emits
187
- * 'critical' — that tier is reserved for keyword-driven escalation. */
188
- const COMPLEXITY_TO_TIER = {
189
- simple: 'low',
190
- moderate: 'medium',
191
- complex: 'high',
192
- };
193
217
  /**
194
218
  * Complexity-tier routing (P: per-task model selection). Returns the model id
195
219
  * to execute this task on, or null to leave the caller's default untouched.
196
- * PURE — unit-tested. Precedence is enforced by the caller: an explicit
197
- * --model always wins; this only fires when routing is requested and no model
198
- * was pinned.
220
+ * PURE — unit-tested. Uses the unified complexity classifier (src/models/
221
+ * complexity.ts) so `critical` is preserved end-to-end the old
222
+ * COMPLEXITY_TO_TIER bridge silently dropped it. Precedence is enforced by the
223
+ * caller: an explicit --model always wins; this only fires when routing is
224
+ * requested and no model was pinned.
199
225
  */
200
226
  export function resolveTierModel(routingId, instruction) {
201
227
  const id = routingId ?? process.env.UAP_DELIVER_ROUTING;
@@ -204,8 +230,7 @@ export function resolveTierModel(routingId, instruction) {
204
230
  const preset = RoutingPresets[id];
205
231
  if (!preset)
206
232
  return null;
207
- const complexity = measureQueryComplexity(instruction, { moderate: 1, complex: 2 });
208
- const tier = COMPLEXITY_TO_TIER[complexity] ?? 'medium';
233
+ const tier = tierToRouting(classifyComplexity({ instruction }).tier);
209
234
  return { model: resolvePresetModel(preset, { complexity: tier, role: 'executor' }), tier, preset: id };
210
235
  }
211
236
  function resolveModel(presetId, endpointOverride) {
@@ -997,8 +1022,52 @@ async function runDeliver(instruction, options) {
997
1022
  }
998
1023
  let evaluatorExecutor = blindExecutor;
999
1024
  let verdictExecutor = jsonBlindExecutor;
1000
- if (evaluatorPresetId) {
1001
- const evalModel = resolveModel(evaluatorPresetId, options.evaluatorEndpoint);
1025
+ // Verification provenance (S1 — Principle 3: "the model cannot verify itself").
1026
+ // Deliver must never let the generator be the SOLE grader of its own output.
1027
+ const allowSelfJudge = process.env.UAP_DELIVER_ALLOW_SELF_JUDGE === '1' ||
1028
+ (() => {
1029
+ try {
1030
+ const c = loadUapConfigRaw(projectRoot);
1031
+ return c?.recipes?.allowSelfJudge === true;
1032
+ }
1033
+ catch {
1034
+ return false;
1035
+ }
1036
+ })();
1037
+ // Q3: when a routing preset is active, prefer ITS review-phase model as the
1038
+ // distinct judge (compose S1 verification with the S3 per-phase matrix) instead
1039
+ // of the hardcoded haiku default — the preset's designated reviewer IS the
1040
+ // Generator≠Evaluator choice. resolveJudgePlan still swaps to the alt judge if
1041
+ // this collides with the generator, and only uses it on the auto-distinct path.
1042
+ const routingIdForJudge = options.routing ?? process.env.UAP_DELIVER_ROUTING;
1043
+ const presetForJudge = routingIdForJudge ? RoutingPresets[routingIdForJudge] : undefined;
1044
+ const reviewJudgeId = presetForJudge
1045
+ ? resolvePhaseChain(presetForJudge, {
1046
+ complexity: tierToRouting(classifyComplexity({ instruction }).tier),
1047
+ phase: 'review',
1048
+ })[0]
1049
+ : undefined;
1050
+ const judgePlan = resolveJudgePlan({
1051
+ evaluatorPresetId,
1052
+ generatorId: model.id,
1053
+ generatorProvider: ModelPresets[presetId]?.provider,
1054
+ allowSelfJudge,
1055
+ distinctJudgeId: reviewJudgeId,
1056
+ hasPreset: (id) => Boolean(ModelPresets[id]),
1057
+ });
1058
+ const judgeModelId = judgePlan.judgeModelId;
1059
+ const judgeDistinct = judgePlan.distinct;
1060
+ // Acceptance-scoped executors: they author the self-gate (verify.sh) and run
1061
+ // the acceptance JUDGE. They default to the generator and are the ONLY channels
1062
+ // the auto-distinct judge redirects — PLANNING (planDeliveryPhases /
1063
+ // deriveUserPaths on verdictExecutor) stays on the generator to avoid a
1064
+ // planning-quality regression (review C1).
1065
+ let gateAuthorExecutor = evaluatorExecutor;
1066
+ let acceptanceJudgeExecutor = verdictExecutor;
1067
+ if (judgePlan.reason === 'configured-evaluator') {
1068
+ // A distinct configured evaluator drives the evaluation channel AND planning
1069
+ // (pre-existing barbell behavior): reassign the globals.
1070
+ const evalModel = resolveModel(judgeModelId, options.evaluatorEndpoint);
1002
1071
  const evalClient = new OpenAICompatClient();
1003
1072
  evaluatorExecutor = async (prompt) => {
1004
1073
  // Evaluators judge cool + deterministic; they do not brainstorm.
@@ -1009,10 +1078,28 @@ async function runDeliver(instruction, options) {
1009
1078
  const r = await evalClient.complete(evalModel, prompt, { temperature: 0, jsonResponse: true });
1010
1079
  return r.content;
1011
1080
  };
1081
+ gateAuthorExecutor = evaluatorExecutor;
1082
+ acceptanceJudgeExecutor = verdictExecutor;
1012
1083
  if (!options.dryRun) {
1013
1084
  console.log(chalk.cyan(`⚖ generator≠evaluator: gen=${model.id} eval=${evalModel.id}`));
1014
1085
  }
1015
1086
  }
1087
+ else if (judgePlan.reason === 'auto-distinct-judge') {
1088
+ // No evaluator configured + cloud generator → run the acceptance JUDGE and
1089
+ // self-gate author on a cheap DISTINCT cloud model, WITHOUT hijacking the
1090
+ // planning executors (C1). The generator still plans; it no longer grades
1091
+ // itself.
1092
+ const jModel = resolveModel(judgeModelId);
1093
+ const jClient = new OpenAICompatClient();
1094
+ gateAuthorExecutor = async (prompt) => (await jClient.complete(jModel, prompt, { temperature: 0 })).content;
1095
+ acceptanceJudgeExecutor = async (prompt) => (await jClient.complete(jModel, prompt, { temperature: 0, jsonResponse: true })).content;
1096
+ if (!options.dryRun) {
1097
+ console.log(chalk.cyan(`⚖ generator≠evaluator (auto distinct acceptance judge): gen=${model.id} judge=${jModel.id}`));
1098
+ }
1099
+ }
1100
+ else if (!judgeDistinct && !allowSelfJudge && !options.dryRun) {
1101
+ console.log(chalk.yellow(`⚠ verify: no distinct judge reachable (gen=${model.id} local/offline) — generator grades its own output`));
1102
+ }
1016
1103
  // Dynamic executor selection. The blind executor is one completion per turn
1017
1104
  // (cheap, but cannot inspect or run anything); the agentic executor runs a
1018
1105
  // tool-using loop (read/list/bash/write) and mutates the repo directly. 'auto'
@@ -1142,23 +1229,40 @@ async function runDeliver(instruction, options) {
1142
1229
  console.log(chalk.cyan('⚖ self-gate: authoring a task-specific acceptance check…'));
1143
1230
  // Author the gate with the blind executor — it is a single-shot script
1144
1231
  // write, not a task to solve agentically.
1145
- const sg = await authorAcceptanceGate({ instruction, projectRoot, executor: evaluatorExecutor });
1232
+ const sg = await authorAcceptanceGate({ instruction, projectRoot, executor: gateAuthorExecutor });
1146
1233
  for (const note of sg.notes)
1147
1234
  console.log(chalk.dim(` ${note}`));
1148
1235
  if (!sg.rung) {
1149
- fail('Could not author an acceptance gate (model produced no runnable script).');
1150
- }
1151
- rungs.push(sg.rung);
1152
- if (sg.vacuous) {
1153
- // P0 hard-fail: a REQUIRED self-gate that passes on the unsolved repo
1154
- // re-opens the false-green door — "delivered" would be meaningless.
1155
- if (process.env.UAP_DELIVER_ALLOW_WEAK_SELF_GATE !== '1') {
1156
- fail('The self-gate is REQUIRED for this run (anti-vacuous floor) but stayed vacuous after retries — it passes on the unsolved repo. Add concrete, checkable ACCEPTANCE CRITERIA to the instruction, or set UAP_DELIVER_ALLOW_WEAK_SELF_GATE=1 to accept the risk.');
1236
+ // A failed self-gate is survivable only when a real acceptance judge will
1237
+ // run and become the convergence target. See selfGateFailureIsFatal — in
1238
+ // particular, max fidelity is NOT a substitute: the vision review lives
1239
+ // inside the acceptance gate, so when that gate is never built there is
1240
+ // nothing for it to block.
1241
+ if (selfGateFailureIsFatal({
1242
+ acceptanceEnabled: Boolean(options.acceptance),
1243
+ judgeSkipped: shouldSkipAcceptanceJudge({
1244
+ acceptanceEnabled: Boolean(options.acceptance),
1245
+ complexity: autoPlan?.complexity,
1246
+ acceptancePrimary,
1247
+ }),
1248
+ })) {
1249
+ fail('Could not author an acceptance gate (model produced no runnable script).');
1157
1250
  }
1158
- console.log(chalk.yellow(' ⚠ acceptance gate may be weak (could not force an initially-failing check); UAP_DELIVER_ALLOW_WEAK_SELF_GATE=1 running multi-turn anyway.'));
1251
+ console.log(chalk.yellow(' ⚠ self-gate authoring failed the acceptance judge is the convergence target for this run.'));
1159
1252
  }
1160
1253
  else {
1161
- console.log(chalk.green(' ✓ acceptance gate authored — fails on the unsolved repo, a real convergence target.'));
1254
+ rungs.push(sg.rung);
1255
+ if (sg.vacuous) {
1256
+ // P0 hard-fail: a REQUIRED self-gate that passes on the unsolved repo
1257
+ // re-opens the false-green door — "delivered" would be meaningless.
1258
+ if (process.env.UAP_DELIVER_ALLOW_WEAK_SELF_GATE !== '1') {
1259
+ fail('The self-gate is REQUIRED for this run (anti-vacuous floor) but stayed vacuous after retries — it passes on the unsolved repo. Add concrete, checkable ACCEPTANCE CRITERIA to the instruction, or set UAP_DELIVER_ALLOW_WEAK_SELF_GATE=1 to accept the risk.');
1260
+ }
1261
+ console.log(chalk.yellow(' ⚠ acceptance gate may be weak (could not force an initially-failing check); UAP_DELIVER_ALLOW_WEAK_SELF_GATE=1 — running multi-turn anyway.'));
1262
+ }
1263
+ else {
1264
+ console.log(chalk.green(' ✓ acceptance gate authored — fails on the unsolved repo, a real convergence target.'));
1265
+ }
1162
1266
  }
1163
1267
  }
1164
1268
  // Phase 5: escalation ladder. Cheap strategies first (widen exploration →
@@ -1388,7 +1492,7 @@ async function runDeliver(instruction, options) {
1388
1492
  ? buildMissionAcceptanceGate({
1389
1493
  primary: acceptancePrimary,
1390
1494
  specs,
1391
- judgeExecutor: verdictExecutor,
1495
+ judgeExecutor: acceptanceJudgeExecutor,
1392
1496
  note: (line) => console.log(chalk.yellow(` ${line}`)),
1393
1497
  })
1394
1498
  : undefined;
@@ -1464,6 +1568,16 @@ async function runDeliver(instruction, options) {
1464
1568
  projectRoot,
1465
1569
  maxTurns,
1466
1570
  rungs,
1571
+ // MIPRO (S8/3a): feed any tuner-selected prompt-fragment variants from
1572
+ // .uap.json to the prompt builder. Empty/absent → the byte-identical default.
1573
+ promptSelection: (() => {
1574
+ try {
1575
+ return promptSelectionFromConfig((loadUapConfigRaw(projectRoot) ?? {}));
1576
+ }
1577
+ catch {
1578
+ return undefined;
1579
+ }
1580
+ })(),
1467
1581
  // The agentic executor mutates the repo directly (no-op applier), so
1468
1582
  // gates must run every turn regardless of applier file count.
1469
1583
  alwaysVerify: agentic ? true : undefined,
@@ -1663,6 +1777,27 @@ async function runDeliver(instruction, options) {
1663
1777
  console.log(chalk.dim(' user-validation: no manifest derived — gate will report NA until one exists'));
1664
1778
  }
1665
1779
  }
1780
+ // Surface the acceptance contract (the user-path journeys + required selectors)
1781
+ // to the executor on EVERY turn. The manifest is authored up front but was
1782
+ // otherwise invisible to the implementer, so a weak model built artifacts that
1783
+ // the real-client gate could not drive (e.g. a canvas-only UI with no DOM
1784
+ // handles) and then could not self-correct from the gate's selector-not-found
1785
+ // feedback. Injecting the contract closes the author→implement loop: the model
1786
+ // builds a complete, drivable artifact from turn 1. Fail-soft — an aid, never a
1787
+ // blocker.
1788
+ if (userValidationMode !== 'off') {
1789
+ try {
1790
+ const loaded = loadUserPaths(projectRoot);
1791
+ const contract = renderAcceptanceContract(loaded?.manifest ?? null);
1792
+ if (contract) {
1793
+ loopConfig.acceptanceContract = contract;
1794
+ console.log(chalk.dim(' user-validation: acceptance contract (journeys + required selectors) injected into the executor prompt'));
1795
+ }
1796
+ }
1797
+ catch {
1798
+ /* contract injection is a best-effort completeness aid */
1799
+ }
1800
+ }
1666
1801
  const runState = {
1667
1802
  runId,
1668
1803
  instruction,
@@ -1695,6 +1830,12 @@ async function runDeliver(instruction, options) {
1695
1830
  runState.checkpoint = cp;
1696
1831
  saveRunState(runState);
1697
1832
  };
1833
+ // Under max fidelity the vision aesthetic review is a BLOCKING release gate;
1834
+ // let the acceptance gate run (and feed vision findings) even when the ladder
1835
+ // is red on a synthetic anti-vacuous self-gate, so the model gets aesthetic
1836
+ // feedback instead of stalling. The gate re-checks operational + behavioral
1837
+ // itself before grading (mission-acceptance), preserving the required ordering.
1838
+ loopConfig.runAcceptanceDespiteLadder = deliverFidelity.max;
1698
1839
  // Cooperative cancel from the dashboard: the loop polls this each turn.
1699
1840
  loopConfig.shouldStop = () => isStopRequested(projectRoot, runId);
1700
1841
  clearStop(projectRoot, runId);
@@ -2122,7 +2263,18 @@ async function runDeliver(instruction, options) {
2122
2263
  // Regression scoring uses only the fast tier — a cheap, synchronous, always-
2123
2264
  // run signal. Integration/deploy-dev gates may not run at baseline (promotion
2124
2265
  // is cheap-first), so they are not a trustworthy regression comparison.
2125
- const fastRungs = rungs.filter((r) => tierOf(r) === 'fast');
2266
+ // keep-best scores the DETERMINISTIC objective gates — build/test/lint (fast) PLUS
2267
+ // the execution smoke (runtime) and user-path (final) gates — not just the fast tier.
2268
+ // On a project whose only meaningful gates are runtime/final (e.g. a vanilla-JS web
2269
+ // app with no build step) the fast subset is trivially 1.00 every turn, so
2270
+ // best-intermediate never advanced and a 100%-functional turn that later regressed
2271
+ // was DISCARDED (octopus, 2026-07-24: the lazy turn hit 100% of gates, the
2272
+ // vision-chasing turns then rewrote game.js and broke the HUD wiring, and the win was
2273
+ // lost — the harness kept the regressed end state). Exclude the LLM judge rungs
2274
+ // (bootstrap/acceptance): non-deterministic, they would make the rollback target flap.
2275
+ const KEEP_BEST_TIERS = new Set(['fast', 'runtime', 'integration', 'final']);
2276
+ const KEEP_BEST_EXCLUDE_IDS = new Set(['bootstrap', 'acceptance']);
2277
+ const fastRungs = rungs.filter((r) => KEEP_BEST_TIERS.has(tierOf(r)) && !KEEP_BEST_EXCLUDE_IDS.has(r.id));
2126
2278
  const keepBest = Boolean(options.keepBest) && fastRungs.length > 0 && !needsSelfGate;
2127
2279
  let regressSnapshot = null;
2128
2280
  let baselineGateScore = 0;
@@ -2168,6 +2320,12 @@ async function runDeliver(instruction, options) {
2168
2320
  redetectRungs: true,
2169
2321
  redetectFilter: loopConfig.redetectFilter,
2170
2322
  protectTests: options.protectTests,
2323
+ // The acceptance contract is a REQUIREMENT spec, not a convergence aid:
2324
+ // carry it into the bare turn so a one-shot build already exposes the
2325
+ // journeys/selectors the gate will drive (otherwise the lazy turn builds
2326
+ // blind, e.g. a canvas-only UI with no DOM handles, and only later turns
2327
+ // — which do get the contract — can recover it).
2328
+ acceptanceContract: loopConfig.acceptanceContract,
2171
2329
  onIteration: (record) => { updateDeliverHeartbeat(projectRoot); printProgress(record); },
2172
2330
  }, executor, seams);
2173
2331
  const lazyResult = await lazyLoop.deliver(instruction);
@@ -2433,6 +2591,15 @@ async function runDeliver(instruction, options) {
2433
2591
  catch {
2434
2592
  // Memory recording is best-effort
2435
2593
  }
2594
+ // Verification provenance banner (S1) — makes the judge boundary observable.
2595
+ if (!options.json && !options.dryRun) {
2596
+ const line = formatVerificationProvenance({
2597
+ executorModel: model.id,
2598
+ judgeModel: judgeModelId,
2599
+ distinct: judgeDistinct,
2600
+ });
2601
+ console.log(judgeDistinct ? chalk.dim(line) : chalk.yellow(line));
2602
+ }
2436
2603
  if (options.json) {
2437
2604
  const { finalOutput, ...rest } = result;
2438
2605
  console.log(JSON.stringify({