@miller-tech/uap 2.21.3 → 2.36.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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,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":"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"}
|
|
@@ -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":"AAyBA,OAAO,EAAkB,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACtE,OAAO,EAML,KAAK,iBAAiB,EAGvB,MAAM,aAAa,CAAC;AAErB,OAAO,EAAoC,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AAIrF,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,OAAO,CAAC;IACZ,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,sBAAsB;IACrC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,kDAAkD;AAClD,MAAM,WAAW,WAAW;IAC1B,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,wEAAwE;IACxE,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC1C;;;kCAG8B;IAC9B,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,EAAE,GAAG,IAAI,CAAC,CAAC;IAC5E,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;IAClD,2EAA2E;IAC3E,YAAY,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IACjC;;yEAEqE;IACrE,YAAY,CAAC,CAAC,QAAQ,EAAE,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;IAC1E,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjC,KAAK,IAAI,MAAM,CAAC;CACjB;AAED,4EAA4E;AAC5E,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,mBAAmB,IAAI,eAAe,CAOrD;AAOD,wBAAgB,kBAAkB,CAAC,IAAI,CAAC,EAAE;IAAE,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,WAAW,CAmErG;AAED,MAAM,WAAW,WAAW;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,WAAW,CAAC;IACnB,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B;6EACyE;IACzE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;0BAIsB;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAaD;0EAC0E;AAC1E,wBAAsB,aAAa,CACjC,QAAQ,EAAE,aAAa,EACvB,cAAc,EAAE,MAAM,EACtB,QAAQ,EAAE,eAAe,EACzB,IAAI,GAAE,WAAgB,GACrB,OAAO,CAAC,aAAa,CAAC,CAoIxB;AA4KD;;;;qBAIqB;AACrB,wBAAsB,kBAAkB,CACtC,IAAI,GAAE;IAAE,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAO,GACxD,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,sBAAsB,EAAE,CAAC,GAAG,IAAI,CAAC,CAavD;AAMD;;;;;;;;;gEASgE;AAChE,wBAAsB,uBAAuB,CAC3C,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,IAAI,GAAE,WAAW,GAAG;IAAE,eAAe,CAAC,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAO,GACtF,OAAO,CAAC,aAAa,CAAC,CA0CxB;AAED,2EAA2E;AAC3E,wBAAgB,uBAAuB,CACrC,WAAW,EAAE,MAAM,EACnB,IAAI,GAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAA;CAAO,GACjC;IAAE,SAAS,EAAE,OAAO,CAAA;CAAE,CASxB;AAED;;8EAE8E;AAC9E,wBAAsB,aAAa,CACjC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE,WAAgB,GACrB,OAAO,CAAC,aAAa,CAAC,CASxB;AAED;2DAC2D;AAC3D,wBAAsB,eAAe,CACnC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE,WAAgB,GACrB,OAAO,CAAC,aAAa,CAAC,CAoDxB;AAMD;;;;;;;;;gCASgC;AAChC,wBAAgB,yBAAyB,CACvC,QAAQ,EAAE,aAAa,EACvB,IAAI,GAAE;IAAE,YAAY,CAAC,EAAE,MAAM,CAAA;CAAO,GACnC;IAAE,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CA6EvD"}
|