@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.
- package/README.md +22 -10
- package/config/model-profiles/qwen38-coder390.json +46 -0
- package/config/model-profiles/qwen38.json +3 -2
- package/config/model-registry.json +110 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/benchmarks/paired/types.d.ts +2 -2
- package/dist/bin/cli.js +315 -20
- package/dist/bin/cli.js.map +1 -1
- package/dist/cli/attention.d.ts +6 -0
- package/dist/cli/attention.d.ts.map +1 -0
- package/dist/cli/attention.js +115 -0
- package/dist/cli/attention.js.map +1 -0
- package/dist/cli/bench-report.d.ts +82 -0
- package/dist/cli/bench-report.d.ts.map +1 -0
- package/dist/cli/bench-report.js +237 -0
- package/dist/cli/bench-report.js.map +1 -0
- package/dist/cli/clients.d.ts +6 -0
- package/dist/cli/clients.d.ts.map +1 -0
- package/dist/cli/clients.js +142 -0
- package/dist/cli/clients.js.map +1 -0
- package/dist/cli/dashboard-grants.d.ts +26 -0
- package/dist/cli/dashboard-grants.d.ts.map +1 -0
- package/dist/cli/dashboard-grants.js +69 -0
- package/dist/cli/dashboard-grants.js.map +1 -0
- package/dist/cli/dashboard-pace.d.ts +31 -0
- package/dist/cli/dashboard-pace.d.ts.map +1 -0
- package/dist/cli/dashboard-pace.js +42 -0
- package/dist/cli/dashboard-pace.js.map +1 -0
- package/dist/cli/dashboard-session.d.ts +25 -0
- package/dist/cli/dashboard-session.d.ts.map +1 -0
- package/dist/cli/dashboard-session.js +814 -0
- package/dist/cli/dashboard-session.js.map +1 -0
- package/dist/cli/dashboard-types.d.ts +18 -0
- package/dist/cli/dashboard-types.d.ts.map +1 -0
- package/dist/cli/dashboard-types.js +9 -0
- package/dist/cli/dashboard-types.js.map +1 -0
- package/dist/cli/dashboard.d.ts +2 -18
- package/dist/cli/dashboard.d.ts.map +1 -1
- package/dist/cli/dashboard.js +43 -793
- package/dist/cli/dashboard.js.map +1 -1
- package/dist/cli/deliver.d.ts +35 -0
- package/dist/cli/deliver.d.ts.map +1 -1
- package/dist/cli/deliver.js +93 -9
- package/dist/cli/deliver.js.map +1 -1
- package/dist/cli/diff.d.ts +85 -0
- package/dist/cli/diff.d.ts.map +1 -0
- package/dist/cli/diff.js +293 -0
- package/dist/cli/diff.js.map +1 -0
- package/dist/cli/eta.d.ts +35 -0
- package/dist/cli/eta.d.ts.map +1 -0
- package/dist/cli/eta.js +101 -0
- package/dist/cli/eta.js.map +1 -0
- package/dist/cli/handsfree.d.ts +7 -0
- package/dist/cli/handsfree.d.ts.map +1 -1
- package/dist/cli/handsfree.js +27 -0
- package/dist/cli/handsfree.js.map +1 -1
- package/dist/cli/hooks.d.ts.map +1 -1
- package/dist/cli/hooks.js +126 -5
- package/dist/cli/hooks.js.map +1 -1
- package/dist/cli/loops.d.ts +24 -5
- package/dist/cli/loops.d.ts.map +1 -1
- package/dist/cli/loops.js +92 -12
- package/dist/cli/loops.js.map +1 -1
- package/dist/cli/models.d.ts +62 -0
- package/dist/cli/models.d.ts.map +1 -0
- package/dist/cli/models.js +567 -0
- package/dist/cli/models.js.map +1 -0
- package/dist/cli/plan.d.ts +9 -0
- package/dist/cli/plan.d.ts.map +1 -1
- package/dist/cli/plan.js +57 -4
- package/dist/cli/plan.js.map +1 -1
- package/dist/cli/proxy-relay.d.ts +55 -0
- package/dist/cli/proxy-relay.d.ts.map +1 -0
- package/dist/cli/proxy-relay.js +116 -0
- package/dist/cli/proxy-relay.js.map +1 -0
- package/dist/cli/proxy.d.ts +4 -0
- package/dist/cli/proxy.d.ts.map +1 -1
- package/dist/cli/proxy.js +26 -1
- package/dist/cli/proxy.js.map +1 -1
- package/dist/cli/sandbox.d.ts +101 -4
- package/dist/cli/sandbox.d.ts.map +1 -1
- package/dist/cli/sandbox.js +393 -44
- package/dist/cli/sandbox.js.map +1 -1
- package/dist/cli/self.d.ts +13 -0
- package/dist/cli/self.d.ts.map +1 -0
- package/dist/cli/self.js +94 -0
- package/dist/cli/self.js.map +1 -0
- package/dist/cli/systemd-services.d.ts +2 -8
- package/dist/cli/systemd-services.d.ts.map +1 -1
- package/dist/cli/systemd-services.js +5 -20
- package/dist/cli/systemd-services.js.map +1 -1
- package/dist/cli/task.js +1 -1
- package/dist/cli/task.js.map +1 -1
- package/dist/cli/wizard-config.d.ts.map +1 -1
- package/dist/cli/wizard-config.js +31 -2
- package/dist/cli/wizard-config.js.map +1 -1
- package/dist/config/settings-registry.d.ts +1 -1
- package/dist/config/settings-registry.d.ts.map +1 -1
- package/dist/config/settings-registry.js +108 -0
- package/dist/config/settings-registry.js.map +1 -1
- package/dist/coordination/capability-router.js +2 -2
- package/dist/coordination/capability-router.js.map +1 -1
- package/dist/coordination/database.d.ts +13 -0
- package/dist/coordination/database.d.ts.map +1 -1
- package/dist/coordination/database.js +63 -7
- package/dist/coordination/database.js.map +1 -1
- package/dist/coordination/eta-inject.d.ts +32 -0
- package/dist/coordination/eta-inject.d.ts.map +1 -0
- package/dist/coordination/eta-inject.js +64 -0
- package/dist/coordination/eta-inject.js.map +1 -0
- package/dist/coordination/reactor.d.ts.map +1 -1
- package/dist/coordination/reactor.js +14 -0
- package/dist/coordination/reactor.js.map +1 -1
- package/dist/coordination/service.d.ts +18 -8
- package/dist/coordination/service.d.ts.map +1 -1
- package/dist/coordination/service.js +38 -28
- package/dist/coordination/service.js.map +1 -1
- package/dist/dashboard/client-registry.d.ts +163 -0
- package/dist/dashboard/client-registry.d.ts.map +1 -0
- package/dist/dashboard/client-registry.js +549 -0
- package/dist/dashboard/client-registry.js.map +1 -0
- package/dist/dashboard/controls.d.ts.map +1 -1
- package/dist/dashboard/controls.js +33 -4
- package/dist/dashboard/controls.js.map +1 -1
- package/dist/dashboard/data-seeder.d.ts +10 -7
- package/dist/dashboard/data-seeder.d.ts.map +1 -1
- package/dist/dashboard/data-seeder.js +31 -75
- package/dist/dashboard/data-seeder.js.map +1 -1
- package/dist/dashboard/data-service.d.ts +9 -0
- package/dist/dashboard/data-service.d.ts.map +1 -1
- package/dist/dashboard/data-service.js +32 -3
- package/dist/dashboard/data-service.js.map +1 -1
- package/dist/dashboard/placement-routes.d.ts +119 -0
- package/dist/dashboard/placement-routes.d.ts.map +1 -0
- package/dist/dashboard/placement-routes.js +331 -0
- package/dist/dashboard/placement-routes.js.map +1 -0
- package/dist/dashboard/server.d.ts.map +1 -1
- package/dist/dashboard/server.js +408 -2
- package/dist/dashboard/server.js.map +1 -1
- package/dist/delivery/attention.d.ts +130 -0
- package/dist/delivery/attention.d.ts.map +1 -0
- package/dist/delivery/attention.js +341 -0
- package/dist/delivery/attention.js.map +1 -0
- package/dist/delivery/decompose.d.ts +18 -0
- package/dist/delivery/decompose.d.ts.map +1 -1
- package/dist/delivery/decompose.js +22 -1
- package/dist/delivery/decompose.js.map +1 -1
- package/dist/delivery/eta-estimate.d.ts +111 -0
- package/dist/delivery/eta-estimate.d.ts.map +1 -0
- package/dist/delivery/eta-estimate.js +171 -0
- package/dist/delivery/eta-estimate.js.map +1 -0
- package/dist/delivery/eta.d.ts +23 -0
- package/dist/delivery/eta.d.ts.map +1 -0
- package/dist/delivery/eta.js +59 -0
- package/dist/delivery/eta.js.map +1 -0
- package/dist/delivery/orchestrated-mission.d.ts +10 -0
- package/dist/delivery/orchestrated-mission.d.ts.map +1 -1
- package/dist/delivery/orchestrated-mission.js +15 -1
- package/dist/delivery/orchestrated-mission.js.map +1 -1
- package/dist/delivery/plan-check.d.ts +7 -1
- package/dist/delivery/plan-check.d.ts.map +1 -1
- package/dist/delivery/plan-check.js +23 -2
- package/dist/delivery/plan-check.js.map +1 -1
- package/dist/delivery/run-state.d.ts +1 -1
- package/dist/delivery/run-state.d.ts.map +1 -1
- package/dist/delivery/run-state.js +8 -1
- package/dist/delivery/run-state.js.map +1 -1
- package/dist/delivery/task-orchestrator.d.ts +8 -3
- package/dist/delivery/task-orchestrator.d.ts.map +1 -1
- package/dist/delivery/task-orchestrator.js +75 -43
- package/dist/delivery/task-orchestrator.js.map +1 -1
- package/dist/delivery/task-sync.d.ts +21 -0
- package/dist/delivery/task-sync.d.ts.map +1 -1
- package/dist/delivery/task-sync.js +44 -8
- package/dist/delivery/task-sync.js.map +1 -1
- package/dist/delivery/task-workspace.d.ts +3 -3
- package/dist/delivery/task-workspace.js +3 -3
- package/dist/generators/self-card.d.ts +65 -0
- package/dist/generators/self-card.d.ts.map +1 -0
- package/dist/generators/self-card.js +256 -0
- package/dist/generators/self-card.js.map +1 -0
- package/dist/inference/probe.js +1 -1
- package/dist/inference/probe.js.map +1 -1
- package/dist/memory/prepopulate.d.ts.map +1 -1
- package/dist/memory/prepopulate.js +2 -0
- package/dist/memory/prepopulate.js.map +1 -1
- package/dist/models/analytics.d.ts +32 -0
- package/dist/models/analytics.d.ts.map +1 -1
- package/dist/models/analytics.js +68 -5
- package/dist/models/analytics.js.map +1 -1
- package/dist/models/plan-readability.d.ts +55 -0
- package/dist/models/plan-readability.d.ts.map +1 -0
- package/dist/models/plan-readability.js +339 -0
- package/dist/models/plan-readability.js.map +1 -0
- package/dist/placement/admission.d.ts +59 -0
- package/dist/placement/admission.d.ts.map +1 -0
- package/dist/placement/admission.js +197 -0
- package/dist/placement/admission.js.map +1 -0
- package/dist/placement/auto.d.ts +44 -0
- package/dist/placement/auto.d.ts.map +1 -0
- package/dist/placement/auto.js +212 -0
- package/dist/placement/auto.js.map +1 -0
- package/dist/placement/enforce.d.ts +128 -0
- package/dist/placement/enforce.d.ts.map +1 -0
- package/dist/placement/enforce.js +655 -0
- package/dist/placement/enforce.js.map +1 -0
- package/dist/placement/idle.d.ts +50 -0
- package/dist/placement/idle.d.ts.map +1 -0
- package/dist/placement/idle.js +213 -0
- package/dist/placement/idle.js.map +1 -0
- package/dist/placement/ledger.d.ts +323 -0
- package/dist/placement/ledger.d.ts.map +1 -0
- package/dist/placement/ledger.js +263 -0
- package/dist/placement/ledger.js.map +1 -0
- package/dist/placement/preview.d.ts +51 -0
- package/dist/placement/preview.d.ts.map +1 -0
- package/dist/placement/preview.js +61 -0
- package/dist/placement/preview.js.map +1 -0
- package/dist/placement/probes.d.ts +47 -0
- package/dist/placement/probes.d.ts.map +1 -0
- package/dist/placement/probes.js +207 -0
- package/dist/placement/probes.js.map +1 -0
- package/dist/placement/proxy-env.d.ts +21 -0
- package/dist/placement/proxy-env.d.ts.map +1 -0
- package/dist/placement/proxy-env.js +42 -0
- package/dist/placement/proxy-env.js.map +1 -0
- package/dist/placement/registry.d.ts +1038 -0
- package/dist/placement/registry.d.ts.map +1 -0
- package/dist/placement/registry.js +274 -0
- package/dist/placement/registry.js.map +1 -0
- package/dist/policies/liveness.d.ts +6 -8
- package/dist/policies/liveness.d.ts.map +1 -1
- package/dist/policies/liveness.js +6 -25
- package/dist/policies/liveness.js.map +1 -1
- package/dist/policies/sandbox-anchor.d.ts +91 -0
- package/dist/policies/sandbox-anchor.d.ts.map +1 -0
- package/dist/policies/sandbox-anchor.js +187 -0
- package/dist/policies/sandbox-anchor.js.map +1 -0
- package/dist/policies/sandbox-grants.d.ts +131 -0
- package/dist/policies/sandbox-grants.d.ts.map +1 -0
- package/dist/policies/sandbox-grants.js +562 -0
- package/dist/policies/sandbox-grants.js.map +1 -0
- package/dist/state/manifest.d.ts +4 -2
- package/dist/state/manifest.d.ts.map +1 -1
- package/dist/state/manifest.js +32 -16
- package/dist/state/manifest.js.map +1 -1
- package/dist/utils/checkout-root.d.ts +2 -0
- package/dist/utils/checkout-root.d.ts.map +1 -0
- package/dist/utils/checkout-root.js +43 -0
- package/dist/utils/checkout-root.js.map +1 -0
- package/dist/utils/resolve-uap-bin.d.ts +5 -0
- package/dist/utils/resolve-uap-bin.d.ts.map +1 -0
- package/dist/utils/resolve-uap-bin.js +33 -0
- package/dist/utils/resolve-uap-bin.js.map +1 -0
- package/dist/utils/terminal-safe.d.ts +12 -0
- package/dist/utils/terminal-safe.d.ts.map +1 -0
- package/dist/utils/terminal-safe.js +53 -0
- package/dist/utils/terminal-safe.js.map +1 -0
- package/docs/ATTRIBUTION.md +1 -0
- package/docs/DASHBOARD_UPLIFT_SPEC.md +8 -8
- package/docs/INDEX.md +6 -5
- package/docs/architecture/OVERVIEW.md +15 -3
- package/docs/architecture/PROTOCOL.md +7 -1
- package/docs/architecture/adr/0006-operator-directed-model-placement.md +86 -0
- package/docs/architecture/adr/0007-auto-displacement-allowlist-as-standing-consent.md +79 -0
- package/docs/architecture/adr/0008-fleet-clients-link-out.md +92 -0
- package/docs/architecture/adr/0009-completion-triggered-dispatch-and-per-leaf-model-dials.md +71 -0
- package/docs/architecture/adr/0010-attention-queue-and-afk-auto-decider.md +159 -0
- package/docs/architecture/adr/0011-sandbox-grants-and-network-egress-boundary.md +219 -0
- package/docs/architecture/adr/0012-operator-legibility-surfaces.md +175 -0
- package/docs/getting-started/INSTALLATION.md +1 -1
- package/docs/guides/AUTOMATIC_FEATURES.md +8 -0
- package/docs/guides/LOCAL_MODELS.md +1 -1
- package/docs/guides/PROXY.md +2 -1
- package/docs/guides/SANDBOX.md +91 -4
- package/docs/integrations/MCP_ROUTER.md +1 -1
- package/docs/plans/model-performance-proof-options.md +176 -0
- package/docs/reference/API.md +1 -1
- package/docs/reference/CLI.md +169 -4
- package/docs/reference/COMPARISON.md +1 -1
- package/docs/reference/CONFIGURATION.md +12 -3
- package/docs/reference/CONFIGURATION_REFERENCE.md +269 -14
- package/docs/reference/DATABASE_SCHEMA.md +1 -1
- package/docs/reference/FEATURES.md +15 -3
- package/docs/reference/PATTERNS.md +1 -1
- package/docs/reference/PLATFORMS.md +14 -1
- package/docs/specs/operator-model-placement.md +1314 -0
- package/docs/specs/operator-model-placement.review.md +339 -0
- package/package.json +2 -2
- package/scripts/maintenance/publish-npm.sh +23 -11
- package/scripts/version-bump.sh +6 -51
- package/src/policies/enforcers/__pycache__/_common.cpython-311.pyc +0 -0
- package/src/policies/enforcers/__pycache__/expert_review_required.cpython-311.pyc +0 -0
- package/src/policies/enforcers/__pycache__/quality_metrics_gate.cpython-311.pyc +0 -0
- package/src/policies/schemas/policies/mandatory-testing-deployment.md +2 -5
- package/templates/hooks/__pycache__/deliver_autoroute.cpython-311.pyc +0 -0
- package/templates/hooks/session-start.sh +44 -0
- package/tools/agents/UAP/cli.py +14 -3
- package/tools/agents/UAP/paths.py +50 -0
- package/tools/agents/scripts/__pycache__/toolcall_path_normalizer.cpython-311.pyc +0 -0
- package/tools/agents/scripts/anthropic_proxy.py +1386 -99
- package/tools/agents/scripts/toolcall_path_normalizer.py +46 -1
- package/tools/agents/tests/test_anthropic_proxy_streaming.py +29 -6
- package/tools/agents/tests/test_doubling_break.py +64 -5
- package/tools/agents/tests/test_error_loop_break.py +10 -4
- package/tools/agents/tests/test_error_loop_ignores_correctives.py +13 -2
- package/tools/agents/tests/test_lockstep_escalation.py +280 -9
- package/tools/agents/tests/test_path_containment.py +94 -0
- package/tools/agents/tests/test_proxy_unix_socket.py +162 -0
- package/tools/agents/tests/test_signature_quality.py +349 -0
- package/tools/agents/tests/test_uap_compliance.py +11 -1
- package/web/dash/core.js +1 -0
- package/web/dash/tab-clients.js +196 -0
- package/web/dash/tab-models.js +387 -8
- package/web/dash/tab-policies.js +87 -4
- package/web/dash/tabs.js +77 -73
- package/web/dashboard.html +6 -0
- package/src/policies/enforcers/__pycache__/_common.cpython-312.pyc +0 -0
- package/src/policies/enforcers/__pycache__/expert_review_required.cpython-312.pyc +0 -0
- package/src/policies/enforcers/__pycache__/quality_metrics_gate.cpython-312.pyc +0 -0
- package/templates/hooks/__pycache__/deliver_autoroute.cpython-312.pyc +0 -0
- package/tools/agents/scripts/__pycache__/toolcall_path_normalizer.cpython-312.pyc +0 -0
- package/web/dash/tab-agents.js +0 -21
- package/web/dash/tab-deliver.js +0 -12
- package/web/dash/tab-memory.js +0 -12
- package/web/dash/tab-orchestration.js +0 -21
- package/web/dash/tab-overview.js +0 -21
- 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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"}
|