@miller-tech/uap 2.1.7 → 2.11.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 (143) hide show
  1. package/README.md +7 -6
  2. package/config/capacity-policy.json +32 -0
  3. package/config/classify-thresholds.json +9 -0
  4. package/config/supervise-policy.json +13 -0
  5. package/dist/.tsbuildinfo +1 -1
  6. package/dist/bin/cli.js +87 -2
  7. package/dist/bin/cli.js.map +1 -1
  8. package/dist/capacity/policy.d.ts +67 -0
  9. package/dist/capacity/policy.d.ts.map +1 -0
  10. package/dist/capacity/policy.js +133 -0
  11. package/dist/capacity/policy.js.map +1 -0
  12. package/dist/capacity/probe.d.ts +23 -0
  13. package/dist/capacity/probe.d.ts.map +1 -0
  14. package/dist/capacity/probe.js +93 -0
  15. package/dist/capacity/probe.js.map +1 -0
  16. package/dist/classify/baseline.d.ts +24 -0
  17. package/dist/classify/baseline.d.ts.map +1 -0
  18. package/dist/classify/baseline.js +140 -0
  19. package/dist/classify/baseline.js.map +1 -0
  20. package/dist/classify/index.d.ts +30 -0
  21. package/dist/classify/index.d.ts.map +1 -0
  22. package/dist/classify/index.js +84 -0
  23. package/dist/classify/index.js.map +1 -0
  24. package/dist/classify/shadow.d.ts +34 -0
  25. package/dist/classify/shadow.d.ts.map +1 -0
  26. package/dist/classify/shadow.js +33 -0
  27. package/dist/classify/shadow.js.map +1 -0
  28. package/dist/classify/types.d.ts +64 -0
  29. package/dist/classify/types.d.ts.map +1 -0
  30. package/dist/classify/types.js +37 -0
  31. package/dist/classify/types.js.map +1 -0
  32. package/dist/cli/classify.d.ts +24 -0
  33. package/dist/cli/classify.d.ts.map +1 -0
  34. package/dist/cli/classify.js +94 -0
  35. package/dist/cli/classify.js.map +1 -0
  36. package/dist/cli/deliver.d.ts.map +1 -1
  37. package/dist/cli/deliver.js +26 -0
  38. package/dist/cli/deliver.js.map +1 -1
  39. package/dist/cli/doctor.d.ts +9 -0
  40. package/dist/cli/doctor.d.ts.map +1 -0
  41. package/dist/cli/doctor.js +49 -0
  42. package/dist/cli/doctor.js.map +1 -0
  43. package/dist/cli/review.d.ts +12 -0
  44. package/dist/cli/review.d.ts.map +1 -0
  45. package/dist/cli/review.js +118 -0
  46. package/dist/cli/review.js.map +1 -0
  47. package/dist/cli/supervise.d.ts +8 -0
  48. package/dist/cli/supervise.d.ts.map +1 -0
  49. package/dist/cli/supervise.js +76 -0
  50. package/dist/cli/supervise.js.map +1 -0
  51. package/dist/delivery/convergence-loop.d.ts +8 -0
  52. package/dist/delivery/convergence-loop.d.ts.map +1 -1
  53. package/dist/delivery/convergence-loop.js +1 -0
  54. package/dist/delivery/convergence-loop.js.map +1 -1
  55. package/dist/delivery/delivery-result.d.ts.map +1 -1
  56. package/dist/delivery/delivery-result.js +5 -0
  57. package/dist/delivery/delivery-result.js.map +1 -1
  58. package/dist/delivery/gate-evidence.d.ts +143 -0
  59. package/dist/delivery/gate-evidence.d.ts.map +1 -0
  60. package/dist/delivery/gate-evidence.js +239 -0
  61. package/dist/delivery/gate-evidence.js.map +1 -0
  62. package/dist/delivery/index.d.ts +1 -0
  63. package/dist/delivery/index.d.ts.map +1 -1
  64. package/dist/delivery/index.js +1 -0
  65. package/dist/delivery/index.js.map +1 -1
  66. package/dist/evals/routing-corpus.d.ts +35 -0
  67. package/dist/evals/routing-corpus.d.ts.map +1 -0
  68. package/dist/evals/routing-corpus.js +142 -0
  69. package/dist/evals/routing-corpus.js.map +1 -0
  70. package/dist/evals/routing-eval.d.ts +104 -0
  71. package/dist/evals/routing-eval.d.ts.map +1 -0
  72. package/dist/evals/routing-eval.js +231 -0
  73. package/dist/evals/routing-eval.js.map +1 -0
  74. package/dist/evals/tfidf.d.ts +35 -0
  75. package/dist/evals/tfidf.d.ts.map +1 -0
  76. package/dist/evals/tfidf.js +132 -0
  77. package/dist/evals/tfidf.js.map +1 -0
  78. package/dist/review/prepass.d.ts +88 -0
  79. package/dist/review/prepass.d.ts.map +1 -0
  80. package/dist/review/prepass.js +428 -0
  81. package/dist/review/prepass.js.map +1 -0
  82. package/dist/review/visual-captures.d.ts +60 -0
  83. package/dist/review/visual-captures.d.ts.map +1 -0
  84. package/dist/review/visual-captures.js +222 -0
  85. package/dist/review/visual-captures.js.map +1 -0
  86. package/dist/supervise/assess.d.ts +73 -0
  87. package/dist/supervise/assess.d.ts.map +1 -0
  88. package/dist/supervise/assess.js +215 -0
  89. package/dist/supervise/assess.js.map +1 -0
  90. package/dist/supervise/config.d.ts +13 -0
  91. package/dist/supervise/config.d.ts.map +1 -0
  92. package/dist/supervise/config.js +81 -0
  93. package/dist/supervise/config.js.map +1 -0
  94. package/dist/supervise/loop.d.ts +78 -0
  95. package/dist/supervise/loop.d.ts.map +1 -0
  96. package/dist/supervise/loop.js +311 -0
  97. package/dist/supervise/loop.js.map +1 -0
  98. package/dist/supervise/observe.d.ts +31 -0
  99. package/dist/supervise/observe.d.ts.map +1 -0
  100. package/dist/supervise/observe.js +172 -0
  101. package/dist/supervise/observe.js.map +1 -0
  102. package/dist/supervise/policy.d.ts +16 -0
  103. package/dist/supervise/policy.d.ts.map +1 -0
  104. package/dist/supervise/policy.js +147 -0
  105. package/dist/supervise/policy.js.map +1 -0
  106. package/dist/supervise/types.d.ts +109 -0
  107. package/dist/supervise/types.d.ts.map +1 -0
  108. package/dist/supervise/types.js +11 -0
  109. package/dist/supervise/types.js.map +1 -0
  110. package/docs/INDEX.md +15 -4
  111. package/docs/architecture/adr/0001-automode-risk-floor.md +75 -0
  112. package/docs/architecture/adr/0002-evidence-bound-ship.md +81 -0
  113. package/docs/guides/CAPACITY_POLICY.md +99 -0
  114. package/docs/performance/automode-risk-calibration.md +72 -0
  115. package/docs/performance/reliability-ladder.md +78 -0
  116. package/docs/plans/system1-uplift-2026-09-18.md +190 -0
  117. package/docs/reference/automode-risk.md +134 -0
  118. package/docs/reference/evidence-bound-ship.md +146 -0
  119. package/docs/reference/review-prepass.md +77 -0
  120. package/docs/reference/routing-evals.md +71 -0
  121. package/docs/reference/semantic-supervisor.md +140 -0
  122. package/docs/reference/system1-classifier.md +103 -0
  123. package/docs/reference/visual-captures.md +113 -0
  124. package/package.json +5 -3
  125. package/src/policies/enforcers/__pycache__/_common.cpython-312.pyc +0 -0
  126. package/src/policies/enforcers/__pycache__/expert_review_required.cpython-312.pyc +0 -0
  127. package/src/policies/enforcers/__pycache__/quality_metrics_gate.cpython-312.pyc +0 -0
  128. package/src/policies/enforcers/enforcement_self_protect.py +25 -2
  129. package/src/policies/enforcers/evidence_bound_ship.py +257 -0
  130. package/src/policies/enforcers/expert_review_required.py +120 -0
  131. package/src/policies/schemas/policies/evidence-bound-ship.md +106 -0
  132. package/src/policies/schemas/policies/visual-verification.md +8 -0
  133. package/templates/hooks/__pycache__/deliver_autoroute.cpython-312.pyc +0 -0
  134. package/tools/agents/scripts/__pycache__/toolcall_path_normalizer.cpython-312.pyc +0 -0
  135. package/tools/agents/scripts/anthropic_proxy.py +782 -19
  136. package/tools/agents/scripts/project_telemetry.py +52 -0
  137. package/tools/agents/scripts/tool_risk.py +789 -0
  138. package/tools/agents/scripts/tool_risk_calibrate.py +255 -0
  139. package/tools/agents/tests/fixtures/tool_risk_cases.json +124 -0
  140. package/tools/agents/tests/test_enforcer_escape_hatches.py +13 -0
  141. package/tools/agents/tests/test_gate_evidence.py +56 -0
  142. package/tools/agents/tests/test_tool_risk.py +521 -0
  143. package/tools/agents/tests/test_verbatim_compact.py +629 -0
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Capacity policy as code (uplift 0.5).
3
+ *
4
+ * Generalizes the 2026-09-18 llama.cpp OOM-fix discipline: every UAP-managed
5
+ * service declares its resource budget, its headroom requirement, and what
6
+ * GREEN/RED/DARK mean for it. `uap doctor` probes each declared service and
7
+ * reports violations. The policy is data (config/capacity-policy.json); the
8
+ * judgment is deterministic code; fail-open posture is per-probe (a missing
9
+ * systemctl/nvidia-smi yields UNKNOWN, never a fabricated GREEN).
10
+ */
11
+ export class PolicyError extends Error {
12
+ }
13
+ /** systemd unit names: strict charset, never a leading dash. A name like
14
+ * "--host=x.service" ends in .service but is parsed by systemctl as a FLAG —
15
+ * --host would open an SSH connection to an attacker-chosen host (CWE-88).
16
+ * The probe also passes `--` before the unit; validation is the first wall. */
17
+ const UNIT_NAME_RE = /^[A-Za-z0-9][A-Za-z0-9:_.@-]*\.service$/;
18
+ /** Parse + validate a policy document. Malformed input throws loudly with
19
+ * the offending path — a capacity policy that half-loads is worse than none. */
20
+ export function parsePolicy(text, source = 'policy') {
21
+ let data;
22
+ try {
23
+ data = JSON.parse(text);
24
+ }
25
+ catch (err) {
26
+ throw new PolicyError(`${source}: invalid JSON — ${err.message}`);
27
+ }
28
+ const policy = data;
29
+ if (policy?.version !== 1)
30
+ throw new PolicyError(`${source}: "version": 1 required`);
31
+ if (!Array.isArray(policy.services) || policy.services.length === 0) {
32
+ throw new PolicyError(`${source}: "services" must be a non-empty array`);
33
+ }
34
+ const seen = new Set();
35
+ for (const [i, svc] of policy.services.entries()) {
36
+ const where = `${source} services[${i}]`;
37
+ if (!svc || typeof svc !== 'object')
38
+ throw new PolicyError(`${where}: must be an object`);
39
+ if (typeof svc.name !== 'string' || !svc.name.trim()) {
40
+ throw new PolicyError(`${where}: "name" is required`);
41
+ }
42
+ if (seen.has(svc.name))
43
+ throw new PolicyError(`${where}: duplicate service "${svc.name}"`);
44
+ seen.add(svc.name);
45
+ if (typeof svc.systemd?.unit !== 'string' || !UNIT_NAME_RE.test(svc.systemd.unit)) {
46
+ throw new PolicyError(`${where}: systemd.unit must match ${UNIT_NAME_RE} (no flags, whitespace, or leading dashes)`);
47
+ }
48
+ if (svc.systemd.scope !== 'user' && svc.systemd.scope !== 'system') {
49
+ throw new PolicyError(`${where}: systemd.scope must be "user" or "system"`);
50
+ }
51
+ for (const [section, keys] of [
52
+ ['budget', ['vramMiB', 'rssMiB']],
53
+ ['headroom', ['gpuMinFreeMiB']],
54
+ ]) {
55
+ const block = svc[section];
56
+ for (const key of keys) {
57
+ const v = block?.[key];
58
+ if (v !== undefined && (typeof v !== 'number' || !Number.isFinite(v) || v < 0)) {
59
+ throw new PolicyError(`${where}: ${section}.${key} must be a non-negative number`);
60
+ }
61
+ }
62
+ }
63
+ const rb = svc.restartBudget;
64
+ if (rb !== undefined) {
65
+ for (const key of ['knownRestarts', 'allowedNew']) {
66
+ const v = rb[key];
67
+ if (typeof v !== 'number' || !Number.isInteger(v) || v < 0) {
68
+ throw new PolicyError(`${where}: restartBudget.${key} must be a non-negative integer`);
69
+ }
70
+ }
71
+ }
72
+ }
73
+ return policy;
74
+ }
75
+ /** Pure health computation over a probed state — the deterministic core,
76
+ * fully testable without systemd or a GPU. */
77
+ export function computeHealth(svc, probed, probeAvailable) {
78
+ const reasons = [];
79
+ if (!probeAvailable.systemd) {
80
+ return { health: 'UNKNOWN', reasons: ['systemctl unavailable — cannot probe the unit'] };
81
+ }
82
+ const state = probed.activeState ?? 'unknown';
83
+ // DARK: not serving. The discipline from the OOM fix: a service that is
84
+ // down or crash-looping is not "degraded", it is absent.
85
+ if (state === 'failed' || state === 'inactive' || state === 'unknown') {
86
+ return { health: 'DARK', reasons: [`unit ${svc.systemd.unit} is ${state} — not serving`] };
87
+ }
88
+ // RED: activating (esp. subState=auto-restart) means the unit is NOT yet
89
+ // serving — a Restart=always crash loop sits exactly here. Down-looping is
90
+ // never GREEN, with or without a declared restartBudget.
91
+ if (state === 'activating' || probed.subState === 'auto-restart') {
92
+ return {
93
+ health: 'RED',
94
+ reasons: [`unit ${svc.systemd.unit} is ${state}/${probed.subState ?? '?'} — not yet serving (crash-loop candidate)`],
95
+ };
96
+ }
97
+ // RED: serving, but a budget is violated.
98
+ const rb = svc.restartBudget;
99
+ if (rb && probed.nRestarts !== undefined) {
100
+ const allowed = rb.knownRestarts + rb.allowedNew;
101
+ if (probed.nRestarts > allowed) {
102
+ reasons.push(`NRestarts=${probed.nRestarts} exceeds budget ${allowed} ` +
103
+ `(${rb.knownRestarts} known + ${rb.allowedNew} new) — new crashes since the baseline`);
104
+ }
105
+ }
106
+ if (svc.headroom?.gpuMinFreeMiB !== undefined) {
107
+ if (!probeAvailable.gpu || probed.gpuFreeMiB === undefined) {
108
+ reasons.push('gpu headroom declared but nvidia-smi unavailable — headroom unverified');
109
+ }
110
+ else if (probed.gpuFreeMiB < svc.headroom.gpuMinFreeMiB) {
111
+ reasons.push(`GPU free ${probed.gpuFreeMiB} MiB below required headroom ${svc.headroom.gpuMinFreeMiB} MiB — OOM territory`);
112
+ }
113
+ }
114
+ // RSS budget: enforced against the unit's MemoryCurrent. Doctrine: a
115
+ // declared budget that cannot be verified is not healthy (RED), matching
116
+ // the unverified-headroom rule. budget.vramMiB is NOT compared against the
117
+ // process's total GPU footprint — it declares the service's own configured
118
+ // allocator limit (e.g. --vbr-vram), which the server enforces internally;
119
+ // the host-side invariant the doctor owns is gpuMinFreeMiB headroom.
120
+ if (svc.budget?.rssMiB !== undefined) {
121
+ if (probed.memoryCurrentMiB === undefined) {
122
+ reasons.push('rssMiB budget declared but MemoryCurrent unavailable — budget unverified');
123
+ }
124
+ else if (probed.memoryCurrentMiB > svc.budget.rssMiB) {
125
+ reasons.push(`RSS ${probed.memoryCurrentMiB} MiB exceeds declared budget ${svc.budget.rssMiB} MiB`);
126
+ }
127
+ }
128
+ if (reasons.length > 0)
129
+ return { health: 'RED', reasons };
130
+ reasons.push(`active (${probed.subState ?? 'running'}), all budgets inside policy`);
131
+ return { health: 'GREEN', reasons };
132
+ }
133
+ //# sourceMappingURL=policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy.js","sourceRoot":"","sources":["../../src/capacity/policy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import { type CapacityPolicy, type Health, type ServiceReport } from './policy.js';
2
+ export type ProbeState = ServiceReport['probed'];
3
+ /** systemctl show <unit> for the declared scope. Undefined fields when the
4
+ * unit or systemctl is missing. `--` terminates option parsing so even a
5
+ * validated unit name is never reinterpreted as a flag. */
6
+ export declare function probeSystemd(unit: string, scope: 'user' | 'system'): ProbeState | null;
7
+ /** Free VRAM (MiB) on GPU 0 via nvidia-smi; null when no GPU/tool. */
8
+ export declare function probeGpuFreeMiB(): number | null;
9
+ /** Policy resolution: explicit path, project config/, then user config. */
10
+ export declare function loadPolicy(projectDir: string, explicitPath?: string): {
11
+ policy: CapacityPolicy;
12
+ path: string;
13
+ };
14
+ export interface DoctorDeps {
15
+ probeSystemd: typeof probeSystemd;
16
+ probeGpuFreeMiB: typeof probeGpuFreeMiB;
17
+ }
18
+ /** Probe every declared service and compute its health. Deps are injectable
19
+ * so the matrix is testable without a host. */
20
+ export declare function runDoctor(policy: CapacityPolicy, deps?: DoctorDeps): ServiceReport[];
21
+ /** Worst-health rollup for exit codes: DARK > RED > UNKNOWN > GREEN. */
22
+ export declare function worstHealth(reports: ServiceReport[]): Health;
23
+ //# sourceMappingURL=probe.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"probe.d.ts","sourceRoot":"","sources":["../../src/capacity/probe.ts"],"names":[],"mappings":"AASA,OAAO,EAGL,KAAK,cAAc,EACnB,KAAK,MAAM,EACX,KAAK,aAAa,EACnB,MAAM,aAAa,CAAC;AAErB,MAAM,MAAM,UAAU,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAC;AAIjD;;2DAE2D;AAC3D,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,QAAQ,GAAG,UAAU,GAAG,IAAI,CA6BtF;AAED,sEAAsE;AACtE,wBAAgB,eAAe,IAAI,MAAM,GAAG,IAAI,CAa/C;AAED,2EAA2E;AAC3E,wBAAgB,UAAU,CAAC,UAAU,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,cAAc,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAgB9G;AAED,MAAM,WAAW,UAAU;IACzB,YAAY,EAAE,OAAO,YAAY,CAAC;IAClC,eAAe,EAAE,OAAO,eAAe,CAAC;CACzC;AAED;+CAC+C;AAC/C,wBAAgB,SAAS,CAAC,MAAM,EAAE,cAAc,EAAE,IAAI,GAAE,UAA8C,GAAG,aAAa,EAAE,CAYvH;AAED,wEAAwE;AACxE,wBAAgB,WAAW,CAAC,OAAO,EAAE,aAAa,EAAE,GAAG,MAAM,CAK5D"}
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Host probes for the capacity doctor: systemd unit state and GPU headroom.
3
+ * execFileSync with argument arrays only (no shell); every probe fails soft
4
+ * to an unavailable flag — the health computation decides what that means.
5
+ */
6
+ import { execFileSync } from 'child_process';
7
+ import { existsSync, readFileSync } from 'fs';
8
+ import { join } from 'path';
9
+ import { homedir } from 'os';
10
+ import { parsePolicy, computeHealth, } from './policy.js';
11
+ const PROBE_TIMEOUT_MS = 5000; // a wedged driver/D-Bus must fail soft, not hang
12
+ /** systemctl show <unit> for the declared scope. Undefined fields when the
13
+ * unit or systemctl is missing. `--` terminates option parsing so even a
14
+ * validated unit name is never reinterpreted as a flag. */
15
+ export function probeSystemd(unit, scope) {
16
+ // Flags BEFORE `--`, unit AFTER it: `show unit -p ...` would make `-p` a
17
+ // unit name (systemctl then reports the real unit as inactive — false DARK).
18
+ const args = scope === 'user' ? ['--user', 'show'] : ['show'];
19
+ try {
20
+ const out = execFileSync('systemctl', [...args, '-p', 'ActiveState,SubState,NRestarts,MainPID,MemoryCurrent', '--', unit], { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], timeout: PROBE_TIMEOUT_MS });
21
+ const props = Object.fromEntries(out.split('\n').filter(Boolean).map((l) => {
22
+ const eq = l.indexOf('=');
23
+ return [l.slice(0, eq), l.slice(eq + 1)];
24
+ }));
25
+ const restarts = Number(props.NRestarts);
26
+ const pid = Number(props.MainPID);
27
+ const mem = Number(props.MemoryCurrent); // bytes; "[not set]" → NaN
28
+ return {
29
+ activeState: props.ActiveState || 'unknown',
30
+ subState: props.SubState || undefined,
31
+ nRestarts: Number.isFinite(restarts) && props.NRestarts ? restarts : undefined,
32
+ mainPid: Number.isFinite(pid) && pid > 0 ? pid : undefined,
33
+ memoryCurrentMiB: Number.isFinite(mem) && mem > 0 ? Math.round(mem / 1048576) : undefined,
34
+ };
35
+ }
36
+ catch {
37
+ return null; // no systemctl / timeout (container, macOS) — UNKNOWN, not GREEN
38
+ }
39
+ }
40
+ /** Free VRAM (MiB) on GPU 0 via nvidia-smi; null when no GPU/tool. */
41
+ export function probeGpuFreeMiB() {
42
+ try {
43
+ const out = execFileSync('nvidia-smi', ['--query-gpu=memory.free', '--format=csv,noheader,nounits'], { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'], timeout: PROBE_TIMEOUT_MS });
44
+ const first = out.split('\n')[0]?.trim();
45
+ const n = Number(first);
46
+ return Number.isFinite(n) ? n : null;
47
+ }
48
+ catch {
49
+ return null;
50
+ }
51
+ }
52
+ /** Policy resolution: explicit path, project config/, then user config. */
53
+ export function loadPolicy(projectDir, explicitPath) {
54
+ const candidates = explicitPath
55
+ ? [explicitPath]
56
+ : [
57
+ join(projectDir, 'config', 'capacity-policy.json'),
58
+ join(homedir(), '.config', 'uap', 'capacity-policy.json'),
59
+ ];
60
+ for (const path of candidates) {
61
+ if (existsSync(path)) {
62
+ return { policy: parsePolicy(readFileSync(path, 'utf-8'), path), path };
63
+ }
64
+ }
65
+ throw new Error(`no capacity policy found (looked: ${candidates.join(', ')}) — ` +
66
+ 'declare one at config/capacity-policy.json; see docs/guides/CAPACITY_POLICY.md');
67
+ }
68
+ /** Probe every declared service and compute its health. Deps are injectable
69
+ * so the matrix is testable without a host. */
70
+ export function runDoctor(policy, deps = { probeSystemd, probeGpuFreeMiB }) {
71
+ // One GPU probe per run, shared across services (nvidia-smi is not cheap).
72
+ const gpuFree = deps.probeGpuFreeMiB();
73
+ return policy.services.map((svc) => {
74
+ const sys = deps.probeSystemd(svc.systemd.unit, svc.systemd.scope);
75
+ const probed = { ...(sys ?? {}), gpuFreeMiB: gpuFree ?? undefined };
76
+ const { health, reasons } = computeHealth(svc, probed, {
77
+ systemd: sys !== null,
78
+ gpu: gpuFree !== null,
79
+ });
80
+ return { name: svc.name, health, reasons, probed };
81
+ });
82
+ }
83
+ /** Worst-health rollup for exit codes: DARK > RED > UNKNOWN > GREEN. */
84
+ export function worstHealth(reports) {
85
+ if (reports.some((r) => r.health === 'DARK'))
86
+ return 'DARK';
87
+ if (reports.some((r) => r.health === 'RED'))
88
+ return 'RED';
89
+ if (reports.some((r) => r.health === 'UNKNOWN'))
90
+ return 'UNKNOWN';
91
+ return 'GREEN';
92
+ }
93
+ //# sourceMappingURL=probe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"probe.js","sourceRoot":"","sources":["../../src/capacity/probe.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Baseline System-1 backend: deterministic, CPU-only, dependency-free.
3
+ *
4
+ * Token-density scoring against per-question seed vocabularies, squashed
5
+ * through a logistic curve. It is deliberately simple: its job is to be the
6
+ * always-available local floor (airgap-pure, <50ms p95 trivially) and the
7
+ * shadow-mode reference that a trained embedding head must beat before
8
+ * promotion. Seeded from UAP's own incident vocabulary (the 2026-09-18 OOM
9
+ * timeline, gate outcomes, escalation telemetry language).
10
+ *
11
+ * v1 scope: the baseline answers ONLY the built-in questions (it keys its
12
+ * vocabulary off the question name). A custom question gets a ClassifyError,
13
+ * not a silently mis-scoped answer — the trained head is the generalizer.
14
+ */
15
+ import { AssessResult, ClassifierBackend, Question } from './types.js';
16
+ export declare class BaselineBackend implements ClassifierBackend {
17
+ readonly name = "baseline-tfidf-v1";
18
+ assess(state: string, questions: Question[]): AssessResult;
19
+ private answer;
20
+ private noul;
21
+ private score;
22
+ private choice;
23
+ }
24
+ //# sourceMappingURL=baseline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"baseline.d.ts","sourceRoot":"","sources":["../../src/classify/baseline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAEL,YAAY,EACZ,iBAAiB,EAGjB,QAAQ,EACT,MAAM,YAAY,CAAC;AA+CpB,qBAAa,eAAgB,YAAW,iBAAiB;IACvD,QAAQ,CAAC,IAAI,uBAAuB;IAEpC,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,YAAY;IAS1D,OAAO,CAAC,MAAM;IAmBd,OAAO,CAAC,IAAI;IAcZ,OAAO,CAAC,KAAK;IAUb,OAAO,CAAC,MAAM;CAmCf"}
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Baseline System-1 backend: deterministic, CPU-only, dependency-free.
3
+ *
4
+ * Token-density scoring against per-question seed vocabularies, squashed
5
+ * through a logistic curve. It is deliberately simple: its job is to be the
6
+ * always-available local floor (airgap-pure, <50ms p95 trivially) and the
7
+ * shadow-mode reference that a trained embedding head must beat before
8
+ * promotion. Seeded from UAP's own incident vocabulary (the 2026-09-18 OOM
9
+ * timeline, gate outcomes, escalation telemetry language).
10
+ *
11
+ * v1 scope: the baseline answers ONLY the built-in questions (it keys its
12
+ * vocabulary off the question name). A custom question gets a ClassifyError,
13
+ * not a silently mis-scoped answer — the trained head is the generalizer.
14
+ */
15
+ import { ClassifyError, BUILTIN_QUESTIONS, } from './types.js';
16
+ /** Tokens that mark elevated escalation risk in a state description. */
17
+ const ESCALATION_TOKENS = [
18
+ 'error', 'failed', 'failure', 'crash', 'oom', 'sigabrt', 'killed', 'stuck',
19
+ 'loop', 'looping', 'restarting', 'timeout', 'timed', 'denied', 'refused',
20
+ 'corrupt', 'unrecoverable', 'exception', 'fatal', 'panic', 'deadlock',
21
+ 'blocked', 'cannot', 'unable', 'unknown', 'missing', 'broken', 'regression',
22
+ 'conflict', 'diverged', 'mismatch', 'unexpected', 'anomaly', 'leak',
23
+ ];
24
+ /** Tokens that mark destructive/irreversible operations (task-risk score). */
25
+ const DESTRUCTIVE_TOKENS = [
26
+ 'delete', 'remove', 'rm', 'drop', 'truncate', 'destroy', 'wipe', 'purge',
27
+ 'overwrite', 'reset', 'force', 'push', 'deploy', 'migrate', 'production',
28
+ 'secrets', 'credentials', 'rotate', 'revoke', 'sudo', 'chmod', 'chown',
29
+ ];
30
+ const STOP = new Set([
31
+ 'the', 'a', 'an', 'and', 'or', 'of', 'to', 'in', 'on', 'for', 'with',
32
+ 'is', 'are', 'was', 'were', 'be', 'been', 'it', 'this', 'that', 'at',
33
+ 'by', 'as', 'from', 'not', 'no', 'we', 'i', 'you', 'they',
34
+ ]);
35
+ function tokenize(text) {
36
+ return text
37
+ .toLowerCase()
38
+ .split(/[^a-z0-9]+/)
39
+ .filter((t) => t.length > 1 && !STOP.has(t));
40
+ }
41
+ /** Sigmoid around a calibrated midpoint. */
42
+ function sigmoid(x) {
43
+ return 1 / (1 + Math.exp(-x));
44
+ }
45
+ /** hits / sqrt(length): sub-linear length normalization — long clean prose
46
+ * dilutes a few keyword hits, but a wall of repeated keywords still
47
+ * saturates (numerator grows linearly). Saturation resistance, if wanted,
48
+ * must come from the calibrated head, not from this ratio. */
49
+ function hitRatio(tokens, vocab) {
50
+ if (tokens.length === 0)
51
+ return 0;
52
+ const set = new Set(vocab);
53
+ const hits = tokens.filter((t) => set.has(t)).length;
54
+ return hits / Math.sqrt(tokens.length);
55
+ }
56
+ export class BaselineBackend {
57
+ name = 'baseline-tfidf-v1';
58
+ assess(state, questions) {
59
+ const tokens = tokenize(state);
60
+ const out = {};
61
+ for (const q of questions) {
62
+ out[q.name] = this.answer(q, tokens);
63
+ }
64
+ return out;
65
+ }
66
+ answer(q, tokens) {
67
+ // Question-blind scoring would silently answer a custom noul question
68
+ // with the escalation vocabulary — refuse instead (the trained head is
69
+ // the generalizer; see module header).
70
+ if (!BUILTIN_QUESTIONS.some((b) => b.name === q.name)) {
71
+ throw new ClassifyError(`baseline backend answers only the built-in questions; got "${q.name}"`);
72
+ }
73
+ switch (q.kind) {
74
+ case 'noul':
75
+ return this.noul(tokens);
76
+ case 'score':
77
+ return this.score(tokens);
78
+ case 'choice':
79
+ return this.choice(q, tokens);
80
+ }
81
+ }
82
+ noul(tokens) {
83
+ const r = hitRatio(tokens, ESCALATION_TOKENS);
84
+ // Midpoint ~0.35 ratio → p 0.5; steep enough to separate clean states.
85
+ const probability = sigmoid((r - 0.35) * 8);
86
+ // Confidence scales with evidence mass; empty states are low-confidence.
87
+ const confidence = Math.min(1, tokens.length / 40) * (0.5 + Math.abs(probability - 0.5));
88
+ return {
89
+ value: probability >= 0.5,
90
+ probability,
91
+ confidence,
92
+ backend: this.name,
93
+ };
94
+ }
95
+ score(tokens) {
96
+ const r = hitRatio(tokens, DESTRUCTIVE_TOKENS);
97
+ // Map 0..~0.8 ratio onto 1..5 with a soft curve.
98
+ const raw = 1 + 4 * Math.min(1, r / 0.8);
99
+ const value = Math.round(raw);
100
+ const probability = 1 - Math.min(1, Math.abs(raw - value) * 2);
101
+ const confidence = Math.min(1, tokens.length / 40);
102
+ return { value, probability, confidence, backend: this.name };
103
+ }
104
+ choice(q, tokens) {
105
+ const options = q.options ?? [];
106
+ if (options.length === 0) {
107
+ return { value: '', probability: 0, confidence: 0, backend: this.name };
108
+ }
109
+ // Deterministic scoring: 'escalate'/'stop' track escalation + destructive
110
+ // density, 'verify-first' tracks moderate signals, 'proceed' is the rest.
111
+ const esc = hitRatio(tokens, ESCALATION_TOKENS);
112
+ const dst = hitRatio(tokens, DESTRUCTIVE_TOKENS);
113
+ const scores = {
114
+ escalate: 2 * esc + dst,
115
+ // stop must beat escalate on pure-destructive-no-incident states, so
116
+ // it gets an escalation-independent margin (escalate >= dst always).
117
+ stop: dst > 0.6 && esc < 0.2 ? 2 * dst + 0.1 : 0,
118
+ 'verify-first': esc > 0.1 && esc < 0.5 ? 0.4 : 0.1,
119
+ proceed: Math.max(0.05, 0.5 - esc - dst),
120
+ };
121
+ let best = options[0];
122
+ let bestScore = -Infinity;
123
+ let total = 0;
124
+ for (const opt of options) {
125
+ const s = scores[opt] ?? 0.05;
126
+ total += s;
127
+ if (s > bestScore) {
128
+ bestScore = s;
129
+ best = opt;
130
+ }
131
+ }
132
+ return {
133
+ value: best,
134
+ probability: total > 0 ? bestScore / total : 1 / options.length,
135
+ confidence: Math.min(1, tokens.length / 40),
136
+ backend: this.name,
137
+ };
138
+ }
139
+ }
140
+ //# sourceMappingURL=baseline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"baseline.js","sourceRoot":"","sources":["../../src/classify/baseline.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Classifier entry point: backend selection + threshold application.
3
+ *
4
+ * Backend chain (first available wins):
5
+ * 1. trained head at ~/.config/uap/classifier-head.json (future; §6 v2)
6
+ * 2. baseline-tfidf-v1 (always available, airgap-pure)
7
+ * Thresholds come from reviewed policy config (config/classify-thresholds.json
8
+ * in the project, or ~/.config/uap/), never from model output.
9
+ */
10
+ import { AssessResult, ClassifierBackend, Question, QuestionThresholds } from './types.js';
11
+ export * from './types.js';
12
+ export { BaselineBackend } from './baseline.js';
13
+ export { recordShadow, shadowLogPath, type ShadowRecord } from './shadow.js';
14
+ export declare function defaultBackend(): ClassifierBackend;
15
+ export declare function thresholdPaths(projectDir: string): string[];
16
+ export declare function loadThresholds(projectDir: string, explicitPath?: string): Record<string, QuestionThresholds>;
17
+ export interface DecidedAssessment {
18
+ name: string;
19
+ value: AssessResult[string]['value'];
20
+ probability: number;
21
+ confidence: number;
22
+ /** True when confidence < floor: the caller must defer to its heuristic. */
23
+ defer: boolean;
24
+ backend: string;
25
+ }
26
+ /** Assess + apply reviewed thresholds. The returned `defer` flag is the
27
+ * shadow-mode contract: below the confidence floor, the classifier advises
28
+ * but the heuristic decides. */
29
+ export declare function assessWithThresholds(state: string, questions: Question[], thresholds: Record<string, QuestionThresholds>, backend?: ClassifierBackend): DecidedAssessment[];
30
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/classify/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,OAAO,EACL,YAAY,EACZ,iBAAiB,EAGjB,QAAQ,EACR,kBAAkB,EAEnB,MAAM,YAAY,CAAC;AAEpB,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAE7E,wBAAgB,cAAc,IAAI,iBAAiB,CAKlD;AAED,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CAK3D;AAED,wBAAgB,cAAc,CAC5B,UAAU,EAAE,MAAM,EAClB,YAAY,CAAC,EAAE,MAAM,GACpB,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CA4BpC;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,YAAY,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC;IACrC,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,4EAA4E;IAC5E,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;gCAEgC;AAChC,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,QAAQ,EAAE,EACrB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,EAC9C,OAAO,GAAE,iBAAoC,GAC5C,iBAAiB,EAAE,CAqBrB"}
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Classifier entry point: backend selection + threshold application.
3
+ *
4
+ * Backend chain (first available wins):
5
+ * 1. trained head at ~/.config/uap/classifier-head.json (future; §6 v2)
6
+ * 2. baseline-tfidf-v1 (always available, airgap-pure)
7
+ * Thresholds come from reviewed policy config (config/classify-thresholds.json
8
+ * in the project, or ~/.config/uap/), never from model output.
9
+ */
10
+ import { existsSync, readFileSync } from 'fs';
11
+ import { join } from 'path';
12
+ import { homedir } from 'os';
13
+ import { BaselineBackend } from './baseline.js';
14
+ import { ClassifyError, DEFAULT_THRESHOLDS, } from './types.js';
15
+ export * from './types.js';
16
+ export { BaselineBackend } from './baseline.js';
17
+ export { recordShadow, shadowLogPath } from './shadow.js';
18
+ export function defaultBackend() {
19
+ // v1: the trained-head slot exists in the chain but no head format is
20
+ // committed yet — landing small, one dimension end-to-end. When a head
21
+ // ships it inserts here without touching consumers.
22
+ return new BaselineBackend();
23
+ }
24
+ export function thresholdPaths(projectDir) {
25
+ return [
26
+ join(projectDir, 'config', 'classify-thresholds.json'),
27
+ join(homedir(), '.config', 'uap', 'classify-thresholds.json'),
28
+ ];
29
+ }
30
+ export function loadThresholds(projectDir, explicitPath) {
31
+ const candidates = explicitPath ? [explicitPath] : thresholdPaths(projectDir);
32
+ for (const p of candidates) {
33
+ if (!existsSync(p))
34
+ continue;
35
+ let cfg;
36
+ try {
37
+ cfg = JSON.parse(readFileSync(p, 'utf-8'));
38
+ }
39
+ catch (err) {
40
+ throw new ClassifyError(`threshold config at ${p} is not valid JSON: ${err.message}`);
41
+ }
42
+ if (cfg.version !== 1 || typeof cfg.questions !== 'object' || cfg.questions === null) {
43
+ throw new ClassifyError(`threshold config at ${p}: expected {version: 1, questions: {...}}`);
44
+ }
45
+ for (const [name, t] of Object.entries(cfg.questions)) {
46
+ const tauOk = t.tau === undefined || (Number.isFinite(t.tau) && t.tau >= 0 && t.tau <= 1);
47
+ const floorOk = Number.isFinite(t.confidenceFloor) && t.confidenceFloor >= 0 && t.confidenceFloor <= 1;
48
+ if (!tauOk || !floorOk) {
49
+ throw new ClassifyError(`threshold config at ${p}: questions.${name} needs confidenceFloor (and optional tau) in [0,1]`);
50
+ }
51
+ }
52
+ return cfg.questions;
53
+ }
54
+ if (explicitPath) {
55
+ throw new ClassifyError(`threshold config not found: ${explicitPath}`);
56
+ }
57
+ return {};
58
+ }
59
+ /** Assess + apply reviewed thresholds. The returned `defer` flag is the
60
+ * shadow-mode contract: below the confidence floor, the classifier advises
61
+ * but the heuristic decides. */
62
+ export function assessWithThresholds(state, questions, thresholds, backend = defaultBackend()) {
63
+ const result = backend.assess(state, questions);
64
+ return questions.map((q) => {
65
+ const a = result[q.name];
66
+ if (!a) {
67
+ throw new ClassifyError(`backend "${backend.name}" returned no assessment for "${q.name}"`);
68
+ }
69
+ const t = thresholds[q.name] ?? DEFAULT_THRESHOLDS;
70
+ let value = a.value;
71
+ if (q.kind === 'noul') {
72
+ value = a.probability >= (t.tau ?? DEFAULT_THRESHOLDS.tau);
73
+ }
74
+ return {
75
+ name: q.name,
76
+ value,
77
+ probability: a.probability,
78
+ confidence: a.confidence,
79
+ defer: a.confidence < t.confidenceFloor,
80
+ backend: a.backend,
81
+ };
82
+ });
83
+ }
84
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/classify/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Shadow-mode logging (uplift §6 risk mitigation): the classifier assesses,
3
+ * heuristics decide, divergence is logged. Promotion to active only after
4
+ * measured agreement.
5
+ *
6
+ * Privacy: the raw state text is NEVER persisted — only a content hash, the
7
+ * word count, and the assessment (no raw-text surface; note the hash is
8
+ * equality-revealing for low-entropy boilerplate states — acceptable for a
9
+ * local-only join key). Calibration replays join outcome labels by hash;
10
+ * the text itself stays in the session where it was produced. The log is
11
+ * local-only (.uap/, git-ignored, mode 0600).
12
+ */
13
+ import { AssessResult, Question } from './types.js';
14
+ export interface ShadowRecord {
15
+ /** Schema version — the first calibration replay will want one. */
16
+ v: 1;
17
+ ts: string;
18
+ /** SHA-256 of the state text — join key for later outcome labels. */
19
+ stateHash: string;
20
+ wordCount: number;
21
+ questions: string[];
22
+ result: AssessResult;
23
+ /** The heuristic decision that actually fired while the classifier was in
24
+ * shadow — the divergence the promotion gate measures. Optional because
25
+ * the CLI's ad-hoc --shadow has no heuristic in the loop; Wave 1 consumers
26
+ * (supervisor, AutoMode) must populate it. */
27
+ heuristicDecision?: Record<string, unknown>;
28
+ }
29
+ export declare function shadowLogPath(projectDir: string): string;
30
+ export declare function recordShadow(projectDir: string, state: string, questions: Question[], result: AssessResult, opts?: {
31
+ logPath?: string;
32
+ heuristicDecision?: Record<string, unknown>;
33
+ }): void;
34
+ //# sourceMappingURL=shadow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shadow.d.ts","sourceRoot":"","sources":["../../src/classify/shadow.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEpD,MAAM,WAAW,YAAY;IAC3B,mEAAmE;IACnE,CAAC,EAAE,CAAC,CAAC;IACL,EAAE,EAAE,MAAM,CAAC;IACX,qEAAqE;IACrE,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,MAAM,EAAE,YAAY,CAAC;IACrB;;;kDAG8C;IAC9C,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC7C;AAED,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAExD;AAED,wBAAgB,YAAY,CAC1B,UAAU,EAAE,MAAM,EAClB,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,QAAQ,EAAE,EACrB,MAAM,EAAE,YAAY,EACpB,IAAI,GAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAO,GAC3E,IAAI,CAaN"}