@miller-tech/uap 2.21.2 → 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,44 @@
1
+ import { enforceOption, type EnforceOpts } from './enforce.js';
2
+ import type { PlacementOption } from './admission.js';
3
+ import type { ModelRegistry } from './registry.js';
4
+ export interface AutoPolicy {
5
+ enabled: boolean;
6
+ /** Registry model keys whose DISPLACEMENT may auto-enforce. Empty by
7
+ * default: displacement always needs an explicit allowlist entry. */
8
+ allow_displace: string[];
9
+ /** Idle-resident unload (§4.4.2): after this many seconds without a
10
+ * gated request served, an `unload_allow`-listed resident is unloaded
11
+ * by the dashboard's idle sweep. Absent (the default) = the feature
12
+ * is OFF — nobody's backend is stopped on a timer without this
13
+ * explicit opt-in. */
14
+ unload_idle_after_secs?: number;
15
+ /** Registry model keys that MAY be auto-unloaded when idle. Empty by
16
+ * default; the standing-consent twin of `allow_displace`. */
17
+ unload_allow?: string[];
18
+ }
19
+ /** Minimum idle window the policy will accept — under a minute the sweep
20
+ * would oscillate (unload, next request auto-loads, unload again). */
21
+ export declare const MIN_UNLOAD_IDLE_SECS = 60;
22
+ /** OPT-IN by design: a missing (or corrupt) policy file means NO auto
23
+ * loading — every park waits for a manual `uap models apply`, exactly as
24
+ * before phase 4. `uap models auto --enable` turns it on deliberately. */
25
+ export declare const DEFAULT_AUTO_POLICY: AutoPolicy;
26
+ export declare function autoPolicyPath(): string;
27
+ export declare function loadAutoPolicy(path?: string): AutoPolicy;
28
+ export declare function saveAutoPolicy(policy: AutoPolicy, path?: string): void;
29
+ /** Pick the option an auto load may enforce, or null when it may not.
30
+ * Includes the runnability gates (in-flight cap, failure cooldown) so the
31
+ * caller never advertises `auto_loading` for a run that will be refused. */
32
+ export declare function autoOptionFor(options: PlacementOption[], policy: AutoPolicy, requestedModel: string): PlacementOption | null;
33
+ /** Fire-and-forget the auto enforcement for one parked placement id.
34
+ * Resolves the pending entry on success; marks it auto_failed on failure.
35
+ * Never throws to the caller (the admission answer is already built) and
36
+ * never REJECTS (a ledger-write throw inside the run would otherwise be an
37
+ * unhandled rejection that kills the dashboard daemon). */
38
+ export declare function scheduleAutoResolution(registry: ModelRegistry, requestedModel: string, option: PlacementOption, placementId: string, opts?: EnforceOpts & {
39
+ enforce?: typeof enforceOption;
40
+ }): void;
41
+ /** Test seam only: the run/failure maps are process-local, and a suite run
42
+ * in one process would otherwise carry cooldowns across test cases. */
43
+ export declare function resetAutoSchedulerForTests(): void;
44
+ //# sourceMappingURL=auto.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auto.d.ts","sourceRoot":"","sources":["../../src/placement/auto.ts"],"names":[],"mappings":"AA2CA,OAAO,EAAE,aAAa,EAAE,KAAK,WAAW,EAAsB,MAAM,cAAc,CAAC;AACnF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAEnD,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB;yEACqE;IACrE,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB;;;;0BAIsB;IACtB,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC;iEAC6D;IAC7D,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AAED;sEACsE;AACtE,eAAO,MAAM,oBAAoB,KAAK,CAAC;AAEvC;;0EAE0E;AAC1E,eAAO,MAAM,mBAAmB,EAAE,UAAmD,CAAC;AAEtF,wBAAgB,cAAc,IAAI,MAAM,CAEvC;AAED,wBAAgB,cAAc,CAAC,IAAI,GAAE,MAAyB,GAAG,UAAU,CAiC1E;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,GAAE,MAAyB,GAAG,IAAI,CAQxF;AAoBD;;4EAE4E;AAC5E,wBAAgB,aAAa,CAC3B,OAAO,EAAE,eAAe,EAAE,EAC1B,MAAM,EAAE,UAAU,EAClB,cAAc,EAAE,MAAM,GACrB,eAAe,GAAG,IAAI,CAUxB;AAED;;;;2DAI2D;AAC3D,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,aAAa,EACvB,cAAc,EAAE,MAAM,EACtB,MAAM,EAAE,eAAe,EACvB,WAAW,EAAE,MAAM,EACnB,IAAI,GAAE,WAAW,GAAG;IAAE,OAAO,CAAC,EAAE,OAAO,aAAa,CAAA;CAAO,GAC1D,IAAI,CA0DN;AAED;uEACuE;AACvE,wBAAgB,0BAA0B,IAAI,IAAI,CAIjD"}
@@ -0,0 +1,212 @@
1
+ /**
2
+ * Placement auto-resolution (spec §4.4.1, phase 4) — request-driven loading.
3
+ *
4
+ * When a gated request parks because its model is not resident, the
5
+ * controller can load it ITSELF instead of waiting for a human `uap models
6
+ * apply`. Two rules, straight from the spec's core invariant ("never evict
7
+ * without explicit operator confirmation"):
8
+ *
9
+ * 1. Non-displacing options (load-alongside, and reuse when a resident
10
+ * already serves) auto-enforce whenever the policy is enabled — nothing
11
+ * is evicted, the budget already said it fits.
12
+ * 2. Displacement auto-enforces ONLY for models the operator explicitly
13
+ * allowlisted (`uap models auto --allow-displace <model> --yes`). The
14
+ * allowlist is the standing operator confirmation, written deliberately
15
+ * and auditable in the policy file — never a default.
16
+ *
17
+ * The park reason while an auto load runs is `auto_loading`; the client's
18
+ * retry (retry_after_ms floor) is the protocol — the next admission after
19
+ * the load answers forward. A failed auto attempt marks the pending entry
20
+ * `auto_failed`.
21
+ *
22
+ * Run-shape rules (three reviews, phase 4):
23
+ * - ONE run per requested MODEL, not per placement id — the client-supplied
24
+ * identity headers can mint fresh ids at request rate, so the id is no
25
+ * dedupe at all; the model is the real "same load" key.
26
+ * - SINGLE-FLIGHT machine-wide: auto runs queue on one tail promise. Two
27
+ * alongside options computed against the same pre-load free-memory
28
+ * snapshot can otherwise both start and oversubscribe the card.
29
+ * - The pending entry's TTL is EXTENDED past the enforcement envelope when
30
+ * a run schedules: drain + stops + verify-free + start can exceed the
31
+ * 120s operator-prompt TTL, and a mid-run expiry would mint a fresh
32
+ * entry + a second concurrent run for the same model.
33
+ * - A FAILED run parks its model in a cooldown window: otherwise a
34
+ * persistently failing load re-enforces every TTL expiry — an unattended
35
+ * drain→stop→fail→rollback oscillation the operator never consented to.
36
+ * - The run NEVER rejects: every ledger write inside it is caught (a lock
37
+ * hiccup must not kill the dashboard daemon with an unhandled
38
+ * rejection), and the entry self-heals via TTL expiry.
39
+ */
40
+ import { existsSync, readFileSync, writeFileSync, renameSync, mkdirSync } from 'fs';
41
+ import { homedir } from 'os';
42
+ import { dirname, join } from 'path';
43
+ import { withLedger, placementLedgerPath } from './ledger.js';
44
+ import { enforceOption } from './enforce.js';
45
+ /** Minimum idle window the policy will accept — under a minute the sweep
46
+ * would oscillate (unload, next request auto-loads, unload again). */
47
+ export const MIN_UNLOAD_IDLE_SECS = 60;
48
+ /** OPT-IN by design: a missing (or corrupt) policy file means NO auto
49
+ * loading — every park waits for a manual `uap models apply`, exactly as
50
+ * before phase 4. `uap models auto --enable` turns it on deliberately. */
51
+ export const DEFAULT_AUTO_POLICY = { enabled: false, allow_displace: [] };
52
+ export function autoPolicyPath() {
53
+ return process.env.UAP_PLACEMENT_AUTO ?? join(homedir(), '.uap', 'placement-auto.json');
54
+ }
55
+ export function loadAutoPolicy(path = autoPolicyPath()) {
56
+ if (!existsSync(path))
57
+ return { ...DEFAULT_AUTO_POLICY };
58
+ try {
59
+ const raw = JSON.parse(readFileSync(path, 'utf-8'));
60
+ // Fail-closed field-by-field: an invalid idle window or a non-array
61
+ // allowlist drops JUST that knob (auto-unload stays off), never the
62
+ // whole policy — the operator's other consent records survive. Absent
63
+ // knobs are OMITTED, not defaulted, so `toEqual` round-trips and a
64
+ // pre-§4.4.2 policy file loads to its exact old shape.
65
+ const idle = typeof raw.unload_idle_after_secs === 'number' &&
66
+ Number.isFinite(raw.unload_idle_after_secs) &&
67
+ raw.unload_idle_after_secs >= MIN_UNLOAD_IDLE_SECS
68
+ ? Math.floor(raw.unload_idle_after_secs)
69
+ : undefined;
70
+ const unloadAllow = Array.isArray(raw.unload_allow)
71
+ ? raw.unload_allow.filter((m) => typeof m === 'string')
72
+ : undefined;
73
+ return {
74
+ // Explicit opt-in in the FILE too: only a literal `enabled: true`
75
+ // enables; anything else (missing key, null, garbage) stays off.
76
+ enabled: raw.enabled === true,
77
+ allow_displace: Array.isArray(raw.allow_displace)
78
+ ? raw.allow_displace.filter((m) => typeof m === 'string')
79
+ : [],
80
+ ...(idle !== undefined ? { unload_idle_after_secs: idle } : {}),
81
+ ...(unloadAllow !== undefined ? { unload_allow: unloadAllow } : {}),
82
+ };
83
+ }
84
+ catch {
85
+ // Fail closed on a corrupt policy: auto alongside off, displacement
86
+ // allowlist empty — parking for the operator is always safe.
87
+ return { enabled: false, allow_displace: [] };
88
+ }
89
+ }
90
+ export function saveAutoPolicy(policy, path = autoPolicyPath()) {
91
+ // Atomic tmp+rename (the ledger's own save shape): a torn or half-written
92
+ // policy fails closed on read, but the operator's consent record should
93
+ // not silently vanish on a crashed write either.
94
+ mkdirSync(dirname(path), { recursive: true });
95
+ const tmp = `${path}.tmp-${process.pid}-${Date.now()}`;
96
+ writeFileSync(tmp, `${JSON.stringify(policy, null, 2)}\n`, { mode: 0o600 });
97
+ renameSync(tmp, path);
98
+ }
99
+ /** Hard cap on in-flight/queued auto runs: single-flight serializes them,
100
+ * the cap bounds the queue and the map so a parked flood cannot queue an
101
+ * unbounded backlog of machine mutations. */
102
+ const AUTO_MAX_CONCURRENT_RUNS = 2;
103
+ /** A failed auto load parks its MODEL for this window (no oscillation). */
104
+ const AUTO_FAILURE_COOLDOWN_MS = 10 * 60_000;
105
+ /** An auto-loading entry must outlive the enforcement it supervises: drain
106
+ * (~60s) + stops (~30s each) + verify-free (~30s) + start/verify-up (~120s)
107
+ * exceeds the 120s operator-prompt TTL, and a queued run waits on the
108
+ * single-flight tail besides. */
109
+ const AUTO_PENDING_WINDOW_MS = 10 * 60_000;
110
+ // Process-local by design: the LEDGER is the cross-process dedupe; these
111
+ // maps only bound what THIS controller process runs.
112
+ const autoRuns = new Map(); // keyed by requested model
113
+ const autoFailures = new Map(); // model → epoch ms of last failure
114
+ let autoTail = Promise.resolve();
115
+ /** Pick the option an auto load may enforce, or null when it may not.
116
+ * Includes the runnability gates (in-flight cap, failure cooldown) so the
117
+ * caller never advertises `auto_loading` for a run that will be refused. */
118
+ export function autoOptionFor(options, policy, requestedModel) {
119
+ if (!policy.enabled || options.length === 0)
120
+ return null;
121
+ if (autoRuns.has(requestedModel))
122
+ return null; // a run for this model is in flight
123
+ if (autoRuns.size >= AUTO_MAX_CONCURRENT_RUNS)
124
+ return null; // envelope cap
125
+ if ((autoFailures.get(requestedModel) ?? 0) > Date.now() - AUTO_FAILURE_COOLDOWN_MS)
126
+ return null;
127
+ const alongside = options.find((o) => o.kind !== 'displace');
128
+ if (alongside)
129
+ return alongside;
130
+ const displace = options.find((o) => o.kind === 'displace');
131
+ if (displace && policy.allow_displace.includes(displace.model))
132
+ return displace;
133
+ return null;
134
+ }
135
+ /** Fire-and-forget the auto enforcement for one parked placement id.
136
+ * Resolves the pending entry on success; marks it auto_failed on failure.
137
+ * Never throws to the caller (the admission answer is already built) and
138
+ * never REJECTS (a ledger-write throw inside the run would otherwise be an
139
+ * unhandled rejection that kills the dashboard daemon). */
140
+ export function scheduleAutoResolution(registry, requestedModel, option, placementId, opts = {}) {
141
+ if (autoRuns.has(requestedModel))
142
+ return; // one run per model
143
+ const ledgerPath = opts.ledgerPath ?? placementLedgerPath();
144
+ // The entry must outlive the enforcement it supervises (and the queue
145
+ // wait in front of it), or the client's next retry mints a fresh entry
146
+ // and a second concurrent run for the same model.
147
+ try {
148
+ withLedger(ledgerPath, (l) => {
149
+ const p = l.pending.find((e) => e.id === placementId);
150
+ if (p)
151
+ p.expires_at = new Date(Date.now() + AUTO_PENDING_WINDOW_MS).toISOString();
152
+ });
153
+ }
154
+ catch {
155
+ // Lock contention at schedule time: run anyway; TTL expiry is the
156
+ // fallback heal for whatever the run cannot update.
157
+ }
158
+ // Snapshot the tail BEFORE reassigning it below: the run must await the
159
+ // PREVIOUS runs' tail, never a promise derived from itself (a self-await
160
+ // deadlocks the run forever — found live during phase-4 review fixes).
161
+ const tail = autoTail;
162
+ const run = (async () => {
163
+ // Return the park answer FIRST: enforcement's synchronous prefix
164
+ // (device probes, systemctl is-active, the ledger lock) must not run
165
+ // inline in the admit handler, which the proxy holds to a 2s timeout.
166
+ await new Promise((r) => setImmediate(r));
167
+ // Single-flight: one machine-wide mutation at a time.
168
+ await tail;
169
+ let result;
170
+ try {
171
+ result = await (opts.enforce ?? enforceOption)(registry, requestedModel, option, opts);
172
+ }
173
+ catch (err) {
174
+ result = { ok: false, steps: [], error: err.message };
175
+ }
176
+ try {
177
+ withLedger(ledgerPath, (l) => {
178
+ if (result.ok) {
179
+ l.pending = l.pending.filter((p) => p.id !== placementId);
180
+ }
181
+ else {
182
+ // One attempt per placement id: mark it so retries park with the
183
+ // honest reason and the operator sees it in `uap models pending`.
184
+ const p = l.pending.find((e) => e.id === placementId);
185
+ if (p)
186
+ p.reason = 'auto_failed';
187
+ }
188
+ });
189
+ }
190
+ catch {
191
+ // Never reject: the entry stays auto_loading and self-heals via TTL
192
+ // expiry; killing the controller over a lock hiccup is the one
193
+ // outcome this background run must never produce.
194
+ }
195
+ if (result.ok)
196
+ autoFailures.delete(requestedModel);
197
+ else
198
+ autoFailures.set(requestedModel, Date.now());
199
+ })();
200
+ const tracked = run.finally(() => autoRuns.delete(requestedModel));
201
+ tracked.catch(() => undefined); // no-op: the body cannot reject, and this keeps it that way
202
+ autoRuns.set(requestedModel, tracked);
203
+ autoTail = run.then(() => undefined, () => undefined);
204
+ }
205
+ /** Test seam only: the run/failure maps are process-local, and a suite run
206
+ * in one process would otherwise carry cooldowns across test cases. */
207
+ export function resetAutoSchedulerForTests() {
208
+ autoRuns.clear();
209
+ autoFailures.clear();
210
+ autoTail = Promise.resolve();
211
+ }
212
+ //# sourceMappingURL=auto.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auto.js","sourceRoot":"","sources":["../../src/placement/auto.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AACpF,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAC7B,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AACrC,OAAO,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAwC,MAAM,cAAc,CAAC;AAoBnF;sEACsE;AACtE,MAAM,CAAC,MAAM,oBAAoB,GAAG,EAAE,CAAC;AAEvC;;0EAE0E;AAC1E,MAAM,CAAC,MAAM,mBAAmB,GAAe,EAAE,OAAO,EAAE,KAAK,EAAE,cAAc,EAAE,EAAE,EAAE,CAAC;AAEtF,MAAM,UAAU,cAAc;IAC5B,OAAO,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,qBAAqB,CAAC,CAAC;AAC1F,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,OAAe,cAAc,EAAE;IAC5D,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,GAAG,mBAAmB,EAAE,CAAC;IACzD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAwB,CAAC;QAC3E,oEAAoE;QACpE,oEAAoE;QACpE,sEAAsE;QACtE,mEAAmE;QACnE,uDAAuD;QACvD,MAAM,IAAI,GACR,OAAO,GAAG,CAAC,sBAAsB,KAAK,QAAQ;YAC9C,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,sBAAsB,CAAC;YAC3C,GAAG,CAAC,sBAAsB,IAAI,oBAAoB;YAChD,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,sBAAsB,CAAC;YACxC,CAAC,CAAC,SAAS,CAAC;QAChB,MAAM,WAAW,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC;YACjD,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC;YACpE,CAAC,CAAC,SAAS,CAAC;QACd,OAAO;YACL,kEAAkE;YAClE,iEAAiE;YACjE,OAAO,EAAE,GAAG,CAAC,OAAO,KAAK,IAAI;YAC7B,cAAc,EAAE,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;gBAC/C,CAAC,CAAC,GAAG,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC;gBACtE,CAAC,CAAC,EAAE;YACN,GAAG,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,sBAAsB,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC/D,GAAG,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACpE,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,oEAAoE;QACpE,6DAA6D;QAC7D,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,cAAc,EAAE,EAAE,EAAE,CAAC;IAChD,CAAC;AACH,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,MAAkB,EAAE,OAAe,cAAc,EAAE;IAChF,0EAA0E;IAC1E,wEAAwE;IACxE,iDAAiD;IACjD,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,MAAM,GAAG,GAAG,GAAG,IAAI,QAAQ,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;IACvD,aAAa,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC5E,UAAU,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;AACxB,CAAC;AAED;;6CAE6C;AAC7C,MAAM,wBAAwB,GAAG,CAAC,CAAC;AACnC,2EAA2E;AAC3E,MAAM,wBAAwB,GAAG,EAAE,GAAG,MAAM,CAAC;AAC7C;;;iCAGiC;AACjC,MAAM,sBAAsB,GAAG,EAAE,GAAG,MAAM,CAAC;AAE3C,yEAAyE;AACzE,qDAAqD;AACrD,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAyB,CAAC,CAAC,2BAA2B;AAC9E,MAAM,YAAY,GAAG,IAAI,GAAG,EAAkB,CAAC,CAAC,mCAAmC;AACnF,IAAI,QAAQ,GAAkB,OAAO,CAAC,OAAO,EAAE,CAAC;AAEhD;;4EAE4E;AAC5E,MAAM,UAAU,aAAa,CAC3B,OAA0B,EAC1B,MAAkB,EAClB,cAAsB;IAEtB,IAAI,CAAC,MAAM,CAAC,OAAO,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACzD,IAAI,QAAQ,CAAC,GAAG,CAAC,cAAc,CAAC;QAAE,OAAO,IAAI,CAAC,CAAC,oCAAoC;IACnF,IAAI,QAAQ,CAAC,IAAI,IAAI,wBAAwB;QAAE,OAAO,IAAI,CAAC,CAAC,eAAe;IAC3E,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,wBAAwB;QAAE,OAAO,IAAI,CAAC;IACjG,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC;IAC7D,IAAI,SAAS;QAAE,OAAO,SAAS,CAAC;IAChC,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC;IAC5D,IAAI,QAAQ,IAAI,MAAM,CAAC,cAAc,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,QAAQ,CAAC;IAChF,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;2DAI2D;AAC3D,MAAM,UAAU,sBAAsB,CACpC,QAAuB,EACvB,cAAsB,EACtB,MAAuB,EACvB,WAAmB,EACnB,OAAyD,EAAE;IAE3D,IAAI,QAAQ,CAAC,GAAG,CAAC,cAAc,CAAC;QAAE,OAAO,CAAC,oBAAoB;IAC9D,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,IAAI,mBAAmB,EAAE,CAAC;IAC5D,sEAAsE;IACtE,uEAAuE;IACvE,kDAAkD;IAClD,IAAI,CAAC;QACH,UAAU,CAAC,UAAU,EAAE,CAAC,CAAC,EAAE,EAAE;YAC3B,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,WAAW,CAAC,CAAC;YACtD,IAAI,CAAC;gBAAE,CAAC,CAAC,UAAU,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,sBAAsB,CAAC,CAAC,WAAW,EAAE,CAAC;QACpF,CAAC,CAAC,CAAC;IACL,CAAC;IAAC,MAAM,CAAC;QACP,kEAAkE;QAClE,oDAAoD;IACtD,CAAC;IACD,wEAAwE;IACxE,yEAAyE;IACzE,uEAAuE;IACvE,MAAM,IAAI,GAAG,QAAQ,CAAC;IACtB,MAAM,GAAG,GAAG,CAAC,KAAK,IAAI,EAAE;QACtB,iEAAiE;QACjE,qEAAqE;QACrE,sEAAsE;QACtE,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC;QAC1C,sDAAsD;QACtD,MAAM,IAAI,CAAC;QACX,IAAI,MAAqB,CAAC;QAC1B,IAAI,CAAC;YACH,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,IAAI,aAAa,CAAC,CAAC,QAAQ,EAAE,cAAc,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC;QACzF,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,GAAG,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAG,GAAa,CAAC,OAAO,EAAE,CAAC;QACnE,CAAC;QACD,IAAI,CAAC;YACH,UAAU,CAAC,UAAU,EAAE,CAAC,CAAC,EAAE,EAAE;gBAC3B,IAAI,MAAM,CAAC,EAAE,EAAE,CAAC;oBACd,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,WAAW,CAAC,CAAC;gBAC5D,CAAC;qBAAM,CAAC;oBACN,iEAAiE;oBACjE,kEAAkE;oBAClE,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,WAAW,CAAC,CAAC;oBACtD,IAAI,CAAC;wBAAE,CAAC,CAAC,MAAM,GAAG,aAAa,CAAC;gBAClC,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACP,oEAAoE;YACpE,+DAA+D;YAC/D,kDAAkD;QACpD,CAAC;QACD,IAAI,MAAM,CAAC,EAAE;YAAE,YAAY,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;;YAC9C,YAAY,CAAC,GAAG,CAAC,cAAc,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;IACpD,CAAC,CAAC,EAAE,CAAC;IACL,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC;IACnE,OAAO,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,4DAA4D;IAC5F,QAAQ,CAAC,GAAG,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC;IACtC,QAAQ,GAAG,GAAG,CAAC,IAAI,CACjB,GAAG,EAAE,CAAC,SAAS,EACf,GAAG,EAAE,CAAC,SAAS,CAChB,CAAC;AACJ,CAAC;AAED;uEACuE;AACvE,MAAM,UAAU,0BAA0B;IACxC,QAAQ,CAAC,KAAK,EAAE,CAAC;IACjB,YAAY,CAAC,KAAK,EAAE,CAAC;IACrB,QAAQ,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;AAC/B,CAAC"}
@@ -0,0 +1,128 @@
1
+ import { type PlacementOption } from './admission.js';
2
+ import { type LedgerDeviceState } from './ledger.js';
3
+ import { type ModelRegistry } from './registry.js';
4
+ export interface EnforceStep {
5
+ name: string;
6
+ ok: boolean;
7
+ detail?: string;
8
+ }
9
+ export interface EnforceResult {
10
+ ok: boolean;
11
+ placementId?: string;
12
+ steps: EnforceStep[];
13
+ rolledBack?: boolean;
14
+ error?: string;
15
+ }
16
+ export interface PlacementInflightEntry {
17
+ client?: string | null;
18
+ session?: string | null;
19
+ started_at?: string;
20
+ }
21
+ /** All external effects, injectable for tests. */
22
+ export interface EnforceDeps {
23
+ isUnitActive(unit: string): boolean;
24
+ stopUnit(unit: string): Promise<void>;
25
+ startUnit(unit: string): Promise<void>;
26
+ /** GPU free MiB for a device like 'gpu0' (index N); null = unprobed. */
27
+ gpuFreeMiB(device: string): number | null;
28
+ /** Live in-flight requests for a target, from the proxy's loopback
29
+ * endpoint (/internal/placement/inflight). NULL = unknown (proxy did
30
+ * not answer) — never guessed: a 401 or a proxy restarted mid-drain
31
+ * reads as busy, not idle. */
32
+ inflightForTarget(target: string): Promise<PlacementInflightEntry[] | null>;
33
+ httpGetJson(url: string): Promise<unknown | null>;
34
+ /** POST the proxy's /internal/placement/refresh; false = not refreshed. */
35
+ refreshProxy(): Promise<boolean>;
36
+ /** Test seam: replaces syncLedger's live device probe (nvidia-smi +
37
+ * MemAvailable vary with machine state; enforcement math must be
38
+ * testable against deterministic facts). Absent = probe for real. */
39
+ probeDevices?(registry: ModelRegistry): Record<string, LedgerDeviceState>;
40
+ sleep(ms: number): Promise<void>;
41
+ nowMs(): number;
42
+ }
43
+ /** UNVALIDATED envelope defaults (spec §10.4) — conservative on purpose. */
44
+ export interface EnforceTimeouts {
45
+ drainMs: number;
46
+ startMs: number;
47
+ freeMs: number;
48
+ pollMs: number;
49
+ }
50
+ export declare function enforcementTimeouts(): EnforceTimeouts;
51
+ export declare function defaultEnforceDeps(opts?: {
52
+ proxyBaseUrl?: string;
53
+ proxyToken?: string;
54
+ }): EnforceDeps;
55
+ export interface EnforceOpts {
56
+ ledgerPath?: string;
57
+ deps?: EnforceDeps;
58
+ timeouts?: EnforceTimeouts;
59
+ /** Registry paths, threaded to loadModelRegistry (defaults: the project's
60
+ * config/model-registry.json merged with ~/.uap/model-registry.json). */
61
+ repoPath?: string;
62
+ localPath?: string;
63
+ /** The request's context need in cells. Step 0 re-derives the approved
64
+ * option from live state, and the cost is per-request (spec §4.4): with
65
+ * the 32k default a small-pool config's cost is unknown, the re-derive
66
+ * finds nothing, and a cells-correct option is refused as "drift" — the
67
+ * load never runs. */
68
+ cells?: number;
69
+ }
70
+ /** The §4.6 sequence for ONE approved option. Re-derives the option from live
71
+ * state and refuses on drift (a preview that lies is worse than none). */
72
+ export declare function enforceOption(registry: ModelRegistry, requestedModel: string, approved: PlacementOption, opts?: EnforceOpts): Promise<EnforceResult>;
73
+ /** Best-effort live in-flight map from the proxy's loopback
74
+ * /internal/placement/inflight, for victim previews at the approval moment
75
+ * (spec §4.5): the operator sees "active generation aborted" versus "session
76
+ * idle" honestly. NULL when the proxy does not answer — unknown, never a
77
+ * guessed "idle". */
78
+ export declare function fetchProxyInflight(opts?: {
79
+ proxyBaseUrl?: string;
80
+ proxyToken?: string;
81
+ }): Promise<Map<string, PlacementInflightEntry[]> | null>;
82
+ /** Resolve a parked request: find its pending entry, recompute options,
83
+ * pick option #optionIndex (1-based, as `uap models pending` prints them),
84
+ * enforce, and remove the pending entry on success.
85
+ *
86
+ * `expectedVictims` is the approval signature: the victim list the operator
87
+ * actually SAW (the impact list the CLI printed, or the dashboard preview).
88
+ * When provided, enforcement refuses if the recomputed option's victims
89
+ * differ — "option N" can silently mean a different swap minutes after the
90
+ * preview, and this feature never displaces what the operator did not see
91
+ * (spec §4.5: a preview that lies is worse than no preview). */
92
+ export declare function resolvePendingPlacement(projectDir: string, placementId: string, optionIndex: number, opts?: EnforceOpts & {
93
+ expectedVictims?: Array<{
94
+ model: string;
95
+ config: string;
96
+ }>;
97
+ }): Promise<EnforceResult>;
98
+ /** Refuse a parked request: the client gets a clean fallback on expiry. */
99
+ export declare function dismissPendingPlacement(placementId: string, opts?: {
100
+ ledgerPath?: string;
101
+ }): {
102
+ dismissed: boolean;
103
+ };
104
+ /** Operator-initiated load: same gate, but NEVER displacement — pick the
105
+ * best non-displacing option (reuse, then load_alongside) and refuse if
106
+ * only displacement would fit. Displacement always needs the pending path. */
107
+ export declare function loadPlacement(projectDir: string, modelKey: string, opts?: EnforceOpts): Promise<EnforceResult>;
108
+ /** Operator-initiated unload: every live resident of the model is drained,
109
+ * stopped, verified free, and the proxy view refreshed. */
110
+ export declare function unloadPlacement(projectDir: string, modelKey: string, opts?: EnforceOpts): Promise<EnforceResult>;
111
+ /** Write `Conflicts=` drop-ins for every registry-named unit so the
112
+ * systemd graph itself refuses two same-device residents (the machine-local
113
+ * graph today knows nothing about placement). Drop-ins, not unit rewrites:
114
+ * the operator's unit files stay theirs. Unit names are validated BEFORE
115
+ * any path or content is built from them — the registry comes from the
116
+ * repo's config/, and a name carrying `../` or a newline must never reach
117
+ * a file path, the drop-in content, or systemctl. Stale drop-ins (a unit
118
+ * whose registry conflicts disappeared, or a unit gone from the registry)
119
+ * are removed so orphan Conflicts= can't brick a legitimate future start.
120
+ * Best-effort daemon-reload. */
121
+ export declare function writePlacementUnitDropins(registry: ModelRegistry, opts?: {
122
+ userUnitsDir?: string;
123
+ }): {
124
+ written: string[];
125
+ removed: string[];
126
+ dir: string;
127
+ };
128
+ //# sourceMappingURL=enforce.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enforce.d.ts","sourceRoot":"","sources":["../../src/placement/enforce.ts"],"names":[],"mappings":"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"}