@miller-tech/uap 2.21.3 → 2.36.1

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 (328) hide show
  1. package/README.md +22 -10
  2. package/config/model-profiles/qwen38-coder390.json +46 -0
  3. package/config/model-profiles/qwen38.json +3 -2
  4. package/config/model-registry.json +110 -0
  5. package/dist/.tsbuildinfo +1 -1
  6. package/dist/benchmarks/paired/types.d.ts +2 -2
  7. package/dist/bin/cli.js +315 -20
  8. package/dist/bin/cli.js.map +1 -1
  9. package/dist/cli/attention.d.ts +6 -0
  10. package/dist/cli/attention.d.ts.map +1 -0
  11. package/dist/cli/attention.js +115 -0
  12. package/dist/cli/attention.js.map +1 -0
  13. package/dist/cli/bench-report.d.ts +82 -0
  14. package/dist/cli/bench-report.d.ts.map +1 -0
  15. package/dist/cli/bench-report.js +237 -0
  16. package/dist/cli/bench-report.js.map +1 -0
  17. package/dist/cli/clients.d.ts +6 -0
  18. package/dist/cli/clients.d.ts.map +1 -0
  19. package/dist/cli/clients.js +142 -0
  20. package/dist/cli/clients.js.map +1 -0
  21. package/dist/cli/dashboard-grants.d.ts +26 -0
  22. package/dist/cli/dashboard-grants.d.ts.map +1 -0
  23. package/dist/cli/dashboard-grants.js +69 -0
  24. package/dist/cli/dashboard-grants.js.map +1 -0
  25. package/dist/cli/dashboard-pace.d.ts +31 -0
  26. package/dist/cli/dashboard-pace.d.ts.map +1 -0
  27. package/dist/cli/dashboard-pace.js +42 -0
  28. package/dist/cli/dashboard-pace.js.map +1 -0
  29. package/dist/cli/dashboard-session.d.ts +25 -0
  30. package/dist/cli/dashboard-session.d.ts.map +1 -0
  31. package/dist/cli/dashboard-session.js +814 -0
  32. package/dist/cli/dashboard-session.js.map +1 -0
  33. package/dist/cli/dashboard-types.d.ts +18 -0
  34. package/dist/cli/dashboard-types.d.ts.map +1 -0
  35. package/dist/cli/dashboard-types.js +9 -0
  36. package/dist/cli/dashboard-types.js.map +1 -0
  37. package/dist/cli/dashboard.d.ts +2 -18
  38. package/dist/cli/dashboard.d.ts.map +1 -1
  39. package/dist/cli/dashboard.js +43 -793
  40. package/dist/cli/dashboard.js.map +1 -1
  41. package/dist/cli/deliver.d.ts +35 -0
  42. package/dist/cli/deliver.d.ts.map +1 -1
  43. package/dist/cli/deliver.js +93 -9
  44. package/dist/cli/deliver.js.map +1 -1
  45. package/dist/cli/diff.d.ts +85 -0
  46. package/dist/cli/diff.d.ts.map +1 -0
  47. package/dist/cli/diff.js +293 -0
  48. package/dist/cli/diff.js.map +1 -0
  49. package/dist/cli/eta.d.ts +35 -0
  50. package/dist/cli/eta.d.ts.map +1 -0
  51. package/dist/cli/eta.js +101 -0
  52. package/dist/cli/eta.js.map +1 -0
  53. package/dist/cli/handsfree.d.ts +7 -0
  54. package/dist/cli/handsfree.d.ts.map +1 -1
  55. package/dist/cli/handsfree.js +27 -0
  56. package/dist/cli/handsfree.js.map +1 -1
  57. package/dist/cli/hooks.d.ts.map +1 -1
  58. package/dist/cli/hooks.js +126 -5
  59. package/dist/cli/hooks.js.map +1 -1
  60. package/dist/cli/loops.d.ts +24 -5
  61. package/dist/cli/loops.d.ts.map +1 -1
  62. package/dist/cli/loops.js +92 -12
  63. package/dist/cli/loops.js.map +1 -1
  64. package/dist/cli/models.d.ts +62 -0
  65. package/dist/cli/models.d.ts.map +1 -0
  66. package/dist/cli/models.js +567 -0
  67. package/dist/cli/models.js.map +1 -0
  68. package/dist/cli/plan.d.ts +9 -0
  69. package/dist/cli/plan.d.ts.map +1 -1
  70. package/dist/cli/plan.js +57 -4
  71. package/dist/cli/plan.js.map +1 -1
  72. package/dist/cli/proxy-relay.d.ts +55 -0
  73. package/dist/cli/proxy-relay.d.ts.map +1 -0
  74. package/dist/cli/proxy-relay.js +116 -0
  75. package/dist/cli/proxy-relay.js.map +1 -0
  76. package/dist/cli/proxy.d.ts +4 -0
  77. package/dist/cli/proxy.d.ts.map +1 -1
  78. package/dist/cli/proxy.js +26 -1
  79. package/dist/cli/proxy.js.map +1 -1
  80. package/dist/cli/sandbox.d.ts +101 -4
  81. package/dist/cli/sandbox.d.ts.map +1 -1
  82. package/dist/cli/sandbox.js +393 -44
  83. package/dist/cli/sandbox.js.map +1 -1
  84. package/dist/cli/self.d.ts +13 -0
  85. package/dist/cli/self.d.ts.map +1 -0
  86. package/dist/cli/self.js +94 -0
  87. package/dist/cli/self.js.map +1 -0
  88. package/dist/cli/systemd-services.d.ts +2 -8
  89. package/dist/cli/systemd-services.d.ts.map +1 -1
  90. package/dist/cli/systemd-services.js +5 -20
  91. package/dist/cli/systemd-services.js.map +1 -1
  92. package/dist/cli/task.js +1 -1
  93. package/dist/cli/task.js.map +1 -1
  94. package/dist/cli/wizard-config.d.ts.map +1 -1
  95. package/dist/cli/wizard-config.js +31 -2
  96. package/dist/cli/wizard-config.js.map +1 -1
  97. package/dist/config/settings-registry.d.ts +1 -1
  98. package/dist/config/settings-registry.d.ts.map +1 -1
  99. package/dist/config/settings-registry.js +108 -0
  100. package/dist/config/settings-registry.js.map +1 -1
  101. package/dist/coordination/capability-router.js +2 -2
  102. package/dist/coordination/capability-router.js.map +1 -1
  103. package/dist/coordination/database.d.ts +13 -0
  104. package/dist/coordination/database.d.ts.map +1 -1
  105. package/dist/coordination/database.js +63 -7
  106. package/dist/coordination/database.js.map +1 -1
  107. package/dist/coordination/eta-inject.d.ts +32 -0
  108. package/dist/coordination/eta-inject.d.ts.map +1 -0
  109. package/dist/coordination/eta-inject.js +64 -0
  110. package/dist/coordination/eta-inject.js.map +1 -0
  111. package/dist/coordination/reactor.d.ts.map +1 -1
  112. package/dist/coordination/reactor.js +14 -0
  113. package/dist/coordination/reactor.js.map +1 -1
  114. package/dist/coordination/service.d.ts +18 -8
  115. package/dist/coordination/service.d.ts.map +1 -1
  116. package/dist/coordination/service.js +38 -28
  117. package/dist/coordination/service.js.map +1 -1
  118. package/dist/dashboard/client-registry.d.ts +163 -0
  119. package/dist/dashboard/client-registry.d.ts.map +1 -0
  120. package/dist/dashboard/client-registry.js +549 -0
  121. package/dist/dashboard/client-registry.js.map +1 -0
  122. package/dist/dashboard/controls.d.ts.map +1 -1
  123. package/dist/dashboard/controls.js +33 -4
  124. package/dist/dashboard/controls.js.map +1 -1
  125. package/dist/dashboard/data-seeder.d.ts +10 -7
  126. package/dist/dashboard/data-seeder.d.ts.map +1 -1
  127. package/dist/dashboard/data-seeder.js +31 -75
  128. package/dist/dashboard/data-seeder.js.map +1 -1
  129. package/dist/dashboard/data-service.d.ts +9 -0
  130. package/dist/dashboard/data-service.d.ts.map +1 -1
  131. package/dist/dashboard/data-service.js +32 -3
  132. package/dist/dashboard/data-service.js.map +1 -1
  133. package/dist/dashboard/placement-routes.d.ts +119 -0
  134. package/dist/dashboard/placement-routes.d.ts.map +1 -0
  135. package/dist/dashboard/placement-routes.js +331 -0
  136. package/dist/dashboard/placement-routes.js.map +1 -0
  137. package/dist/dashboard/server.d.ts.map +1 -1
  138. package/dist/dashboard/server.js +408 -2
  139. package/dist/dashboard/server.js.map +1 -1
  140. package/dist/delivery/attention.d.ts +130 -0
  141. package/dist/delivery/attention.d.ts.map +1 -0
  142. package/dist/delivery/attention.js +341 -0
  143. package/dist/delivery/attention.js.map +1 -0
  144. package/dist/delivery/decompose.d.ts +18 -0
  145. package/dist/delivery/decompose.d.ts.map +1 -1
  146. package/dist/delivery/decompose.js +22 -1
  147. package/dist/delivery/decompose.js.map +1 -1
  148. package/dist/delivery/eta-estimate.d.ts +111 -0
  149. package/dist/delivery/eta-estimate.d.ts.map +1 -0
  150. package/dist/delivery/eta-estimate.js +171 -0
  151. package/dist/delivery/eta-estimate.js.map +1 -0
  152. package/dist/delivery/eta.d.ts +23 -0
  153. package/dist/delivery/eta.d.ts.map +1 -0
  154. package/dist/delivery/eta.js +59 -0
  155. package/dist/delivery/eta.js.map +1 -0
  156. package/dist/delivery/orchestrated-mission.d.ts +10 -0
  157. package/dist/delivery/orchestrated-mission.d.ts.map +1 -1
  158. package/dist/delivery/orchestrated-mission.js +15 -1
  159. package/dist/delivery/orchestrated-mission.js.map +1 -1
  160. package/dist/delivery/plan-check.d.ts +7 -1
  161. package/dist/delivery/plan-check.d.ts.map +1 -1
  162. package/dist/delivery/plan-check.js +23 -2
  163. package/dist/delivery/plan-check.js.map +1 -1
  164. package/dist/delivery/run-state.d.ts +1 -1
  165. package/dist/delivery/run-state.d.ts.map +1 -1
  166. package/dist/delivery/run-state.js +8 -1
  167. package/dist/delivery/run-state.js.map +1 -1
  168. package/dist/delivery/task-orchestrator.d.ts +8 -3
  169. package/dist/delivery/task-orchestrator.d.ts.map +1 -1
  170. package/dist/delivery/task-orchestrator.js +75 -43
  171. package/dist/delivery/task-orchestrator.js.map +1 -1
  172. package/dist/delivery/task-sync.d.ts +21 -0
  173. package/dist/delivery/task-sync.d.ts.map +1 -1
  174. package/dist/delivery/task-sync.js +44 -8
  175. package/dist/delivery/task-sync.js.map +1 -1
  176. package/dist/delivery/task-workspace.d.ts +3 -3
  177. package/dist/delivery/task-workspace.js +3 -3
  178. package/dist/generators/self-card.d.ts +65 -0
  179. package/dist/generators/self-card.d.ts.map +1 -0
  180. package/dist/generators/self-card.js +256 -0
  181. package/dist/generators/self-card.js.map +1 -0
  182. package/dist/inference/probe.js +1 -1
  183. package/dist/inference/probe.js.map +1 -1
  184. package/dist/memory/prepopulate.d.ts.map +1 -1
  185. package/dist/memory/prepopulate.js +2 -0
  186. package/dist/memory/prepopulate.js.map +1 -1
  187. package/dist/models/analytics.d.ts +32 -0
  188. package/dist/models/analytics.d.ts.map +1 -1
  189. package/dist/models/analytics.js +68 -5
  190. package/dist/models/analytics.js.map +1 -1
  191. package/dist/models/plan-readability.d.ts +55 -0
  192. package/dist/models/plan-readability.d.ts.map +1 -0
  193. package/dist/models/plan-readability.js +339 -0
  194. package/dist/models/plan-readability.js.map +1 -0
  195. package/dist/placement/admission.d.ts +59 -0
  196. package/dist/placement/admission.d.ts.map +1 -0
  197. package/dist/placement/admission.js +197 -0
  198. package/dist/placement/admission.js.map +1 -0
  199. package/dist/placement/auto.d.ts +44 -0
  200. package/dist/placement/auto.d.ts.map +1 -0
  201. package/dist/placement/auto.js +212 -0
  202. package/dist/placement/auto.js.map +1 -0
  203. package/dist/placement/enforce.d.ts +128 -0
  204. package/dist/placement/enforce.d.ts.map +1 -0
  205. package/dist/placement/enforce.js +655 -0
  206. package/dist/placement/enforce.js.map +1 -0
  207. package/dist/placement/idle.d.ts +50 -0
  208. package/dist/placement/idle.d.ts.map +1 -0
  209. package/dist/placement/idle.js +213 -0
  210. package/dist/placement/idle.js.map +1 -0
  211. package/dist/placement/ledger.d.ts +323 -0
  212. package/dist/placement/ledger.d.ts.map +1 -0
  213. package/dist/placement/ledger.js +263 -0
  214. package/dist/placement/ledger.js.map +1 -0
  215. package/dist/placement/preview.d.ts +51 -0
  216. package/dist/placement/preview.d.ts.map +1 -0
  217. package/dist/placement/preview.js +61 -0
  218. package/dist/placement/preview.js.map +1 -0
  219. package/dist/placement/probes.d.ts +47 -0
  220. package/dist/placement/probes.d.ts.map +1 -0
  221. package/dist/placement/probes.js +207 -0
  222. package/dist/placement/probes.js.map +1 -0
  223. package/dist/placement/proxy-env.d.ts +21 -0
  224. package/dist/placement/proxy-env.d.ts.map +1 -0
  225. package/dist/placement/proxy-env.js +42 -0
  226. package/dist/placement/proxy-env.js.map +1 -0
  227. package/dist/placement/registry.d.ts +1038 -0
  228. package/dist/placement/registry.d.ts.map +1 -0
  229. package/dist/placement/registry.js +274 -0
  230. package/dist/placement/registry.js.map +1 -0
  231. package/dist/policies/liveness.d.ts +6 -8
  232. package/dist/policies/liveness.d.ts.map +1 -1
  233. package/dist/policies/liveness.js +6 -25
  234. package/dist/policies/liveness.js.map +1 -1
  235. package/dist/policies/sandbox-anchor.d.ts +91 -0
  236. package/dist/policies/sandbox-anchor.d.ts.map +1 -0
  237. package/dist/policies/sandbox-anchor.js +187 -0
  238. package/dist/policies/sandbox-anchor.js.map +1 -0
  239. package/dist/policies/sandbox-grants.d.ts +131 -0
  240. package/dist/policies/sandbox-grants.d.ts.map +1 -0
  241. package/dist/policies/sandbox-grants.js +562 -0
  242. package/dist/policies/sandbox-grants.js.map +1 -0
  243. package/dist/state/manifest.d.ts +4 -2
  244. package/dist/state/manifest.d.ts.map +1 -1
  245. package/dist/state/manifest.js +32 -16
  246. package/dist/state/manifest.js.map +1 -1
  247. package/dist/utils/checkout-root.d.ts +2 -0
  248. package/dist/utils/checkout-root.d.ts.map +1 -0
  249. package/dist/utils/checkout-root.js +43 -0
  250. package/dist/utils/checkout-root.js.map +1 -0
  251. package/dist/utils/resolve-uap-bin.d.ts +5 -0
  252. package/dist/utils/resolve-uap-bin.d.ts.map +1 -0
  253. package/dist/utils/resolve-uap-bin.js +33 -0
  254. package/dist/utils/resolve-uap-bin.js.map +1 -0
  255. package/dist/utils/terminal-safe.d.ts +12 -0
  256. package/dist/utils/terminal-safe.d.ts.map +1 -0
  257. package/dist/utils/terminal-safe.js +53 -0
  258. package/dist/utils/terminal-safe.js.map +1 -0
  259. package/docs/ATTRIBUTION.md +1 -0
  260. package/docs/DASHBOARD_UPLIFT_SPEC.md +8 -8
  261. package/docs/INDEX.md +6 -5
  262. package/docs/architecture/OVERVIEW.md +15 -3
  263. package/docs/architecture/PROTOCOL.md +7 -1
  264. package/docs/architecture/adr/0006-operator-directed-model-placement.md +86 -0
  265. package/docs/architecture/adr/0007-auto-displacement-allowlist-as-standing-consent.md +79 -0
  266. package/docs/architecture/adr/0008-fleet-clients-link-out.md +92 -0
  267. package/docs/architecture/adr/0009-completion-triggered-dispatch-and-per-leaf-model-dials.md +71 -0
  268. package/docs/architecture/adr/0010-attention-queue-and-afk-auto-decider.md +159 -0
  269. package/docs/architecture/adr/0011-sandbox-grants-and-network-egress-boundary.md +219 -0
  270. package/docs/architecture/adr/0012-operator-legibility-surfaces.md +175 -0
  271. package/docs/getting-started/INSTALLATION.md +1 -1
  272. package/docs/guides/AUTOMATIC_FEATURES.md +8 -0
  273. package/docs/guides/LOCAL_MODELS.md +1 -1
  274. package/docs/guides/PROXY.md +2 -1
  275. package/docs/guides/SANDBOX.md +91 -4
  276. package/docs/integrations/MCP_ROUTER.md +1 -1
  277. package/docs/plans/model-performance-proof-options.md +176 -0
  278. package/docs/reference/API.md +1 -1
  279. package/docs/reference/CLI.md +169 -4
  280. package/docs/reference/COMPARISON.md +1 -1
  281. package/docs/reference/CONFIGURATION.md +12 -3
  282. package/docs/reference/CONFIGURATION_REFERENCE.md +269 -14
  283. package/docs/reference/DATABASE_SCHEMA.md +1 -1
  284. package/docs/reference/FEATURES.md +15 -3
  285. package/docs/reference/PATTERNS.md +1 -1
  286. package/docs/reference/PLATFORMS.md +14 -1
  287. package/docs/specs/operator-model-placement.md +1314 -0
  288. package/docs/specs/operator-model-placement.review.md +339 -0
  289. package/package.json +2 -2
  290. package/scripts/maintenance/publish-npm.sh +23 -11
  291. package/scripts/version-bump.sh +6 -51
  292. package/src/policies/enforcers/__pycache__/_common.cpython-311.pyc +0 -0
  293. package/src/policies/enforcers/__pycache__/expert_review_required.cpython-311.pyc +0 -0
  294. package/src/policies/enforcers/__pycache__/quality_metrics_gate.cpython-311.pyc +0 -0
  295. package/src/policies/schemas/policies/mandatory-testing-deployment.md +2 -5
  296. package/templates/hooks/__pycache__/deliver_autoroute.cpython-311.pyc +0 -0
  297. package/templates/hooks/session-start.sh +44 -0
  298. package/tools/agents/UAP/cli.py +14 -3
  299. package/tools/agents/UAP/paths.py +50 -0
  300. package/tools/agents/scripts/__pycache__/toolcall_path_normalizer.cpython-311.pyc +0 -0
  301. package/tools/agents/scripts/anthropic_proxy.py +1386 -99
  302. package/tools/agents/scripts/toolcall_path_normalizer.py +46 -1
  303. package/tools/agents/tests/test_anthropic_proxy_streaming.py +29 -6
  304. package/tools/agents/tests/test_doubling_break.py +64 -5
  305. package/tools/agents/tests/test_error_loop_break.py +10 -4
  306. package/tools/agents/tests/test_error_loop_ignores_correctives.py +13 -2
  307. package/tools/agents/tests/test_lockstep_escalation.py +280 -9
  308. package/tools/agents/tests/test_path_containment.py +94 -0
  309. package/tools/agents/tests/test_proxy_unix_socket.py +162 -0
  310. package/tools/agents/tests/test_signature_quality.py +349 -0
  311. package/tools/agents/tests/test_uap_compliance.py +11 -1
  312. package/web/dash/core.js +1 -0
  313. package/web/dash/tab-clients.js +196 -0
  314. package/web/dash/tab-models.js +387 -8
  315. package/web/dash/tab-policies.js +87 -4
  316. package/web/dash/tabs.js +77 -73
  317. package/web/dashboard.html +6 -0
  318. package/src/policies/enforcers/__pycache__/_common.cpython-312.pyc +0 -0
  319. package/src/policies/enforcers/__pycache__/expert_review_required.cpython-312.pyc +0 -0
  320. package/src/policies/enforcers/__pycache__/quality_metrics_gate.cpython-312.pyc +0 -0
  321. package/templates/hooks/__pycache__/deliver_autoroute.cpython-312.pyc +0 -0
  322. package/tools/agents/scripts/__pycache__/toolcall_path_normalizer.cpython-312.pyc +0 -0
  323. package/web/dash/tab-agents.js +0 -21
  324. package/web/dash/tab-deliver.js +0 -12
  325. package/web/dash/tab-memory.js +0 -12
  326. package/web/dash/tab-orchestration.js +0 -21
  327. package/web/dash/tab-overview.js +0 -21
  328. package/web/dash/tab-tasks.js +0 -21
@@ -0,0 +1,339 @@
1
+ /**
2
+ * B1 · Plan comprehension gate — deterministic readability rules for
3
+ * markdown plan artifacts.
4
+ *
5
+ * After Lynch's "agents suck at communicating plans" (mtlynch.io,
6
+ * "Why Are Coding Agents So Dumb?"): agents enumerate low-level details and
7
+ * call it a plan. A competent developer starts at a high level and works
8
+ * toward the minutiae; a human reading the artifact top-down should be able to
9
+ * skim WHAT is being built before HOW. These rules are the deterministic floor
10
+ * under that expectation.
11
+ *
12
+ * Enforcement posture (mirrors `runPlanThoughtExperiment`'s adjudicated-hints
13
+ * contract in src/delivery/plan-check.ts): findings are HINTS the judge-model
14
+ * review confirms or dismisses on the merits — pattern-matched heuristics do
15
+ * not hard-block on their own. Operators who want deterministic teeth set
16
+ * `strict: true` (config or UAP_PLAN_READABILITY_STRICT=1): then error-severity
17
+ * findings fail `uap plan validate` outright, subject to the existing --force
18
+ * escape hatch.
19
+ */
20
+ import { existsSync, readFileSync } from 'fs';
21
+ import { join } from 'path';
22
+ export const DEFAULT_PLAN_READABILITY = {
23
+ minSummaryWords: 20,
24
+ maxWordsPerSection: 300,
25
+ jargonDensityThreshold: 0.35,
26
+ requireDiagramAtTasks: 5,
27
+ };
28
+ /**
29
+ * Merge caller overrides, `.uap.json` `plan.readability`, and env kill
30
+ * switches over the defaults. Pure with respect to `partial`; env and config
31
+ * file reads happen here so the analysis itself stays pure and unit-testable.
32
+ */
33
+ export function resolvePlanReadabilityConfig(partial, projectDir) {
34
+ let fromFile = {};
35
+ const dir = projectDir ?? process.cwd();
36
+ const configPath = join(dir, '.uap.json');
37
+ if (existsSync(configPath)) {
38
+ try {
39
+ const parsed = JSON.parse(readFileSync(configPath, 'utf-8'));
40
+ if (parsed?.plan?.readability && typeof parsed.plan.readability === 'object') {
41
+ fromFile = parsed.plan.readability;
42
+ }
43
+ }
44
+ catch {
45
+ // Config parse failure is non-fatal — defaults apply.
46
+ }
47
+ }
48
+ const merged = {
49
+ enabled: true,
50
+ strict: false,
51
+ ...DEFAULT_PLAN_READABILITY,
52
+ ...fromFile,
53
+ ...partial,
54
+ };
55
+ if (process.env.UAP_PLAN_READABILITY === '0')
56
+ merged.enabled = false;
57
+ if (process.env.UAP_PLAN_READABILITY_STRICT === '1')
58
+ merged.strict = true;
59
+ return merged;
60
+ }
61
+ const HEADING_RE = /^(#{1,6}) (.+)$/;
62
+ const FENCE_RE = /^\s*(```|~~~)/;
63
+ const CHECKBOX_RE = /^\s*[-*]\s+\[[ xX]\]\s+/;
64
+ const ORDERED_RE = /^\s*\d+[.)]\s+/;
65
+ const IMAGE_RE = /!\[[^\]]*\]\(/;
66
+ const MERMAID_FENCE_RE = /^\s*(```|~~~)\s*mermaid\b/i;
67
+ function splitSections(lines) {
68
+ const sections = [];
69
+ let current = { heading: '', level: 0, lines: [] };
70
+ for (const line of lines) {
71
+ const m = line.match(HEADING_RE);
72
+ if (m) {
73
+ if (current.lines.length > 0 || current.heading)
74
+ sections.push(current);
75
+ current = { heading: m[2].trim(), level: m[1].length, lines: [] };
76
+ }
77
+ else {
78
+ current.lines.push(line);
79
+ }
80
+ }
81
+ sections.push(current);
82
+ return sections.filter((s) => s.heading !== '' || s.lines.some((l) => l.trim() !== ''));
83
+ }
84
+ /** Lines outside code fences — fences are detail, not prose. */
85
+ function proseLines(lines) {
86
+ const out = [];
87
+ let inFence = false;
88
+ for (const line of lines) {
89
+ if (FENCE_RE.test(line)) {
90
+ inFence = !inFence;
91
+ continue;
92
+ }
93
+ if (!inFence)
94
+ out.push(line);
95
+ }
96
+ return out;
97
+ }
98
+ function countWords(lines) {
99
+ let words = 0;
100
+ for (const line of lines) {
101
+ for (const tok of line.split(/\s+/)) {
102
+ if (/[A-Za-z0-9]/.test(tok))
103
+ words += 1;
104
+ }
105
+ }
106
+ return words;
107
+ }
108
+ /** Strips list markers so "1. build" counts as prose word "build", not list scaffolding. */
109
+ function stripListMarkers(line) {
110
+ return line.replace(CHECKBOX_RE, '').replace(ORDERED_RE, '');
111
+ }
112
+ function detailMarkers(lines) {
113
+ let markers = 0;
114
+ let inFence = false;
115
+ for (const line of lines) {
116
+ if (FENCE_RE.test(line)) {
117
+ // Count a fenced block once (on its opening line), not per fence marker.
118
+ if (!inFence)
119
+ markers += 1;
120
+ inFence = !inFence;
121
+ continue;
122
+ }
123
+ if (inFence)
124
+ continue;
125
+ markers += (line.match(/[A-Za-z0-9_-]+\/[A-Za-z0-9_./-]+/g) ?? []).length;
126
+ markers += (line.match(/`[^`]+`/g) ?? []).length;
127
+ }
128
+ return markers;
129
+ }
130
+ const JARGON_RES = [
131
+ // Flattened, deliberately non-nested camelCase shape (security review: the
132
+ // nested `\b[a-z]+(?:[A-Z][A-Za-z0-9]*)+\b` variant backtracks
133
+ // exponentially on crafted input and hung `uap plan validate` at ~30 chars).
134
+ /\b[a-z]+[A-Z][A-Za-z0-9]*\b/g,
135
+ /\b[a-z]+_[a-z0-9]+\b/g, // snake_case
136
+ /\b[A-Z]{2,}\b/g, // acronyms
137
+ ];
138
+ /**
139
+ * Clip a plan-authored fragment before it is echoed into a finding message
140
+ * (which later ships to the judge-model endpoint): bounded length, no
141
+ * control characters. Plan text is untrusted input — the messages carry it,
142
+ * so the messages must not carry MORE of it than the rule needs.
143
+ */
144
+ function clip(s, max = 80) {
145
+ // eslint-disable-next-line no-control-regex -- stripping control characters IS the point
146
+ const clean = s.replace(/[\x00-\x1f\x7f]/g, ' ');
147
+ return clean.length > max ? clean.slice(0, max - 1).trimEnd() + '…' : clean;
148
+ }
149
+ // ---------------------------------------------------------------------------
150
+ // Rules
151
+ // ---------------------------------------------------------------------------
152
+ function checkSummaryFirst(preambleLines, firstContent, minWords, findings) {
153
+ const prose = proseLines(preambleLines);
154
+ const fenceBeforeProse = preambleLines.findIndex((l) => FENCE_RE.test(l)) !== -1 &&
155
+ preambleLines.findIndex((l) => FENCE_RE.test(l)) <
156
+ preambleLines.findIndex((l) => l.trim() !== '' && !FENCE_RE.test(l));
157
+ if (fenceBeforeProse) {
158
+ findings.push({
159
+ rule: 'summary-first',
160
+ severity: 'error',
161
+ message: 'plan opens with a code block — the executive summary must come before any code or configuration detail',
162
+ });
163
+ }
164
+ const words = countWords(prose.map(stripListMarkers));
165
+ if (words >= minWords)
166
+ return;
167
+ // The common `# Title` + `## Summary` shape has an EMPTY preamble by
168
+ // construction (code-review finding: strict mode must not hard-block it).
169
+ // When the first section IS a summary, it satisfies the rule; when it is
170
+ // merely long prose, the plan is readable but front-loads the summary in
171
+ // the wrong place — warn instead of blocking.
172
+ const firstWords = firstContent
173
+ ? countWords(proseLines(firstContent.lines).map(stripListMarkers))
174
+ : 0;
175
+ if (firstContent && firstWords >= minWords) {
176
+ const summaryLike = /summary|overview|goal|objective|intent/i.test(firstContent.heading);
177
+ if (!summaryLike) {
178
+ findings.push({
179
+ rule: 'summary-first',
180
+ severity: 'warning',
181
+ message: `the executive summary should sit in the preamble, before the first section ("${clip(firstContent.heading)}") — move it up so a reader skims top-down`,
182
+ });
183
+ }
184
+ return;
185
+ }
186
+ findings.push({
187
+ rule: 'summary-first',
188
+ severity: 'error',
189
+ message: `plan must open with a ${minWords}-word executive summary before the first section (found ${words} word${words === 1 ? '' : 's'} of prose) — a reader should learn WHAT is being built before HOW`,
190
+ });
191
+ }
192
+ function checkAbstractionProgress(sections, findings) {
193
+ const headed = sections.filter((s) => s.heading !== '');
194
+ // The title section carries the preamble prose, so its detail density is low
195
+ // by construction — the rule compares CONTENT sections, i.e. everything
196
+ // after the document title.
197
+ const content = headed[0]?.level === 1 ? headed.slice(1) : headed;
198
+ if (content.length < 2)
199
+ return;
200
+ const first = detailMarkers(content[0].lines);
201
+ const last = detailMarkers(content[content.length - 1].lines);
202
+ if (first >= 2 && first > last) {
203
+ findings.push({
204
+ rule: 'abstraction-progress',
205
+ severity: 'warning',
206
+ message: `detail density is front-loaded (section "${clip(content[0].heading)}" has ${first} detail markers, the last section "${clip(content[content.length - 1].heading)}" has ${last}) — start at a high level of abstraction and progress toward the minutiae`,
207
+ });
208
+ }
209
+ }
210
+ function checkSectionBudget(sections, maxWords, findings) {
211
+ const flagged = [];
212
+ for (const s of sections) {
213
+ const words = countWords(proseLines(s.lines).map(stripListMarkers));
214
+ if (words > maxWords)
215
+ flagged.push(`"${clip(s.heading || '(preamble)')}" (${words} words)`);
216
+ }
217
+ if (flagged.length > 0) {
218
+ findings.push({
219
+ rule: 'section-budget',
220
+ severity: 'warning',
221
+ message: `section(s) exceed the ${maxWords}-word budget: ${flagged.slice(0, 3).join(', ')}${flagged.length > 3 ? `, +${flagged.length - 3} more` : ''} — split them so each section makes one point`,
222
+ });
223
+ }
224
+ }
225
+ function checkJargonDensity(lines, threshold, findings) {
226
+ // Headings are navigation, not prose — a token-dense heading must not move
227
+ // the prose density either way.
228
+ const prose = proseLines(lines)
229
+ .filter((l) => !HEADING_RE.test(l))
230
+ .map(stripListMarkers)
231
+ .join(' ');
232
+ const words = countWords([prose]);
233
+ if (words < 30)
234
+ return; // too little text for a density to mean anything
235
+ const samples = new Set();
236
+ let jargon = 0;
237
+ for (const re of JARGON_RES) {
238
+ const matches = prose.match(re);
239
+ if (matches) {
240
+ jargon += matches.length;
241
+ matches.slice(0, 5).forEach((m) => samples.add(m));
242
+ }
243
+ }
244
+ const density = jargon / words;
245
+ if (density > threshold) {
246
+ const list = [...samples].slice(0, 5).map((m) => clip(m, 40)).join(', ');
247
+ findings.push({
248
+ rule: 'jargon-density',
249
+ severity: 'warning',
250
+ message: `jargon density ${(100 * density).toFixed(0)}% exceeds the ${(100 * threshold).toFixed(0)}% budget (${jargon} jargon tokens in ${words} words; e.g. ${list}) — define or replace identifiers a reviewer may not share`,
251
+ });
252
+ }
253
+ }
254
+ function checkDiagramRequired(lines, atTasks, findings) {
255
+ if (atTasks <= 0)
256
+ return { taskItemCount: 0 };
257
+ let taskItemCount = 0;
258
+ let inFence = false;
259
+ let hasDiagram = false;
260
+ for (const line of lines) {
261
+ if (FENCE_RE.test(line)) {
262
+ if (MERMAID_FENCE_RE.test(line))
263
+ hasDiagram = true;
264
+ inFence = !inFence;
265
+ continue;
266
+ }
267
+ if (!inFence) {
268
+ if (CHECKBOX_RE.test(line) || ORDERED_RE.test(line))
269
+ taskItemCount += 1;
270
+ if (IMAGE_RE.test(line))
271
+ hasDiagram = true;
272
+ }
273
+ }
274
+ if (taskItemCount >= atTasks && !hasDiagram) {
275
+ findings.push({
276
+ rule: 'diagram-required',
277
+ severity: 'error',
278
+ message: `${taskItemCount} task items but no diagram — plans with ≥${atTasks} tasks require a data-flow diagram (mermaid or image) so a reader can see the moving parts before the steps`,
279
+ });
280
+ }
281
+ return { taskItemCount };
282
+ }
283
+ // ---------------------------------------------------------------------------
284
+ // Entry point
285
+ // ---------------------------------------------------------------------------
286
+ /**
287
+ * Pure readability analysis of a markdown plan artifact. No I/O; config
288
+ * resolution (`.uap.json`, env) happens in `resolvePlanReadabilityConfig`,
289
+ * and callers may pass a pre-resolved config to pin behavior in tests.
290
+ */
291
+ export function analyzePlanReadability(text, config = {}) {
292
+ const cfg = {
293
+ ...DEFAULT_PLAN_READABILITY,
294
+ enabled: true,
295
+ strict: false,
296
+ ...config,
297
+ };
298
+ if (!cfg.enabled) {
299
+ return { passed: true, findings: [], hintBlock: [], taskItemCount: 0, disabled: true, strict: false };
300
+ }
301
+ const lines = text.split('\n');
302
+ const sections = splitSections(lines);
303
+ // Preamble = the prose between the title heading and the FIRST section
304
+ // heading (or the whole document when there are no headings at all). It is
305
+ // deliberately bounded: a plan whose body contains plenty of words but
306
+ // whose OPENING is empty still fails summary-first.
307
+ const titleIdx = lines.findIndex((l) => HEADING_RE.test(l));
308
+ const after = titleIdx === -1 ? lines : lines.slice(titleIdx + 1);
309
+ const nextHeadingIdx = after.findIndex((l) => HEADING_RE.test(l));
310
+ const preambleLines = nextHeadingIdx === -1 ? after : after.slice(0, nextHeadingIdx);
311
+ const findings = [];
312
+ const headed = sections.filter((s) => s.heading !== '');
313
+ // First CONTENT section: after the title section when a title heading
314
+ // exists, else the first heading section (the doc then opens with prose).
315
+ const firstContent = titleIdx === -1 ? headed[0] : headed[1];
316
+ checkSummaryFirst(preambleLines, firstContent, cfg.minSummaryWords, findings);
317
+ checkAbstractionProgress(sections, findings);
318
+ checkSectionBudget(sections, cfg.maxWordsPerSection, findings);
319
+ checkJargonDensity(lines, cfg.jargonDensityThreshold, findings);
320
+ const { taskItemCount } = checkDiagramRequired(lines, cfg.requireDiagramAtTasks, findings);
321
+ const errors = findings.filter((f) => f.severity === 'error');
322
+ const hintBlock = findings.length > 0
323
+ ? [
324
+ '',
325
+ 'AUTOMATED READABILITY FINDINGS (a cheap static check flagged these — treat as',
326
+ 'leads to verify, NOT as verdicts; confirm or dismiss each on the merits):',
327
+ ...findings.map((f, i) => ` ${i + 1}. [${f.severity}] ${f.rule}: ${f.message}`),
328
+ ]
329
+ : [];
330
+ return {
331
+ passed: errors.length === 0,
332
+ findings,
333
+ hintBlock,
334
+ taskItemCount,
335
+ disabled: false,
336
+ strict: cfg.strict === true,
337
+ };
338
+ }
339
+ //# sourceMappingURL=plan-readability.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plan-readability.js","sourceRoot":"","sources":["../../src/models/plan-readability.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Admission math and option generation (spec §4.4).
3
+ *
4
+ * The device budget is a SUM, not a free-memory reading:
5
+ *
6
+ * fits_gpu = cost_mib ≤ total − Σ(resident figures of everything staying)
7
+ * − device.reserved_mib
8
+ * fits_host = host_rss_mib ≤ cpu free + Σ(victims' host_rss_mib) − cpu reserved
9
+ * fits = fits_gpu AND fits_host
10
+ *
11
+ * free_mib from nvidia-smi describes what is left AFTER the operator's chosen
12
+ * configuration filled the card — true and useless at the same time. The
13
+ * correct question is whether the intended set fits the device budget.
14
+ *
15
+ * Fail-closed everywhere: an unmeasured config is never a load candidate;
16
+ * an unknown device total, an unknown host free, or an unknown marginal-KV
17
+ * rate each kill the option rather than pass it on a guess.
18
+ */
19
+ import { type ModelRegistry } from './registry.js';
20
+ import type { LedgerResident, PlacementLedger } from './ledger.js';
21
+ export type OptionKind = 'reuse' | 'load_alongside' | 'displace';
22
+ export interface PlacementOption {
23
+ kind: OptionKind;
24
+ model: string;
25
+ config: string;
26
+ device: string;
27
+ /** Admission cost in MiB on the device; null = unknown (only ever present
28
+ * on a reuse option, where nothing is loaded). */
29
+ cost_mib: number | null;
30
+ /** Present only on displace options: the residents that must go. */
31
+ victims?: LedgerResident[];
32
+ /** True when the candidate's task_affinity matches the requesting task. */
33
+ affinityMatch?: boolean;
34
+ }
35
+ export interface AdmissionResult {
36
+ requested: string;
37
+ /** Does the request map to any registry config at all? */
38
+ matched: boolean;
39
+ /** not_resident_only | not_in_registry | no_measured_config | ok */
40
+ reason: string;
41
+ options: PlacementOption[];
42
+ /** Why each candidate that was not offered was refused. */
43
+ notes: string[];
44
+ }
45
+ /** Registry configs whose advertised ids (or model key) match the requested
46
+ * wire model. Empty = not in registry → park, never auto-load. */
47
+ export declare function matchingConfigs(registry: ModelRegistry, requested: string): Array<{
48
+ model: string;
49
+ config: string;
50
+ entry: ModelRegistry['models'][string]['configs'][string];
51
+ }>;
52
+ /** Compute ranked options for a requested model (spec §4.4). Ranking:
53
+ * reuse > load_alongside (affinity match first) > smallest displace >
54
+ * largest displace. Unknown anything = refused, with a note saying why. */
55
+ export declare function computeOptions(registry: ModelRegistry, ledger: PlacementLedger, requested: string, opts?: {
56
+ cells?: number;
57
+ taskKind?: string;
58
+ }): AdmissionResult;
59
+ //# sourceMappingURL=admission.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admission.d.ts","sourceRoot":"","sources":["../../src/placement/admission.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAA6D,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9G,OAAO,KAAK,EAAqB,cAAc,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEtF,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,gBAAgB,GAAG,UAAU,CAAC;AAEjE,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,UAAU,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf;sDACkD;IAClD,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,oEAAoE;IACpE,OAAO,CAAC,EAAE,cAAc,EAAE,CAAC;IAC3B,2EAA2E;IAC3E,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,0DAA0D;IAC1D,OAAO,EAAE,OAAO,CAAC;IACjB,oEAAoE;IACpE,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,eAAe,EAAE,CAAC;IAC3B,2DAA2D;IAC3D,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED;kEACkE;AAClE,wBAAgB,eAAe,CAAC,QAAQ,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,GAAG,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,CAAA;CAAE,CAAC,CAU/K;AAwDD;;2EAE2E;AAC3E,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,aAAa,EACvB,MAAM,EAAE,eAAe,EACvB,SAAS,EAAE,MAAM,EACjB,IAAI,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GAC3C,eAAe,CAoGjB"}
@@ -0,0 +1,197 @@
1
+ /**
2
+ * Admission math and option generation (spec §4.4).
3
+ *
4
+ * The device budget is a SUM, not a free-memory reading:
5
+ *
6
+ * fits_gpu = cost_mib ≤ total − Σ(resident figures of everything staying)
7
+ * − device.reserved_mib
8
+ * fits_host = host_rss_mib ≤ cpu free + Σ(victims' host_rss_mib) − cpu reserved
9
+ * fits = fits_gpu AND fits_host
10
+ *
11
+ * free_mib from nvidia-smi describes what is left AFTER the operator's chosen
12
+ * configuration filled the card — true and useless at the same time. The
13
+ * correct question is whether the intended set fits the device budget.
14
+ *
15
+ * Fail-closed everywhere: an unmeasured config is never a load candidate;
16
+ * an unknown device total, an unknown host free, or an unknown marginal-KV
17
+ * rate each kill the option rather than pass it on a guess.
18
+ */
19
+ import { configCostMiB, isMeasuredConfig } from './registry.js';
20
+ /** Registry configs whose advertised ids (or model key) match the requested
21
+ * wire model. Empty = not in registry → park, never auto-load. */
22
+ export function matchingConfigs(registry, requested) {
23
+ const out = [];
24
+ for (const [mKey, model] of Object.entries(registry.models)) {
25
+ const advertises = model.advertises ?? [];
26
+ if (!advertises.includes(requested) && mKey !== requested)
27
+ continue;
28
+ for (const [cKey, entry] of Object.entries(model.configs)) {
29
+ out.push({ model: mKey, config: cKey, entry });
30
+ }
31
+ }
32
+ return out;
33
+ }
34
+ function stayingGpuMiB(ledger, device, victims) {
35
+ const evicted = new Set(victims.map((v) => `${v.model}/${v.config}`));
36
+ let sum = 0;
37
+ for (const r of ledger.residents) {
38
+ if (r.device !== device || evicted.has(`${r.model}/${r.config}`))
39
+ continue;
40
+ // A resident without a measured gpu figure still holds its registry
41
+ // measurement when one exists; without any figure the sum is unknown and
42
+ // the caller must fail closed — handled by returning NaN.
43
+ if (r.gpu_mib === undefined)
44
+ return Number.NaN;
45
+ sum += r.gpu_mib;
46
+ }
47
+ return sum;
48
+ }
49
+ /** Enumerate eviction subsets of a device's residents, smallest freed MiB
50
+ * first (spec: smallest displacement ranks above larger). Capped at pairs
51
+ * when the resident set is large, so this stays linear-ish; three+ mutually
52
+ * resident backends on one card is not a real machine state today. */
53
+ function evictionSets(residents) {
54
+ const sets = [];
55
+ const byKey = new Map(residents.map((r) => [`${r.model}/${r.config}`, r]));
56
+ const keys = [...byKey.keys()];
57
+ const max = keys.length <= 6 ? keys.length : 2;
58
+ for (let size = 1; size <= max; size++) {
59
+ const combo = [];
60
+ const walk = (start) => {
61
+ if (combo.length === size) {
62
+ sets.push(combo.map((i) => byKey.get(keys[i])));
63
+ return;
64
+ }
65
+ for (let i = start; i < keys.length; i++) {
66
+ combo.push(i);
67
+ walk(i + 1);
68
+ combo.pop();
69
+ }
70
+ };
71
+ walk(0);
72
+ }
73
+ return sets.sort((a, b) => freedGpuMiB(a) - freedGpuMiB(b));
74
+ }
75
+ function freedGpuMiB(victims) {
76
+ return victims.reduce((sum, v) => sum + (v.gpu_mib ?? 0), 0);
77
+ }
78
+ function freedHostMiB(victims) {
79
+ let sum = 0;
80
+ for (const v of victims) {
81
+ if (v.host_rss_mib === undefined)
82
+ return null;
83
+ sum += v.host_rss_mib;
84
+ }
85
+ return sum;
86
+ }
87
+ /** Compute ranked options for a requested model (spec §4.4). Ranking:
88
+ * reuse > load_alongside (affinity match first) > smallest displace >
89
+ * largest displace. Unknown anything = refused, with a note saying why. */
90
+ export function computeOptions(registry, ledger, requested, opts) {
91
+ const cells = opts?.cells ?? 32768;
92
+ const candidates = matchingConfigs(registry, requested);
93
+ if (candidates.length === 0) {
94
+ return { requested, matched: false, reason: 'not_in_registry', options: [], notes: [] };
95
+ }
96
+ const notes = [];
97
+ const options = [];
98
+ // 1. reuse — a resident already satisfies the request. Unmeasured configs
99
+ // are servable when resident; that is the fail-closed rule's other half.
100
+ for (const c of candidates) {
101
+ const resident = ledger.residents.find((r) => r.model === c.model && r.config === c.config && r.state === 'hot');
102
+ if (resident) {
103
+ options.push({ kind: 'reuse', model: c.model, config: c.config, device: resident.device, cost_mib: null });
104
+ }
105
+ }
106
+ if (options.length > 0)
107
+ return { requested, matched: true, reason: 'ok', options, notes };
108
+ // 2/3. load_alongside and displace — both need a measured config.
109
+ const measured = candidates.filter((c) => isMeasuredConfig(c.entry));
110
+ if (measured.length === 0) {
111
+ return {
112
+ requested,
113
+ matched: true,
114
+ reason: 'no_measured_config',
115
+ options: [],
116
+ notes: ['every matching config is unmeasured — run `uap models measure` against a live instance; fail closed'],
117
+ };
118
+ }
119
+ const cpu = Object.values(ledger.devices).find((d) => d.kind === 'cpu');
120
+ for (const c of measured) {
121
+ const cfg = c.entry;
122
+ const model = registry.models[c.model];
123
+ const cost = configCostMiB(cfg, cells);
124
+ if (cost === null) {
125
+ notes.push(`${c.model}/${c.config}: cost beyond the measured KV pool (cells > ${cfg.kv_resident_cells}) is unknown — refused`);
126
+ continue;
127
+ }
128
+ const affinityMatch = opts?.taskKind !== undefined && (model.task_affinity ?? []).includes(opts.taskKind);
129
+ for (const device of model.affinity?.device ?? [`${cfg.measured_on}`]) {
130
+ const dev = ledger.devices[device];
131
+ if (!dev) {
132
+ notes.push(`${c.model}/${c.config}: device '${device}' not in ledger`);
133
+ continue;
134
+ }
135
+ if (dev.total_mib === undefined || dev.reserved_mib === undefined) {
136
+ notes.push(`${c.model}/${c.config}: device '${device}' total/reserve unmeasured — cannot evaluate load_alongside or displace`);
137
+ continue;
138
+ }
139
+ // 2. load_alongside — nothing displaced.
140
+ const staying = stayingGpuMiB(ledger, device, []);
141
+ const alongsideFits = Number.isFinite(staying) && cost <= dev.total_mib - dev.reserved_mib - staying;
142
+ const hostFits = hostFitsFor(cpu, cfg.host_rss_mib, 0);
143
+ if (alongsideFits && hostFits === true) {
144
+ options.push({ kind: 'load_alongside', model: c.model, config: c.config, device, cost_mib: cost, affinityMatch });
145
+ continue;
146
+ }
147
+ if (alongsideFits && hostFits === null) {
148
+ notes.push(`${c.model}/${c.config}: GPU fits but host free memory unknown — refused`);
149
+ }
150
+ // 3. displace — minimal eviction sets, smallest first.
151
+ const residentsHere = ledger.residents.filter((r) => r.device === device && r.state !== 'draining');
152
+ for (const victims of evictionSets(residentsHere)) {
153
+ const stays = stayingGpuMiB(ledger, device, victims);
154
+ if (!Number.isFinite(stays)) {
155
+ notes.push(`${c.model}/${c.config}: a resident has no measured gpu_mib — cannot compute displacement`);
156
+ break;
157
+ }
158
+ const gpuOk = cost <= dev.total_mib - dev.reserved_mib - stays;
159
+ const freedHost = freedHostMiB(victims);
160
+ const hostOk = freedHost === null ? null : hostFitsFor(cpu, cfg.host_rss_mib, freedHost);
161
+ if (gpuOk && hostOk === true) {
162
+ options.push({ kind: 'displace', model: c.model, config: c.config, device, cost_mib: cost, victims, affinityMatch });
163
+ break; // first (smallest) feasible set per device wins
164
+ }
165
+ if (gpuOk && hostOk === null) {
166
+ notes.push(`${c.model}/${c.config}: victim host_rss unknown — displacement refused`);
167
+ break;
168
+ }
169
+ }
170
+ }
171
+ }
172
+ const rank = (o) => {
173
+ if (o.kind === 'reuse')
174
+ return 0;
175
+ if (o.kind === 'load_alongside')
176
+ return o.affinityMatch ? 1 : 2;
177
+ return 3 + (o.victims ? freedGpuMiB(o.victims) : 0);
178
+ };
179
+ options.sort((a, b) => rank(a) - rank(b));
180
+ return {
181
+ requested,
182
+ matched: true,
183
+ reason: options.length > 0 ? 'ok' : 'no_feasible_option',
184
+ options,
185
+ notes,
186
+ };
187
+ }
188
+ /** Host RAM gate: config RSS must fit in (host free + what victims free)
189
+ * minus the cpu reserve. null = unknown (host free unprobeable) → refused. */
190
+ function hostFitsFor(cpu, rssMib, freedMib) {
191
+ if (!cpu)
192
+ return null;
193
+ if (cpu.free_mib === undefined || cpu.reserved_mib === undefined)
194
+ return null;
195
+ return rssMib <= cpu.free_mib + freedMib - cpu.reserved_mib;
196
+ }
197
+ //# sourceMappingURL=admission.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admission.js","sourceRoot":"","sources":["../../src/placement/admission.ts"],"names":[],"mappings":"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"}